mod client;
mod models;
pub(crate) mod naming;
mod package;
use soothfast_spec::dialect::Info;
use crate::model::{Sdk, Ty};
use crate::{SdkFileSet, SdkOptions};
pub(crate) const HEADER: &str = "# Code generated by `cargo soothfast sdk gen`. DO NOT EDIT.";
const RUNTIME: &str = include_str!("../../assets/python/_runtime.py");
const SERVER: &str = include_str!("../../assets/python/_server.py");
const HATCH_HOOK: &str = include_str!("../../assets/python/hatch_build.py");
pub(crate) fn emit(info: &Info, sdk: &Sdk, opts: &SdkOptions) -> Result<SdkFileSet, String> {
let base_url = crate::base_url_for(info, opts)?;
let src = format!("src/{}", opts.module);
let mut out = SdkFileSet {
notes: sdk.notes.clone(),
..SdkFileSet::default()
};
let files = &mut out.files;
files.insert("pyproject.toml".into(), package::pyproject(info, opts));
files.insert(
"README.md".into(),
package::readme(info, sdk, opts, base_url.as_deref()),
);
files.insert(format!("{src}/py.typed"), String::new());
files.insert(format!("{src}/_runtime.py"), RUNTIME.to_string());
files.insert(format!("{src}/__init__.py"), package::init(info, sdk, opts));
files.insert(format!("{src}/models.py"), models::render(sdk, opts));
files.insert(
format!("{src}/client.py"),
client::render(sdk, opts, base_url.as_deref()),
);
if opts.embed.is_some() {
files.insert(format!("{src}/_server.py"), SERVER.to_string());
files.insert(
"hatch_build.py".into(),
HATCH_HOOK.replace("{module}", &opts.module),
);
}
Ok(out)
}
pub(crate) fn annotation(ty: &Ty, prefix: &str) -> String {
match ty {
Ty::Str => "str".into(),
Ty::Int => "int".into(),
Ty::Float => "float".into(),
Ty::Bool => "bool".into(),
Ty::Any => "typing.Any".into(),
Ty::Unit => "None".into(),
Ty::List(t) => format!("list[{}]", annotation(t, prefix)),
Ty::Map(t) => format!("dict[str, {}]", annotation(t, prefix)),
Ty::Optional(t) => format!("{} | None", annotation(t, prefix)),
Ty::Named(n) => format!("{prefix}{n}"),
Ty::Union(arms) => arms
.iter()
.map(|a| annotation(a, prefix))
.collect::<Vec<_>>()
.join(" | "),
Ty::Literal(values) => {
let quoted: Vec<String> = values.iter().map(|v| format!("{v:?}")).collect();
format!("typing.Literal[{}]", quoted.join(", "))
}
}
}
pub(crate) fn into_expr(ty: &Ty) -> String {
match ty {
Ty::Any | Ty::Unit => "None".into(),
_ => annotation(ty, "models."),
}
}
pub(crate) fn doc_escape(text: &str) -> String {
text.replace('\\', "\\\\").replace("\"\"\"", "\\\"\\\"\\\"")
}