use pyo3::prelude::*;
mod config;
mod doc;
mod esc;
#[cfg(test)]
mod md_spike;
mod mermaid;
mod plot;
mod table;
#[cfg(feature = "tectonic")]
mod tectonic;
mod texenv;
#[pyfunction]
fn flowchart_to_tikz(src: &str) -> PyResult<String> {
mermaid::flowchart_to_tikz(src)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
}
#[pyfunction]
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (title, ylabel, log_y, group_labels, series_names, values, mins, maxs, refline, legend="auto"))]
fn barchart_to_tikz(
title: &str,
ylabel: &str,
log_y: bool,
group_labels: Vec<String>,
series_names: Vec<String>,
values: Vec<Vec<f64>>,
mins: Vec<Vec<f64>>,
maxs: Vec<Vec<f64>>,
refline: Option<(f64, f64, f64, String)>,
legend: &str,
) -> PyResult<String> {
let legend = plot::LegendPos::parse(legend)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
plot::barchart(
title,
ylabel,
log_y,
&group_labels,
&series_names,
&values,
&mins,
&maxs,
refline,
legend,
)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
}
#[pyfunction]
#[pyo3(signature = (title, xlabel, ylabel, xs, names, yss, errs, legend="auto"))]
fn lineplot_to_tikz(
title: &str,
xlabel: &str,
ylabel: &str,
xs: Vec<f64>,
names: Vec<String>,
yss: Vec<Vec<f64>>,
errs: Vec<Vec<f64>>,
legend: &str,
) -> PyResult<String> {
let legend = plot::LegendPos::parse(legend)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
if names.len() != yss.len() || names.len() != errs.len() {
return Err(pyo3::exceptions::PyValueError::new_err(
"lineplot: names/yss/errs length mismatch",
));
}
let series: Vec<(&str, Vec<f64>, Vec<f64>)> = names
.iter()
.zip(yss.into_iter())
.zip(errs.into_iter())
.map(|((n, y), e)| (n.as_str(), y, e))
.collect();
plot::lineplot(title, xlabel, ylabel, &xs, &series, legend)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
}
#[pyfunction]
fn parse_config(src: &str) -> PyResult<std::collections::HashMap<String, String>> {
let cfg = config::Config::from_toml(src)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
let mut m = std::collections::HashMap::new();
m.insert("class".to_string(), cfg.document.class.clone());
m.insert(
"class_options".to_string(),
cfg.document.class_options.join(","),
);
m.insert("columns".to_string(), cfg.document.columns.to_string());
m.insert("margins".to_string(), cfg.document.margins.clone());
m.insert("template_preamble_file".to_string(), cfg.template.preamble_file.clone());
m.insert("template_preamble_append".to_string(), cfg.template.preamble_append.join("\n"));
m.insert(
"template_abstract_before_maketitle".to_string(),
cfg.template.abstract_before_maketitle.to_string(),
);
m.insert("float_pos_default".to_string(), format!("{:?}", cfg.floats.pos_default).to_lowercase());
m.insert("float_topfraction".to_string(), cfg.floats.topfraction.to_string());
m.insert("float_bottomfraction".to_string(), cfg.floats.bottomfraction.to_string());
m.insert("float_textfraction".to_string(), cfg.floats.textfraction.to_string());
m.insert("float_floatpagefraction".to_string(), cfg.floats.floatpagefraction.to_string());
m.insert("float_topnumber".to_string(), cfg.floats.topnumber.to_string());
m.insert("float_bottomnumber".to_string(), cfg.floats.bottomnumber.to_string());
m.insert("float_barrier_sections".to_string(), cfg.floats.barrier_sections.to_string());
m.insert("template_skeleton".to_string(), cfg.template.skeleton.clone());
for kind in ["diagram", "plot", "table", "picture", "default"] {
let span = cfg.spans.for_kind(kind);
m.insert(format!("span_{kind}"), span.latex_env().to_string());
m.insert(format!("width_{kind}"), span.latex_width().to_string());
}
Ok(m)
}
#[pyfunction]
fn build_document(md_text: &str, toml_src: &str, spec_json: &str) -> PyResult<String> {
let spec: doc::BuildSpec = serde_json::from_str(spec_json)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("spec: {e}")))?;
let r = doc::build_document(md_text, toml_src, &spec)
.map_err(pyo3::exceptions::PyValueError::new_err)?;
serde_json::to_string(&r)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
}
#[pyfunction]
fn bib_safe(bib_src: &str) -> String {
table::bib_safe(bib_src)
}
#[pyfunction]
fn bib_keys(bib_src: &str) -> Vec<String> {
let mut v: Vec<String> = table::bib_keys_of(bib_src).into_iter().collect();
v.sort();
v
}
#[pyfunction]
fn tex_requirements(has_tikz: bool, has_plots: bool) -> Vec<String> {
doc::tex_requirements(has_tikz, has_plots)
}
#[pyfunction]
fn used_packages(tex: &str) -> Vec<String> {
texenv::used_packages(tex)
}
#[pyfunction]
fn document_class(tex: &str) -> Option<String> {
texenv::document_class(tex)
}
#[pyfunction]
fn package_needs(names: Vec<String>, class: Option<String>) -> Vec<(String, String)> {
texenv::package_needs(&names, class.as_deref())
}
#[pyfunction]
fn tex_extra_packages(tex: &str) -> Vec<String> {
doc::tex_extra_packages(tex)
}
#[cfg(feature = "tectonic")]
#[pyfunction]
fn compile_tectonic_pdf(tex: &str) -> PyResult<Vec<u8>> {
crate::tectonic::compile_tectonic(tex).map_err(pyo3::exceptions::PyRuntimeError::new_err)
}
#[pymodule]
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(flowchart_to_tikz, m)?)?;
m.add_function(wrap_pyfunction!(barchart_to_tikz, m)?)?;
m.add_function(wrap_pyfunction!(lineplot_to_tikz, m)?)?;
m.add_function(wrap_pyfunction!(parse_config, m)?)?;
m.add_function(wrap_pyfunction!(build_document, m)?)?;
m.add_function(wrap_pyfunction!(bib_safe, m)?)?;
m.add_function(wrap_pyfunction!(bib_keys, m)?)?;
m.add_function(wrap_pyfunction!(tex_requirements, m)?)?;
m.add_function(wrap_pyfunction!(tex_extra_packages, m)?)?;
m.add_function(wrap_pyfunction!(used_packages, m)?)?;
m.add_function(wrap_pyfunction!(document_class, m)?)?;
m.add_function(wrap_pyfunction!(package_needs, m)?)?;
#[cfg(feature = "tectonic")]
m.add_function(wrap_pyfunction!(compile_tectonic_pdf, m)?)?;
Ok(())
}