Also I hope that the separate py_set() helper will help transition
to proper PySet support in rust-cpython
Took the opportunity to replace explict for loop with iteration
and collect().
( )
| kevincox |
| hg-reviewers |
Also I hope that the separate py_set() helper will help transition
to proper PySet support in rust-cpython
Took the opportunity to replace explict for loop with iteration
and collect().
| Automatic diff as part of commit; lint not applicable. |
| Automatic diff as part of commit; unit tests not applicable. |
| Path | Packages | |||
|---|---|---|---|---|
| M | rust/hg-cpython/src/ancestors.rs (46 lines) | |||
| M | tests/test-rust-ancestor.py (2 lines) |
| // ancestors.rs | // ancestors.rs | ||||
| // | // | ||||
| // Copyright 2018 Georges Racinet <gracinet@anybox.fr> | // Copyright 2018 Georges Racinet <gracinet@anybox.fr> | ||||
| // | // | ||||
| // This software may be used and distributed according to the terms of the | // This software may be used and distributed according to the terms of the | ||||
| // GNU General Public License version 2 or any later version. | // GNU General Public License version 2 or any later version. | ||||
| //! Bindings for the `hg::ancestors` module provided by the | //! Bindings for the `hg::ancestors` module provided by the | ||||
| //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor` | //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor` | ||||
| //! and can be used as replacement for the the pure `ancestor` Python module. | //! and can be used as replacement for the the pure `ancestor` Python module. | ||||
| //! | //! | ||||
| //! # Classes visible from Python: | //! # Classes visible from Python: | ||||
| //! - [`LazyAncestors`] is the Rust implementation of | //! - [`LazyAncestors`] is the Rust implementation of | ||||
| //! `mercurial.ancestor.lazyancestors`. | //! `mercurial.ancestor.lazyancestors`. The only difference is that it is | ||||
| //! The only difference is that it is instantiated with a C `parsers.index` | //! instantiated with a C `parsers.index` instance instead of a parents | ||||
| //! instance instead of a parents function. | //! function. | ||||
| //! | //! | ||||
| //! - [`MissingAncestors`] is the Rust implementation of | //! - [`MissingAncestors`] is the Rust implementation of | ||||
| //! `mercurial.ancestor.incrementalmissingancestors`. | //! `mercurial.ancestor.incrementalmissingancestors`. | ||||
| //! | //! | ||||
| //! API differences: | //! API differences: | ||||
| //! + it is instantiated with a C `parsers.index` | //! + it is instantiated with a C `parsers.index` | ||||
| //! instance instead of a parents function. | //! instance instead of a parents function. | ||||
| //! + `MissingAncestors.bases` is a method returning a tuple instead of | //! + `MissingAncestors.bases` is a method returning a tuple instead of | ||||
| //! a set-valued attribute. We could return a Python set easily if our | //! a set-valued attribute. We could return a Python set easily if our | ||||
| //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165) | //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165) | ||||
| //! is accepted. | //! is accepted. | ||||
| //! | //! | ||||
| //! - [`AncestorsIterator`] is the Rust counterpart of the | //! - [`AncestorsIterator`] is the Rust counterpart of the | ||||
| //! `ancestor._lazyancestorsiter` Python generator. | //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of | ||||
| //! From Python, instances of this should be mainly obtained by calling | //! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`] | ||||
| //! `iter()` on a [`LazyAncestors`] instance. | //! instance. | ||||
| //! | //! | ||||
| //! [`LazyAncestors`]: struct.LazyAncestors.html | //! [`LazyAncestors`]: struct.LazyAncestors.html | ||||
| //! [`MissingAncestors`]: struct.MissingAncestors.html | //! [`MissingAncestors`]: struct.MissingAncestors.html | ||||
| //! [`AncestorsIterator`]: struct.AncestorsIterator.html | //! [`AncestorsIterator`]: struct.AncestorsIterator.html | ||||
| use crate::conversion::rev_pyiter_collect; | use crate::conversion::rev_pyiter_collect; | ||||
| use cindex::Index; | use cindex::Index; | ||||
| use cpython::{ | use cpython::{ | ||||
| ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, | ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult, | ||||
| PyResult, PyTuple, Python, PythonObject, ToPyObject, | PyTuple, Python, PythonObject, ToPyObject, | ||||
| }; | }; | ||||
| use exceptions::GraphError; | use exceptions::GraphError; | ||||
| use hg::Revision; | use hg::Revision; | ||||
| use hg::{ | use hg::{ | ||||
| AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, | AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, | ||||
| MissingAncestors as CoreMissing, | MissingAncestors as CoreMissing, | ||||
| }; | }; | ||||
| use std::cell::RefCell; | use std::cell::RefCell; | ||||
| }); | }); | ||||
| impl AncestorsIterator { | impl AncestorsIterator { | ||||
| pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> { | pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> { | ||||
| Self::create_instance(py, RefCell::new(Box::new(ait))) | Self::create_instance(py, RefCell::new(Box::new(ait))) | ||||
| } | } | ||||
| } | } | ||||
| /// Copy and convert an `HashSet<Revision>` in a Python set | |||||
| /// | |||||
| /// This will probably turn useless once `PySet` support lands in | |||||
| /// `rust-cpython`. | |||||
| /// | |||||
| /// This builds a Python tuple, then calls Python's "set()" on it | |||||
| fn py_set(py: Python, set: &HashSet<Revision>) -> PyResult<PyObject> { | |||||
| let as_vec: Vec<PyObject> = set | |||||
| .iter() | |||||
| .map(|rev| rev.to_py_object(py).into_object()) | |||||
| .collect(); | |||||
| let as_pytuple = PyTuple::new(py, as_vec.as_slice()); | |||||
| let locals = PyDict::new(py); | |||||
| locals.set_item(py, "obj", as_pytuple.to_py_object(py))?; | |||||
| py.eval("set(obj)", None, Some(&locals)) | |||||
| } | |||||
| py_class!(pub class LazyAncestors |py| { | py_class!(pub class LazyAncestors |py| { | ||||
| data inner: RefCell<Box<CoreLazy<Index>>>; | data inner: RefCell<Box<CoreLazy<Index>>>; | ||||
| def __contains__(&self, rev: Revision) -> PyResult<bool> { | def __contains__(&self, rev: Revision) -> PyResult<bool> { | ||||
| self.inner(py) | self.inner(py) | ||||
| .borrow_mut() | .borrow_mut() | ||||
| .contains(rev) | .contains(rev) | ||||
| .map_err(|e| GraphError::pynew(py, e)) | .map_err(|e| GraphError::pynew(py, e)) | ||||
| let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?; | ||||
| inner.add_bases(bases_vec); | inner.add_bases(bases_vec); | ||||
| // cpython doc has examples with PyResult<()> but this gives me | // cpython doc has examples with PyResult<()> but this gives me | ||||
| // the trait `cpython::ToPyObject` is not implemented for `()` | // the trait `cpython::ToPyObject` is not implemented for `()` | ||||
| // so let's return an explicit None | // so let's return an explicit None | ||||
| Ok(py.None()) | Ok(py.None()) | ||||
| } | } | ||||
| def bases(&self) -> PyResult<PyTuple> { | def bases(&self) -> PyResult<PyObject> { | ||||
| let inner = self.inner(py).borrow(); | py_set(py, self.inner(py).borrow().get_bases()) | ||||
| let bases_set = inner.get_bases(); | |||||
| // convert as Python tuple TODO how to return a proper Python set? | |||||
| let mut bases_vec: Vec<PyObject> = Vec::with_capacity( | |||||
| bases_set.len()); | |||||
| for rev in bases_set { | |||||
| bases_vec.push(rev.to_py_object(py).into_object()); | |||||
| } | |||||
| Ok(PyTuple::new(py, bases_vec.as_slice())) | |||||
| } | } | ||||
| def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> { | def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> { | ||||
| let mut inner = self.inner(py).borrow_mut(); | let mut inner = self.inner(py).borrow_mut(); | ||||
| // this is very lame: we convert to a Rust set, update it in place | // 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 | // and then convert back to Python, only to have Python remove the | ||||
| // excess (thankfully, Python is happy with a list or even an iterator) | // excess (thankfully, Python is happy with a list or even an iterator) | ||||
| // Leads to improve this: | // Leads to improve this: | ||||
| self.assertFalse(LazyAncestors(idx, [0], 0, False)) | self.assertFalse(LazyAncestors(idx, [0], 0, False)) | ||||
| def testmissingancestors(self): | def testmissingancestors(self): | ||||
| idx = self.parseindex() | idx = self.parseindex() | ||||
| missanc = MissingAncestors(idx, [1]) | missanc = MissingAncestors(idx, [1]) | ||||
| self.assertTrue(missanc.hasbases()) | self.assertTrue(missanc.hasbases()) | ||||
| self.assertEqual(missanc.missingancestors([3]), [2, 3]) | self.assertEqual(missanc.missingancestors([3]), [2, 3]) | ||||
| missanc.addbases({2}) | missanc.addbases({2}) | ||||
| self.assertEqual(set(missanc.bases()), {1, 2}) | self.assertEqual(missanc.bases(), {1, 2}) | ||||
| self.assertEqual(missanc.missingancestors([3]), [3]) | self.assertEqual(missanc.missingancestors([3]), [3]) | ||||
| def testmissingancestorsremove(self): | def testmissingancestorsremove(self): | ||||
| idx = self.parseindex() | idx = self.parseindex() | ||||
| missanc = MissingAncestors(idx, [1]) | missanc = MissingAncestors(idx, [1]) | ||||
| revs = {0, 1, 2, 3} | revs = {0, 1, 2, 3} | ||||
| missanc.removeancestorsfrom(revs) | missanc.removeancestorsfrom(revs) | ||||
| self.assertEqual(revs, {2, 3}) | self.assertEqual(revs, {2, 3}) | ||||