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:
Jun Wu 2018-08-08 17:15:20 -07:00 committed by Facebook Github Bot
parent d1e4252154
commit ec34cbf739
4 changed files with 244 additions and 3 deletions

View 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"

View 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)
}

View File

@ -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
View 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