#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RemoteFile {
pub url: &'static str,
pub sha256: &'static str,
pub bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CataloguePack {
pub language: &'static str,
pub stem: &'static str,
pub aff: RemoteFile,
pub dic: RemoteFile,
pub licence: &'static str,
pub provenance: &'static str,
}
pub const ALLOWED_HOSTS: &[&str] = &["raw.githubusercontent.com"];
pub const CATALOGUE: &[CataloguePack] = &[CataloguePack {
language: "he",
stem: "he_IL",
aff: RemoteFile {
url: "https://raw.githubusercontent.com/LibreOffice/dictionaries/master/he_IL/he_IL.aff",
sha256: "6caf86b3a545be5614f135d33a48baa244a59aca43f051dda6173d5d9cbc7700",
bytes: 78_883,
},
dic: RemoteFile {
url: "https://raw.githubusercontent.com/LibreOffice/dictionaries/master/he_IL/he_IL.dic",
sha256: "5f5331f90ed775bd527f6fb7ad1ead9a1b7d8ce46ad640c2387d9d1dc91d3058",
bytes: 7_796_259,
},
licence: "AGPL-3.0-only",
provenance: "Hspell 1.4, via the LibreOffice dictionaries repository",
}];
#[must_use]
pub fn find(language: &str) -> Option<&'static CataloguePack> {
let primary = language
.split(['-', '_'])
.next()
.unwrap_or(language)
.to_ascii_lowercase();
if primary.is_empty() {
return None;
}
CATALOGUE
.iter()
.find(|pack| pack.language.eq_ignore_ascii_case(&primary))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_entry_is_fetched_over_https_from_an_allowed_host() {
for pack in CATALOGUE {
for file in [&pack.aff, &pack.dic] {
assert!(
file.url.starts_with("https://"),
"{} is not https: {}",
pack.language,
file.url
);
let host = file
.url
.trim_start_matches("https://")
.split('/')
.next()
.unwrap_or_default();
assert!(
ALLOWED_HOSTS.contains(&host),
"{} points at {host}, which is not on the allowlist",
pack.language
);
}
}
}
#[test]
fn every_entry_pins_a_full_length_digest_and_a_size() {
for pack in CATALOGUE {
for file in [&pack.aff, &pack.dic] {
assert_eq!(
file.sha256.len(),
64,
"{}: a SHA-256 is 64 hex characters",
pack.language
);
assert!(
file.sha256
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"{}: digest must be lowercase hex",
pack.language
);
assert!(
file.bytes > 0,
"{}: a size of zero pins nothing",
pack.language
);
}
}
}
#[test]
fn every_entry_states_its_licence_and_where_the_words_came_from() {
for pack in CATALOGUE {
assert!(!pack.licence.is_empty(), "{}", pack.language);
assert!(!pack.provenance.is_empty(), "{}", pack.language);
}
}
#[test]
fn a_language_is_found_by_its_primary_subtag() {
assert_eq!(find("he").map(|p| p.stem), Some("he_IL"));
assert_eq!(find("he-IL").map(|p| p.stem), Some("he_IL"));
assert_eq!(find("HE").map(|p| p.stem), Some("he_IL"));
assert!(find("la").is_none(), "Latin ships only as an archive");
assert!(find("").is_none());
}
}