use crate::{kind, ns, res, sep, yog};
macro_rules! formats {
($( $(#[$doc:meta])* $variant:ident = $ext:expr, $noun:expr; )*) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Format { $( $(#[$doc])* $variant ),* }
impl Format {
pub const ALL: &'static [Format] = &[$(Format::$variant),*];
#[must_use]
pub const fn ext(self) -> &'static str {
match self { $(Format::$variant => $ext),* }
}
#[must_use]
pub const fn noun(self) -> &'static str {
match self { $(Format::$variant => $noun),* }
}
}
};
}
formats! {
Block = "yblock", kind::BLOCK;
Item = "yitem", kind::ITEM;
Entity = "yentity", kind::ENTITY;
Recipe = "yrecipe", kind::RECIPE;
Book = "ybook", kind::BOOK;
Def = "ydef", "definition";
Res = "yres", "resources";
Ui = "yui", "screen";
Model = "ymodel", "model";
Anim = "yanim", "animation";
Font = "yfont", "font";
Texture = "ytexture", kind::TEXTURE;
Material = "ymaterial", kind::MATERIAL;
Data = "ydata", "compiled data";
Mod = "yog", "mod";
}
impl Format {
#[must_use]
pub fn of(ext: &str) -> Option<Format> {
let ext = ext.trim_start_matches('.');
Format::ALL.iter().copied().find(|f| f.ext() == ext)
}
#[must_use]
pub fn of_path(path: &std::path::Path) -> Option<Format> {
Format::of(path.extension()?.to_str()?)
}
#[must_use]
pub fn is(self, path: &std::path::Path) -> bool {
Format::of_path(path) == Some(self)
}
#[must_use]
pub const fn is_data(self) -> bool {
!matches!(self, Format::Ui | Format::Mod)
}
#[must_use]
pub fn file(self, name: &str) -> String {
format!("{name}.{}", self.ext())
}
}
#[must_use]
pub fn texture_file(path: &str) -> String {
format!("{}{path}{}", res::TEXTURES, res::PNG)
}
#[must_use]
pub fn model_file(path: &str) -> String {
format!("{}{path}{}", res::MODELS, res::JSON)
}
#[must_use]
pub fn item_definition_file(path: &str) -> String {
format!("{}{path}{}", res::ITEM_DEFINITIONS, res::JSON)
}
#[must_use]
pub fn blockstate_file(path: &str) -> String {
format!("{}{path}{}", res::BLOCKSTATES, res::JSON)
}
const ENTITY_REGISTRY: &str = crate::renamed_tag_dir::ENTITY_TYPE;
const BIOME_REGISTRY: &str = crate::concatcp!(crate::pack::WORLDGEN, sep::PATH, kind::BIOME);
#[must_use]
pub fn asset_file(ns: &str, rel: &str) -> String {
format!("{}{}{ns}{}{rel}", crate::pack::ASSETS, sep::PATH, sep::PATH)
}
#[must_use]
pub fn rust_name(name: &str) -> String {
name.replace(sep::HYPHEN, sep::WORD)
}
#[must_use]
pub fn registry_of(kind: &str) -> &str {
match kind {
kind::ENTITY => ENTITY_REGISTRY,
kind::BIOME => BIOME_REGISTRY,
other => other,
}
}
fn renamed_in_1_21(dir: &str) -> bool {
crate::renamed_data_dir::ALL_VALUES.contains(&dir)
|| crate::renamed_tag_dir::ALL_VALUES.contains(&dir)
}
#[must_use]
pub fn registry_dirs(kind: &str) -> Vec<String> {
let dir = registry_of(kind);
let mut out = vec![dir.to_string()];
if renamed_in_1_21(dir) {
out.push(format!("{dir}{}", crate::data_layout::PLURAL));
}
out
}
#[must_use]
pub fn lang_file(locale: &str) -> String {
format!("{}{locale}{}", res::LANG, res::JSON)
}
#[must_use]
pub fn lang_prefix(kind: &str, ns: &str) -> String {
format!("{kind}.{ns}.")
}
#[must_use]
pub fn lang_key(kind: &str, id: &str) -> String {
let (ns, path) = split(id);
format!("{}{}", lang_prefix(kind, ns), path.replace(sep::PATH, "."))
}
pub fn join(ns: &str, path: &str) -> String {
format!("{ns}{}{path}", sep::NAMESPACE)
}
pub fn valid_namespace(s: &str) -> bool {
!s.is_empty()
&& s.bytes().all(|b| {
b.is_ascii_lowercase() || b.is_ascii_digit() || matches!(b, b'_' | b'.' | b'-')
})
}
pub fn valid_path(s: &str) -> bool {
let sep = sep::PATH.as_bytes()[0];
!s.is_empty()
&& !s.starts_with(sep::PATH)
&& !s.ends_with(sep::PATH)
&& s.bytes().all(|b| {
b.is_ascii_lowercase()
|| b.is_ascii_digit()
|| matches!(b, b'_' | b'.' | b'-')
|| b == sep
})
}
pub fn valid_id(s: &str) -> bool {
match s.split_once(sep::NAMESPACE) {
Some((ns, path)) => valid_namespace(ns) && valid_path(path),
None => valid_path(s),
}
}
pub fn valid_name(s: &str) -> bool {
!s.is_empty()
&& s.bytes()
.all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == sep::WORD.as_bytes()[0])
}
pub fn split(id: &str) -> (&str, &str) {
id.split_once(sep::NAMESPACE).unwrap_or((ns::MINECRAFT, id))
}
pub fn game_path(id: &str) -> Option<&str> {
id.split_once(sep::NAMESPACE)
.and_then(|(n, path)| (n == ns::MINECRAFT).then_some(path))
}
pub fn qualify(id: &str) -> String {
qualify_in(ns::MINECRAFT, id)
}
pub fn qualify_in(ns: &str, id: &str) -> String {
if id.contains(sep::NAMESPACE) {
id.to_string()
} else {
join(ns, id)
}
}
#[must_use]
pub fn game_font_of(font: &str) -> Option<String> {
font.strip_prefix(yog::FONT_ALIAS)
.filter(|name| !name.is_empty())
.map(|name| join(ns::MINECRAFT, name))
}
#[must_use]
pub fn font_alias(name: &str) -> String {
format!("{}{name}", yog::FONT_ALIAS)
}