mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
configparser: expose interface to Python world
Summary: This allows the Python world to access its features. Reviewed By: DurhamG Differential Revision: D8790922 fbshipit-source-id: e7e561c86596159c3766d6da8e0834c6fe441cbd
This commit is contained in:
parent
d1e4252154
commit
ec34cbf739
21
mercurial/rust/config/Cargo.toml
Normal file
21
mercurial/rust/config/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[dependencies]
|
||||
configparser = { path = "../../../lib/configparser/" }
|
||||
pathencoding = { path = "../../../lib/pathencoding/" }
|
||||
|
||||
[dependencies.cpython]
|
||||
version = "0.2"
|
||||
default-features = false
|
||||
features = ["extension-module-2-7"]
|
||||
|
||||
[dependencies.python27-sys]
|
||||
version = "0.2"
|
142
mercurial/rust/config/src/lib.rs
Normal file
142
mercurial/rust/config/src/lib.rs
Normal file
@ -0,0 +1,142 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
extern crate configparser;
|
||||
#[macro_use]
|
||||
extern crate cpython;
|
||||
extern crate pathencoding;
|
||||
|
||||
use configparser::config::{ConfigSet, Options};
|
||||
use configparser::hg::{ConfigSetHgExt, OptionsHgExt};
|
||||
use cpython::{PyBytes, PyErr, PyObject, PyResult, Python};
|
||||
use cpython::exc::UnicodeDecodeError;
|
||||
use pathencoding::{local_bytes_to_path, path_to_local_bytes};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
||||
py_module_initializer!(config, initconfig, PyInit_config, |py, m| {
|
||||
m.add_class::<config>(py)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
py_class!(class config |py| {
|
||||
data cfg: RefCell<ConfigSet>;
|
||||
|
||||
def __new__(_cls) -> PyResult<config> {
|
||||
config::create_instance(py, RefCell::new(ConfigSet::new()))
|
||||
}
|
||||
|
||||
def readpath(
|
||||
&self,
|
||||
path: &PyBytes,
|
||||
source: &PyBytes,
|
||||
sections: Option<Vec<PyBytes>>,
|
||||
remap: Option<Vec<(PyBytes, PyBytes)>>,
|
||||
readonly_items: Option<Vec<(PyBytes, PyBytes)>>
|
||||
) -> PyResult<PyObject> {
|
||||
let path = local_bytes_to_path(path.data(py)).map_err(|_| encoding_error(py, path))?;
|
||||
let mut cfg = self.cfg(py).borrow_mut();
|
||||
|
||||
let mut opts = Options::new().source(source.data(py)).process_hgplain();
|
||||
if let Some(sections) = sections {
|
||||
let sections = sections.into_iter().map(|section| section.data(py).to_vec()).collect();
|
||||
opts = opts.whitelist_sections(sections);
|
||||
}
|
||||
if let Some(remap) = remap {
|
||||
let mut map = HashMap::new();
|
||||
for (key, value) in remap {
|
||||
map.insert(key.data(py).to_vec(), value.data(py).to_vec());
|
||||
}
|
||||
opts = opts.remap_sections(map);
|
||||
}
|
||||
if let Some(readonly_items) = readonly_items {
|
||||
let items: Vec<(Vec<u8>, Vec<u8>)> = readonly_items.iter()
|
||||
.map(|(section, name)| {
|
||||
(section.data(py).to_vec(), name.data(py).to_vec())
|
||||
}).collect();
|
||||
opts = opts.readonly_items(items);
|
||||
}
|
||||
|
||||
cfg.load_path(path, &opts);
|
||||
Ok(py.None())
|
||||
}
|
||||
|
||||
def parse(&self, content: &PyBytes, source: &PyBytes) -> PyResult<PyObject> {
|
||||
let mut cfg = self.cfg(py).borrow_mut();
|
||||
let opts = source.data(py).into();
|
||||
cfg.parse(content.data(py), &opts);
|
||||
Ok(py.None())
|
||||
}
|
||||
|
||||
def get(&self, section: &PyBytes, name: &PyBytes) -> PyResult<Option<PyBytes>> {
|
||||
let cfg = self.cfg(py).borrow();
|
||||
Ok(cfg.get(section.data(py), name.data(py)).map(|bytes| PyBytes::new(py, &bytes)))
|
||||
}
|
||||
|
||||
def sources(
|
||||
&self, section: &PyBytes, name: &PyBytes
|
||||
) -> PyResult<Vec<(Option<PyBytes>, Option<(PyBytes, usize, usize)>, PyBytes)>> {
|
||||
// Return [(value, file_source, source)]
|
||||
let cfg = self.cfg(py).borrow();
|
||||
let sources = cfg.get_sources(section.data(py), name.data(py));
|
||||
let mut result = Vec::with_capacity(sources.len());
|
||||
for source in sources {
|
||||
let value = source.value().clone().map(|bytes| PyBytes::new(py, &bytes));
|
||||
let file = source.location().map(|(path, range)| {
|
||||
let bytes = path_to_local_bytes(&path).unwrap();
|
||||
(PyBytes::new(py, bytes), range.start, range.end)
|
||||
});
|
||||
let source = PyBytes::new(py, source.source());
|
||||
result.push((value, file, source));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
def set(
|
||||
&self, section: &PyBytes, name: &PyBytes, value: Option<&PyBytes>, source: &PyBytes
|
||||
) -> PyResult<PyObject> {
|
||||
let mut cfg = self.cfg(py).borrow_mut();
|
||||
let opts = source.data(py).into();
|
||||
cfg.set(section.data(py), name.data(py), value.map(|v| v.data(py)), &opts);
|
||||
Ok(py.None())
|
||||
}
|
||||
|
||||
def errors(&self) -> PyResult<Vec<PyBytes>> {
|
||||
let cfg = self.cfg(py).borrow();
|
||||
let errors: Vec<PyBytes> = cfg.errors()
|
||||
.iter().map(|err| PyBytes::new(py, format!("{}", err).as_bytes())).collect();
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
def sections(&self) -> PyResult<Vec<PyBytes>> {
|
||||
let cfg = self.cfg(py).borrow();
|
||||
let sections: Vec<PyBytes> = cfg.sections()
|
||||
.iter().map(|bytes| PyBytes::new(py, bytes)).collect();
|
||||
Ok(sections)
|
||||
}
|
||||
|
||||
def names(&self, section: &PyBytes) -> PyResult<Vec<PyBytes>> {
|
||||
let cfg = self.cfg(py).borrow();
|
||||
let keys: Vec<PyBytes> = cfg.keys(section.data(py))
|
||||
.iter().map(|bytes| PyBytes::new(py, bytes)).collect();
|
||||
Ok(keys)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def load(datapath: PyBytes) -> PyResult<config> {
|
||||
let datapath = local_bytes_to_path(datapath.data(py))
|
||||
.map_err(|_| encoding_error(py, &datapath))?;
|
||||
let mut cfg = ConfigSet::new();
|
||||
cfg.load_system(datapath);
|
||||
cfg.load_user();
|
||||
config::create_instance(py, RefCell::new(cfg))
|
||||
}
|
||||
});
|
||||
|
||||
fn encoding_error(py: Python, input: &PyBytes) -> PyErr {
|
||||
use std::ffi::CStr;
|
||||
let utf8 = CStr::from_bytes_with_nul(b"utf8\0").unwrap();
|
||||
let reason = CStr::from_bytes_with_nul(b"invalid encoding\0").unwrap();
|
||||
let input = input.data(py);
|
||||
let err = UnicodeDecodeError::new(py, utf8, input, 0..input.len(), reason).unwrap();
|
||||
PyErr::from_instance(py, err)
|
||||
}
|
9
setup.py
9
setup.py
@ -1567,9 +1567,7 @@ with open(".cargo/config", "w") as f:
|
||||
|
||||
rustextmodules = [
|
||||
RustExtension(
|
||||
"pyrevisionstore",
|
||||
package="hgext.extlib",
|
||||
manifest="hgext/extlib/pyrevisionstore/Cargo.toml",
|
||||
"config", package="mercurial.rust", manifest="mercurial/rust/config/Cargo.toml"
|
||||
),
|
||||
RustExtension(
|
||||
"indexes", package="hgext.extlib", manifest="hgext/extlib/indexes/Cargo.toml"
|
||||
@ -1579,6 +1577,11 @@ rustextmodules = [
|
||||
package="mercurial.rust",
|
||||
manifest="mercurial/rust/matcher/Cargo.toml",
|
||||
),
|
||||
RustExtension(
|
||||
"pyrevisionstore",
|
||||
package="hgext.extlib",
|
||||
manifest="hgext/extlib/pyrevisionstore/Cargo.toml",
|
||||
),
|
||||
RustExtension(
|
||||
"treestate",
|
||||
package="mercurial.rust",
|
||||
|
75
tests/test-configparser.t
Normal file
75
tests/test-configparser.t
Normal file
@ -0,0 +1,75 @@
|
||||
$ cat >> a.rc << EOF
|
||||
> [a]
|
||||
> x=1
|
||||
> y=2
|
||||
> %include b.rc
|
||||
> EOF
|
||||
|
||||
$ cat >> b.rc << EOF
|
||||
> %include b.rc
|
||||
> [b]
|
||||
> z = 3
|
||||
> [a]
|
||||
> %unset y
|
||||
> %not-implemented
|
||||
> EOF
|
||||
|
||||
>>> from mercurial.rust import config
|
||||
>>> cfg = config.config()
|
||||
>>> cfg.readpath("a.rc", "readpath", None, None, None)
|
||||
>>> cfg.parse("[c]\nx=1", "parse")
|
||||
>>> cfg.set("d", "y", "2", "set1")
|
||||
>>> cfg.set("d", "x", None, "set2")
|
||||
>>> for section in cfg.sections():
|
||||
... print("section [%s] has names %r" % (section, cfg.names(section)))
|
||||
section [a] has names ['x', 'y']
|
||||
section [b] has names ['z']
|
||||
section [c] has names ['x']
|
||||
section [d] has names ['y', 'x']
|
||||
>>> print("errors: %r" % cfg.errors())
|
||||
errors: ['"$TESTTMP/b.rc": parse error around byte 37: unknown instruction']
|
||||
>>> for item in ["a.x", "a.y", "b.z", "c.x", "d.x", "d.y", "e.x"]:
|
||||
... section, name = item.split(".")
|
||||
... print("%s = %r" % (item, cfg.get(section, name)))
|
||||
... print(" sources: %r" % (cfg.sources(section, name)))
|
||||
a.x = '1'
|
||||
sources: [('1', ('$TESTTMP/a.rc', 6, 7), 'readpath')]
|
||||
a.y = None
|
||||
sources: [('2', ('$TESTTMP/a.rc', 10, 11), 'readpath'), (None, ('$TESTTMP/b.rc', 28, 36), 'readpath')]
|
||||
b.z = '3'
|
||||
sources: [('3', ('$TESTTMP/b.rc', 22, 23), 'readpath')]
|
||||
c.x = '1'
|
||||
sources: [('1', ('', 6, 7), 'parse')]
|
||||
d.x = None
|
||||
sources: [(None, None, 'set2')]
|
||||
d.y = '2'
|
||||
sources: [('2', None, 'set1')]
|
||||
e.x = None
|
||||
sources: []
|
||||
|
||||
Section whitelist
|
||||
|
||||
>>> from mercurial.rust import config
|
||||
>>> cfg = config.config()
|
||||
>>> cfg.readpath("a.rc", "readpath", ["a"], None, None)
|
||||
>>> print(cfg.sections())
|
||||
['a']
|
||||
|
||||
Section remap
|
||||
|
||||
>>> from mercurial.rust import config
|
||||
>>> cfg = config.config()
|
||||
>>> cfg.readpath("a.rc", "readpath", None, {'a': 'x'}.items(), None)
|
||||
>>> print(cfg.sections())
|
||||
['x', 'b']
|
||||
|
||||
Config whitelist
|
||||
|
||||
>>> from mercurial.rust import config
|
||||
>>> cfg = config.config()
|
||||
>>> cfg.readpath("a.rc", "readpath", None, None, [('a', 'y')])
|
||||
>>> print(cfg.get('a', 'y'))
|
||||
None
|
||||
>>> print(cfg.get('a', 'x'))
|
||||
1
|
||||
|
Loading…
Reference in New Issue
Block a user