use core::fmt::Write as _;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
fn main() -> Result<(), Box<dyn core::error::Error>> {
println!("cargo::rustc-check-cfg=cfg(coverage)");
generate_corpus_tests("pandoc", "pandoc_corpus_tests.rs")?;
generate_corpus_tests("docspec", "docspec_corpus_tests.rs")?;
Ok(())
}
fn generate_corpus_tests(
corpus: &str,
output_file: &str,
) -> Result<(), Box<dyn core::error::Error>> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?);
let fixture_dir = manifest_dir.join(format!("../../tests/fixtures/docx/{corpus}"));
println!("cargo::rerun-if-changed={}", fixture_dir.display());
let corpus_literal = rust_string_literal(corpus);
let mut generated = String::from("// @generated by crates/docspec-docx-reader/build.rs\n\n");
if fixture_dir.try_exists()? {
let mut fixtures = fs::read_dir(&fixture_dir)?
.filter_map(|entry_result| fixture_file_name(entry_result).transpose())
.collect::<Result<Vec<_>, _>>()?;
fixtures.sort();
for fixture in fixtures {
let test_name = test_name_for_fixture(&fixture)?;
let fixture_literal = rust_string_literal(&fixture);
writeln!(
&mut generated,
"#[test]\nfn {test_name}() {{\n super::assert_fixture_snapshot({corpus_literal}, {fixture_literal});\n}}\n"
)?;
}
}
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
fs::write(out_dir.join(output_file), generated)?;
Ok(())
}
fn rust_string_literal(value: &str) -> String {
let mut literal = String::from("\"");
for character in value.chars() {
literal.extend(character.escape_default());
}
literal.push('"');
literal
}
fn fixture_file_name(entry_result: io::Result<fs::DirEntry>) -> io::Result<Option<String>> {
let directory_entry = entry_result?;
let path = directory_entry.path();
if path.extension() != Some(OsStr::new("docx")) {
return Ok(None);
}
path.file_name()
.and_then(OsStr::to_str)
.map(|file_name| Some(file_name.to_owned()))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "fixture filename is not UTF-8"))
}
fn test_name_for_fixture(file_name: &str) -> Result<String, Box<dyn core::error::Error>> {
let stem = Path::new(file_name)
.file_stem()
.and_then(OsStr::to_str)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "fixture stem is not UTF-8"))?;
let test_name = match stem {
"0_level_headers" => "_0_level_headers",
"compact-style-removal" => "compact_style_removal",
"custom-style-reference" => "custom_style_reference",
"german-reference" => "german_reference",
"instrText_hyperlink" => "instr_text_hyperlink",
"lists-compact" => "lists_compact",
"nested_instrText" => "nested_instr_text",
"preformatted-boundaries" => "preformatted_boundaries",
other => other,
};
if is_valid_rust_identifier(test_name) {
Ok(test_name.to_owned())
} else {
Err(format!(
"Pandoc fixture {file_name:?} does not map to a valid Rust test name; add an explicit case in build.rs"
)
.into())
}
}
fn is_valid_rust_identifier(identifier: &str) -> bool {
let mut chars = identifier.chars();
let Some(first) = chars.next() else {
return false;
};
(first == '_' || first.is_ascii_alphabetic())
&& chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
}