use super::parser::{parse_lino, split_pipe_list, LinoNode};
use super::FACTS_LINO;
#[derive(Debug, Clone, Default)]
pub struct LocalizedFact {
pub language: String,
pub summary: String,
pub source: String,
pub source_kind: String,
}
#[derive(Debug, Clone)]
pub struct FactRecord {
pub slug: String,
pub intent: String,
pub category: String,
pub wikidata: Vec<String>,
pub subject_aliases: Vec<String>,
pub question_keywords: Vec<String>,
pub summary: String,
pub source: String,
pub source_kind: String,
pub localized: Vec<LocalizedFact>,
}
impl FactRecord {
#[must_use]
pub fn localized_for(&self, language: &str) -> Option<&LocalizedFact> {
self.localized
.iter()
.find(|loc| loc.language == language)
.or_else(|| self.localized.iter().find(|loc| loc.language == "en"))
}
#[must_use]
pub fn summary_for(&self, language: &str) -> &str {
self.localized_for(language)
.map(|loc| loc.summary.as_str())
.filter(|s| !s.is_empty())
.unwrap_or(self.summary.as_str())
}
#[must_use]
pub fn source_for(&self, language: &str) -> &str {
self.localized_for(language)
.map(|loc| loc.source.as_str())
.filter(|s| !s.is_empty())
.unwrap_or(self.source.as_str())
}
#[must_use]
pub fn matches_normalized(&self, normalized: &str) -> bool {
let has_subject = self
.subject_aliases
.iter()
.any(|alias| !alias.is_empty() && normalized.contains(alias.as_str()));
if !has_subject {
return false;
}
if self.question_keywords.is_empty() {
return true;
}
self.question_keywords
.iter()
.any(|keyword| !keyword.is_empty() && normalized.contains(keyword.as_str()))
}
}
#[must_use]
pub fn facts() -> Vec<FactRecord> {
let tree = parse_lino(FACTS_LINO);
let mut out = Vec::new();
let entries: &[LinoNode] = if tree.name.is_empty() {
tree.children.as_slice()
} else {
std::slice::from_ref(&tree)
};
for entry in entries {
if !entry.name.starts_with("fact_") {
continue;
}
let summary = entry.find_child_value("summary").to_string();
if summary.is_empty() {
continue;
}
let subject_aliases = split_pipe_list(entry.find_child_value("subject_aliases"))
.into_iter()
.map(|s| s.to_lowercase())
.collect();
let question_keywords = split_pipe_list(entry.find_child_value("question_keywords"))
.into_iter()
.map(|s| s.to_lowercase())
.collect();
let wikidata = split_pipe_list(entry.find_child_value("wikidata"));
let mut localized = Vec::new();
for child in entry.children.iter().filter(|c| c.name == "localized") {
let lang = child.id.clone();
if lang.is_empty() {
continue;
}
localized.push(LocalizedFact {
language: lang,
summary: child.find_child_value("summary").to_string(),
source: child.find_child_value("source").to_string(),
source_kind: child.find_child_value("source_kind").to_string(),
});
}
out.push(FactRecord {
slug: entry.name.clone(),
intent: entry.find_child_value("intent").to_string(),
category: entry.find_child_value("category").to_string(),
wikidata,
subject_aliases,
question_keywords,
summary,
source: entry.find_child_value("source").to_string(),
source_kind: entry.find_child_value("source_kind").to_string(),
localized,
});
}
out
}