1use pyo3::prelude::*;
6
7mod config;
8mod doc;
9mod esc;
10#[cfg(test)]
11mod md_spike;
12mod mermaid;
13mod plot;
14mod table;
15#[cfg(feature = "tectonic")]
16mod tectonic;
17mod texenv;
18
19#[pyfunction]
24fn flowchart_to_tikz(src: &str) -> PyResult<String> {
25 mermaid::flowchart_to_tikz(src)
26 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
27}
28
29#[pyfunction]
37#[allow(clippy::too_many_arguments)]
38#[pyo3(signature = (title, ylabel, log_y, group_labels, series_names, values, mins, maxs, refline, legend="auto"))]
39fn barchart_to_tikz(
40 title: &str,
41 ylabel: &str,
42 log_y: bool,
43 group_labels: Vec<String>,
44 series_names: Vec<String>,
45 values: Vec<Vec<f64>>,
46 mins: Vec<Vec<f64>>,
47 maxs: Vec<Vec<f64>>,
48 refline: Option<(f64, f64, f64, String)>,
49 legend: &str,
50) -> PyResult<String> {
51 let legend = plot::LegendPos::parse(legend)
52 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
53 plot::barchart(
54 title,
55 ylabel,
56 log_y,
57 &group_labels,
58 &series_names,
59 &values,
60 &mins,
61 &maxs,
62 refline,
63 legend,
64 )
65 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
66}
67
68#[pyfunction]
71#[pyo3(signature = (title, xlabel, ylabel, xs, names, yss, errs, legend="auto"))]
72fn lineplot_to_tikz(
73 title: &str,
74 xlabel: &str,
75 ylabel: &str,
76 xs: Vec<f64>,
77 names: Vec<String>,
78 yss: Vec<Vec<f64>>,
79 errs: Vec<Vec<f64>>,
80 legend: &str,
81) -> PyResult<String> {
82 let legend = plot::LegendPos::parse(legend)
83 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
84 if names.len() != yss.len() || names.len() != errs.len() {
85 return Err(pyo3::exceptions::PyValueError::new_err(
86 "lineplot: names/yss/errs length mismatch",
87 ));
88 }
89 let series: Vec<(&str, Vec<f64>, Vec<f64>)> = names
90 .iter()
91 .zip(yss.into_iter())
92 .zip(errs.into_iter())
93 .map(|((n, y), e)| (n.as_str(), y, e))
94 .collect();
95 plot::lineplot(title, xlabel, ylabel, &xs, &series, legend)
96 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
97}
98
99#[pyfunction]
102fn parse_config(src: &str) -> PyResult<std::collections::HashMap<String, String>> {
103 let cfg = config::Config::from_toml(src)
104 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
105 let mut m = std::collections::HashMap::new();
106 m.insert("class".to_string(), cfg.document.class.clone());
107 m.insert(
108 "class_options".to_string(),
109 cfg.document.class_options.join(","),
110 );
111 m.insert("columns".to_string(), cfg.document.columns.to_string());
112 m.insert("margins".to_string(), cfg.document.margins.clone());
113 m.insert("template_preamble_file".to_string(), cfg.template.preamble_file.clone());
114 m.insert("template_preamble_append".to_string(), cfg.template.preamble_append.join("\n"));
115 m.insert(
116 "template_abstract_before_maketitle".to_string(),
117 cfg.template.abstract_before_maketitle.to_string(),
118 );
119 m.insert("float_pos_default".to_string(), format!("{:?}", cfg.floats.pos_default).to_lowercase());
120 m.insert("float_topfraction".to_string(), cfg.floats.topfraction.to_string());
121 m.insert("float_bottomfraction".to_string(), cfg.floats.bottomfraction.to_string());
122 m.insert("float_textfraction".to_string(), cfg.floats.textfraction.to_string());
123 m.insert("float_floatpagefraction".to_string(), cfg.floats.floatpagefraction.to_string());
124 m.insert("float_topnumber".to_string(), cfg.floats.topnumber.to_string());
125 m.insert("float_bottomnumber".to_string(), cfg.floats.bottomnumber.to_string());
126 m.insert("float_barrier_sections".to_string(), cfg.floats.barrier_sections.to_string());
127 m.insert("template_skeleton".to_string(), cfg.template.skeleton.clone());
128 for kind in ["diagram", "plot", "table", "picture", "default"] {
129 let span = cfg.spans.for_kind(kind);
130 m.insert(format!("span_{kind}"), span.latex_env().to_string());
131 m.insert(format!("width_{kind}"), span.latex_width().to_string());
132 }
133 Ok(m)
134}
135
136#[pyfunction]
142fn build_document(md_text: &str, toml_src: &str, spec_json: &str) -> PyResult<String> {
143 let spec: doc::BuildSpec = serde_json::from_str(spec_json)
144 .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("spec: {e}")))?;
145 let r = doc::build_document(md_text, toml_src, &spec)
146 .map_err(pyo3::exceptions::PyValueError::new_err)?;
147 serde_json::to_string(&r)
148 .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
149}
150
151#[pyfunction]
154fn bib_safe(bib_src: &str) -> String {
155 table::bib_safe(bib_src)
156}
157
158#[pyfunction]
160fn bib_keys(bib_src: &str) -> Vec<String> {
161 let mut v: Vec<String> = table::bib_keys_of(bib_src).into_iter().collect();
162 v.sort();
163 v
164}
165
166#[pyfunction]
169fn tex_requirements(has_tikz: bool, has_plots: bool) -> Vec<String> {
170 doc::tex_requirements(has_tikz, has_plots)
171}
172
173#[pyfunction]
177fn used_packages(tex: &str) -> Vec<String> {
178 texenv::used_packages(tex)
179}
180
181#[pyfunction]
183fn document_class(tex: &str) -> Option<String> {
184 texenv::document_class(tex)
185}
186
187#[pyfunction]
190fn package_needs(names: Vec<String>, class: Option<String>) -> Vec<(String, String)> {
191 texenv::package_needs(&names, class.as_deref())
192}
193
194#[pyfunction]
197fn tex_extra_packages(tex: &str) -> Vec<String> {
198 doc::tex_extra_packages(tex)
199}
200
201#[cfg(feature = "tectonic")]
204#[pyfunction]
205fn compile_tectonic_pdf(tex: &str) -> PyResult<Vec<u8>> {
206 crate::tectonic::compile_tectonic(tex).map_err(pyo3::exceptions::PyRuntimeError::new_err)
207}
208
209#[pymodule]
211fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
212 m.add_function(wrap_pyfunction!(flowchart_to_tikz, m)?)?;
213 m.add_function(wrap_pyfunction!(barchart_to_tikz, m)?)?;
214 m.add_function(wrap_pyfunction!(lineplot_to_tikz, m)?)?;
215 m.add_function(wrap_pyfunction!(parse_config, m)?)?;
216 m.add_function(wrap_pyfunction!(build_document, m)?)?;
217 m.add_function(wrap_pyfunction!(bib_safe, m)?)?;
218 m.add_function(wrap_pyfunction!(bib_keys, m)?)?;
219 m.add_function(wrap_pyfunction!(tex_requirements, m)?)?;
220 m.add_function(wrap_pyfunction!(tex_extra_packages, m)?)?;
221 m.add_function(wrap_pyfunction!(used_packages, m)?)?;
222 m.add_function(wrap_pyfunction!(document_class, m)?)?;
223 m.add_function(wrap_pyfunction!(package_needs, m)?)?;
224 #[cfg(feature = "tectonic")]
225 m.add_function(wrap_pyfunction!(compile_tectonic_pdf, m)?)?;
226 Ok(())
227}