+use anyhow::{anyhow, Result as AnyResult};
+use std::{
+ collections::{BTreeMap, HashSet, VecDeque},
+ env::var_os,
+ fs::{read_to_string, File},
+ io::{Error as IoError, Write},
+ path::{Path, PathBuf},
+};
+
+#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
+enum Source {
+ CP,
+ IBM,
+ Windows,
+}
+
+// Code page number.
+type CPNumber = usize;
+
+fn process_converter<'a>(
+ fields: &Vec<&'a str>,
+ codepages: &mut BTreeMap<CPNumber, BTreeMap<Source, Vec<&'a str>>>,
+) {
+ if fields.is_empty() || fields[0] == "{" {
+ return;
+ }
+
+ let mut cps: BTreeMap<Source, CPNumber> = BTreeMap::new();
+ let mut iana = VecDeque::new();
+ let mut other = VecDeque::new();
+
+ let mut iter = fields.iter().peekable();
+ while let Some(&name) = iter.next() {
+ if iter.next_if(|&&s| s == "{").is_some() {
+ let mut standards = HashSet::new();
+ loop {
+ let &standard = iter.next().expect("missing `}` in list of standards");
+ if standard == "}" {
+ break;
+ }
+ standards.insert(standard);
+ }
+
+ if standards.contains("IANA*") {
+ iana.push_front(name);
+ } else if standards.contains("IANA") {
+ iana.push_back(name);
+ } else if standards.iter().any(|&s| s.ends_with('*')) {
+ other.push_front(name);
+ } else {
+ other.push_back(name);
+ }
+ } else {
+ // Untagged names are completely nonstandard.
+ continue;
+ }
+
+ if let Some(number) = name.strip_prefix("cp") {
+ if let Ok(number) = number.parse::<CPNumber>() {
+ cps.insert(Source::CP, number);
+ }
+ }
+
+ if let Some(number) = name.strip_prefix("windows-") {
+ if let Ok(number) = number.parse::<CPNumber>() {
+ cps.insert(Source::Windows, number);
+ }
+ }
+
+ if let Some(number) = name.strip_prefix("ibm-") {
+ if let Ok(number) = number.parse::<CPNumber>() {
+ cps.insert(Source::IBM, number);
+ }
+ }
+ }
+
+ // If there are no tagged names then this is completely nonstandard.
+ if iana.is_empty() && other.is_empty() {
+ return;
+ }
+
+ let all: Vec<&str> = iana.into_iter().chain(other.into_iter()).collect();
+ for (source, number) in cps {
+ codepages
+ .entry(number)
+ .or_insert_with(BTreeMap::new)
+ .insert(source, all.clone());
+ }
+}
+
+fn write_output(
+ codepages: &BTreeMap<CPNumber, BTreeMap<Source, Vec<&str>>>,
+ file_name: &PathBuf,
+) -> Result<(), IoError> {
+ let mut file = File::create(file_name)?;
+
+ write!(file, "{}", "\
+use lazy_static::lazy_static;
+use std::collections::HashMap;
+
+lazy_static! {
+ static ref CODEPAGE_NUMBER_TO_NAME: HashMap<u32, &'static str> = {
+ let mut map = HashMap::new();
+")?;
+
+ for (&cpnumber, value) in codepages.iter() {
+ let source = value.keys().max().unwrap();
+ let name = value[source][0];
+ writeln!(file, " map.insert({cpnumber}, \"{name}\");")?;
+ }
+ write!(file, "{}", "\
+ map
+ };
+}
+")?;
+
+ let mut names: BTreeMap<&str, BTreeMap<Source, Vec<CPNumber>>> = BTreeMap::new();
+ for (&cpnumber, value) in codepages.iter() {
+ for (&source, value2) in value.iter() {
+ for &name in value2.iter() {
+ names
+ .entry(name)
+ .or_insert_with(BTreeMap::new)
+ .entry(source)
+ .or_insert_with(Vec::new)
+ .push(cpnumber);
+ }
+ }
+ }
+
+ for (&name, value) in names.iter() {
+ for (_source, numbers) in value.iter().rev() {
+ println!(" {{ {}, \"{name}\" }},", numbers[0]);
+ break;
+ }
+ }
+
+ Ok(())
+}
+
+fn main() -> AnyResult<()> {
+ println!("cargo:rerun-if-changed=build.rs");
+
+ let input_file = Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/data/convrtrs.txt");
+ println!("cargo:rerun-if-changed={}", input_file.to_string_lossy());
+ let input = read_to_string(&input_file)
+ .map_err(|e| anyhow!("{}: read failed ({e})", input_file.to_string_lossy()))?;
+
+ let mut codepages: BTreeMap<CPNumber, BTreeMap<Source, Vec<&str>>> = BTreeMap::new();
+ let mut converter: Vec<&str> = Vec::new();
+ for line in input.lines() {
+ let line = line
+ .find('#')
+ .map(|position| &line[..position])
+ .unwrap_or(line)
+ .trim_end();
+ if !line.starts_with(&[' ', '\t']) {
+ process_converter(&converter, &mut codepages);
+ converter.clear();
+ }
+ converter.extend(line.split_whitespace());
+ }
+ process_converter(&converter, &mut codepages);
+
+ let output_file_name = Path::new(&var_os("OUT_DIR").unwrap()).join("encodings.rs");
+
+ write_output(&codepages, &output_file_name)
+ .map_err(|e| anyhow!("{}: write failed ({e})", output_file_name.to_string_lossy()))?;
+
+ Ok(())
+}