use std::path::Path;
use mf2_resource::{Style, code, parse, serialize_with};
use crate::error::{Error, Result};
use crate::loader::{Loaded, Loader, Problem, Property, Record, SourceFile};
#[derive(Clone, Copy, Debug, Default)]
pub struct Resources;
pub const FMT_STYLE: Style = Style {
wrap_over: Some(100),
wrap_at: 76,
indent: " ",
};
impl Loader for Resources {
fn name(&self) -> &'static str {
"W3C Message Resource"
}
fn load(&self, path: &Path) -> Result<Loaded> {
let mut loaded = Loaded::default();
for file in files_in(path)? {
let file = SourceFile::read(file)?;
let index = loaded.files.len();
read_into(&mut loaded, index, &file);
loaded.files.push(file);
}
Ok(loaded)
}
}
fn files_in(dir: &Path) -> Result<Vec<std::path::PathBuf>> {
let mut files = Vec::new();
let entries = std::fs::read_dir(dir).map_err(|source| Error::io(dir.to_path_buf(), source))?;
for entry in entries {
let entry = entry.map_err(|source| Error::io(dir.to_path_buf(), source))?;
let path = entry.path();
if path.extension().is_some_and(|e| e == "mf2") {
files.push(path);
}
}
files.sort();
if files.is_empty() {
return Err(Error::Layout(format!("{}: no .mf2 files", dir.display())));
}
Ok(files)
}
fn read_into(loaded: &mut Loaded, index: usize, file: &SourceFile) {
let (resource, diagnostics) = parse(&file.text);
for d in diagnostics {
loaded.problems.push(Problem {
file: index,
span: d.span,
code: d.code,
message: code::message(d.code),
});
}
loaded.declared.push(resource.locale().map(str::to_owned));
let resource_dnt = has_dnt(&resource.meta);
for entry in resource.iter() {
let id = entry.id().to_string();
let e = entry.entry;
let mut meta: Vec<Property> = e
.meta
.iter()
.map(|m| Property {
name: m.name.to_string(),
value: m.value.as_ref().map(ToString::to_string),
})
.collect();
if !has_dnt(&e.meta) && (resource_dnt || entry.section.is_some_and(|h| has_dnt(&h.meta))) {
meta.push(Property {
name: DO_NOT_TRANSLATE.to_owned(),
value: None,
});
}
loaded.records.push(Record {
id,
source: e.value.to_string(),
file: index,
span: e.span,
id_span: e.id_span,
value_span: e.value_span,
map: e.map.clone(),
comment: e.comment.as_ref().map(|c| c.text.to_string()),
meta,
});
}
}
const DO_NOT_TRANSLATE: &str = "do-not-translate";
fn has_dnt(meta: &[mf2_resource::Meta<'_>]) -> bool {
meta.iter().any(|m| m.name == DO_NOT_TRANSLATE)
}
pub fn format(text: &str) -> Option<String> {
let (resource, diagnostics) = parse(text);
if !diagnostics.is_empty() {
return None;
}
serialize_with(&resource, &FMT_STYLE).ok()
}