use std::collections::HashSet;
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use icu_properties::props::GeneralCategory;
use icu_properties::{
props::{Dash, QuotationMark, Script},
CodePointMapData, CodePointSetData,
};
use phf_codegen::Map as PhfMap;
use unicode_normalization::UnicodeNormalization;
const GREEK_BASE_NAMES: &[(char, &str)] = &[
('\u{03B1}', "alpha"),
('\u{03B2}', "beta"),
('\u{03B3}', "gamma"),
('\u{03B4}', "delta"),
('\u{03B5}', "epsilon"),
('\u{03B6}', "zeta"),
('\u{03B7}', "eta"),
('\u{03B8}', "theta"),
('\u{03B9}', "iota"),
('\u{03BA}', "kappa"),
('\u{03BB}', "lambda"),
('\u{03BC}', "mu"),
('\u{03BD}', "nu"),
('\u{03BE}', "xi"),
('\u{03BF}', "omicron"),
('\u{03C0}', "pi"),
('\u{03C1}', "rho"),
('\u{03C3}', "sigma"),
('\u{03C4}', "tau"),
('\u{03C5}', "upsilon"),
('\u{03C6}', "phi"),
('\u{03C7}', "chi"),
('\u{03C8}', "psi"),
('\u{03C9}', "omega"),
];
const QUOTE_DOUBLE_OVERRIDES: &[char] = &[
'\u{00AB}', '\u{00BB}', '\u{02BA}', '\u{02EE}', '\u{201C}', '\u{201D}', '\u{201E}', '\u{201F}', '\u{2033}', '\u{2036}', '\u{275D}', '\u{275E}', '\u{2760}', '\u{276E}', '\u{276F}', '\u{1F676}', '\u{1F677}', '\u{1F678}', '\u{2E42}', '\u{301D}', '\u{301E}', '\u{301F}', ];
const DOUBLE_QUOTE_REPLACEMENT: &str = "'\"'";
const SINGLE_QUOTE_REPLACEMENT: &str = "'\\''";
const QUOTE_SINGLE_OVERRIDES: &[char] = &[
'\u{0149}', '\u{02B9}', '\u{02BC}', '\u{055A}', '\u{07F4}', '\u{07F5}', '\u{2018}', '\u{2019}', '\u{201A}', '\u{201B}', '\u{2032}', '\u{2034}', '\u{2035}', '\u{2037}', '\u{2039}', '\u{203A}', '\u{2057}', '\u{275B}', '\u{275C}', '\u{275F}', '\u{FF07}', '\u{E0027}', ];
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
let dest_path = out_dir.join("generated_tables.rs");
let mut file = File::create(&dest_path).expect("failed to create generated tables");
let mut space_map = PhfMap::<char>::new();
let mut dash_map = PhfMap::<char>::new();
let mut quote_map = PhfMap::<char>::new();
for range in CodePointMapData::<GeneralCategory>::new()
.iter_ranges_for_value(GeneralCategory::SpaceSeparator)
{
for codepoint in range {
let ch = char::from_u32(codepoint).expect("valid code point");
if ch != ' ' {
space_map.entry(ch, "' '");
}
}
}
for range in CodePointSetData::new::<Dash>().iter_ranges() {
for codepoint in range {
let ch = char::from_u32(codepoint).expect("valid code point");
if ch != '-' {
dash_map.entry(ch, "'-'");
}
}
}
let mut seen_quotes = HashSet::new();
for range in CodePointSetData::new::<QuotationMark>().iter_ranges() {
for codepoint in range {
let ch = char::from_u32(codepoint).expect("valid code point");
if ch == '\'' || ch == '"' {
continue;
}
let mapped = if QUOTE_DOUBLE_OVERRIDES.contains(&ch) {
DOUBLE_QUOTE_REPLACEMENT
} else {
SINGLE_QUOTE_REPLACEMENT
};
if seen_quotes.insert(ch) {
quote_map.entry(ch, mapped);
}
}
}
for &ch in QUOTE_SINGLE_OVERRIDES {
if ch == '\'' || ch == '"' {
continue;
}
if seen_quotes.insert(ch) {
quote_map.entry(ch, SINGLE_QUOTE_REPLACEMENT);
}
}
for &ch in QUOTE_DOUBLE_OVERRIDES {
if ch == '\'' || ch == '"' {
continue;
}
if seen_quotes.insert(ch) {
quote_map.entry(ch, DOUBLE_QUOTE_REPLACEMENT);
}
}
let greek_entries = build_greek_entries();
let mut greek_map = PhfMap::<char>::new();
for (ch, quoted_name) in &greek_entries {
greek_map.entry(*ch, quoted_name);
}
writeln!(file, "// AUTO-GENERATED FILE, DO NOT EDIT\n").unwrap();
write_map(&mut file, "SPACE_MAP", &space_map);
write_map(&mut file, "DASH_MAP", &dash_map);
write_map(&mut file, "QUOTE_MAP", "e_map);
let greek_display = greek_map.build().to_string();
writeln!(
file,
"pub static GREEK_MAP: ::phf::Map<char, &'static str> = {greek_display};\n"
)
.unwrap();
}
fn build_greek_entries() -> Vec<(char, String)> {
let gc = CodePointMapData::<GeneralCategory>::new();
let mut entries = Vec::new();
let scripts = CodePointMapData::<Script>::new();
for script in [Script::Greek, Script::Common] {
for range in scripts.iter_ranges_for_value(script) {
for codepoint in range {
let Some(ch) = char::from_u32(codepoint) else {
continue;
};
let source = ch.to_string();
let mut bases = source
.nfkd()
.filter(|&d| gc.get(d) != GeneralCategory::NonspacingMark)
.map(|d| match d {
'\u{03C2}' | '\u{03F2}' => '\u{03C3}', other => other,
});
let (Some(base), None) = (bases.next(), bases.next()) else {
continue;
};
let lowered = base.to_lowercase().next().unwrap_or(base);
let Some(&(_, name)) = GREEK_BASE_NAMES.iter().find(|&&(b, _)| b == lowered) else {
continue;
};
if base.is_uppercase() {
let mut capitalized = String::with_capacity(name.len());
capitalized.push(name.chars().next().unwrap().to_ascii_uppercase());
capitalized.push_str(&name[1..]);
entries.push((ch, format!("\"{capitalized}\"")));
} else {
entries.push((ch, format!("\"{name}\"")));
}
}
}
}
entries
}
fn write_map(file: &mut File, name: &str, map: &PhfMap<char>) {
let display = map.build().to_string();
writeln!(
file,
"pub static {name}: ::phf::Map<char, char> = {display};\n"
)
.unwrap();
}