use std::collections::BTreeSet;
use std::env;
use std::fs;
use std::io::{self, Write};
use std::path::Path;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let out_dir = env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed=registries/tsCAS.names");
println!("cargo:rerun-if-changed=registries/tsPDS.names");
let ca_entries = parse_names_file(
&Path::new(&manifest_dir).join("registries/tsCAS.names"),
"CASystemId",
);
let pds_entries = parse_names_file(
&Path::new(&manifest_dir).join("registries/tsPDS.names"),
"PrivateDataSpecifier",
);
let dest = Path::new(&out_dir).join("registry_names.rs");
let mut f = fs::File::create(&dest).unwrap();
write_ca_function(&mut f, &ca_entries).unwrap();
writeln!(f).unwrap();
write_pds_function(&mut f, &pds_entries).unwrap();
}
struct Entry {
lo: u32,
hi: u32,
name: String,
is_range: bool,
}
fn parse_names_file(path: &Path, section_name: &str) -> Vec<Entry> {
let content = fs::read_to_string(path).unwrap_or_else(|e| {
panic!("failed to read {}: {}", path.display(), e);
});
let mut entries: Vec<Entry> = Vec::new();
let mut in_section = false;
for line in content.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with('[') {
in_section = line == format!("[{}]", section_name);
continue;
}
if !in_section {
continue;
}
if line.starts_with("Bits") {
continue;
}
let (key_part, value_part) = match line.find('=') {
Some(pos) => {
let k = line[..pos].trim();
let v = &line[pos + 1..];
(k, v)
}
None => continue,
};
let value_part = match value_part.find('#') {
Some(pos) => value_part[..pos].trim(),
None => value_part.trim(),
};
if value_part.is_empty() {
continue;
}
let (lo, hi, is_range) = if let Some(dash_pos) = key_part.find('-') {
let lo_str = key_part[..dash_pos].trim();
let hi_str = key_part[dash_pos + 1..].trim();
let lo = parse_hex(lo_str);
let hi = parse_hex(hi_str);
(lo, hi, true)
} else {
let lo = parse_hex(key_part);
(lo, lo, false)
};
entries.push(Entry {
lo,
hi,
name: value_part.to_string(),
is_range,
});
}
entries
}
fn parse_hex(s: &str) -> u32 {
let s = s.strip_prefix("0x").unwrap_or(s);
u32::from_str_radix(s, 16).unwrap_or_else(|_| panic!("invalid hex: {}", s))
}
fn escape_name(name: &str) -> String {
name.replace('\\', "\\\\").replace('"', "\\\"")
}
fn write_ca_function(f: &mut fs::File, entries: &[Entry]) -> io::Result<()> {
writeln!(f, "// Generated by build.rs. Do not edit.")?;
writeln!(f, "// Source: registries/tsCAS.names [CASystemId]")?;
writeln!(f, "#[allow(clippy::match_same_arms)]")?;
writeln!(
f,
"pub(crate) fn ca_system_name_generated(id: u16) -> Option<&'static str> {{"
)?;
writeln!(f, " match id {{")?;
let mut seen_exact: BTreeSet<u32> = BTreeSet::new();
let mut exact_pairs: Vec<(u32, &str)> = Vec::new();
let mut range_pairs: Vec<(u32, u32, &str)> = Vec::new();
let mut seen_range_starts: BTreeSet<u32> = BTreeSet::new();
for entry in entries {
if entry.is_range {
if seen_range_starts.contains(&entry.lo) {
continue;
}
seen_range_starts.insert(entry.lo);
range_pairs.push((entry.lo, entry.hi, &entry.name));
} else {
if seen_exact.contains(&entry.lo) {
continue;
}
seen_exact.insert(entry.lo);
exact_pairs.push((entry.lo, &entry.name));
}
}
exact_pairs.sort_by_key(|(k, _)| *k);
range_pairs.sort_by_key(|(lo, hi, _)| (*lo, *hi));
for (k, name) in &exact_pairs {
writeln!(f, " 0x{:04X} => Some(\"{}\"),", k, escape_name(name))?;
}
if !range_pairs.is_empty() {
for (lo, hi, name) in &range_pairs {
writeln!(
f,
" 0x{:04X}..=0x{:04X} => Some(\"{}\"),",
lo,
hi,
escape_name(name)
)?;
}
}
writeln!(f, " _ => None,")?;
writeln!(f, " }}")?;
writeln!(f, "}}")?;
Ok(())
}
fn write_pds_function(f: &mut fs::File, entries: &[Entry]) -> io::Result<()> {
writeln!(f, "// Generated by build.rs. Do not edit.")?;
writeln!(
f,
"// Source: registries/tsPDS.names [PrivateDataSpecifier]"
)?;
writeln!(f, "#[allow(clippy::match_same_arms)]")?;
writeln!(
f,
"pub(crate) fn private_data_specifier_name_generated(v: u32) -> Option<&'static str> {{"
)?;
writeln!(f, " match v {{")?;
let mut seen_exact: BTreeSet<u32> = BTreeSet::new();
let mut exact_pairs: Vec<(u32, &str)> = Vec::new();
let mut range_pairs: Vec<(u32, u32, &str)> = Vec::new();
let mut seen_range_starts: BTreeSet<u32> = BTreeSet::new();
for entry in entries {
if entry.is_range {
if seen_range_starts.contains(&entry.lo) {
continue;
}
seen_range_starts.insert(entry.lo);
range_pairs.push((entry.lo, entry.hi, &entry.name));
} else {
if seen_exact.contains(&entry.lo) {
continue;
}
seen_exact.insert(entry.lo);
exact_pairs.push((entry.lo, &entry.name));
}
}
exact_pairs.sort_by_key(|(k, _)| *k);
range_pairs.sort_by_key(|(lo, hi, _)| (*lo, *hi));
for (k, name) in &exact_pairs {
writeln!(f, " 0x{:08X} => Some(\"{}\"),", k, escape_name(name))?;
}
if !range_pairs.is_empty() {
for (lo, hi, name) in &range_pairs {
writeln!(
f,
" 0x{:08X}..=0x{:08X} => Some(\"{}\"),",
lo,
hi,
escape_name(name)
)?;
}
}
writeln!(f, " _ => None,")?;
writeln!(f, " }}")?;
writeln!(f, "}}")?;
Ok(())
}