use std::{fmt::Write, fs}; #[test] fn generated_code_is_fresh() { let mut fresh = generate_consts( "Name", "src/consts/names.txt", "src/consts/names.rs", &[ ("vnd.", "vnd", "Vendor subtypes starting with `vnd.`."), ("x-", "x_", "Unregistered subtypes starting with `x-`."), ("", "", ""), ], NAMES_HEADER, ); fresh = fresh && generate_consts( "Value", "src/consts/values.txt", "src/consts/values.rs", &[("", "", "")], VALUES_HEADER, ); if !fresh { panic!("generated code is not fresh, please commit updates"); } } fn generate_consts( ty: &str, input: &str, dst: &str, prefixes: &[(&str, &str, &str)], header: &str, ) -> bool { let mut prefixes = prefixes .iter() .map(|&(pf, name, comment)| (pf, name, comment, String::with_capacity(1024))) .collect::>(); let input = fs::read_to_string(&input).expect("failed to read input file"); for line in input.lines() { let (ident, name) = if let Some(pair) = line.split_once('=') { pair } else { (line, line) }; if let Some((pf, _, _, out)) = prefixes.iter_mut().find(|(pf, ..)| ident.starts_with(pf)) { let ident = upper_snake_case(ident.trim_start_matches(*pf)); let indent = if pf.is_empty() { "" } else { " " }; writeln!(out, "{}/// `{}`", indent, name).unwrap(); writeln!( out, "{}pub const {}: crate::{} = crate::{}::new_unchecked(\"{}\");", indent, ident, ty, ty, name ) .unwrap(); } } let mut new = String::with_capacity(1024); new.push_str(header); new.push_str("\n\n"); for (pf, name, comment, out) in prefixes { if !pf.is_empty() { writeln!(new, "/// {}", comment).unwrap(); writeln!(new, "pub mod {} {{", name).unwrap(); } new.push_str(&out); if !pf.is_empty() { new.push_str("}\n\n"); } } let old = fs::read_to_string(dst).expect("failed to read destination file"); if old != new { fs::write(dst, &new).expect("failed to write destination file"); } old == new } fn upper_snake_case(s: &str) -> String { let s = s .split_inclusive(char::is_uppercase) .map(|chunk| { if chunk.ends_with(char::is_uppercase) { let prefix = chunk.trim_end_matches(char::is_uppercase); if prefix.ends_with(char::is_lowercase) { return format!("{}_{}", prefix, chunk.split_at(chunk.len() - 1).1); } } chunk.to_string() }) .collect::() .replace('+', "_plus") .replace(|c| !char::is_ascii_alphanumeric(&c), "_") .to_ascii_uppercase(); if s.starts_with(char::is_numeric) { format!("_{}", s) } else { s } } const NAMES_HEADER: &str = r#"//! Predefined names, @generated in tests/codegen.rs //! //! # Sources //! - //! - //! - //! - //! - //! - "#; const VALUES_HEADER: &str = r#"//! Predefined parameter values, @generated in tests/codegen.rs //! //! # Sources //! - //! - "#;