ancestors.rs
279 lines
| 9.3 KiB
| application/rls-services+xml
|
RustLexer
Georges Racinet
|
r41001 | // ancestors.rs | ||
// | ||||
// Copyright 2018 Georges Racinet <gracinet@anybox.fr> | ||||
// | ||||
// This software may be used and distributed according to the terms of the | ||||
// GNU General Public License version 2 or any later version. | ||||
Georges Racinet
|
r41220 | //! Bindings for the `hg::ancestors` module provided by the | ||
Georges Racinet
|
r41001 | //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor` | ||
Georges Racinet
|
r41220 | //! and can be used as replacement for the the pure `ancestor` Python module. | ||
//! | ||||
//! # Classes visible from Python: | ||||
//! - [`LazyAncestors`] is the Rust implementation of | ||||
Georges Racinet
|
r41279 | //! `mercurial.ancestor.lazyancestors`. The only difference is that it is | ||
//! instantiated with a C `parsers.index` instance instead of a parents | ||||
//! function. | ||||
Georges Racinet
|
r41220 | //! | ||
Georges Racinet
|
r41224 | //! - [`MissingAncestors`] is the Rust implementation of | ||
//! `mercurial.ancestor.incrementalmissingancestors`. | ||||
//! | ||||
//! API differences: | ||||
//! + it is instantiated with a C `parsers.index` | ||||
//! instance instead of a parents function. | ||||
//! + `MissingAncestors.bases` is a method returning a tuple instead of | ||||
//! a set-valued attribute. We could return a Python set easily if our | ||||
//! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165) | ||||
//! is accepted. | ||||
//! | ||||
Georges Racinet
|
r41220 | //! - [`AncestorsIterator`] is the Rust counterpart of the | ||
Georges Racinet
|
r41279 | //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of | ||
//! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`] | ||||
//! instance. | ||||
Georges Racinet
|
r41220 | //! | ||
//! [`LazyAncestors`]: struct.LazyAncestors.html | ||||
Georges Racinet
|
r41224 | //! [`MissingAncestors`]: struct.MissingAncestors.html | ||
Georges Racinet
|
r41220 | //! [`AncestorsIterator`]: struct.AncestorsIterator.html | ||
r44398 | use crate::revlog::pyindex_to_graph; | |||
Raphaël Gomès
|
r51872 | use crate::PyRevision; | ||
Raphaël Gomès
|
r42828 | use crate::{ | ||
Georges Racinet
|
r43563 | cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError, | ||
Raphaël Gomès
|
r42828 | }; | ||
Georges Racinet
|
r41083 | use cpython::{ | ||
Georges Racinet
|
r41279 | ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult, | ||
Georges Racinet
|
r41842 | Python, PythonObject, ToPyObject, | ||
Georges Racinet
|
r41083 | }; | ||
pacien
|
r49350 | use hg::MissingAncestors as CoreMissing; | ||
Georges Racinet
|
r41083 | use hg::Revision; | ||
use std::cell::RefCell; | ||||
Georges Racinet
|
r41224 | use std::collections::HashSet; | ||
pacien
|
r49350 | use vcsgraph::lazy_ancestors::{ | ||
AncestorsIterator as VCGAncestorsIterator, | ||||
LazyAncestors as VCGLazyAncestors, | ||||
}; | ||||
Georges Racinet
|
r41083 | |||
Georges Racinet
|
r41220 | py_class!(pub class AncestorsIterator |py| { | ||
pacien
|
r49350 | data inner: RefCell<Box<VCGAncestorsIterator<Index>>>; | ||
Georges Racinet
|
r41083 | |||
Raphaël Gomès
|
r51872 | def __next__(&self) -> PyResult<Option<PyRevision>> { | ||
Georges Racinet
|
r41083 | match self.inner(py).borrow_mut().next() { | ||
pacien
|
r49350 | Some(Err(e)) => Err(GraphError::pynew_from_vcsgraph(py, e)), | ||
Georges Racinet
|
r41083 | None => Ok(None), | ||
Raphaël Gomès
|
r51872 | Some(Ok(r)) => Ok(Some(PyRevision(r))), | ||
Georges Racinet
|
r41083 | } | ||
} | ||||
Raphaël Gomès
|
r51872 | def __contains__(&self, rev: PyRevision) -> PyResult<bool> { | ||
self.inner(py).borrow_mut().contains(rev.0) | ||||
pacien
|
r49350 | .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) | ||
Georges Racinet
|
r41083 | } | ||
def __iter__(&self) -> PyResult<Self> { | ||||
Ok(self.clone_ref(py)) | ||||
} | ||||
Raphaël Gomès
|
r51872 | def __new__( | ||
_cls, | ||||
index: PyObject, | ||||
initrevs: PyObject, | ||||
stoprev: PyRevision, | ||||
inclusive: bool | ||||
) -> PyResult<AncestorsIterator> { | ||||
let index = pyindex_to_graph(py, index)?; | ||||
let initvec: Vec<_> = rev_pyiter_collect(py, &initrevs, &index)?; | ||||
pacien
|
r49350 | let ait = VCGAncestorsIterator::new( | ||
Raphaël Gomès
|
r51872 | index, | ||
initvec.into_iter().map(|r| r.0), | ||||
stoprev.0, | ||||
Georges Racinet
|
r41222 | inclusive, | ||
) | ||||
pacien
|
r49350 | .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?; | ||
Georges Racinet
|
r41083 | AncestorsIterator::from_inner(py, ait) | ||
} | ||||
}); | ||||
impl AncestorsIterator { | ||||
pacien
|
r49350 | pub fn from_inner( | ||
py: Python, | ||||
ait: VCGAncestorsIterator<Index>, | ||||
) -> PyResult<Self> { | ||||
Georges Racinet
|
r41083 | Self::create_instance(py, RefCell::new(Box::new(ait))) | ||
} | ||||
} | ||||
Georges Racinet
|
r41001 | |||
Georges Racinet
|
r41220 | py_class!(pub class LazyAncestors |py| { | ||
pacien
|
r49350 | data inner: RefCell<Box<VCGLazyAncestors<Index>>>; | ||
Georges Racinet
|
r41149 | |||
Raphaël Gomès
|
r51872 | def __contains__(&self, rev: PyRevision) -> PyResult<bool> { | ||
Georges Racinet
|
r41149 | self.inner(py) | ||
.borrow_mut() | ||||
Raphaël Gomès
|
r51872 | .contains(rev.0) | ||
pacien
|
r49350 | .map_err(|e| GraphError::pynew_from_vcsgraph(py, e)) | ||
Georges Racinet
|
r41149 | } | ||
def __iter__(&self) -> PyResult<AncestorsIterator> { | ||||
AncestorsIterator::from_inner(py, self.inner(py).borrow().iter()) | ||||
} | ||||
def __bool__(&self) -> PyResult<bool> { | ||||
Ok(!self.inner(py).borrow().is_empty()) | ||||
} | ||||
Raphaël Gomès
|
r51872 | def __new__( | ||
_cls, | ||||
index: PyObject, | ||||
initrevs: PyObject, | ||||
stoprev: PyRevision, | ||||
inclusive: bool | ||||
) -> PyResult<Self> { | ||||
let index = pyindex_to_graph(py, index)?; | ||||
let initvec: Vec<_> = rev_pyiter_collect(py, &initrevs, &index)?; | ||||
Georges Racinet
|
r41149 | |||
let lazy = | ||||
Raphaël Gomès
|
r51872 | VCGLazyAncestors::new( | ||
index, | ||||
initvec.into_iter().map(|r| r.0), | ||||
stoprev.0, | ||||
inclusive | ||||
) | ||||
.map_err(|e| GraphError::pynew_from_vcsgraph(py, e))?; | ||||
Georges Racinet
|
r41149 | |||
Self::create_instance(py, RefCell::new(Box::new(lazy))) | ||||
} | ||||
}); | ||||
Georges Racinet
|
r41224 | py_class!(pub class MissingAncestors |py| { | ||
data inner: RefCell<Box<CoreMissing<Index>>>; | ||||
Raphaël Gomès
|
r51872 | data index: RefCell<Index>; | ||
Georges Racinet
|
r41224 | |||
Raphaël Gomès
|
r44927 | def __new__( | ||
_cls, | ||||
index: PyObject, | ||||
bases: PyObject | ||||
) | ||||
-> PyResult<MissingAncestors> { | ||||
Raphaël Gomès
|
r51872 | let index = pyindex_to_graph(py, index)?; | ||
let bases_vec: Vec<_> = rev_pyiter_collect(py, &bases, &index)?; | ||||
let inner = CoreMissing::new(index.clone_ref(py), bases_vec); | ||||
MissingAncestors::create_instance( | ||||
py, | ||||
RefCell::new(Box::new(inner)), | ||||
RefCell::new(index) | ||||
) | ||||
Georges Racinet
|
r41224 | } | ||
def hasbases(&self) -> PyResult<bool> { | ||||
Ok(self.inner(py).borrow().has_bases()) | ||||
} | ||||
def addbases(&self, bases: PyObject) -> PyResult<PyObject> { | ||||
Raphaël Gomès
|
r51872 | let index = self.index(py).borrow(); | ||
let bases_vec: Vec<_> = rev_pyiter_collect(py, &bases, &*index)?; | ||||
Georges Racinet
|
r41224 | let mut inner = self.inner(py).borrow_mut(); | ||
inner.add_bases(bases_vec); | ||||
// cpython doc has examples with PyResult<()> but this gives me | ||||
// the trait `cpython::ToPyObject` is not implemented for `()` | ||||
// so let's return an explicit None | ||||
Ok(py.None()) | ||||
} | ||||
Raphaël Gomès
|
r51872 | def bases(&self) -> PyResult<HashSet<PyRevision>> { | ||
Ok( | ||||
self.inner(py) | ||||
.borrow() | ||||
.get_bases() | ||||
.iter() | ||||
.map(|r| PyRevision(r.0)) | ||||
.collect() | ||||
) | ||||
Georges Racinet
|
r41224 | } | ||
Raphaël Gomès
|
r51872 | def basesheads(&self) -> PyResult<HashSet<PyRevision>> { | ||
Georges Racinet
|
r41282 | let inner = self.inner(py).borrow(); | ||
Raphaël Gomès
|
r51872 | Ok( | ||
inner | ||||
.bases_heads() | ||||
.map_err(|e| GraphError::pynew(py, e))? | ||||
.into_iter() | ||||
.map(|r| PyRevision(r.0)) | ||||
.collect() | ||||
) | ||||
Georges Racinet
|
r41282 | } | ||
Georges Racinet
|
r41224 | def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> { | ||
Raphaël Gomès
|
r51872 | let index = self.index(py).borrow(); | ||
Georges Racinet
|
r41224 | // this is very lame: we convert to a Rust set, update it in place | ||
// and then convert back to Python, only to have Python remove the | ||||
// excess (thankfully, Python is happy with a list or even an iterator) | ||||
// Leads to improve this: | ||||
// - have the CoreMissing instead do something emit revisions to | ||||
// discard | ||||
// - define a trait for sets of revisions in the core and implement | ||||
// it for a Python set rewrapped with the GIL marker | ||||
Raphaël Gomès
|
r51872 | let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect( | ||
py, &revs, &*index | ||||
)?; | ||||
let mut inner = self.inner(py).borrow_mut(); | ||||
Georges Racinet
|
r41224 | inner.remove_ancestors_from(&mut revs_pyset) | ||
.map_err(|e| GraphError::pynew(py, e))?; | ||||
// convert as Python list | ||||
let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity( | ||||
revs_pyset.len()); | ||||
for rev in revs_pyset { | ||||
Raphaël Gomès
|
r51872 | remaining_pyint_vec.push( | ||
PyRevision(rev.0).to_py_object(py).into_object() | ||||
); | ||||
Georges Racinet
|
r41224 | } | ||
let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice()); | ||||
revs.call_method(py, "intersection_update", (remaining_pylist, ), None) | ||||
} | ||||
def missingancestors(&self, revs: PyObject) -> PyResult<PyList> { | ||||
Raphaël Gomès
|
r51872 | let index = self.index(py).borrow(); | ||
let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs, &*index)?; | ||||
Georges Racinet
|
r41224 | let mut inner = self.inner(py).borrow_mut(); | ||
let missing_vec = match inner.missing_ancestors(revs_vec) { | ||||
Ok(missing) => missing, | ||||
Err(e) => { | ||||
return Err(GraphError::pynew(py, e)); | ||||
} | ||||
}; | ||||
// convert as Python list | ||||
let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity( | ||||
missing_vec.len()); | ||||
for rev in missing_vec { | ||||
Raphaël Gomès
|
r51872 | missing_pyint_vec.push( | ||
PyRevision(rev.0).to_py_object(py).into_object() | ||||
); | ||||
Georges Racinet
|
r41224 | } | ||
Ok(PyList::new(py, missing_pyint_vec.as_slice())) | ||||
} | ||||
}); | ||||
/// Create the module, with __package__ given from parent | ||||
Georges Racinet
|
r41001 | pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> { | ||
let dotted_name = &format!("{}.ancestor", package); | ||||
let m = PyModule::new(py, dotted_name)?; | ||||
m.add(py, "__package__", package)?; | ||||
m.add( | ||||
py, | ||||
"__doc__", | ||||
"Generic DAG ancestor algorithms - Rust implementation", | ||||
)?; | ||||
Georges Racinet
|
r41083 | m.add_class::<AncestorsIterator>(py)?; | ||
Georges Racinet
|
r41149 | m.add_class::<LazyAncestors>(py)?; | ||
Georges Racinet
|
r41224 | m.add_class::<MissingAncestors>(py)?; | ||
Georges Racinet
|
r41001 | |||
let sys = PyModule::import(py, "sys")?; | ||||
let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?; | ||||
sys_modules.set_item(py, dotted_name, &m)?; | ||||
// Example C code (see pyexpat.c and import.c) will "give away the | ||||
// reference", but we won't because it will be consumed once the | ||||
// Rust PyObject is dropped. | ||||
Ok(m) | ||||
} | ||||