# HG changeset patch # User Raphaël Gomès # Date 2019-05-17 13:36:29 # Node ID 94f3a73b66726a0a660ea6869b2b97b5c1050908 # Parent e8f3740cc067772e0f5f794d1cb54687cc2e349b rust-filepatterns: add `rust-cpython` bindings for `filepatterns` This change adds the `rust-cpython` interface for top-level functions and exceptions in the filepatterns module. Contrary to the Python implementation, this tries to have finer-grained exceptions to allow for better readability and flow control down the line. Differential Revision: https://phab.mercurial-scm.org/D6272 diff --git a/rust/hg-cpython/src/exceptions.rs b/rust/hg-cpython/src/exceptions.rs --- a/rust/hg-cpython/src/exceptions.rs +++ b/rust/hg-cpython/src/exceptions.rs @@ -12,8 +12,8 @@ //! existing Python exceptions if appropriate. //! //! [`GraphError`]: struct.GraphError.html -use cpython::exc::ValueError; -use cpython::{PyErr, Python}; +use cpython::exc::{ValueError, RuntimeError}; +use cpython::{PyErr, Python, exc}; use hg; py_exception!(rustext, GraphError, ValueError); @@ -28,9 +28,43 @@ impl GraphError { match py .import("mercurial.error") .and_then(|m| m.get(py, "WdirUnsupported")) - { - Err(e) => e, - Ok(cls) => PyErr::from_instance(py, cls), + { + Err(e) => e, + Ok(cls) => PyErr::from_instance(py, cls), + } + } + } + } +} + +py_exception!(rustext, PatternError, RuntimeError); +py_exception!(rustext, PatternFileError, RuntimeError); + +impl PatternError { + pub fn pynew(py: Python, inner: hg::PatternError) -> PyErr { + match inner { + hg::PatternError::UnsupportedSyntax(m) => { + PatternError::new(py, ("PatternError", m)) + } + } + } +} + + +impl PatternFileError { + pub fn pynew(py: Python, inner: hg::PatternFileError) -> PyErr { + match inner { + hg::PatternFileError::IO(e) => { + let value = ( + e.raw_os_error().unwrap_or(2), + e.to_string() + ); + PyErr::new::(py, value) + } + hg::PatternFileError::Pattern(e, l) => { + match e { + hg::PatternError::UnsupportedSyntax(m) => + PatternFileError::new(py, ("PatternFileError", m, l)) } } } diff --git a/rust/hg-cpython/src/ancestors.rs b/rust/hg-cpython/src/filepatterns.rs copy from rust/hg-cpython/src/ancestors.rs copy to rust/hg-cpython/src/filepatterns.rs --- a/rust/hg-cpython/src/ancestors.rs +++ b/rust/hg-cpython/src/filepatterns.rs @@ -1,220 +1,115 @@ -// ancestors.rs +// filepatterns.rs // -// Copyright 2018 Georges Racinet +// Copyright 2019, Georges Racinet , +// Raphaël Gomès // // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -//! Bindings for the `hg::ancestors` module provided by the -//! `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. -//! -//! # Classes visible from Python: -//! - [`LazyAncestors`] is the Rust implementation of -//! `mercurial.ancestor.lazyancestors`. The only difference is that it is -//! instantiated with a C `parsers.index` instance instead of a parents -//! function. -//! -//! - [`MissingAncestors`] is the Rust implementation of -//! `mercurial.ancestor.incrementalmissingancestors`. +//! Bindings for the `hg::filepatterns` module provided by the +//! `hg-core` crate. From Python, this will be seen as `rustext.filepatterns` +//! and can be used as replacement for the the pure `filepatterns` Python module. //! -//! 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. -//! -//! - [`AncestorsIterator`] is the Rust counterpart of the -//! `ancestor._lazyancestorsiter` Python generator. From Python, instances of -//! this should be mainly obtained by calling `iter()` on a [`LazyAncestors`] -//! instance. -//! -//! [`LazyAncestors`]: struct.LazyAncestors.html -//! [`MissingAncestors`]: struct.MissingAncestors.html -//! [`AncestorsIterator`]: struct.AncestorsIterator.html -use crate::conversion::{py_set, rev_pyiter_collect}; -use cindex::Index; use cpython::{ - ObjectProtocol, PyClone, PyDict, PyList, PyModule, PyObject, PyResult, - Python, PythonObject, ToPyObject, + exc, PyDict, PyErr, PyModule, PyResult, PyString, PyTuple, Python, + ToPyObject, }; -use exceptions::GraphError; -use hg::Revision; -use hg::{ - AncestorsIterator as CoreIterator, LazyAncestors as CoreLazy, - MissingAncestors as CoreMissing, +use hg::{build_single_regex, read_pattern_file, PatternTuple}; +use exceptions::{ + PatternError, + PatternFileError, }; -use std::cell::RefCell; -use std::collections::HashSet; -py_class!(pub class AncestorsIterator |py| { - data inner: RefCell>>; - - def __next__(&self) -> PyResult> { - match self.inner(py).borrow_mut().next() { - Some(Err(e)) => Err(GraphError::pynew(py, e)), - None => Ok(None), - Some(Ok(r)) => Ok(Some(r)), +/// Rust does not like functions with different return signatures. +/// The 3-tuple version is always returned by the hg-core function, +/// the (potential) conversion is handled at this level since it is not likely +/// to have any measurable impact on performance. +/// +/// The Python implementation passes a function reference for `warn` instead +/// of a boolean that is used to emit warnings while parsing. The Rust +/// implementation chooses to accumulate the warnings and propagate them to +/// Python upon completion. See the `readpatternfile` function in `match.py` +/// for more details. +fn read_pattern_file_wrapper( + py: Python, + file_path: String, + warn: bool, + source_info: bool, +) -> PyResult { + match read_pattern_file(file_path, warn) { + Ok((patterns, warnings)) => { + if source_info { + return Ok((patterns, warnings).to_py_object(py)); + } + let itemgetter = |x: &PatternTuple| x.0.to_py_object(py); + let results: Vec = + patterns.iter().map(itemgetter).collect(); + Ok((results, warnings).to_py_object(py)) } - } - - def __contains__(&self, rev: Revision) -> PyResult { - self.inner(py).borrow_mut().contains(rev) - .map_err(|e| GraphError::pynew(py, e)) - } - - def __iter__(&self) -> PyResult { - Ok(self.clone_ref(py)) - } - - def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision, - inclusive: bool) -> PyResult { - let initvec: Vec = rev_pyiter_collect(py, &initrevs)?; - let ait = CoreIterator::new( - Index::new(py, index)?, - initvec, - stoprev, - inclusive, - ) - .map_err(|e| GraphError::pynew(py, e))?; - AncestorsIterator::from_inner(py, ait) - } - -}); - -impl AncestorsIterator { - pub fn from_inner(py: Python, ait: CoreIterator) -> PyResult { - Self::create_instance(py, RefCell::new(Box::new(ait))) + Err(e) => Err(PatternFileError::pynew(py, e)), } } -py_class!(pub class LazyAncestors |py| { - data inner: RefCell>>; - - def __contains__(&self, rev: Revision) -> PyResult { - self.inner(py) - .borrow_mut() - .contains(rev) - .map_err(|e| GraphError::pynew(py, e)) - } - - def __iter__(&self) -> PyResult { - AncestorsIterator::from_inner(py, self.inner(py).borrow().iter()) - } - - def __bool__(&self) -> PyResult { - Ok(!self.inner(py).borrow().is_empty()) +fn build_single_regex_wrapper( + py: Python, + kind: String, + pat: String, + globsuffix: String, +) -> PyResult { + match build_single_regex( + kind.as_ref(), + pat.as_bytes(), + globsuffix.as_bytes(), + ) { + Ok(regex) => match String::from_utf8(regex) { + Ok(regex) => Ok(regex.to_py_object(py)), + Err(e) => Err(PyErr::new::( + py, + e.to_string(), + )), + }, + Err(e) => Err(PatternError::pynew(py, e)), } - - def __new__(_cls, index: PyObject, initrevs: PyObject, stoprev: Revision, - inclusive: bool) -> PyResult { - let initvec: Vec = rev_pyiter_collect(py, &initrevs)?; - - let lazy = - CoreLazy::new(Index::new(py, index)?, initvec, stoprev, inclusive) - .map_err(|e| GraphError::pynew(py, e))?; - - Self::create_instance(py, RefCell::new(Box::new(lazy))) - } - -}); - -py_class!(pub class MissingAncestors |py| { - data inner: RefCell>>; - - def __new__(_cls, index: PyObject, bases: PyObject) -> PyResult { - let bases_vec: Vec = rev_pyiter_collect(py, &bases)?; - let inner = CoreMissing::new(Index::new(py, index)?, bases_vec); - MissingAncestors::create_instance(py, RefCell::new(Box::new(inner))) - } - - def hasbases(&self) -> PyResult { - Ok(self.inner(py).borrow().has_bases()) - } - - def addbases(&self, bases: PyObject) -> PyResult { - let mut inner = self.inner(py).borrow_mut(); - let bases_vec: Vec = 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()) - } +} - def bases(&self) -> PyResult { - py_set(py, self.inner(py).borrow().get_bases()) - } - - def basesheads(&self) -> PyResult { - let inner = self.inner(py).borrow(); - py_set(py, &inner.bases_heads().map_err(|e| GraphError::pynew(py, e))?) - } - - def removeancestorsfrom(&self, revs: PyObject) -> PyResult { - 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 = rev_pyiter_collect(py, &revs)?; - inner.remove_ancestors_from(&mut revs_pyset) - .map_err(|e| GraphError::pynew(py, e))?; +pub fn init_module(py: Python, package: &str) -> PyResult { + let dotted_name = &format!("{}.filepatterns", package); + let m = PyModule::new(py, dotted_name)?; - // convert as Python list - let mut remaining_pyint_vec: Vec = 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 { - let mut inner = self.inner(py).borrow_mut(); - let revs_vec: Vec = 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 = 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 -pub fn init_module(py: Python, package: &str) -> PyResult { - 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", + "Patterns files parsing - Rust implementation", )?; - m.add_class::(py)?; - m.add_class::(py)?; - m.add_class::(py)?; + m.add( + py, + "build_single_regex", + py_fn!( + py, + build_single_regex_wrapper( + kind: String, + pat: String, + globsuffix: String + ) + ), + )?; + m.add( + py, + "read_pattern_file", + py_fn!( + py, + read_pattern_file_wrapper( + file_path: String, + warn: bool, + source_info: bool + ) + ), + )?; 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) } diff --git a/rust/hg-cpython/src/lib.rs b/rust/hg-cpython/src/lib.rs --- a/rust/hg-cpython/src/lib.rs +++ b/rust/hg-cpython/src/lib.rs @@ -32,6 +32,7 @@ pub mod dagops; pub mod discovery; pub mod exceptions; pub mod dirstate; +pub mod filepatterns; py_module_initializer!(rustext, initrustext, PyInit_rustext, |py, m| { m.add( @@ -45,6 +46,9 @@ py_module_initializer!(rustext, initrust m.add(py, "dagop", dagops::init_module(py, &dotted_name)?)?; m.add(py, "discovery", discovery::init_module(py, &dotted_name)?)?; m.add(py, "dirstate", dirstate::init_module(py, &dotted_name)?)?; + m.add(py, "filepatterns", filepatterns::init_module(py, &dotted_name)?)?; m.add(py, "GraphError", py.get_type::())?; + m.add(py, "PatternFileError", py.get_type::())?; + m.add(py, "PatternError", py.get_type::())?; Ok(()) });