use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use jacquard_lexicon::codegen::{CodeGenerator, CodegenMode};
use jacquard_lexicon::corpus::LexiconCorpus;
use jacquard_lexicon::lexicon::LexUserType;
use walkdir::WalkDir;
const DEFAULT_LEXICONS_SUBDIR: &str = "lexicons";
const STAGED_SUBDIR: &str = "lexicons-staged";
const GENERATED_SUBDIR: &str = "_lex";
const GENERATED_ROOT_FILE: &str = "_lex_root.rs";
const RECORD_COLLECTIONS_FILE: &str = "record_collections.rs";
fn main() -> Result<()> {
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?);
let workspace_root = manifest_dir
.parent()
.and_then(Path::parent)
.context("resolve workspace root from manifest dir")?
.to_path_buf();
let lexicons_dir = std::env::var("TANGLED_LEXICONS_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| workspace_root.join(DEFAULT_LEXICONS_SUBDIR));
println!("cargo:rerun-if-env-changed=TANGLED_LEXICONS_DIR");
println!("cargo:rerun-if-changed={}", lexicons_dir.display());
println!("cargo:rerun-if-changed=build.rs");
let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
let staged = out_dir.join(STAGED_SUBDIR);
if staged.exists() {
std::fs::remove_dir_all(&staged).context("clean staged lexicons")?;
}
stage_lexicons(&lexicons_dir, &staged)?;
let corpus = LexiconCorpus::load_from_dir(&staged)
.map_err(|e| anyhow::anyhow!("load lexicon corpus: {e:?}"))?;
let generated = out_dir.join(GENERATED_SUBDIR);
if generated.exists() {
std::fs::remove_dir_all(&generated).context("clean generated dir")?;
}
std::fs::create_dir_all(&generated).context("create generated dir")?;
let codegen = CodeGenerator::with_mode(&corpus, "crate", CodegenMode::Pretty);
codegen
.write_to_disk(&generated)
.map_err(|e| anyhow::anyhow!("write generated code: {e:?}"))?;
write_generated_root(&generated, &out_dir.join(GENERATED_ROOT_FILE))?;
write_record_collections(&corpus, &out_dir.join(RECORD_COLLECTIONS_FILE))?;
Ok(())
}
fn write_record_collections(corpus: &LexiconCorpus, path: &Path) -> Result<()> {
let mut nsids: Vec<&str> = corpus
.iter()
.filter(|(_, doc)| matches!(doc.defs.get("main"), Some(LexUserType::Record(_))))
.map(|(nsid, _)| nsid.as_str())
.collect();
nsids.sort_unstable();
let entries: String = nsids
.iter()
.map(|nsid| format!(" {nsid:?},\n"))
.collect();
let source = format!(
"/// every record collection in the lexicons, sorted so lookups can binary search\n\
pub const RECORD_COLLECTIONS: &[&str] = &[\n{entries}];\n"
);
std::fs::write(path, source).context("write record collections")
}
/// absolute #[path] so the generated `mod x;` files resolve inside OUT_DIR, not next to lib.rs
fn write_generated_root(generated: &Path, path: &Path) -> Result<()> {
let source = format!(
"#[path = {:?}]\nmod generated;\npub use generated::*;\n",
generated.join("lib.rs")
);
std::fs::write(path, source).context("write generated root")
}
fn stage_lexicons(src: &Path, dst: &Path) -> Result<()> {
WalkDir::new(src)
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| e.file_type().is_file())
.filter(|e| {
e.path()
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
})
.try_for_each(|entry| -> Result<()> {
let rel = entry.path().strip_prefix(src).context("strip src prefix")?;
let target = dst.join(rel);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent).context("create staged parent")?;
}
std::fs::copy(entry.path(), &target).context("copy lexicon file")?;
Ok(())
})
}