use pyo3::prelude::*;
use pyo3::types::PyDict;
use lerna::defaults::GroupDefault;
use lerna::defaults_list::{DefaultsListBuilder, DefaultsListResult, Overrides};
#[pyclass(name = "RustOverrides")]
#[derive(Clone)]
pub struct PyOverrides {
inner: Overrides,
}
#[pymethods]
impl PyOverrides {
#[new]
fn new() -> Self {
Self {
inner: Overrides::default(),
}
}
#[staticmethod]
fn from_strings(overrides: Vec<String>) -> Self {
Self {
inner: Overrides::from_overrides(&overrides),
}
}
fn get_choices(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let dict = PyDict::new(py);
for (k, v) in &self.inner.choices {
match v {
Some(val) => dict.set_item(k, val)?,
None => dict.set_item(k, py.None())?,
}
}
Ok(dict.into())
}
fn get_deletions(&self) -> Vec<String> {
self.inner.deletions.keys().cloned().collect()
}
fn get_appends(&self) -> Vec<(String, String)> {
self.inner
.appends
.iter()
.filter_map(|gd| {
gd.value
.as_single()
.map(|v| (gd.group.clone(), v.to_string()))
})
.collect()
}
fn is_overridden(&self, group: &str) -> bool {
self.inner.choices.contains_key(group)
}
fn is_deleted(&self, group: &str) -> bool {
self.inner.deletions.contains_key(group)
}
fn get_choice(&self, group: &str) -> Option<String> {
self.inner.choices.get(group).and_then(|v| v.clone())
}
fn add_choice(&mut self, group: String, value: Option<String>) {
self.inner.choices.insert(group, value);
}
fn add_deletion(&mut self, group: String) {
self.inner
.deletions
.insert(group, lerna::defaults_list::Deletion::default());
}
fn add_append(&mut self, group: String, value: String) {
self.inner.appends.push(GroupDefault::new(group, value));
}
fn __repr__(&self) -> String {
format!(
"RustOverrides(choices={:?}, deletions={:?}, appends={})",
self.inner.choices,
self.inner.deletions,
self.inner.appends.len()
)
}
}
#[pyclass(name = "RustDefaultsListResult")]
pub struct PyDefaultsListResult {
inner: DefaultsListResult,
}
#[pymethods]
impl PyDefaultsListResult {
fn get_defaults(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
let mut results = Vec::new();
for rd in &self.inner.defaults {
let dict = PyDict::new(py);
match &rd.config_path {
Some(p) => dict.set_item("config_path", p)?,
None => dict.set_item("config_path", py.None())?,
}
match &rd.parent {
Some(p) => dict.set_item("parent", p)?,
None => dict.set_item("parent", py.None())?,
}
match &rd.package {
Some(p) => dict.set_item("package", p)?,
None => dict.set_item("package", py.None())?,
}
dict.set_item("is_self", rd.is_self)?;
dict.set_item("primary", rd.primary)?;
match &rd.override_key {
Some(k) => dict.set_item("override_key", k)?,
None => dict.set_item("override_key", py.None())?,
}
results.push(dict.into());
}
Ok(results)
}
fn get_config_overrides(&self) -> Vec<String> {
self.inner.config_overrides.clone()
}
fn get_known_choices(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let dict = PyDict::new(py);
for (k, v) in &self.inner.known_choices {
match v {
Some(val) => dict.set_item(k, val)?,
None => dict.set_item(k, py.None())?,
}
}
Ok(dict.into())
}
fn __len__(&self) -> usize {
self.inner.defaults.len()
}
fn __repr__(&self) -> String {
format!(
"RustDefaultsListResult({} defaults)",
self.inner.defaults.len()
)
}
}
#[pyfunction]
#[pyo3(signature = (config_path, overrides, config_loader))]
pub fn build_defaults_list(
py: Python<'_>,
config_path: Option<&str>,
overrides: Vec<String>,
config_loader: Py<PyAny>,
) -> PyResult<PyDefaultsListResult> {
let loader = config_loader.clone_ref(py);
let loader2 = config_loader.clone_ref(py);
let loader3 = config_loader.clone_ref(py);
let config_loader_fn = move |path: &str| -> Result<
lerna::config::value::ConfigDict,
lerna::config::parser::ConfigLoadError,
> {
Python::attach(|py| {
let result = loader.call_method1(py, "load_config", (path,));
match result {
Ok(_obj) => {
Ok(lerna::config::value::ConfigDict::new())
}
Err(_) => Err(lerna::config::parser::ConfigLoadError::with_path(
"Config not found",
path,
)),
}
})
};
let config_exists_fn = move |path: &str| -> bool {
Python::attach(|py| {
let result = loader2.call_method1(py, "config_exists", (path,));
match result {
Ok(obj) => obj.extract::<bool>(py).unwrap_or(false),
Err(_) => false,
}
})
};
let group_exists_fn = move |group: &str| -> bool {
Python::attach(|py| {
let result = loader3.call_method1(py, "group_exists", (group,));
match result {
Ok(obj) => obj.extract::<bool>(py).unwrap_or(false),
Err(_) => false,
}
})
};
let builder = DefaultsListBuilder::new(
config_loader_fn,
config_exists_fn,
group_exists_fn,
&overrides,
);
let result = builder
.build(config_path)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
Ok(PyDefaultsListResult { inner: result })
}
#[pyfunction]
pub fn parse_overrides(overrides: Vec<String>) -> PyOverrides {
PyOverrides::from_strings(overrides)
}
pub fn register(parent: &Bound<'_, PyModule>) -> PyResult<()> {
let m = PyModule::new(parent.py(), "defaults_list")?;
m.add_class::<PyOverrides>()?;
m.add_class::<PyDefaultsListResult>()?;
m.add_function(wrap_pyfunction!(build_defaults_list, &m)?)?;
m.add_function(wrap_pyfunction!(parse_overrides, &m)?)?;
parent.add_submodule(&m)?;
Ok(())
}