##// END OF EJS Templates
rust-index: add a function to convert PyObject index for hg-core...
marmoute -
r44398:f98f0e3d default
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1 // revlog.rs
2 //
3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
4 //
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
7
8 use crate::cindex;
9 use cpython::{PyObject, PyResult, Python};
10
11 /// Return a Struct implementing the Graph trait
12 pub(crate) fn pyindex_to_graph(
13 py: Python,
14 index: PyObject,
15 ) -> PyResult<cindex::Index> {
16 cindex::Index::new(py, index)
17 }
@@ -1,220 +1,222 b''
1 1 // ancestors.rs
2 2 //
3 3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::ancestors` module provided by the
9 9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
10 10 //! and can be used as replacement for the the pure `ancestor` Python module.
11 11 //!
12 12 //! # Classes visible from Python:
13 13 //! - [`LazyAncestors`] is the Rust implementation of
14 14 //! `mercurial.ancestor.lazyancestors`. The only difference is that it is
15 15 //! instantiated with a C `parsers.index` instance instead of a parents
16 16 //! function.
17 17 //!
18 18 //! - [`MissingAncestors`] is the Rust implementation of
19 19 //! `mercurial.ancestor.incrementalmissingancestors`.
20 20 //!
21 21 //! API differences:
22 22 //! + it is instantiated with a C `parsers.index`
23 23 //! instance instead of a parents function.
24 24 //! + `MissingAncestors.bases` is a method returning a tuple instead of
25 25 //! a set-valued attribute. We could return a Python set easily if our
26 26 //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165)
27 27 //! is accepted.
28 28 //!
29 29 //! - [`AncestorsIterator`] is the Rust counterpart of the
30 30 //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of
31 31 //! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`]
32 32 //! instance.
33 33 //!
34 34 //! [`LazyAncestors`]: struct.LazyAncestors.html
35 35 //! [`MissingAncestors`]: struct.MissingAncestors.html
36 36 //! [`AncestorsIterator`]: struct.AncestorsIterator.html
37 use crate::revlog::pyindex_to_graph;
37 38 use crate::{
38 39 cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError,
39 40 };
40 41 use cpython::{
41 42 ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult,
42 43 Python, PythonObject, ToPyObject,
43 44 };
44 45 use hg::Revision;
45 46 use hg::{
46 47 AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy,
47 48 MissingAncestors as CoreMissing,
48 49 };
49 50 use std::cell::RefCell;
50 51 use std::collections::HashSet;
51 52
52 53 py_class!(pub class AncestorsIterator |py| {
53 54 data inner: RefCell<Box<CoreIterator<Index>>>;
54 55
55 56 def __next__(&self) -> PyResult<Option<Revision>> {
56 57 match self.inner(py).borrow_mut().next() {
57 58 Some(Err(e)) => Err(GraphError::pynew(py, e)),
58 59 None => Ok(None),
59 60 Some(Ok(r)) => Ok(Some(r)),
60 61 }
61 62 }
62 63
63 64 def __contains__(&self, rev: Revision) -> PyResult<bool> {
64 65 self.inner(py).borrow_mut().contains(rev)
65 66 .map_err(|e| GraphError::pynew(py, e))
66 67 }
67 68
68 69 def __iter__(&self) -> PyResult<Self> {
69 70 Ok(self.clone_ref(py))
70 71 }
71 72
72 73 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
73 74 inclusive: bool) -> PyResult<AncestorsIterator> {
74 75 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
75 76 let ait = CoreIterator::new(
76 Index::new(py, index)?,
77 pyindex_to_graph(py, index)?,
77 78 initvec,
78 79 stoprev,
79 80 inclusive,
80 81 )
81 82 .map_err(|e| GraphError::pynew(py, e))?;
82 83 AncestorsIterator::from_inner(py, ait)
83 84 }
84 85
85 86 });
86 87
87 88 impl AncestorsIterator {
88 89 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
89 90 Self::create_instance(py, RefCell::new(Box::new(ait)))
90 91 }
91 92 }
92 93
93 94 py_class!(pub class LazyAncestors |py| {
94 95 data inner: RefCell<Box<CoreLazy<Index>>>;
95 96
96 97 def __contains__(&self, rev: Revision) -> PyResult<bool> {
97 98 self.inner(py)
98 99 .borrow_mut()
99 100 .contains(rev)
100 101 .map_err(|e| GraphError::pynew(py, e))
101 102 }
102 103
103 104 def __iter__(&self) -> PyResult<AncestorsIterator> {
104 105 AncestorsIterator::from_inner(py, self.inner(py).borrow().iter())
105 106 }
106 107
107 108 def __bool__(&self) -> PyResult<bool> {
108 109 Ok(!self.inner(py).borrow().is_empty())
109 110 }
110 111
111 112 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
112 113 inclusive: bool) -> PyResult<Self> {
113 114 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
114 115
115 116 let lazy =
116 CoreLazy::new(Index::new(py, index)?, initvec, stoprev, inclusive)
117 CoreLazy::new(pyindex_to_graph(py, index)?,
118 initvec, stoprev, inclusive)
117 119 .map_err(|e| GraphError::pynew(py, e))?;
118 120
119 121 Self::create_instance(py, RefCell::new(Box::new(lazy)))
120 122 }
121 123
122 124 });
123 125
124 126 py_class!(pub class MissingAncestors |py| {
125 127 data inner: RefCell<Box<CoreMissing<Index>>>;
126 128
127 129 def __new__(_cls, index: PyObject, bases: PyObject) -> PyResult<MissingAncestors> {
128 130 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
129 let inner = CoreMissing::new(Index::new(py, index)?, bases_vec);
131 let inner = CoreMissing::new(pyindex_to_graph(py, index)?, bases_vec);
130 132 MissingAncestors::create_instance(py, RefCell::new(Box::new(inner)))
131 133 }
132 134
133 135 def hasbases(&self) -> PyResult<bool> {
134 136 Ok(self.inner(py).borrow().has_bases())
135 137 }
136 138
137 139 def addbases(&self, bases: PyObject) -> PyResult<PyObject> {
138 140 let mut inner = self.inner(py).borrow_mut();
139 141 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
140 142 inner.add_bases(bases_vec);
141 143 // cpython doc has examples with PyResult<()> but this gives me
142 144 // the trait `cpython::ToPyObject` is not implemented for `()`
143 145 // so let's return an explicit None
144 146 Ok(py.None())
145 147 }
146 148
147 149 def bases(&self) -> PyResult<HashSet<Revision>> {
148 150 Ok(self.inner(py).borrow().get_bases().clone())
149 151 }
150 152
151 153 def basesheads(&self) -> PyResult<HashSet<Revision>> {
152 154 let inner = self.inner(py).borrow();
153 155 inner.bases_heads().map_err(|e| GraphError::pynew(py, e))
154 156 }
155 157
156 158 def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> {
157 159 let mut inner = self.inner(py).borrow_mut();
158 160 // this is very lame: we convert to a Rust set, update it in place
159 161 // and then convert back to Python, only to have Python remove the
160 162 // excess (thankfully, Python is happy with a list or even an iterator)
161 163 // Leads to improve this:
162 164 // - have the CoreMissing instead do something emit revisions to
163 165 // discard
164 166 // - define a trait for sets of revisions in the core and implement
165 167 // it for a Python set rewrapped with the GIL marker
166 168 let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?;
167 169 inner.remove_ancestors_from(&mut revs_pyset)
168 170 .map_err(|e| GraphError::pynew(py, e))?;
169 171
170 172 // convert as Python list
171 173 let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity(
172 174 revs_pyset.len());
173 175 for rev in revs_pyset {
174 176 remaining_pyint_vec.push(rev.to_py_object(py).into_object());
175 177 }
176 178 let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice());
177 179 revs.call_method(py, "intersection_update", (remaining_pylist, ), None)
178 180 }
179 181
180 182 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> {
181 183 let mut inner = self.inner(py).borrow_mut();
182 184 let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?;
183 185 let missing_vec = match inner.missing_ancestors(revs_vec) {
184 186 Ok(missing) => missing,
185 187 Err(e) => {
186 188 return Err(GraphError::pynew(py, e));
187 189 }
188 190 };
189 191 // convert as Python list
190 192 let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity(
191 193 missing_vec.len());
192 194 for rev in missing_vec {
193 195 missing_pyint_vec.push(rev.to_py_object(py).into_object());
194 196 }
195 197 Ok(PyList::new(py, missing_pyint_vec.as_slice()))
196 198 }
197 199 });
198 200
199 201 /// Create the module, with __package__ given from parent
200 202 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
201 203 let dotted_name = &format!("{}.ancestor", package);
202 204 let m = PyModule::new(py, dotted_name)?;
203 205 m.add(py, "__package__", package)?;
204 206 m.add(
205 207 py,
206 208 "__doc__",
207 209 "Generic DAG ancestor algorithms - Rust implementation",
208 210 )?;
209 211 m.add_class::<AncestorsIterator>(py)?;
210 212 m.add_class::<LazyAncestors>(py)?;
211 213 m.add_class::<MissingAncestors>(py)?;
212 214
213 215 let sys = PyModule::import(py, "sys")?;
214 216 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
215 217 sys_modules.set_item(py, dotted_name, &m)?;
216 218 // Example C code (see pyexpat.c and import.c) will "give away the
217 219 // reference", but we won't because it will be consumed once the
218 220 // Rust PyObject is dropped.
219 221 Ok(m)
220 222 }
@@ -1,53 +1,53 b''
1 1 // dagops.rs
2 2 //
3 3 // Copyright 2019 Georges Racinet <georges.racinet@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::dagops` module provided by the
9 9 //! `hg-core` package.
10 10 //!
11 11 //! From Python, this will be seen as `mercurial.rustext.dagop`
12 use crate::{
13 cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError,
14 };
12 use crate::{conversion::rev_pyiter_collect, exceptions::GraphError};
15 13 use cpython::{PyDict, PyModule, PyObject, PyResult, Python};
16 14 use hg::dagops;
17 15 use hg::Revision;
18 16 use std::collections::HashSet;
19 17
18 use crate::revlog::pyindex_to_graph;
19
20 20 /// Using the the `index`, return heads out of any Python iterable of Revisions
21 21 ///
22 22 /// This is the Rust counterpart for `mercurial.dagop.headrevs`
23 23 pub fn headrevs(
24 24 py: Python,
25 25 index: PyObject,
26 26 revs: PyObject,
27 27 ) -> PyResult<HashSet<Revision>> {
28 28 let mut as_set: HashSet<Revision> = rev_pyiter_collect(py, &revs)?;
29 dagops::retain_heads(&Index::new(py, index)?, &mut as_set)
29 dagops::retain_heads(&pyindex_to_graph(py, index)?, &mut as_set)
30 30 .map_err(|e| GraphError::pynew(py, e))?;
31 31 Ok(as_set)
32 32 }
33 33
34 34 /// Create the module, with `__package__` given from parent
35 35 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
36 36 let dotted_name = &format!("{}.dagop", package);
37 37 let m = PyModule::new(py, dotted_name)?;
38 38 m.add(py, "__package__", package)?;
39 39 m.add(py, "__doc__", "DAG operations - Rust implementation")?;
40 40 m.add(
41 41 py,
42 42 "headrevs",
43 43 py_fn!(py, headrevs(index: PyObject, revs: PyObject)),
44 44 )?;
45 45
46 46 let sys = PyModule::import(py, "sys")?;
47 47 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
48 48 sys_modules.set_item(py, dotted_name, &m)?;
49 49 // Example C code (see pyexpat.c and import.c) will "give away the
50 50 // reference", but we won't because it will be consumed once the
51 51 // Rust PyObject is dropped.
52 52 Ok(m)
53 53 }
@@ -1,159 +1,161 b''
1 1 // discovery.rs
2 2 //
3 3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Bindings for the `hg::discovery` module provided by the
9 9 //! `hg-core` crate. From Python, this will be seen as `rustext.discovery`
10 10 //!
11 11 //! # Classes visible from Python:
12 12 //! - [`PartialDiscover`] is the Rust implementation of
13 13 //! `mercurial.setdiscovery.partialdiscovery`.
14 14
15 15 use crate::{
16 16 cindex::Index, conversion::rev_pyiter_collect, exceptions::GraphError,
17 17 };
18 18 use cpython::{
19 19 ObjectProtocol, PyDict, PyModule, PyObject, PyResult, PyTuple, Python,
20 20 PythonObject, ToPyObject,
21 21 };
22 22 use hg::discovery::PartialDiscovery as CorePartialDiscovery;
23 23 use hg::Revision;
24 24 use std::collections::HashSet;
25 25
26 26 use std::cell::RefCell;
27 27
28 use crate::revlog::pyindex_to_graph;
29
28 30 py_class!(pub class PartialDiscovery |py| {
29 31 data inner: RefCell<Box<CorePartialDiscovery<Index>>>;
30 32
31 33 // `_respectsize` is currently only here to replicate the Python API and
32 34 // will be used in future patches inside methods that are yet to be
33 35 // implemented.
34 36 def __new__(
35 37 _cls,
36 38 repo: PyObject,
37 39 targetheads: PyObject,
38 40 respectsize: bool,
39 41 randomize: bool = true
40 42 ) -> PyResult<PartialDiscovery> {
41 43 let index = repo.getattr(py, "changelog")?.getattr(py, "index")?;
42 44 Self::create_instance(
43 45 py,
44 46 RefCell::new(Box::new(CorePartialDiscovery::new(
45 Index::new(py, index)?,
47 pyindex_to_graph(py, index)?,
46 48 rev_pyiter_collect(py, &targetheads)?,
47 49 respectsize,
48 50 randomize,
49 51 )))
50 52 )
51 53 }
52 54
53 55 def addcommons(&self, commons: PyObject) -> PyResult<PyObject> {
54 56 let mut inner = self.inner(py).borrow_mut();
55 57 let commons_vec: Vec<Revision> = rev_pyiter_collect(py, &commons)?;
56 58 inner.add_common_revisions(commons_vec)
57 59 .map_err(|e| GraphError::pynew(py, e))?;
58 60 Ok(py.None())
59 61 }
60 62
61 63 def addmissings(&self, missings: PyObject) -> PyResult<PyObject> {
62 64 let mut inner = self.inner(py).borrow_mut();
63 65 let missings_vec: Vec<Revision> = rev_pyiter_collect(py, &missings)?;
64 66 inner.add_missing_revisions(missings_vec)
65 67 .map_err(|e| GraphError::pynew(py, e))?;
66 68 Ok(py.None())
67 69 }
68 70
69 71 def addinfo(&self, sample: PyObject) -> PyResult<PyObject> {
70 72 let mut missing: Vec<Revision> = Vec::new();
71 73 let mut common: Vec<Revision> = Vec::new();
72 74 for info in sample.iter(py)? { // info is a pair (Revision, bool)
73 75 let mut revknown = info?.iter(py)?;
74 76 let rev: Revision = revknown.next().unwrap()?.extract(py)?;
75 77 let known: bool = revknown.next().unwrap()?.extract(py)?;
76 78 if known {
77 79 common.push(rev);
78 80 } else {
79 81 missing.push(rev);
80 82 }
81 83 }
82 84 let mut inner = self.inner(py).borrow_mut();
83 85 inner.add_common_revisions(common)
84 86 .map_err(|e| GraphError::pynew(py, e))?;
85 87 inner.add_missing_revisions(missing)
86 88 .map_err(|e| GraphError::pynew(py, e))?;
87 89 Ok(py.None())
88 90 }
89 91
90 92 def hasinfo(&self) -> PyResult<bool> {
91 93 Ok(self.inner(py).borrow().has_info())
92 94 }
93 95
94 96 def iscomplete(&self) -> PyResult<bool> {
95 97 Ok(self.inner(py).borrow().is_complete())
96 98 }
97 99
98 100 def stats(&self) -> PyResult<PyDict> {
99 101 let stats = self.inner(py).borrow().stats();
100 102 let as_dict: PyDict = PyDict::new(py);
101 103 as_dict.set_item(py, "undecided",
102 104 stats.undecided.map(
103 105 |l| l.to_py_object(py).into_object())
104 106 .unwrap_or_else(|| py.None()))?;
105 107 Ok(as_dict)
106 108 }
107 109
108 110 def commonheads(&self) -> PyResult<HashSet<Revision>> {
109 111 self.inner(py).borrow().common_heads()
110 112 .map_err(|e| GraphError::pynew(py, e))
111 113 }
112 114
113 115 def takefullsample(&self, _headrevs: PyObject,
114 116 size: usize) -> PyResult<PyObject> {
115 117 let mut inner = self.inner(py).borrow_mut();
116 118 let sample = inner.take_full_sample(size)
117 119 .map_err(|e| GraphError::pynew(py, e))?;
118 120 let as_vec: Vec<PyObject> = sample
119 121 .iter()
120 122 .map(|rev| rev.to_py_object(py).into_object())
121 123 .collect();
122 124 Ok(PyTuple::new(py, as_vec.as_slice()).into_object())
123 125 }
124 126
125 127 def takequicksample(&self, headrevs: PyObject,
126 128 size: usize) -> PyResult<PyObject> {
127 129 let mut inner = self.inner(py).borrow_mut();
128 130 let revsvec: Vec<Revision> = rev_pyiter_collect(py, &headrevs)?;
129 131 let sample = inner.take_quick_sample(revsvec, size)
130 132 .map_err(|e| GraphError::pynew(py, e))?;
131 133 let as_vec: Vec<PyObject> = sample
132 134 .iter()
133 135 .map(|rev| rev.to_py_object(py).into_object())
134 136 .collect();
135 137 Ok(PyTuple::new(py, as_vec.as_slice()).into_object())
136 138 }
137 139
138 140 });
139 141
140 142 /// Create the module, with __package__ given from parent
141 143 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
142 144 let dotted_name = &format!("{}.discovery", package);
143 145 let m = PyModule::new(py, dotted_name)?;
144 146 m.add(py, "__package__", package)?;
145 147 m.add(
146 148 py,
147 149 "__doc__",
148 150 "Discovery of common node sets - Rust implementation",
149 151 )?;
150 152 m.add_class::<PartialDiscovery>(py)?;
151 153
152 154 let sys = PyModule::import(py, "sys")?;
153 155 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
154 156 sys_modules.set_item(py, dotted_name, &m)?;
155 157 // Example C code (see pyexpat.c and import.c) will "give away the
156 158 // reference", but we won't because it will be consumed once the
157 159 // Rust PyObject is dropped.
158 160 Ok(m)
159 161 }
@@ -1,81 +1,82 b''
1 1 // lib.rs
2 2 //
3 3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Python bindings of `hg-core` objects using the `cpython` crate.
9 9 //! Once compiled, the resulting single shared library object can be placed in
10 10 //! the `mercurial` package directly as `rustext.so` or `rustext.dll`.
11 11 //! It holds several modules, so that from the point of view of Python,
12 12 //! it behaves as the `cext` package.
13 13 //!
14 14 //! Example:
15 15 //!
16 16 //! ```text
17 17 //! >>> from mercurial.rustext import ancestor
18 18 //! >>> ancestor.__doc__
19 19 //! 'Generic DAG ancestor algorithms - Rust implementation'
20 20 //! ```
21 21
22 22 /// This crate uses nested private macros, `extern crate` is still needed in
23 23 /// 2018 edition.
24 24 #[macro_use]
25 25 extern crate cpython;
26 26
27 27 pub mod ancestors;
28 28 mod cindex;
29 29 mod conversion;
30 30 #[macro_use]
31 31 pub mod ref_sharing;
32 32 pub mod dagops;
33 33 pub mod dirstate;
34 34 pub mod discovery;
35 35 pub mod exceptions;
36 36 pub mod filepatterns;
37 37 pub mod parsers;
38 pub mod revlog;
38 39 pub mod utils;
39 40
40 41 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
41 42 m.add(
42 43 py,
43 44 "__doc__",
44 45 "Mercurial core concepts - Rust implementation",
45 46 )?;
46 47
47 48 let dotted_name: String = m.get(py, "__name__")?.extract(py)?;
48 49 m.add(py, "ancestor", ancestors::init_module(py, &dotted_name)?)?;
49 50 m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?;
50 51 m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?;
51 52 m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?;
52 53 m.add(
53 54 py,
54 55 "filepatterns",
55 56 filepatterns::init_module(py, &dotted_name)?,
56 57 )?;
57 58 m.add(
58 59 py,
59 60 "parsers",
60 61 parsers::init_parsers_module(py, &dotted_name)?,
61 62 )?;
62 63 m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?;
63 64 m.add(
64 65 py,
65 66 "PatternFileError",
66 67 py.get_type::<exceptions::PatternFileError>(),
67 68 )?;
68 69 m.add(
69 70 py,
70 71 "PatternError",
71 72 py.get_type::<exceptions::PatternError>(),
72 73 )?;
73 74 Ok(())
74 75 });
75 76
76 77 #[cfg(not(any(feature = "python27-bin", feature = "python3-bin")))]
77 78 #[test]
78 79 #[ignore]
79 80 fn libpython_must_be_linked_to_run_tests() {
80 81 // stub function to tell that some tests wouldn't run
81 82 }
General Comments 0
You need to be logged in to leave comments. Login now