mod accessors;
mod compile;
mod load;
mod names;
mod outputs;
mod placeholders;
pub use compile::compile;
pub use load::{ResFile, Set};
pub use names::{
accessor_name, default_key, name_problem, valid_locale, valid_name, valid_set_name,
};
pub use outputs::write_sources;
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
pub const NEUTRAL_LOCALE: &str = "en_us";
pub const RESOURCES_DIR: &str = yog_ids::project::RESOURCES;
pub const GENERATED_FILE: &str = ".yog-build/generated/resources.g.rs";
pub const GENERATED_DIR: &str = ".yog-build/generated/resources";
pub type Output = (String, Vec<u8>);
pub use yog_formats::yres::{Entry, EntryObject, Kind, Value, parse, write};
#[derive(Debug, Clone, Default)]
pub struct Refs {
pub texts: HashMap<String, (String, String)>,
pub images: HashMap<String, String>,
pub colors: HashMap<String, u32>,
pub pictures: HashMap<String, Picture>,
}
impl Refs {
pub fn text(&self, r: &yog_data::Res) -> Result<&(String, String), String> {
self.texts
.get(&r.name())
.ok_or_else(|| format!("`{r}`: no text `{}` in resources/*.yres", r.name()))
}
pub fn color(&self, r: &yog_data::Res) -> Result<u32, String> {
self.colors
.get(&r.name())
.copied()
.ok_or_else(|| format!("`{r}`: no color `{}` in resources/*.yres", r.name()))
}
pub fn image(&self, r: &yog_data::Res) -> Result<&str, String> {
self.images
.get(&r.name())
.map(String::as_str)
.ok_or_else(|| format!("`{r}`: no image `{}` in resources/*.yres", r.name()))
}
pub fn key_of(&self, t: &yog_data::Text) -> Result<Option<String>, String> {
use yog_data::Text;
Ok(match t {
Text::Literal(_) => None,
Text::Lang(k) => Some(k.clone()),
Text::NameOf(r) => Some(yog_ids::lang_key(
&r.kind,
yog_formats::book::refs::bare(&r.id),
)),
Text::Res(r) => Some(self.text(r)?.0.clone()),
})
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Picture {
pub png: Vec<u8>,
pub output: String,
}
#[derive(Debug, Default)]
pub struct Compiled {
pub outputs: Vec<Output>,
pub sources: BTreeMap<String, String>,
pub refs: Refs,
pub warnings: Vec<String>,
pub files: usize,
}
pub fn find(root: &Path) -> Vec<PathBuf> {
crate::files::under(&root.join(RESOURCES_DIR), &|p| yog_ids::Format::Res.is(p))
}
pub fn value_problem(e: &Entry) -> Option<String> {
let media = |what: &str| format!("{:?}: {what} are bytes (`@bytes:…`)", e.name);
match (e.kind, &e.value) {
(Kind::Image | Kind::File | Kind::Sound, Value::Bytes(_))
if !yog_ids::valid_path(&e.name.replace('.', yog_ids::sep::PATH)) =>
{
Some(format!(
"{:?}: Minecraft resource paths are lower case — only a-z, 0-9, `_`, `-`, `.`",
e.name
))
}
(Kind::Sound, Value::Bytes(b)) if !b.starts_with(yog_formats::yres::OGG_MAGIC) => {
Some(format!("{:?}: sounds are Ogg Vorbis", e.name))
}
(Kind::Image | Kind::File | Kind::Sound, Value::Bytes(_)) => None,
(Kind::Image, _) => Some(media("images")),
(Kind::File, _) => Some(media("files")),
(Kind::Sound, _) => Some(media("sounds")),
(Kind::Color, _) => parse_color(e.text()).is_none().then(|| {
format!(
"{:?}: `{}` isn't a color — #RRGGBB or #AARRGGBB",
e.name,
e.text()
)
}),
(Kind::Number, Value::Num(_))
| (Kind::Bool, Value::Bool(_))
| (Kind::String, Value::Str(_)) => None,
(Kind::Number, _) => Some(format!("{:?}: a number", e.name)),
(Kind::Bool, _) => Some(format!("{:?}: true or false", e.name)),
(Kind::String, _) => Some(format!("{:?}: a text", e.name)),
}
}
pub fn color_of(e: &Entry) -> Option<u32> {
(e.kind == Kind::Color)
.then(|| parse_color(e.text()))
.flatten()
}
fn parse_color(s: &str) -> Option<u32> {
crate::yui::color(s).filter(|_| s.trim().starts_with(crate::yui::HEX_MARK))
}
pub fn resolve_text(refs: &Refs, value: &str) -> Result<(String, Option<String>), String> {
match value.strip_prefix('@') {
Some(rest) if rest.starts_with('@') => Ok((rest.to_string(), None)),
Some(rest) => match refs.texts.get(rest) {
Some((key, text)) => Ok((text.clone(), Some(key.clone()))),
None => Err(format!("`{value}`: no text `{rest}` in resources/*.yres")),
},
None => Ok((value.to_string(), None)),
}
}
pub fn resolve_image(refs: &Refs, value: &str) -> Result<String, String> {
match value.strip_prefix('@') {
Some(rest) => refs
.images
.get(rest)
.cloned()
.ok_or_else(|| format!("`{value}`: no image `{rest}` in resources/*.yres")),
None => Ok(value.to_string()),
}
}
#[cfg(test)]
mod tests;