##// END OF EJS Templates
rust-filepatterns: add `rust-cpython` bindings for `filepatterns`...
Raphaël Gomès -
r42515:94f3a73b default
parent child Browse files
Show More
@@ -12,8 +12,8 b''
12 //! existing Python exceptions if appropriate.
12 //! existing Python exceptions if appropriate.
13 //!
13 //!
14 //! [`GraphError`]: struct.GraphError.html
14 //! [`GraphError`]: struct.GraphError.html
15 use cpython::exc::ValueError;
15 use cpython::exc::{ValueError, RuntimeError};
16 use cpython::{PyErr, Python};
16 use cpython::{PyErr, Python, exc};
17 use hg;
17 use hg;
18
18
19 py_exception!(rustext, GraphError, ValueError);
19 py_exception!(rustext, GraphError, ValueError);
@@ -28,9 +28,43 b' impl GraphError {'
28 match py
28 match py
29 .import("mercurial.error")
29 .import("mercurial.error")
30 .and_then(|m| m.get(py, "WdirUnsupported"))
30 .and_then(|m| m.get(py, "WdirUnsupported"))
31 {
31 {
32 Err(e) => e,
32 Err(e) => e,
33 Ok(cls) => PyErr::from_instance(py, cls),
33 Ok(cls) => PyErr::from_instance(py, cls),
34 }
35 }
36 }
37 }
38 }
39
40 py_exception!(rustext, PatternError, RuntimeError);
41 py_exception!(rustext, PatternFileError, RuntimeError);
42
43 impl PatternError {
44 pub fn pynew(py: Python, inner: hg::PatternError) -> PyErr {
45 match inner {
46 hg::PatternError::UnsupportedSyntax(m) => {
47 PatternError::new(py, ("PatternError", m))
48 }
49 }
50 }
51 }
52
53
54 impl PatternFileError {
55 pub fn pynew(py: Python, inner: hg::PatternFileError) -> PyErr {
56 match inner {
57 hg::PatternFileError::IO(e) => {
58 let value = (
59 e.raw_os_error().unwrap_or(2),
60 e.to_string()
61 );
62 PyErr::new::<exc::IOError, _>(py, value)
63 }
64 hg::PatternFileError::Pattern(e, l) => {
65 match e {
66 hg::PatternError::UnsupportedSyntax(m) =>
67 PatternFileError::new(py, ("PatternFileError", m, l))
34 }
68 }
35 }
69 }
36 }
70 }
@@ -1,220 +1,115 b''
1 // ancestors.rs
1 // filepatterns.rs
2 //
2 //
3 // Copyright 2018 Georges Racinet <gracinet@anybox.fr>
3 // Copyright 2019, Georges Racinet <gracinet@anybox.fr>,
4 // Raphaël Gomès <rgomes@octobus.net>
4 //
5 //
5 // This software may be used and distributed according to the terms of the
6 // 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 // GNU General Public License version 2 or any later version.
7
8
8 //! Bindings for the `hg::ancestors` module provided by the
9 //! Bindings for the `hg::filepatterns` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as `rustext.ancestor`
10 //! `hg-core` crate. From Python, this will be seen as `rustext.filepatterns`
10 //! and can be used as replacement for the the pure `ancestor` Python module.
11 //! and can be used as replacement for the the pure `filepatterns` Python module.
11 //!
12 //! # Classes visible from Python:
13 //! - [`LazyAncestors`] is the Rust implementation of
14 //! `mercurial.ancestor.lazyancestors`. The only difference is that it is
15 //! instantiated with a C `parsers.index` instance instead of a parents
16 //! function.
17 //!
18 //! - [`MissingAncestors`] is the Rust implementation of
19 //! `mercurial.ancestor.incrementalmissingancestors`.
20 //!
12 //!
21 //! API differences:
22 //! + it is instantiated with a C `parsers.index`
23 //! instance instead of a parents function.
24 //! + `MissingAncestors.bases` is a method returning a tuple instead of
25 //! a set-valued attribute. We could return a Python set easily if our
26 //! [PySet PR](https://github.com/dgrunwald/rust-cpython/pull/165)
27 //! is accepted.
28 //!
29 //! - [`AncestorsIterator`] is the Rust counterpart of the
30 //! `ancestor._lazyancestorsiter` Python generator. From Python, instances of
31 //! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`]
32 //! instance.
33 //!
34 //! [`LazyAncestors`]: struct.LazyAncestors.html
35 //! [`MissingAncestors`]: struct.MissingAncestors.html
36 //! [`AncestorsIterator`]: struct.AncestorsIterator.html
37 use crate::conversion::{py_set, rev_pyiter_collect};
38 use cindex::Index;
39 use cpython::{
13 use cpython::{
40 ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult,
14 exc, PyDict, PyErr, PyModule, PyResult, PyString, PyTuple, Python,
41 Python, PythonObject, ToPyObject,
15 ToPyObject,
42 };
16 };
43 use exceptions::GraphError;
17 use hg::{build_single_regex, read_pattern_file, PatternTuple};
44 use hg::Revision;
18 use exceptions::{
45 use hg::{
19 PatternError,
46 AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy,
20 PatternFileError,
47 MissingAncestors as CoreMissing,
48 };
21 };
49 use std::cell::RefCell;
50 use std::collections::HashSet;
51
22
52 py_class!(pub class AncestorsIterator |py| {
23 /// Rust does not like functions with different return signatures.
53 data inner: RefCell<Box<CoreIterator<Index>>>;
24 /// The 3-tuple version is always returned by the hg-core function,
54
25 /// the (potential) conversion is handled at this level since it is not likely
55 def __next__(&self) -> PyResult<Option<Revision>> {
26 /// to have any measurable impact on performance.
56 match self.inner(py).borrow_mut().next() {
27 ///
57 Some(Err(e)) => Err(GraphError::pynew(py, e)),
28 /// The Python implementation passes a function reference for `warn` instead
58 None => Ok(None),
29 /// of a boolean that is used to emit warnings while parsing. The Rust
59 Some(Ok(r)) => Ok(Some(r)),
30 /// implementation chooses to accumulate the warnings and propagate them to
31 /// Python upon completion. See the `readpatternfile` function in `match.py`
32 /// for more details.
33 fn read_pattern_file_wrapper(
34 py: Python,
35 file_path: String,
36 warn: bool,
37 source_info: bool,
38 ) -> PyResult<PyTuple> {
39 match read_pattern_file(file_path, warn) {
40 Ok((patterns, warnings)) => {
41 if source_info {
42 return Ok((patterns, warnings).to_py_object(py));
43 }
44 let itemgetter = |x: &PatternTuple| x.0.to_py_object(py);
45 let results: Vec<PyString> =
46 patterns.iter().map(itemgetter).collect();
47 Ok((results, warnings).to_py_object(py))
60 }
48 }
61 }
49 Err(e) => Err(PatternFileError::pynew(py, e)),
62
63 def __contains__(&self, rev: Revision) -> PyResult<bool> {
64 self.inner(py).borrow_mut().contains(rev)
65 .map_err(|e| GraphError::pynew(py, e))
66 }
67
68 def __iter__(&self) -> PyResult<Self> {
69 Ok(self.clone_ref(py))
70 }
71
72 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
73 inclusive: bool) -> PyResult<AncestorsIterator> {
74 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
75 let ait = CoreIterator::new(
76 Index::new(py, index)?,
77 initvec,
78 stoprev,
79 inclusive,
80 )
81 .map_err(|e| GraphError::pynew(py, e))?;
82 AncestorsIterator::from_inner(py, ait)
83 }
84
85 });
86
87 impl AncestorsIterator {
88 pub fn from_inner(py: Python, ait: CoreIterator<Index>) -> PyResult<Self> {
89 Self::create_instance(py, RefCell::new(Box::new(ait)))
90 }
50 }
91 }
51 }
92
52
93 py_class!(pub class LazyAncestors |py| {
53 fn build_single_regex_wrapper(
94 data inner: RefCell<Box<CoreLazy<Index>>>;
54 py: Python,
95
55 kind: String,
96 def __contains__(&self, rev: Revision) -> PyResult<bool> {
56 pat: String,
97 self.inner(py)
57 globsuffix: String,
98 .borrow_mut()
58 ) -> PyResult<PyString> {
99 .contains(rev)
59 match build_single_regex(
100 .map_err(|e| GraphError::pynew(py, e))
60 kind.as_ref(),
101 }
61 pat.as_bytes(),
102
62 globsuffix.as_bytes(),
103 def __iter__(&self) -> PyResult<AncestorsIterator> {
63 ) {
104 AncestorsIterator::from_inner(py, self.inner(py).borrow().iter())
64 Ok(regex) => match String::from_utf8(regex) {
105 }
65 Ok(regex) => Ok(regex.to_py_object(py)),
106
66 Err(e) => Err(PyErr::new::<exc::UnicodeDecodeError, _>(
107 def __bool__(&self) -> PyResult<bool> {
67 py,
108 Ok(!self.inner(py).borrow().is_empty())
68 e.to_string(),
69 )),
70 },
71 Err(e) => Err(PatternError::pynew(py, e)),
109 }
72 }
110
73 }
111 def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision,
112 inclusive: bool) -> PyResult<Self> {
113 let initvec: Vec<Revision> = rev_pyiter_collect(py, &initrevs)?;
114
115 let lazy =
116 CoreLazy::new(Index::new(py, index)?, initvec, stoprev, inclusive)
117 .map_err(|e| GraphError::pynew(py, e))?;
118
119 Self::create_instance(py, RefCell::new(Box::new(lazy)))
120 }
121
122 });
123
124 py_class!(pub class MissingAncestors |py| {
125 data inner: RefCell<Box<CoreMissing<Index>>>;
126
127 def __new__(_cls, index: PyObject, bases: PyObject) -> PyResult<MissingAncestors> {
128 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
129 let inner = CoreMissing::new(Index::new(py, index)?, bases_vec);
130 MissingAncestors::create_instance(py, RefCell::new(Box::new(inner)))
131 }
132
133 def hasbases(&self) -> PyResult<bool> {
134 Ok(self.inner(py).borrow().has_bases())
135 }
136
137 def addbases(&self, bases: PyObject) -> PyResult<PyObject> {
138 let mut inner = self.inner(py).borrow_mut();
139 let bases_vec: Vec<Revision> = rev_pyiter_collect(py, &bases)?;
140 inner.add_bases(bases_vec);
141 // cpython doc has examples with PyResult<()> but this gives me
142 // the trait `cpython::ToPyObject` is not implemented for `()`
143 // so let's return an explicit None
144 Ok(py.None())
145 }
146
74
147 def bases(&self) -> PyResult<PyObject> {
75 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
148 py_set(py, self.inner(py).borrow().get_bases())
76 let dotted_name = &format!("{}.filepatterns", package);
149 }
77 let m = PyModule::new(py, dotted_name)?;
150
151 def basesheads(&self) -> PyResult<PyObject> {
152 let inner = self.inner(py).borrow();
153 py_set(py, &inner.bases_heads().map_err(|e| GraphError::pynew(py, e))?)
154 }
155
156 def removeancestorsfrom(&self, revs: PyObject) -> PyResult<PyObject> {
157 let mut inner = self.inner(py).borrow_mut();
158 // this is very lame: we convert to a Rust set, update it in place
159 // and then convert back to Python, only to have Python remove the
160 // excess (thankfully, Python is happy with a list or even an iterator)
161 // Leads to improve this:
162 // - have the CoreMissing instead do something emit revisions to
163 // discard
164 // - define a trait for sets of revisions in the core and implement
165 // it for a Python set rewrapped with the GIL marker
166 let mut revs_pyset: HashSet<Revision> = rev_pyiter_collect(py, &revs)?;
167 inner.remove_ancestors_from(&mut revs_pyset)
168 .map_err(|e| GraphError::pynew(py, e))?;
169
78
170 // convert as Python list
171 let mut remaining_pyint_vec: Vec<PyObject> = Vec::with_capacity(
172 revs_pyset.len());
173 for rev in revs_pyset {
174 remaining_pyint_vec.push(rev.to_py_object(py).into_object());
175 }
176 let remaining_pylist = PyList::new(py, remaining_pyint_vec.as_slice());
177 revs.call_method(py, "intersection_update", (remaining_pylist, ), None)
178 }
179
180 def missingancestors(&self, revs: PyObject) -> PyResult<PyList> {
181 let mut inner = self.inner(py).borrow_mut();
182 let revs_vec: Vec<Revision> = rev_pyiter_collect(py, &revs)?;
183 let missing_vec = match inner.missing_ancestors(revs_vec) {
184 Ok(missing) => missing,
185 Err(e) => {
186 return Err(GraphError::pynew(py, e));
187 }
188 };
189 // convert as Python list
190 let mut missing_pyint_vec: Vec<PyObject> = Vec::with_capacity(
191 missing_vec.len());
192 for rev in missing_vec {
193 missing_pyint_vec.push(rev.to_py_object(py).into_object());
194 }
195 Ok(PyList::new(py, missing_pyint_vec.as_slice()))
196 }
197 });
198
199 /// Create the module, with __package__ given from parent
200 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
201 let dotted_name = &format!("{}.ancestor", package);
202 let m = PyModule::new(py, dotted_name)?;
203 m.add(py, "__package__", package)?;
79 m.add(py, "__package__", package)?;
204 m.add(
80 m.add(
205 py,
81 py,
206 "__doc__",
82 "__doc__",
207 "Generic DAG ancestor algorithms - Rust implementation",
83 "Patterns files parsing - Rust implementation",
208 )?;
84 )?;
209 m.add_class::<AncestorsIterator>(py)?;
85 m.add(
210 m.add_class::<LazyAncestors>(py)?;
86 py,
211 m.add_class::<MissingAncestors>(py)?;
87 "build_single_regex",
88 py_fn!(
89 py,
90 build_single_regex_wrapper(
91 kind: String,
92 pat: String,
93 globsuffix: String
94 )
95 ),
96 )?;
97 m.add(
98 py,
99 "read_pattern_file",
100 py_fn!(
101 py,
102 read_pattern_file_wrapper(
103 file_path: String,
104 warn: bool,
105 source_info: bool
106 )
107 ),
108 )?;
212
109
213 let sys = PyModule::import(py, "sys")?;
110 let sys = PyModule::import(py, "sys")?;
214 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
111 let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
215 sys_modules.set_item(py, dotted_name, &m)?;
112 sys_modules.set_item(py, dotted_name, &m)?;
216 // Example C code (see pyexpat.c and import.c) will "give away the
113
217 // reference", but we won't because it will be consumed once the
218 // Rust PyObject is dropped.
219 Ok(m)
114 Ok(m)
220 }
115 }
@@ -32,6 +32,7 b' pub mod dagops;'
32 pub mod discovery;
32 pub mod discovery;
33 pub mod exceptions;
33 pub mod exceptions;
34 pub mod dirstate;
34 pub mod dirstate;
35 pub mod filepatterns;
35
36
36 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
37 py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| {
37 m.add(
38 m.add(
@@ -45,6 +46,9 b' py_module_initializer!(rustext, initrust'
45 m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?;
46 m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?;
46 m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?;
47 m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?;
47 m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?;
48 m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?;
49 m.add(py, "filepatterns", filepatterns::init_module(py, &dotted_name)?)?;
48 m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?;
50 m.add(py, "GraphError", py.get_type::<exceptions::GraphError>())?;
51 m.add(py, "PatternFileError", py.get_type::<exceptions::PatternFileError>())?;
52 m.add(py, "PatternError", py.get_type::<exceptions::PatternError>())?;
49 Ok(())
53 Ok(())
50 });
54 });
General Comments 0
You need to be logged in to leave comments. Login now