1use 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
15fn 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
28pub(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
70fn banner(source: &str) -> String {
72 Header {
73 tool: "`yog build`",
74 source,
75 regenerate: Regenerate::EveryBuild,
76 }
77 .comment::<Rust>()
78 + "\n"
79}
80const SOURCE_EXT: &str = ".g.rs";
82
83pub 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 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}