use crate::config::{self, FileConfig as RustFileConfig};
use crate::error::BumpError;
use crate::files::apply_file_change;
use crate::version::{bump_version, parse_version, serialize_version};
use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
#[pyclass(name = "BumpConfig")]
pub struct PyBumpConfig {
inner: config::BumpConfig,
}
#[pymethods]
impl PyBumpConfig {
#[new]
#[pyo3(signature = (parse=None, serialize=None, search=None, replace=None))]
pub fn new(
parse: Option<String>,
serialize: Option<String>,
search: Option<String>,
replace: Option<String>,
) -> Self {
let mut inner = config::BumpConfig::default();
if let Some(p) = parse {
inner.parse = p;
}
if let Some(s) = serialize {
inner.serialize = vec![s];
}
if let Some(s) = search {
inner.search = s;
}
if let Some(r) = replace {
inner.replace = r;
}
Self { inner }
}
#[getter]
pub fn parse(&self) -> &str {
&self.inner.parse
}
#[getter]
pub fn serialize(&self) -> &str {
self.inner
.serialize
.first()
.map(String::as_str)
.unwrap_or("")
}
#[getter]
pub fn search(&self) -> &str {
&self.inner.search
}
#[getter]
pub fn replace(&self) -> &str {
&self.inner.replace
}
pub fn __repr__(&self) -> String {
format!(
"BumpConfig(parse={:?}, serialize={:?})",
self.inner.parse,
self.inner.serialize.first().unwrap_or(&String::new())
)
}
}
#[pyfunction]
#[pyo3(name = "bump_version", signature = (current_version, part, config=None, config_path=None))]
pub fn bump_version_py(
current_version: &str,
part: &str,
config: Option<&PyBumpConfig>,
config_path: Option<&str>,
) -> PyResult<String> {
let mut cfg = if let Some(path) = config_path {
config::parse_config_file(path).map_err(|e| PyRuntimeError::new_err(e.to_string()))?
} else {
config::BumpConfig::default()
};
if let Some(py_cfg) = config {
cfg.parse = py_cfg.inner.parse.clone();
cfg.serialize = py_cfg.inner.serialize.clone();
cfg.search = py_cfg.inner.search.clone();
cfg.replace = py_cfg.inner.replace.clone();
}
let version =
parse_version(current_version, &cfg).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
let bumped = bump_version(&version, part, &cfg).map_err(|e| match e {
BumpError::UnknownComponent(c) => PyValueError::new_err(format!("Unknown component: {c}")),
other => PyRuntimeError::new_err(other.to_string()),
})?;
Ok(serialize_version(&bumped, &cfg))
}
#[pyfunction]
#[pyo3(name = "apply_file_change", signature = (content, current_version, new_version, search=None, replace=None, ignore_missing=false))]
pub fn apply_file_change_py(
content: &str,
current_version: &str,
new_version: &str,
search: Option<&str>,
replace: Option<&str>,
ignore_missing: bool,
) -> PyResult<String> {
let cfg = config::BumpConfig::default();
let mut fc = RustFileConfig::new("<python-caller>");
fc.ignore_missing_version = ignore_missing;
if let Some(s) = search {
fc.search = Some(s.to_string());
}
if let Some(r) = replace {
fc.replace = Some(r.to_string());
}
apply_file_change(content, &fc, &cfg, current_version, new_version)
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
}
pub fn register_python_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyBumpConfig>()?;
m.add_function(wrap_pyfunction!(bump_version_py, m)?)?;
m.add_function(wrap_pyfunction!(apply_file_change_py, m)?)?;
Ok(())
}