use std::collections::HashMap; use std::sync::OnceLock; /// whois server overrides from Lists.toml ("io:whois.nic.io" style entries) #[derive(Debug, Clone, Default)] pub struct WhoisOverrides { map: HashMap, } impl WhoisOverrides { pub fn get_server(&self, tld: &str) -> Option<&str> { self.map.get(&tld.to_lowercase()).map(|s| s.as_str()) } } struct NamedList { name: String, tlds: Vec, } struct ParsedLists { lists: Vec, whois_overrides: WhoisOverrides, } // parse "tld" or "tld:whois_server" fn parse_entry(entry: &str) -> (String, Option) { if let Some(pos) = entry.find(':') { (entry[..pos].to_string(), Some(entry[pos + 1..].to_string())) } else { (entry.to_string(), None) } } // parse more fn parse_list(entries: &[toml::Value], overrides: &mut HashMap) -> Vec { entries .iter() .filter_map(|v| v.as_str()) .map(|entry| { let (tld, server) = parse_entry(entry); if let Some(s) = server { overrides.insert(tld.to_lowercase(), s); } tld }) .collect() } static PARSED_LISTS: OnceLock = OnceLock::new(); fn parsed_lists() -> &'static ParsedLists { PARSED_LISTS.get_or_init(|| { let raw: toml::Value = toml::from_str(include_str!("../Lists.toml")).expect("Lists.toml must be valid TOML"); let table = raw.as_table().expect("Lists.toml must be a TOML table"); // Build list names in the order build.rs found em let ordered_names: Vec<&str> = env!("HOARDOM_LIST_NAMES").split(',').collect(); let mut overrides = HashMap::new(); let mut lists = Vec::new(); for name in &ordered_names { if let Some(toml::Value::Array(arr)) = table.get(*name) { let tlds = parse_list(arr, &mut overrides); lists.push(NamedList { name: name.to_string(), tlds, }); } } ParsedLists { lists, whois_overrides: WhoisOverrides { map: overrides }, } }) } pub fn list_names() -> Vec<&'static str> { parsed_lists() .lists .iter() .map(|l| l.name.as_str()) .collect() } pub fn default_list_name() -> &'static str { list_names().first().copied().unwrap_or("standard") } pub fn get_tlds(name: &str) -> Option> { let lower = name.to_lowercase(); parsed_lists() .lists .iter() .find(|l| l.name == lower) .map(|l| l.tlds.iter().map(String::as_str).collect()) } pub fn get_tlds_or_default(name: &str) -> Vec<&'static str> { get_tlds(name).unwrap_or_else(|| get_tlds(default_list_name()).unwrap_or_default()) } pub fn whois_overrides() -> &'static WhoisOverrides { &parsed_lists().whois_overrides } pub fn apply_top_tlds(tlds: Vec<&'static str>, top: &[String]) -> Vec<&'static str> { let mut result: Vec<&'static str> = Vec::with_capacity(tlds.len()); // add the top ones in the order specified for t in top { let lower = t.to_lowercase(); if let Some(&found) = tlds.iter().find(|&&tld| tld.to_lowercase() == lower) { if !result.contains(&found) { result.push(found); } } } // then add the rest lol for tld in &tlds { if !result.contains(tld) { result.push(tld); } } result }