1mod accessors;
19mod compile;
20mod load;
21mod names;
22mod outputs;
23mod placeholders;
24
25pub use compile::compile;
26pub(crate) use names::{accessor_name, default_key, name_problem};
27pub use outputs::write_sources;
28
29use crate::diagnostics::Diagnostic;
30use std::collections::{BTreeMap, HashMap};
31use std::path::{Path, PathBuf};
32
33pub const NEUTRAL_LOCALE: &str = "en_us";
35pub(crate) const RESOURCES_DIR: &str = yog_ids::project::RESOURCES;
37pub const GENERATED_FILE: &str = ".yog-build/generated/resources.g.rs";
41pub const GENERATED_DIR: &str = ".yog-build/generated/resources";
43
44pub(crate) type Output = (String, Vec<u8>);
46
47pub use yog_formats::yres::{Entry, Kind, Value, parse, write};
48
49#[derive(Debug, Clone, Default)]
51pub struct Refs {
52 pub(crate) texts: HashMap<String, (String, String)>,
54 pub(crate) images: HashMap<String, String>,
56 pub(crate) colors: HashMap<String, u32>,
58 pub(crate) pictures: HashMap<String, Picture>,
60}
61
62impl Refs {
63 pub(crate) fn text(&self, r: &yog_data::Res) -> Result<&(String, String), String> {
65 self.texts
66 .get(&r.name())
67 .ok_or_else(|| format!("`{r}`: no text `{}` in resources/*.yres", r.name()))
68 }
69
70 pub fn color(&self, r: &yog_data::Res) -> Result<u32, String> {
72 self.colors
73 .get(&r.name())
74 .copied()
75 .ok_or_else(|| format!("`{r}`: no color `{}` in resources/*.yres", r.name()))
76 }
77
78 pub(crate) fn key_of(&self, t: &yog_data::Text) -> Result<Option<String>, String> {
81 use yog_data::Text;
82 Ok(match t {
83 Text::Literal(_) => None,
84 Text::Lang(k) => Some(k.clone()),
85 Text::NameOf(r) => Some(yog_ids::lang_key(
86 &r.kind,
87 yog_formats::book::refs::bare(&r.id),
88 )),
89 Text::Res(r) => Some(self.text(r)?.0.clone()),
90 })
91 }
92}
93
94#[derive(Debug, Clone, Default, PartialEq)]
97pub(crate) struct Picture {
98 pub(crate) png: Vec<u8>,
99 pub(crate) output: String,
100}
101
102#[derive(Debug, Default)]
104pub struct Compiled {
105 pub outputs: Vec<Output>,
109 pub sources: BTreeMap<String, String>,
111 pub refs: Refs,
113 pub warnings: Vec<Diagnostic>,
115 pub files: usize,
117}
118
119pub(crate) fn find(root: &Path) -> Vec<PathBuf> {
121 crate::files::under(&root.join(RESOURCES_DIR), &|p| yog_ids::Format::Res.is(p))
122}
123
124pub(crate) fn value_problem(e: &Entry) -> Option<String> {
127 let media = |what: &str| format!("{:?}: {what} are bytes (`@bytes:…`)", e.name);
128 match (e.kind, &e.value) {
129 (Kind::Image | Kind::File | Kind::Sound, Value::Bytes(_))
130 if !yog_ids::valid_path(&e.name.replace('.', yog_ids::sep::PATH)) =>
131 {
132 Some(format!(
133 "{:?}: Minecraft resource paths are lower case — only a-z, 0-9, `_`, `-`, `.`",
134 e.name
135 ))
136 }
137 (Kind::Sound, Value::Bytes(b)) if !b.starts_with(yog_formats::yres::OGG_MAGIC) => {
138 Some(format!("{:?}: sounds are Ogg Vorbis", e.name))
139 }
140 (Kind::Image | Kind::File | Kind::Sound, Value::Bytes(_)) => None,
141 (Kind::Image, _) => Some(media("images")),
142 (Kind::File, _) => Some(media("files")),
143 (Kind::Sound, _) => Some(media("sounds")),
144 (Kind::Color, _) => parse_color(e.text()).is_none().then(|| {
145 format!(
146 "{:?}: `{}` isn't a color — #RRGGBB or #AARRGGBB",
147 e.name,
148 e.text()
149 )
150 }),
151 (Kind::Number, Value::Num(_))
152 | (Kind::Bool, Value::Bool(_))
153 | (Kind::String, Value::Str(_)) => None,
154 (Kind::Number, _) => Some(format!("{:?}: a number", e.name)),
155 (Kind::Bool, _) => Some(format!("{:?}: true or false", e.name)),
156 (Kind::String, _) => Some(format!("{:?}: a text", e.name)),
157 }
158}
159
160pub(crate) fn color_of(e: &Entry) -> Option<u32> {
162 (e.kind == Kind::Color)
163 .then(|| parse_color(e.text()))
164 .flatten()
165}
166
167fn parse_color(s: &str) -> Option<u32> {
169 crate::yui::color(s).filter(|_| s.trim().starts_with(crate::yui::HEX_MARK))
170}
171
172pub fn resolve_text(refs: &Refs, value: &str) -> Result<(String, Option<String>), String> {
175 match value.strip_prefix('@') {
176 Some(rest) if rest.starts_with('@') => Ok((rest.to_string(), None)),
177 Some(rest) => match refs.texts.get(rest) {
178 Some((key, text)) => Ok((text.clone(), Some(key.clone()))),
179 None => Err(format!("`{value}`: no text `{rest}` in resources/*.yres")),
180 },
181 None => Ok((value.to_string(), None)),
182 }
183}
184
185pub fn resolve_image(refs: &Refs, value: &str) -> Result<String, String> {
187 match value.strip_prefix('@') {
188 Some(rest) => refs
189 .images
190 .get(rest)
191 .cloned()
192 .ok_or_else(|| format!("`{value}`: no image `{rest}` in resources/*.yres")),
193 None => Ok(value.to_string()),
194 }
195}
196
197#[cfg(test)]
198mod tests;
199
200pub(crate) fn problem(msg: impl std::fmt::Display) -> crate::diagnostics::Diagnostic {
202 crate::diagnostics::Diagnostic::error(yog_ids::build_problem::RESOURCES, msg)
203}
204
205pub(crate) fn caveat(msg: impl std::fmt::Display) -> crate::diagnostics::Diagnostic {
207 crate::diagnostics::Diagnostic::warning(yog_ids::build_problem::RESOURCES, msg)
208}