use std::{env, fs, io, path::Path};
fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let output = Path::new(&out_dir).join("iri.rs");
let content = inline_file("src/uri/mod.rs").unwrap();
fs::write(output, add_header(replace(content))).unwrap();
}
fn inline_file(path: impl AsRef<Path>) -> Result<String, io::Error> {
let path = path.as_ref();
let content = fs::read_to_string(path)?;
println!("cargo::rerun-if-changed={}", path.display());
let dir = path.parent().unwrap();
inline_mod_declarations(dir, content)
}
fn inline_mod_declarations(dir: &Path, content: String) -> Result<String, io::Error> {
let mut result = String::with_capacity(content.len());
let mut skip_until_grammar = false;
for line in content.lines() {
if line.trim().starts_with("#[grammar(") {
skip_until_grammar = true;
continue;
}
if skip_until_grammar {
if line.trim().starts_with("pub(crate) mod grammar") {
skip_until_grammar = false;
}
continue;
}
if let Some(name) = parse_mod_declaration(line) {
let inlined = resolve_and_inline(dir, name)?;
result.push_str("mod ");
result.push_str(name);
result.push_str(" {\n");
result.push_str(&inlined);
result.push_str("}\n");
} else {
result.push_str(line);
result.push('\n');
}
}
Ok(result)
}
fn parse_mod_declaration(line: &str) -> Option<&str> {
let name = line.trim().strip_prefix("mod ")?.strip_suffix(';')?;
Some(name)
}
fn resolve_and_inline(dir: &Path, name: &str) -> Result<String, io::Error> {
let file_name = name.strip_prefix("r#").unwrap_or(name);
let subdir = dir.join(file_name);
if subdir.is_dir() {
inline_file(subdir.join("mod.rs"))
} else {
inline_file(dir.join(format!("{file_name}.rs")))
}
}
fn replace(s: impl AsRef<str>) -> String {
s.as_ref()
.replace("URI", "IRI")
.replace("Uri", "Iri")
.replace("uri", "iri")
.replace("macro_rules! ", "macro_rules! i")
.replace("macro_rules! iiri", "macro_rules! iri")
}
fn add_header(content: String) -> String {
format!(
"// DO NOT EDIT THIS FILE.\n// It is auto-generated by `build.rs` from its URI counterpart.\n\n{content}"
)
}