use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use crate::data::types::{Gender, Language};
use crate::dto::parse_config_json;
fn parse_gender(s: &str) -> PyResult<Gender> {
match s.to_lowercase().as_str() {
"male" => Ok(Gender::Male),
"female" => Ok(Gender::Female),
_ => Err(PyValueError::new_err(format!(
"Invalid gender '{}'. Expected 'male' or 'female'.",
s
))),
}
}
fn parse_language(s: &str) -> PyResult<Language> {
match s.to_lowercase().as_str() {
"zh_cn" => Ok(Language::ZhCN),
"zh_tw" => Ok(Language::ZhTW),
"en_us" => Ok(Language::EnUS),
"ja_jp" => Ok(Language::JaJP),
"ko_kr" => Ok(Language::KoKR),
"vi_vn" => Ok(Language::ViVN),
_ => Err(PyValueError::new_err(format!(
"Invalid language '{}'. Expected one of: zh_cn, zh_tw, en_us, ja_jp, ko_kr, vi_vn.",
s
))),
}
}
fn to_python<T: serde::Serialize>(py: Python<'_>, value: &T) -> PyResult<PyObject> {
pythonize::pythonize(py, value)
.map(|bound| bound.unbind())
.map_err(|e| PyValueError::new_err(format!("Failed to convert to Python object: {}", e)))
}
fn compute<T>(
f: impl FnOnce() -> Result<T, crate::IztroError> + std::panic::UnwindSafe,
) -> PyResult<T> {
match std::panic::catch_unwind(f) {
Ok(result) => result.map_err(|e| PyValueError::new_err(e.to_string())),
Err(panic) => Err(PyValueError::new_err(format!(
"Invalid input: {}",
crate::dto::panic_message(panic.as_ref())
))),
}
}
#[pyfunction]
#[pyo3(signature = (solar_date, time_index, gender, fix_leap, language, config_json=None))]
fn by_solar(
py: Python<'_>,
solar_date: &str,
time_index: u8,
gender: &str,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
) -> PyResult<PyObject> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
let astrolabe =
compute(|| crate::by_solar(solar_date, time_index, gender, fix_leap, language, config))?;
to_python(py, &astrolabe.to_dto())
}
#[pyfunction]
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (lunar_date, time_index, gender, is_leap_month, fix_leap, language, config_json=None))]
fn by_lunar(
py: Python<'_>,
lunar_date: &str,
time_index: u8,
gender: &str,
is_leap_month: bool,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
) -> PyResult<PyObject> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
let astrolabe = compute(|| {
crate::by_lunar(
lunar_date,
time_index,
gender,
is_leap_month,
fix_leap,
language,
config,
)
})?;
to_python(py, &astrolabe.to_dto())
}
#[pyfunction]
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (solar_date, time_index, gender, fix_leap, language, config_json, target_date, target_time_index))]
fn get_horoscope(
py: Python<'_>,
solar_date: &str,
time_index: u8,
gender: &str,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
target_date: &str,
target_time_index: u8,
) -> PyResult<PyObject> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
let horoscope = compute(|| {
let astrolabe =
crate::by_solar(solar_date, time_index, gender, fix_leap, language, config)?;
crate::get_horoscope(&astrolabe, target_date, target_time_index, language)
})?;
to_python(py, &horoscope.to_dto(language))
}
#[pyfunction]
#[pyo3(signature = (solar_date, time_index, gender, fix_leap, language, config_json=None))]
fn astrolabe_to_prompt(
solar_date: &str,
time_index: u8,
gender: &str,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
) -> PyResult<String> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
compute(|| {
let astrolabe =
crate::by_solar(solar_date, time_index, gender, fix_leap, language, config)?;
Ok(crate::astrolabe_to_prompt(&astrolabe, language))
})
}
#[pyfunction]
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (solar_date, time_index, gender, fix_leap, language, config_json, target_date, target_time_index))]
fn horoscope_to_prompt(
solar_date: &str,
time_index: u8,
gender: &str,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
target_date: &str,
target_time_index: u8,
) -> PyResult<String> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
compute(|| {
let astrolabe =
crate::by_solar(solar_date, time_index, gender, fix_leap, language, config)?;
let horoscope = crate::get_horoscope(&astrolabe, target_date, target_time_index, language)?;
Ok(crate::horoscope_to_prompt(&astrolabe, &horoscope, language))
})
}
#[pyfunction]
#[pyo3(signature = (solar_date, time_index, gender, fix_leap, language, config_json=None))]
fn by_solar_json(
solar_date: &str,
time_index: u8,
gender: &str,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
) -> PyResult<String> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
compute(|| crate::by_solar_json(solar_date, time_index, gender, fix_leap, language, config))
}
#[pyfunction]
#[allow(clippy::too_many_arguments)]
#[pyo3(signature = (lunar_date, time_index, gender, is_leap_month, fix_leap, language, config_json=None))]
fn by_lunar_json(
lunar_date: &str,
time_index: u8,
gender: &str,
is_leap_month: bool,
fix_leap: bool,
language: &str,
config_json: Option<&str>,
) -> PyResult<String> {
let gender = parse_gender(gender)?;
let language = parse_language(language)?;
let config = parse_config_json(config_json).map_err(PyValueError::new_err)?;
compute(|| {
crate::by_lunar_json(
lunar_date,
time_index,
gender,
is_leap_month,
fix_leap,
language,
config,
)
})
}
#[pymodule]
fn _x_iztro(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_function(wrap_pyfunction!(by_solar, m)?)?;
m.add_function(wrap_pyfunction!(by_lunar, m)?)?;
m.add_function(wrap_pyfunction!(get_horoscope, m)?)?;
m.add_function(wrap_pyfunction!(astrolabe_to_prompt, m)?)?;
m.add_function(wrap_pyfunction!(horoscope_to_prompt, m)?)?;
m.add_function(wrap_pyfunction!(by_solar_json, m)?)?;
m.add_function(wrap_pyfunction!(by_lunar_json, m)?)?;
Ok(())
}