use std::collections::{HashMap, HashSet};
use crate::build::LangBundle;
use crate::build::typed::{ElementKind, Id, Message, RefKind};
#[derive(Debug)]
pub struct Analyzed {
pub common: HashSet<Id>,
pub warnings: Vec<String>,
}
impl Analyzed {
pub fn from(langs: &[LangBundle], default: &LangBundle) -> Self {
let others: Vec<(&LangBundle, HashMap<&Id, &Message>)> = langs
.iter()
.filter(|l| l.language_id != default.language_id)
.map(|l| {
let mut by_id: HashMap<&Id, &Message> = HashMap::new();
for m in &l.messages {
by_id.entry(&m.id).or_insert(m);
}
(l, by_id)
})
.collect();
let mut common = HashSet::new();
let mut warnings = Vec::new();
for contract in &default.messages {
let id = &contract.id;
let mut missing_in: Vec<String> = Vec::new();
let mut incompatible_in: Vec<String> = Vec::new();
for (lang, by_id) in &others {
match by_id.get(id) {
None => missing_in.push(lang.language_id.clone()),
Some(msg) if !compatible(contract, msg) => {
incompatible_in
.push(format!("{} ({}:{})", lang.language_id, msg.file, msg.line));
}
Some(_) => {}
}
}
if !missing_in.is_empty() {
warnings.push(format!(
"{}:{}: {id} is not generated — missing from locale(s): {}",
contract.file,
contract.line,
missing_in.join(", "),
));
} else if !incompatible_in.is_empty() {
warnings.push(format!(
"{}:{}: {id} is not generated — incompatible variables or \
elements in locale(s): {}",
contract.file,
contract.line,
incompatible_in.join(", "),
));
} else {
common.insert(id.clone());
}
}
warnings.extend(orphan_warnings(&others, default));
warnings.sort();
Self { common, warnings }
}
}
fn orphan_warnings(
others: &[(&LangBundle, HashMap<&Id, &Message>)],
default: &LangBundle,
) -> Vec<String> {
let default_ids: HashSet<&Id> = default.messages.iter().map(|m| &m.id).collect();
let mut orphans: HashMap<Id, Vec<String>> = HashMap::new();
for (lang, _) in others {
for msg in &lang.messages {
if !default_ids.contains(&msg.id) {
orphans
.entry(msg.id.clone())
.or_default()
.push(lang.language_id.clone());
}
}
}
orphans
.into_iter()
.map(|(id, mut langs)| {
langs.sort();
format!(
"{id} is not generated — present in locale(s) {} but missing from \
the default locale '{}'",
langs.join(", "),
default.language_id,
)
})
.collect()
}
fn compatible(contract: &Message, other: &Message) -> bool {
let args: HashSet<&str> = contract.variables.iter().map(|v| v.id.as_str()).collect();
if contract.elements.is_empty() {
other
.pattern_refs
.iter()
.filter(|r| r.kind == RefKind::Variable)
.all(|r| args.contains(r.name.as_str()))
} else {
let element_names: HashSet<&str> =
contract.elements.iter().map(|e| e.name.as_str()).collect();
let contract_elems: Vec<(&str, ElementKind)> = contract
.elements
.iter()
.map(|e| (e.name.as_str(), e.kind))
.collect();
let other_elems: Vec<(&str, ElementKind)> = other
.pattern_refs
.iter()
.filter(|r| element_names.contains(r.name.as_str()))
.map(|r| (r.name.as_str(), element_kind(r.kind)))
.collect();
if contract_elems != other_elems {
return false;
}
other
.pattern_refs
.iter()
.filter(|r| r.kind == RefKind::Variable && !element_names.contains(r.name.as_str()))
.all(|r| args.contains(r.name.as_str()))
}
}
fn element_kind(kind: RefKind) -> ElementKind {
match kind {
RefKind::Variable => ElementKind::Variable,
RefKind::Term => ElementKind::Term,
}
}