Skip to main content

yog_cli/yres/
names.rs

1//! The names in `.yres` files: entries, sets, locales — and what the
2//! generated code and the lang files call them.
3
4use yog_ids::code_set;
5use yog_ids::kind;
6
7use crate::rust_code::{is_ident, is_keyword};
8
9/// An entry name: identifiers joined by `.` (`title`, `screen.title`).
10pub fn valid_name(name: &str) -> bool {
11    yog_data::typed::is_res_key(name)
12}
13
14/// What's wrong with entry name `name`, if anything.
15pub fn name_problem(name: &str) -> Option<String> {
16    (!valid_name(name)).then(|| {
17        format!("{name:?} isn't a valid name — identifiers joined by `.`, like screen.title")
18    })
19}
20
21/// A resource set name: one identifier (`Strings`, `Icons`).
22pub fn valid_set_name(name: &str) -> bool {
23    is_ident(name) && !is_keyword(name)
24}
25
26/// How long a locale's language and region are: `en`/`lol`, `us`/`419`/`hant`.
27const LANGUAGE_LEN: std::ops::RangeInclusive<usize> = 2..=3;
28const REGION_LEN: std::ops::RangeInclusive<usize> = 2..=4;
29/// What joins them: `en_us`.
30const LOCALE_SEP: char = '_';
31
32/// Minecraft's locale codes: lower case, `en_us`, `pt_br`, `lol_us`, ...
33pub fn valid_locale(locale: &str) -> bool {
34    let Some((lang, region)) = locale.split_once(LOCALE_SEP) else {
35        return false;
36    };
37    LANGUAGE_LEN.contains(&lang.len())
38        && lang.bytes().all(|b| b.is_ascii_lowercase())
39        && REGION_LEN.contains(&region.len())
40        && region
41            .bytes()
42            .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit())
43}
44
45/// The accessor for entry `name`: `screen.title` → `screen_title`,
46/// `ModName` → `mod_name`.
47pub fn accessor_name(name: &str) -> String {
48    let mut out = String::new();
49    for (n, part) in name.split(yog_ids::sep::EXT).enumerate() {
50        if n > 0 {
51            out.push_str(yog_ids::sep::WORD);
52        }
53        let mut prev_lower = false;
54        for c in part.chars() {
55            if c.is_ascii_uppercase() && prev_lower {
56                out.push_str(yog_ids::sep::WORD);
57            }
58            prev_lower = c.is_ascii_lowercase() || c.is_ascii_digit();
59            out.push(c.to_ascii_lowercase());
60        }
61    }
62    out
63}
64
65/// The sets whose texts are content's names, and the kind of content.
66const NAME_SETS: [(&str, &str); 3] = [
67    (code_set::ITEMS, kind::ITEM),
68    (code_set::BLOCKS, kind::BLOCK),
69    (code_set::ENTITIES, kind::ENTITY),
70];
71
72/// The default translation key of text `name` in set `set`: a content
73/// name set's the game's key of that content, any other `<mod>.<name>`.
74pub fn default_key(set: &str, mod_id: &str, name: &str) -> String {
75    match NAME_SETS.iter().find(|(s, _)| *s == set) {
76        Some((_, kind)) => yog_ids::lang_key(kind, &yog_ids::join(mod_id, name)),
77        None => [mod_id, name].join(yog_ids::sep::EXT),
78    }
79}