Temporary change to make cross builds work.
[pspp] / rust / build.rs
1 use anyhow::{anyhow, Result as AnyResult};
2 use std::{
3     collections::{BTreeMap, HashSet, VecDeque},
4     env::var_os,
5     fs::{read_to_string, File},
6     io::{Error as IoError, Write},
7     path::{Path, PathBuf},
8 };
9
10 #[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd)]
11 enum Source {
12     CP,
13     IBM,
14     Windows,
15 }
16
17 // Code page number.
18 type CPNumber = usize;
19
20 fn process_converter<'a>(
21     fields: &Vec<&'a str>,
22     codepages: &mut BTreeMap<CPNumber, BTreeMap<Source, Vec<&'a str>>>,
23 ) {
24     if fields.is_empty() || fields[0] == "{" {
25         return;
26     }
27
28     let mut cps: BTreeMap<Source, CPNumber> = BTreeMap::new();
29     let mut iana = VecDeque::new();
30     let mut other = VecDeque::new();
31
32     let mut iter = fields.iter().peekable();
33     while let Some(&name) = iter.next() {
34         if iter.next_if(|&&s| s == "{").is_some() {
35             let mut standards = HashSet::new();
36             loop {
37                 let &standard = iter.next().expect("missing `}` in list of standards");
38                 if standard == "}" {
39                     break;
40                 }
41                 standards.insert(standard);
42             }
43
44             if standards.contains("IANA*") {
45                 iana.push_front(name);
46             } else if standards.contains("IANA") {
47                 iana.push_back(name);
48             } else if standards.iter().any(|&s| s.ends_with('*')) {
49                 other.push_front(name);
50             } else {
51                 other.push_back(name);
52             }
53         } else {
54             // Untagged names are completely nonstandard.
55             continue;
56         }
57
58         if let Some(number) = name.strip_prefix("cp") {
59             if let Ok(number) = number.parse::<CPNumber>() {
60                 cps.insert(Source::CP, number);
61             }
62         }
63
64         if let Some(number) = name.strip_prefix("windows-") {
65             if let Ok(number) = number.parse::<CPNumber>() {
66                 cps.insert(Source::Windows, number);
67             }
68         }
69
70         if let Some(number) = name.strip_prefix("ibm-") {
71             if let Ok(number) = number.parse::<CPNumber>() {
72                 cps.insert(Source::IBM, number);
73             }
74         }
75     }
76
77     // If there are no tagged names then this is completely nonstandard.
78     if iana.is_empty() && other.is_empty() {
79         return;
80     }
81
82     let all: Vec<&str> = iana.into_iter().chain(other.into_iter()).collect();
83     for (source, number) in cps {
84         codepages
85             .entry(number)
86             .or_insert_with(BTreeMap::new)
87             .insert(source, all.clone());
88     }
89 }
90
91 fn write_output(
92     codepages: &BTreeMap<CPNumber, BTreeMap<Source, Vec<&str>>>,
93     file_name: &PathBuf,
94 ) -> Result<(), IoError> {
95     let mut file = File::create(file_name)?;
96
97     write!(
98         file,
99         "{}",
100         "\
101 use lazy_static::lazy_static;
102 use std::collections::HashMap;
103
104 lazy_static! {
105     static ref CODEPAGE_NUMBER_TO_NAME: HashMap<u32, &'static str> = {
106         let mut map = HashMap::new();
107 "
108     )?;
109
110     for (&cpnumber, value) in codepages.iter() {
111         let source = value.keys().max().unwrap();
112         let name = value[source][0];
113         writeln!(file, "        map.insert({cpnumber}, \"{name}\");")?;
114     }
115     write!(
116         file,
117         "{}",
118         "        map
119     };
120
121     static ref CODEPAGE_NAME_TO_NUMBER: HashMap<&'static str, u32> = {
122         let mut map = HashMap::new();
123 "
124     )?;
125
126     let mut names: BTreeMap<String, BTreeMap<Source, Vec<CPNumber>>> = BTreeMap::new();
127     for (&cpnumber, value) in codepages.iter() {
128         for (&source, value2) in value.iter() {
129             for name in value2.iter().map(|name| name.to_ascii_lowercase()) {
130                 names
131                     .entry(name)
132                     .or_insert_with(BTreeMap::new)
133                     .entry(source)
134                     .or_insert_with(Vec::new)
135                     .push(cpnumber);
136             }
137         }
138     }
139
140     for (name, value) in names.iter() {
141         for (_source, numbers) in value.iter().rev().take(1) {
142             writeln!(file, "        map.insert(\"{name}\", {});", numbers[0])?;
143         }
144     }
145     write!(
146         file,
147         "{}",
148         "        map
149     };
150 }
151 "
152     )?;
153
154     Ok(())
155 }
156
157 fn main() -> AnyResult<()> {
158     println!("cargo:rerun-if-changed=build.rs");
159
160     let input_file = Path::new(env!("CARGO_MANIFEST_DIR")).join("convrtrs.txt");
161     println!("cargo:rerun-if-changed={}", input_file.to_string_lossy());
162     let input = read_to_string(&input_file)
163         .map_err(|e| anyhow!("{}: read failed ({e})", input_file.to_string_lossy()))?;
164
165     let mut codepages: BTreeMap<CPNumber, BTreeMap<Source, Vec<&str>>> = BTreeMap::new();
166     let mut converter: Vec<&str> = Vec::new();
167     for line in input.lines() {
168         let line = line
169             .find('#')
170             .map(|position| &line[..position])
171             .unwrap_or(line)
172             .trim_end();
173         if !line.starts_with(&[' ', '\t']) {
174             process_converter(&converter, &mut codepages);
175             converter.clear();
176         }
177         converter.extend(line.split_whitespace());
178     }
179     process_converter(&converter, &mut codepages);
180
181     let output_file_name = Path::new(&var_os("OUT_DIR").unwrap()).join("encodings.rs");
182
183     write_output(&codepages, &output_file_name)
184         .map_err(|e| anyhow!("{}: write failed ({e})", output_file_name.to_string_lossy()))?;
185
186     Ok(())
187 }