use std::fmt::Write as _;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use elm_client_gen_builder::{
build_merged_module, group_by_module, DefaultStrategy, MaybeEncoderRef, NameMap, TypeOverrides,
};
use elm_client_gen_core::ElmTypeInfo;
pub struct CodegenOptions<'a> {
pub output: PathBuf,
pub filter_names: &'a [String],
pub dry_run: bool,
}
#[derive(Debug)]
pub enum CodegenOutcome {
Wrote { module_count: usize, root: PathBuf },
DryRun(String),
}
pub fn run_codegen(types: Vec<ElmTypeInfo>, options: CodegenOptions<'_>) -> Result<CodegenOutcome> {
let overrides = TypeOverrides::new();
let types: Vec<ElmTypeInfo> = types.into_iter().map(|t| overrides.apply(t)).collect();
let mut types = types;
types.sort_by(|a, b| {
a.module_path
.cmp(&b.module_path)
.then_with(|| a.type_name.cmp(b.type_name))
});
let names = NameMap::from_types(&types);
let targets: Vec<ElmTypeInfo> = if options.filter_names.is_empty() {
types
} else {
types
.into_iter()
.filter(|t| options.filter_names.iter().any(|n| n == t.type_name))
.collect()
};
if targets.is_empty() {
anyhow::bail!("No types matched. Did you `use my_schema_crate as _;` in main.rs?");
}
let strategy = DefaultStrategy;
let maybe = MaybeEncoderRef::new(vec!["Json", "Encode", "Extra"], "maybe");
let groups = group_by_module(&targets);
if options.dry_run {
let mut buffer = String::new();
for (module_path, group) in &groups {
let module = build_merged_module(module_path, group, &names, &strategy, &maybe);
writeln!(
&mut buffer,
"-- {}.elm --\n{}\n",
module_path.join("."),
elm_ast::pretty_print(&module)
)
.expect("writing to String can't fail");
}
return Ok(CodegenOutcome::DryRun(buffer));
}
for (module_path, group) in &groups {
let module = build_merged_module(module_path, group, &names, &strategy, &maybe);
write_module(&options.output, module_path, &module)?;
}
Ok(CodegenOutcome::Wrote {
module_count: groups.len(),
root: options.output,
})
}
fn write_module(
output_dir: &Path,
module_path: &[&str],
module: &elm_ast::file::ElmModule,
) -> Result<()> {
let mut file_path = output_dir.to_path_buf();
for segment in module_path {
file_path.push(segment);
}
file_path.set_extension("elm");
let parent = file_path
.parent()
.with_context(|| format!("no parent directory for {}", file_path.display()))?;
std::fs::create_dir_all(parent)
.with_context(|| format!("creating directory {}", parent.display()))?;
std::fs::write(&file_path, elm_ast::pretty_print(module))
.with_context(|| format!("writing {}", file_path.display()))?;
Ok(())
}