use std::env;
use std::fmt::Write as _;
use std::fs;
use std::path::{Path, PathBuf};
fn step_files(dir: &Path) -> Vec<(String, PathBuf)> {
let mut out = Vec::new();
let Ok(entries) = fs::read_dir(dir) else {
return out;
};
for entry in entries {
let path = entry.expect("readable directory entry").path();
let Some(stem) = path.file_stem().and_then(|s| s.to_str()) else {
continue;
};
if path.extension().and_then(|e| e.to_str()) != Some("rs") || stem == "mod" || stem.starts_with('_') {
continue;
}
assert!(
stem.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
"step file names must be snake_case: {}",
path.display()
);
out.push((stem.to_string(), path));
}
out.sort();
out
}
fn generate_dir(out_dir: &Path, name: &str, dir: &Path, kind: &str) {
let entry_type = match kind {
"parsers" => "crate::core::registry::ParserEntry",
_ => "crate::core::registry::RefinerEntry",
};
let mut code = String::from("// @generated by build.rs. Do not edit.\n");
let files = step_files(dir);
for (stem, path) in &files {
let abs = path.canonicalize().expect("canonical path");
writeln!(code, "#[path = {:?}]\npub mod {stem};", abs.display().to_string()).unwrap();
}
writeln!(code, "pub(crate) const ENTRIES: &[{entry_type}] = &[").unwrap();
for (stem, _) in &files {
writeln!(
code,
" {entry_type} {{ upstream: {stem}::UPSTREAM, create: {stem}::create }},"
)
.unwrap();
}
code.push_str("];\n");
fs::write(out_dir.join(format!("{name}.rs")), code).expect("write generated registry");
}
fn generate_locales(out_dir: &Path, root: &Path, locales: &[String]) {
let mut code = String::from("// @generated by build.rs. Do not edit.\n");
let mut named = Vec::new();
for lang in locales {
let dir = root.join("src/locales").join(lang);
if !dir.join("locale.json").is_file() {
continue;
}
let mod_rs = dir
.join("mod.rs")
.canonicalize()
.expect("generated locale has a mod.rs");
writeln!(code, "#[path = {:?}]\npub mod {lang};", mod_rs.display().to_string()).unwrap();
named.push(format!("{lang}::NAMED"));
let mut support = String::from("// @generated by build.rs. Do not edit.\n");
let mut files: Vec<(String, PathBuf)> = fs::read_dir(&dir)
.expect("readable locale directory")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.is_file() && p.extension().and_then(|e| e.to_str()) == Some("rs"))
.filter_map(|p| Some((p.file_stem()?.to_str()?.to_string(), p.clone())))
.filter(|(stem, _)| stem != "mod" && stem != "configuration" && !stem.starts_with('_'))
.collect();
files.sort();
for (stem, path) in files {
assert!(
stem.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_'),
"locale support file names must be snake_case: {}",
path.display()
);
let abs = path.canonicalize().expect("canonical path");
writeln!(support, "#[path = {:?}]\npub mod {stem};", abs.display().to_string()).unwrap();
}
fs::write(out_dir.join(format!("{lang}_support.rs")), support).expect("write locale support");
}
writeln!(
code,
"/// Named configurations of the generated locales.\npub const GENERATED: &[&[crate::locales::NamedConfiguration]] = &[{}];",
named.join(", ")
)
.unwrap();
fs::write(out_dir.join("locales.rs"), code).expect("write generated locales");
}
fn main() {
let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("registry");
fs::create_dir_all(&out_dir).unwrap();
println!("cargo:rerun-if-changed=src/common");
println!("cargo:rerun-if-changed=src/locales");
println!("cargo:rerun-if-changed=build.rs");
let mut sources: Vec<(String, String)> = Vec::new(); for kind in ["parsers", "refiners"] {
generate_dir(
&out_dir,
&format!("common_{kind}"),
&root.join("src/common").join(kind),
kind,
);
sources.push((format!("crate::common::{kind}"), kind.to_string()));
}
let mut locales: Vec<String> = fs::read_dir(root.join("src/locales"))
.map(|rd| {
rd.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.filter_map(|e| e.file_name().to_str().map(str::to_string))
.collect()
})
.unwrap_or_default();
locales.sort();
for lang in &locales {
for kind in ["parsers", "refiners"] {
generate_dir(
&out_dir,
&format!("{lang}_{kind}"),
&root.join("src/locales").join(lang).join(kind),
kind,
);
sources.push((format!("crate::locales::{lang}::{kind}"), kind.to_string()));
}
}
generate_locales(&out_dir, &root, &locales);
let mut all = String::from("// @generated by build.rs. Do not edit.\n");
for kind in ["parsers", "refiners"] {
let entry_type = if kind == "parsers" {
"ParserEntry"
} else {
"RefinerEntry"
};
writeln!(
all,
"pub(crate) fn all_{kind}() -> Vec<&'static {entry_type}> {{\n let mut v = Vec::new();"
)
.unwrap();
for (module, k) in &sources {
if k == kind {
writeln!(all, " v.extend({module}::ENTRIES.iter());").unwrap();
}
}
all.push_str(" v\n}\n");
}
fs::write(out_dir.join("all.rs"), all).unwrap();
}