use crate::{ProviderChoice, SyncOptions, UpdateMode, cli, sync_files};
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::PyDict;
use std::path::PathBuf;
fn parse_provider(value: &str) -> PyResult<ProviderChoice> {
match value {
"auto" => Ok(ProviderChoice::Auto),
"ads" => Ok(ProviderChoice::Ads),
"inspire" => Ok(ProviderChoice::Inspire),
other => Err(PyValueError::new_err(format!(
"provider must be 'auto', 'ads', or 'inspire', got {other:?}"
))),
}
}
fn parse_update_mode(value: &str) -> PyResult<UpdateMode> {
match value {
"preprints-only" | "preprints_only" => Ok(UpdateMode::PreprinsOnly),
"never" => Ok(UpdateMode::Never),
"always" => Ok(UpdateMode::Always),
other => Err(PyValueError::new_err(format!(
"update_mode must be 'preprints-only', 'never', or 'always', got {other:?}"
))),
}
}
fn pathbufs(paths: Option<Vec<String>>) -> Vec<PathBuf> {
paths
.unwrap_or_default()
.into_iter()
.map(PathBuf::from)
.collect()
}
#[allow(clippy::fn_params_excessive_bools, clippy::too_many_arguments)]
#[pyfunction(name = "sync_files")]
#[pyo3(signature = (
files,
output = None,
other_bibliographies = None,
provider = "auto",
update_mode = "preprints-only",
force_regenerate = false,
merge_other = false,
backup = true,
check = true,
cache = false,
refresh_cache = false,
cache_dir = None,
ignore_file = None
))]
fn sync_files_py<'py>(
py: Python<'py>,
files: Vec<String>,
output: Option<String>,
other_bibliographies: Option<Vec<String>>,
provider: &str,
update_mode: &str,
force_regenerate: bool,
merge_other: bool,
backup: bool,
check: bool,
cache: bool,
refresh_cache: bool,
cache_dir: Option<String>,
ignore_file: Option<String>,
) -> PyResult<Bound<'py, PyDict>> {
let options = SyncOptions {
output: output.map(PathBuf::from),
other_bibliographies: pathbufs(other_bibliographies),
provider: parse_provider(provider)?,
update_mode: parse_update_mode(update_mode)?,
force_regenerate,
merge_other,
backup,
check,
cache,
refresh_cache,
cache_dir: cache_dir.map(PathBuf::from),
ignore_file: ignore_file.map(PathBuf::from),
};
let files = files.into_iter().map(PathBuf::from).collect::<Vec<_>>();
let report =
sync_files(&files, &options).map_err(|error| PyValueError::new_err(error.to_string()))?;
let dict = PyDict::new(py);
dict.set_item("output", report.output.to_string_lossy().as_ref())?;
dict.set_item("added", report.added)?;
dict.set_item("updated", report.updated)?;
dict.set_item("existing", report.existing)?;
dict.set_item("found_in_other", report.found_in_other)?;
dict.set_item("unresolved", report.unresolved)?;
let unresolved_details = report
.unresolved_details
.into_iter()
.map(|detail| {
let item = PyDict::new(py);
item.set_item("key", detail.key)?;
item.set_item("reason", detail.reason.to_string())?;
Ok(item)
})
.collect::<PyResult<Vec<_>>>()?;
dict.set_item("unresolved_details", unresolved_details)?;
dict.set_item("changed", report.changed)?;
dict.set_item("check_mode", report.check_mode)?;
Ok(dict)
}
#[pyfunction]
fn run_cli(args: Vec<String>) -> i32 {
cli::run_cli_from(args)
}
#[pymodule]
fn _bibsync(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_function(wrap_pyfunction!(sync_files_py, module)?)?;
module.add_function(wrap_pyfunction!(run_cli, module)?)?;
Ok(())
}