Skip to main content

yog_cli/yres/
outputs.rs

1//! What a mod's resources become besides code: lang files and
2//! `sounds.json` (merged with hand-written ones), and the generated
3//! sources written out.
4
5use std::collections::HashMap;
6use std::fmt::Write as _;
7use std::path::Path;
8
9use super::{Compiled, GENERATED_DIR, GENERATED_FILE, parse};
10use crate::diagnostics::Diagnostic;
11use crate::yres::problem;
12use yog_codegen::{Header, Regenerate};
13use yog_codegen_rust::Rust;
14
15/// The lang JSON of `entries` (key, text), order kept.
16fn lang_json(entries: &[(String, String)]) -> String {
17    let mut out = String::from("{");
18    for (n, (k, v)) in entries.iter().enumerate() {
19        out.push_str(if n == 0 { "\n  " } else { ",\n  " });
20        out.push_str(&serde_json::to_string(k).unwrap_or_default());
21        out.push_str(": ");
22        out.push_str(&serde_json::to_string(v).unwrap_or_default());
23    }
24    out.push_str(if entries.is_empty() { "}\n" } else { "\n}\n" });
25    out
26}
27
28/// Locale `locale`'s lang file of the mod: its `entries` after a
29/// hand-written file's, whose keys mustn't be the `.yres` texts' (`origin`:
30/// where each of those is).
31pub(super) fn lang_file(
32    root: &Path,
33    mod_id: &str,
34    locale: &str,
35    entries: Vec<(String, String)>,
36    origin: &HashMap<String, String>,
37) -> Result<(String, Vec<u8>), Vec<Diagnostic>> {
38    let path = yog_ids::asset_file(mod_id, &yog_ids::lang_file(locale));
39    let Ok(text) = std::fs::read_to_string(root.join(&path)) else {
40        return Ok((path, lang_json(&entries).into_bytes()));
41    };
42    let hand = parse(&text).map_err(|found| {
43        found
44            .0
45            .iter()
46            .map(|l| l.clone().in_file(&path))
47            .collect::<Vec<_>>()
48    })?;
49    let twice: Vec<Diagnostic> = hand
50        .iter()
51        .filter_map(|h| {
52            origin.get(&h.name).map(|at| {
53                problem(format!("{:?} is also defined at {at}", h.name))
54                    .in_file(&path)
55                    .at_line(h.line)
56            })
57        })
58        .collect();
59    if !twice.is_empty() {
60        return Err(twice);
61    }
62    let mut merged: Vec<(String, String)> = hand
63        .iter()
64        .map(|h| (h.name.clone(), h.text().to_string()))
65        .collect();
66    merged.extend(entries);
67    Ok((path, lang_json(&merged).into_bytes()))
68}
69
70/// The generated sources' header, `source` what each is generated from.
71fn banner(source: &str) -> String {
72    Header {
73        tool: "`yog build`",
74        source,
75        regenerate: Regenerate::EveryBuild,
76    }
77    .comment::<Rust>()
78        + "\n"
79}
80/// What a generated source's file name ends with.
81const SOURCE_EXT: &str = ".g.rs";
82
83/// Writes `obj/generated/resources/<Set>.g.rs` per set and
84/// `obj/generated/resources.g.rs` including them all (what
85/// `yog_api::resources!()` pulls in as `crate::res`); deletes those of sets
86/// that are gone.
87pub fn write_sources(root: &Path, compiled: &Compiled) -> Result<(), String> {
88    let dir = root.join(GENERATED_DIR);
89    if let Ok(entries) = std::fs::read_dir(&dir) {
90        for entry in entries.flatten() {
91            let name = entry.file_name().to_string_lossy().into_owned();
92            if name
93                .strip_suffix(SOURCE_EXT)
94                .is_some_and(|set| !compiled.sources.contains_key(set))
95            {
96                crate::files::remove(&entry.path());
97            }
98        }
99    }
100    let mut index = banner("resources/**/*.yres");
101    index.push_str("// Included by `yog_api::resources!()` as `crate::res`.\n");
102    for (set, code) in &compiled.sources {
103        let source = code
104            .lines()
105            .next()
106            .and_then(|l| l.split('`').nth(1))
107            .unwrap_or("resources/");
108        let mut file = banner(source);
109        file.push_str(code);
110        crate::write_if_changed(&dir.join(format!("{set}{SOURCE_EXT}")), &file)?;
111        let _ = writeln!(
112            index,
113            "include!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/generated/resources/{set}{SOURCE_EXT}\"));"
114        );
115    }
116    crate::write_if_changed(&root.join(GENERATED_FILE), &index)?;
117    // The same sets for the mod's Yog Script.
118    let script = root.join(super::GENERATED_SCRIPT);
119    match super::script::source(&compiled.scripts) {
120        Some(code) => crate::write_if_changed(&script, &code),
121        None => {
122            crate::files::remove(&script);
123            Ok(())
124        }
125    }
126}