#![allow(clippy::useless_conversion)]
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList};
fn json_to_py<'py>(py: Python<'py>, v: &serde_json::Value) -> PyResult<pyo3::Bound<'py, PyAny>> {
use serde_json::Value;
Ok(match v {
Value::Null => py.None().into_bound(py),
Value::Bool(b) => (*b).into_pyobject(py)?.to_owned().into_any(),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
i.into_pyobject(py)?.into_any()
} else if let Some(u) = n.as_u64() {
u.into_pyobject(py)?.into_any()
} else {
n.as_f64().unwrap_or(f64::NAN).into_pyobject(py)?.into_any()
}
}
Value::String(s) => s.into_pyobject(py)?.into_any(),
Value::Array(a) => {
let list = PyList::empty(py);
for item in a {
list.append(json_to_py(py, item)?)?;
}
list.into_any()
}
Value::Object(o) => {
let dict = PyDict::new(py);
for (k, val) in o {
dict.set_item(k, json_to_py(py, val)?)?;
}
dict.into_any()
}
})
}
#[pyclass(name = "RunOutput")]
#[derive(Clone)]
struct PyRunOutput {
#[pyo3(get)]
json: String,
#[pyo3(get)]
svg: String,
#[pyo3(get)]
summary: String,
}
#[pymethods]
impl PyRunOutput {
fn data<'py>(&self, py: Python<'py>) -> PyResult<pyo3::Bound<'py, PyAny>> {
let v: serde_json::Value =
serde_json::from_str(&self.json).map_err(|e| PyValueError::new_err(e.to_string()))?;
json_to_py(py, &v)
}
fn __repr__(&self) -> String {
format!(
"RunOutput(json={} chars, svg={} chars, summary={:?})",
self.json.len(),
self.svg.len(),
self.summary.chars().take(48).collect::<String>()
)
}
}
#[pyfunction]
fn run_typed(toml: &str) -> PyResult<PyRunOutput> {
crate::api::run_toml(toml)
.map(|o| PyRunOutput {
json: o.json,
svg: o.svg,
summary: o.summary,
})
.map_err(PyValueError::new_err)
}
#[pyfunction]
fn scenario_kinds(py: Python<'_>) -> PyResult<PyObject> {
let json = crate::api::list_scenario_kinds_json();
let v: serde_json::Value =
serde_json::from_str(&json).map_err(|e| PyValueError::new_err(e.to_string()))?;
Ok(json_to_py(py, &v)?.unbind())
}
#[pyfunction]
fn validate_toml(toml: &str) -> Vec<String> {
match crate::api::run_scenario(toml) {
Ok(_) => vec![],
Err(e) => vec![e.to_string()],
}
}
#[pyfunction]
fn run(toml: &str) -> PyResult<String> {
crate::api::run_toml(toml)
.map(|o| o.json)
.map_err(PyValueError::new_err)
}
#[pyfunction]
fn run_full(toml: &str) -> PyResult<(String, String, String)> {
crate::api::run_toml(toml)
.map(|o| (o.json, o.svg, o.summary))
.map_err(PyValueError::new_err)
}
#[pyfunction]
fn list_kinds() -> String {
crate::api::list_scenario_kinds_json()
}
#[pyfunction]
fn error_kind(toml: &str) -> Option<String> {
crate::api::run_scenario(toml)
.err()
.map(|e| e.kind_tag().to_string())
}
#[pyfunction]
fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[pymodule]
fn kshana(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyRunOutput>()?;
m.add_function(wrap_pyfunction!(run, m)?)?;
m.add_function(wrap_pyfunction!(run_full, m)?)?;
m.add_function(wrap_pyfunction!(run_typed, m)?)?;
m.add_function(wrap_pyfunction!(scenario_kinds, m)?)?;
m.add_function(wrap_pyfunction!(validate_toml, m)?)?;
m.add_function(wrap_pyfunction!(list_kinds, m)?)?;
m.add_function(wrap_pyfunction!(error_kind, m)?)?;
m.add_function(wrap_pyfunction!(version, m)?)?;
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
Ok(())
}