ancestors.rs
227 lines
| 8.2 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
|
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 | }; | ||
use hg::Revision; | ||||
Georges Racinet
|
r41224 | use hg::{ | ||
AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, | ||||
MissingAncestors as CoreMissing, | ||||
}; | ||||
Georges Racinet
|
r41083 | use std::cell::RefCell; | ||
Georges Racinet
|
r41224 | use std::collections::HashSet; | ||
Georges Racinet
|
r41083 | |||
Georges Racinet
|
r41220 | py_class!(pub class AncestorsIterator |py| { | ||
Georges Racinet
|
r41083 | data inner: RefCell<Box<CoreIterator<Index>>>; | ||
def __next__(&self) -> PyResult<Option<Revision>> { | ||||
match self.inner(py).borrow_mut().next() { | ||||
Some(Err(e)) => Err(GraphError::pynew(py, e)), | ||||
None => Ok(None), | ||||
Some(Ok(r)) => Ok(Some(r)), | ||||
} | ||||
} | ||||
def __contains__(&self, rev: Revision) -> PyResult<bool> { | ||||
Georges Racinet
|
r41222 | self.inner(py).borrow_mut().contains(rev) | ||
.map_err(|e| GraphError::pynew(py, e)) | ||||
Georges Racinet
|
r41083 | } | ||
def __iter__(&self) -> PyResult<Self> { | ||||
Ok(self.clone_ref(py)) | ||||
} | ||||
def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision, | ||||
inclusive: bool) -> PyResult<AncestorsIterator> { | ||||
Georges Racinet
|
r41223 | let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?; | ||
Georges Racinet
|
r41222 | let ait = CoreIterator::new( | ||
r44398 | pyindex_to_graph(py, index)?, | |||
Georges Racinet
|
r41222 | initvec, | ||
stoprev, | ||||
inclusive, | ||||
) | ||||
.map_err(|e| GraphError::pynew(py, e))?; | ||||
Georges Racinet
|
r41083 | AncestorsIterator::from_inner(py, ait) | ||
} | ||||
}); | ||||
impl AncestorsIterator { | ||||
pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> { | ||||
Self::create_instance(py, RefCell::new(Box::new(ait))) | ||||
} | ||||
} | ||||
Georges Racinet
|
r41001 | |||
Georges Racinet
|
r41220 | py_class!(pub class LazyAncestors |py| { | ||
Georges Racinet
|
r41149 | data inner: RefCell<Box<CoreLazy<Index>>>; | ||
def __contains__(&self, rev: Revision) -> PyResult<bool> { | ||||
self.inner(py) | ||||
.borrow_mut() | ||||
.contains(rev) | ||||
.map_err(|e| GraphError::pynew(py, e)) | ||||
} | ||||
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()) | ||||
} | ||||
def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision, | ||||
inclusive: bool) -> PyResult<Self> { | ||||
Georges Racinet
|
r41223 | let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?; | ||
Georges Racinet
|
r41149 | |||
let lazy = | ||||
r44398 | CoreLazy::new(pyindex_to_graph(py, index)?, | |||
initvec, stoprev, inclusive) | ||||
Georges Racinet
|
r41149 | .map_err(|e| GraphError::pynew(py, e))?; | ||
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
|
r44927 | def __new__( | ||
_cls, | ||||
index: PyObject, | ||||
bases: PyObject | ||||
) | ||||
-> PyResult<MissingAncestors> { | ||||
Georges Racinet
|
r41224 | let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | ||
r44398 | let inner = CoreMissing::new(pyindex_to_graph(py, index)?, bases_vec); | |||
Georges Racinet
|
r41224 | MissingAncestors::create_instance(py, RefCell::new(Box::new(inner))) | ||
} | ||||
def hasbases(&self) -> PyResult<bool> { | ||||
Ok(self.inner(py).borrow().has_bases()) | ||||
} | ||||
def addbases(&self, bases: PyObject) -> PyResult<PyObject> { | ||||
let mut inner = self.inner(py).borrow_mut(); | ||||
let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | ||||
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()) | ||||
} | ||||
Georges Racinet
|
r43563 | def bases(&self) -> PyResult<HashSet<Revision>> { | ||
Ok(self.inner(py).borrow().get_bases().clone()) | ||||
Georges Racinet
|
r41224 | } | ||
Georges Racinet
|
r43563 | def basesheads(&self) -> PyResult<HashSet<Revision>> { | ||
Georges Racinet
|
r41282 | let inner = self.inner(py).borrow(); | ||
Georges Racinet
|
r43563 | inner.bases_heads().map_err(|e| GraphError::pynew(py, e)) | ||
Georges Racinet
|
r41282 | } | ||
Georges Racinet
|
r41224 | def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> { | ||
let mut inner = self.inner(py).borrow_mut(); | ||||
// 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 | ||||
let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?; | ||||
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 { | ||||
remaining_pyint_vec.push(rev.to_py_object(py).into_object()); | ||||
} | ||||
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> { | ||||
let mut inner = self.inner(py).borrow_mut(); | ||||
let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?; | ||||
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 { | ||||
missing_pyint_vec.push(rev.to_py_object(py).into_object()); | ||||
} | ||||
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) | ||||
} | ||||