use std::collections::{BTreeMap, HashMap};
use std::fmt::Write as _;
use std::path::Path;
use super::{Compiled, GENERATED_DIR, GENERATED_FILE, parse};
fn lang_json(entries: &[(String, String)]) -> String {
let mut out = String::from("{");
for (n, (k, v)) in entries.iter().enumerate() {
out.push_str(if n == 0 { "\n " } else { ",\n " });
out.push_str(&serde_json::to_string(k).unwrap_or_default());
out.push_str(": ");
out.push_str(&serde_json::to_string(v).unwrap_or_default());
}
out.push_str(if entries.is_empty() { "}\n" } else { "\n}\n" });
out
}
pub(super) fn lang_file(
root: &Path,
mod_id: &str,
locale: &str,
entries: Vec<(String, String)>,
origin: &HashMap<String, String>,
) -> Result<(String, Vec<u8>), Vec<String>> {
let path = yog_ids::asset_file(mod_id, &yog_ids::lang_file(locale));
let Ok(text) = std::fs::read_to_string(root.join(&path)) else {
return Ok((path, lang_json(&entries).into_bytes()));
};
let hand =
parse(&text).map_err(|e| vec![format!("{path}:{}", e.trim_start_matches("line "))])?;
let twice: Vec<String> = hand
.iter()
.filter_map(|h| {
origin
.get(&h.name)
.map(|at| format!("{path}:{}: {:?} is also defined at {at}", h.line, h.name))
})
.collect();
if !twice.is_empty() {
return Err(twice);
}
let mut merged: Vec<(String, String)> = hand
.iter()
.map(|h| (h.name.clone(), h.text().to_string()))
.collect();
merged.extend(entries);
Ok((path, lang_json(&merged).into_bytes()))
}
pub(super) fn sounds_json(
root: &Path,
mod_id: &str,
sounds: BTreeMap<String, serde_json::Value>,
) -> Result<(String, Vec<u8>), Vec<String>> {
let path = yog_ids::asset_file(mod_id, yog_ids::res::SOUNDS_JSON);
let mut all = serde_json::Map::new();
if let Ok(text) = std::fs::read_to_string(root.join(&path)) {
all = serde_json::from_str(&text).map_err(|e| vec![format!("{path}: {e}")])?;
}
let mut errors = Vec::new();
for (event, def) in sounds {
if all.insert(event.clone(), def).is_some() {
errors.push(format!(
"{path}: sound event {event:?} is also defined by a .yres sound"
));
}
}
if !errors.is_empty() {
return Err(errors);
}
let json = serde_json::to_string_pretty(&serde_json::Value::Object(all)).unwrap_or_default();
Ok((path, format!("{json}\n").into_bytes()))
}
const BANNER: &str = "// <auto-generated>\n// Generated by `yog build` from {source} — don't edit,\n// it's rewritten on every build.\n// </auto-generated>\n\n";
const SOURCE_EXT: &str = ".g.rs";
pub fn write_sources(root: &Path, compiled: &Compiled) -> Result<(), String> {
let dir = root.join(GENERATED_DIR);
if let Ok(entries) = std::fs::read_dir(&dir) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().into_owned();
if name
.strip_suffix(SOURCE_EXT)
.is_some_and(|set| !compiled.sources.contains_key(set))
{
let _ = std::fs::remove_file(entry.path());
}
}
}
let mut index = BANNER.replace("{source}", "resources/**/*.yres");
index.push_str("// Included by `yog_api::resources!()` as `crate::res`.\n");
for (set, code) in &compiled.sources {
let source = code
.lines()
.next()
.and_then(|l| l.split('`').nth(1))
.unwrap_or("resources/");
let mut file = BANNER.replace("{source}", source);
file.push_str(code);
crate::write_if_changed(&dir.join(format!("{set}{SOURCE_EXT}")), &file)?;
let _ = writeln!(
index,
"include!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/generated/resources/{set}{SOURCE_EXT}\"));"
);
}
crate::write_if_changed(&root.join(GENERATED_FILE), &index)
}