use super::{parse_lmf, WordNet};
pub struct Source {
pub lang: &'static str,
pub name: &'static str,
pub url: &'static str,
pub format: Format,
pub license: &'static str,
}
#[derive(PartialEq)]
pub enum Format {
XmlGz,
TarXz,
}
pub const SOURCES: &[Source] = &[
Source {
lang: "en",
name: "Open English WordNet 2025",
url: "https://en-word.net/static/english-wordnet-2025-plus.xml.gz",
format: Format::XmlGz,
license: "CC BY 4.0",
},
Source {
lang: "fr",
name: "WOLF — WordNet Libre du Français",
url: "https://github.com/omwn/omw-data/releases/download/v2.0/omw-fr-2.0.tar.xz",
format: Format::TarXz,
license: "CeCILL-C",
},
Source {
lang: "de",
name: "Open German WordNet (ODeNet)",
url: "https://github.com/hdaSprachtechnologie/odenet/releases/download/v1.4/odenet-1.4.tar.xz",
format: Format::TarXz,
license: "CC BY-SA 4.0",
},
Source {
lang: "es",
name: "Spanish WordNet (OMW)",
url: "https://github.com/omwn/omw-data/releases/download/v2.0/omw-es-2.0.tar.xz",
format: Format::TarXz,
license: "CC BY 3.0",
},
];
pub fn source(lang: &str) -> Option<&'static Source> {
SOURCES.iter().find(|s| s.lang.eq_ignore_ascii_case(lang))
}
pub async fn fetch(lang: &str) -> Result<WordNet, String> {
let src = source(lang).ok_or_else(|| {
let known = SOURCES.iter().map(|s| s.lang).collect::<Vec<_>>().join(", ");
format!("no wordnet source for `{lang}` — known languages: {known}")
})?;
if src.format == Format::TarXz {
return Err(format!(
"`{lang}` ({}) ships as a .tar.xz archive; only English (`en`) is fetchable in this \
release — the OMW languages arrive next",
src.name
));
}
let client = reqwest::Client::builder()
.user_agent(concat!("inkhaven/", env!("CARGO_PKG_VERSION")))
.build()
.map_err(|e| format!("http client: {e}"))?;
let resp = client
.get(src.url)
.send()
.await
.map_err(|e| format!("download {}: {e}", src.url))?
.error_for_status()
.map_err(|e| format!("download {}: {e}", src.url))?;
let bytes = resp.bytes().await.map_err(|e| format!("read body: {e}"))?;
let xml = gunzip(&bytes)?;
parse_lmf(&xml)
}
fn gunzip(bytes: &[u8]) -> Result<String, String> {
use std::io::Read;
let mut decoder = flate2::read::GzDecoder::new(bytes);
let mut out = String::new();
decoder.read_to_string(&mut out).map_err(|e| format!("gunzip: {e}"))?;
Ok(out)
}