Skip to main content

yog_ids/
lib.rs

1//! yog-ids — the names every part of Yog shares, defined once: namespaces,
2//! kinds of content, typed-value words, image-source schemes, separators and
3//! Yog's own ids. The lowest crate: everything above takes these from here
4//! (composing its own constants with [`concatcp!`] / [`id!`]); the IDE
5//! (TypeScript) and the hosts (Java) get them generated ([`export`]).
6
7pub use const_format::concatcp;
8
9/// `id!(ns::YOG, "state")` → `"yog:state"`, at compile time.
10#[macro_export]
11macro_rules! id {
12    ($ns:expr, $path:expr) => {
13        $crate::concatcp!($ns, $crate::sep::NAMESPACE, $path)
14    };
15}
16
17macro_rules! names {
18    ($( $(#[$gdoc:meta])* $group:ident { $( $(#[$doc:meta])* $name:ident = $value:expr ),* $(,)? } )*) => {
19        $(
20            $(#[$gdoc])*
21            pub mod $group {
22                #[allow(unused_imports)] // a group composes names from the ones above
23                use super::*;
24                $( $(#[$doc])* pub const $name: &str = $value; )*
25            }
26        )*
27        /// Every name, by group — what [`export`] writes.
28        pub const ALL: &[(&str, &[(&str, &str)])] = &[ $( (stringify!($group), &[ $( (stringify!($name), $group::$name) ),* ]) ),* ];
29    };
30}
31
32names! {
33    /// Namespaces.
34    ns {
35        /// The game's: ids without a namespace are its.
36        MINECRAFT = "minecraft",
37        /// Yog's own: its tags, its channels, its aliases of the game's content.
38        YOG = "yog",
39    }
40
41    /// What separates the parts of names.
42    sep {
43        /// A namespace from its path: `minecraft:stone`.
44        NAMESPACE = ":",
45        /// A path's parts: `textures/block/stone`.
46        PATH = "/",
47        /// What starts a typed value: `@item:minecraft:stick`.
48        TYPED = "@",
49        /// What marks a tag where an ingredient is shown: `#c:dusts`.
50        TAG_SHOWN = "#",
51        /// What joins an ingredient's alternatives where it's shown: `a | b`.
52        ALTERNATIVES = "|",
53        /// What separates the fields of a host's reply to the runtime.
54        FIELD = "\t",
55    }
56
57    /// The kinds of content: what a reference names (`@item:…`), what a
58    /// `.ydef` defines (`kind: "ladder"`), what a mod registers with the
59    /// host (`content("potion", …)`).
60    kind {
61        ITEM = "item",
62        BLOCK = "block",
63        ENTITY = "entity",
64        RECIPE = "recipe",
65        ENTRY = "entry",
66        CATEGORY = "category",
67        BOOK = "book",
68        FLUID = "fluid",
69        BIOME = "biome",
70        BIOME_FEATURE = "biome_feature",
71        DIMENSION = "dimension",
72        ATTRIBUTE = "attribute",
73        EFFECT = "effect",
74        POTION = "potion",
75        INVENTORY = "inventory",
76        STARTUP_GRANT = "startup_grant",
77        ADVANCEMENT = "advancement",
78        LADDER = "ladder",
79        LEVEL = "level",
80        SOUND_GROUP = "sound_group",
81        RECIPE_KIND = "recipe_kind",
82        TOOL_MATERIAL = "tool_material",
83        ARMOR_MATERIAL = "armor_material",
84        ORE = "ore",
85        TABLE = "table",
86        TAG = "tag",
87        DAMAGE_TYPE = "damage_type",
88        TREE = "tree",
89        LOOT_MODIFIER = "loot_modifier",
90        LOOT_TABLE = "loot_table",
91        TEXTURE = "texture",
92        MATERIAL = "material",
93    }
94
95    /// What a reference to content of a kind starts with: `@item:`.
96    reference {
97        ITEM = concatcp!(sep::TYPED, kind::ITEM, sep::NAMESPACE),
98        BLOCK = concatcp!(sep::TYPED, kind::BLOCK, sep::NAMESPACE),
99        ENTITY = concatcp!(sep::TYPED, kind::ENTITY, sep::NAMESPACE),
100        RECIPE = concatcp!(sep::TYPED, kind::RECIPE, sep::NAMESPACE),
101        ENTRY = concatcp!(sep::TYPED, kind::ENTRY, sep::NAMESPACE),
102        CATEGORY = concatcp!(sep::TYPED, kind::CATEGORY, sep::NAMESPACE),
103        FLUID = concatcp!(sep::TYPED, kind::FLUID, sep::NAMESPACE),
104        BIOME = concatcp!(sep::TYPED, kind::BIOME, sep::NAMESPACE),
105        DIMENSION = concatcp!(sep::TYPED, kind::DIMENSION, sep::NAMESPACE),
106        TEXTURE = concatcp!(sep::TYPED, kind::TEXTURE, sep::NAMESPACE),
107        MATERIAL = concatcp!(sep::TYPED, kind::MATERIAL, sep::NAMESPACE),
108    }
109
110    /// Typed values that aren't references to content.
111    typed {
112        TAG = kind::TAG,
113        LANG = "lang",
114        IMAGE = "image",
115        /// Bytes as they are, base64 in text: `@bytes:iVBORw0…`.
116        BYTES = "bytes",
117    }
118
119    /// A `.yres` entry's type (`{ "type": "sound", ... }`).
120    res_kind {
121        STRING = "string",
122        IMAGE = typed::IMAGE,
123        FILE = "file",
124        SOUND = "sound",
125        COLOR = "color",
126        NUMBER = "number",
127        BOOL = "bool",
128    }
129
130    /// What an image source starts with (`item:apple`).
131    image_scheme {
132        ITEM = kind::ITEM,
133        BLOCK = kind::BLOCK,
134        SPRITE = "sprite",
135        MOD = "mod",
136    }
137
138    /// Yog's files with a fixed name.
139    file {
140        /// A mod's manifest.
141        MANIFEST = "yog.toml",
142        /// The versions a mod's build resolved.
143        LOCK = "yog.lock",
144    }
145
146    /// The pack the build writes for the hosts.
147    pack {
148        /// The game's data: `data/<ns>/…`.
149        DATA = "data",
150        /// The game's assets: `assets/<ns>/…`.
151        ASSETS = "assets",
152        /// Tags, under a namespace's data: `data/<ns>/tags/<registry>/<path>.json`.
153        TAGS = "tags",
154        /// Overlays for one loader and version: `when/<loader>/<version>/<file>`.
155        OVERLAYS = "when",
156        /// An overlay's loader when it's for every loader.
157        ANY_LOADER = "any",
158        /// An overlay's version when it's for every version.
159        ANY_VERSION = "any",
160    }
161
162    /// A mod project's folders.
163    project {
164        /// Its resources: `.yres` sets, `.ytexture`s, ….
165        RESOURCES = "resources",
166        /// Its content: blocks, items, recipes, books, definitions.
167        CONTENT = "content",
168        /// Its code and screens.
169        SRC = "src",
170        /// Where `yog build` writes: the generated crate, generated code, listings.
171        BUILD = ".yog-build",
172    }
173
174    /// A game's folders Yog uses.
175    game_dir {
176        /// The `.yog` mods the game loads: `<game>/yog-mods`.
177        MODS = "yog-mods",
178    }
179
180    /// Yog's programs.
181    program {
182        /// The command-line tool (`yog build`, `yog schema`, …).
183        CLI = "yog",
184        /// The CLI's service command: tools read and write every format through it.
185        SERVE = "serve",
186    }
187
188    /// Where the game keeps resources, and what their files end with.
189    res {
190        /// The textures' folder: `<ns>:textures/<path>.png`.
191        TEXTURES = concatcp!("textures", sep::PATH),
192        /// The models' folder: `<ns>:models/<path>.json`.
193        MODELS = concatcp!("models", sep::PATH),
194        /// The block states' folder: `<ns>:blockstates/<path>.json`.
195        BLOCKSTATES = concatcp!("blockstates", sep::PATH),
196        /// Items' textures and models: `item/apple`.
197        ITEM_DIR = concatcp!(kind::ITEM, sep::PATH),
198        /// Item definitions (1.21.4+): `<ns>:items/<path>.json`.
199        ITEM_DEFINITIONS = concatcp!("items", sep::PATH),
200        /// Equipment's worn looks (1.21.4+): `<ns>:equipment/<path>.json`.
201        EQUIPMENT = concatcp!("equipment", sep::PATH),
202        /// Worn equipment's textures (1.21.4+): `entity/equipment/<layer>/<path>`.
203        EQUIPMENT_TEXTURES = concatcp!("entity", sep::PATH, "equipment", sep::PATH),
204        /// Armor's worn textures before 1.21.4: `models/armor/<path>_layer_<n>`.
205        ARMOR_TEXTURES = concatcp!("models", sep::PATH, "armor", sep::PATH),
206        /// Between an armor texture's name and its layer's number.
207        ARMOR_LAYER = "_layer_",
208        /// What a spawn egg's item path ends with: `<mob>_spawn_egg`.
209        SPAWN_EGG_SUFFIX = "_spawn_egg",
210        /// Blocks' textures and models: `block/stone`.
211        BLOCK_DIR = concatcp!(kind::BLOCK, sep::PATH),
212        /// The GUI atlas's sprites under `textures/` (1.20.2+).
213        GUI_SPRITES = concatcp!("gui", sep::PATH, "sprites", sep::PATH),
214        /// The atlases' definitions: `<ns>:atlases/<atlas>.json` (every pack's sources combine).
215        ATLASES = concatcp!("atlases", sep::PATH),
216        /// The language files' folder: `<ns>:lang/<locale>.json`.
217        LANG = concatcp!("lang", sep::PATH),
218        /// Models the game draws in code (`builtin/entity`), not from elements.
219        BUILTIN = concatcp!("builtin", sep::PATH),
220        PNG = ".png",
221        SVG = ".svg",
222        JSON = ".json",
223        MCMETA = ".mcmeta",
224    }
225
226    /// Yog's own ids.
227    yog {
228        /// An entity's data the hosts keep for the mods.
229        STATE = id!(ns::YOG, "state"),
230        /// A hanging entity's attachment (its face, size, item).
231        HANGING = id!(ns::YOG, "hanging"),
232        /// The channel entity data is synced on.
233        ENTITY_DATA = id!(ns::YOG, "entity_data"),
234        /// The entity data key an entity's own hitbox travels under: width,
235        /// height, eye height as three little-endian f32.
236        DIMENSIONS = id!(ns::YOG, "dimensions"),
237        /// An inventory slot widget's id: `yog:slot:<index>`.
238        SLOT_PREFIX = id!(ns::YOG, "slot:"),
239        /// The player's inventory widget.
240        PLAYER_INVENTORY = id!(ns::YOG, "player_inventory"),
241        /// A machine screen's id: `yog:machine:<id>`.
242        MACHINE_PREFIX = id!(ns::YOG, "machine:"),
243        /// Yog's alias of a game potion: `yog:potion/<name>`.
244        POTION_ALIAS = id!(ns::YOG, "potion/"),
245        /// The model every mod's spawn egg inherits: Yog's egg in two layers, base and spots.
246        SPAWN_EGG_MODEL = id!(ns::YOG, "item/spawn_egg"),
247        /// The book's pictures.
248        BOOK_SHEET = id!(ns::YOG, "gui/book/book"),
249        CRAFTING_SHEET = id!(ns::YOG, "gui/book/crafting"),
250        /// A screen being designed in the IDE.
251        DESIGNER_SCREEN = id!(ns::YOG, "designer"),
252        /// A book's default texture (its sheet is [`BOOK_SHEET`]).
253        BOOK_TEXTURE = id!(ns::YOG, "gui/book"),
254        /// An image source that draws nothing (a bare surface).
255        NO_IMAGE = id!(ns::YOG, "none"),
256        /// The textures the runtime renders SVG images into: `<prefix><key>_<w>x<h>`.
257        DYNAMIC_SVG_PREFIX = id!(ns::YOG, "dynamic/svg_"),
258    }
259
260    /// The game's ids Yog's code names.
261    game {
262        /// The font text is drawn in by default.
263        DEFAULT_FONT = id!(ns::MINECRAFT, "default"),
264        /// The UI theme drawn like the game's own screens.
265        VANILLA_THEME = id!(ns::MINECRAFT, "vanilla"),
266        OVERWORLD = id!(ns::MINECRAFT, "overworld"),
267        THE_NETHER = id!(ns::MINECRAFT, "the_nether"),
268        THE_END = id!(ns::MINECRAFT, "the_end"),
269        /// The texture the game draws where one is missing.
270        MISSING_TEXTURE = id!(ns::MINECRAFT, "missingno"),
271        /// What a book is given to players as.
272        WRITTEN_BOOK = id!(ns::MINECRAFT, "written_book"),
273        BOOK = id!(ns::MINECRAFT, "book"),
274        /// The language every lang file falls back to, and a mod's texts are written in.
275        LOCALE = "en_us",
276    }
277
278    /// What `[external.<namespace>]` in `yog.toml` lists another mod's content under.
279    external {
280        ITEMS = "items",
281        BLOCKS = "blocks",
282        ENTITIES = "entities",
283        RECIPES = "recipes",
284    }
285
286    /// The elements of a `.yui` screen or component.
287    yui {
288        SCREEN = "Screen",
289        COMPONENT = "Component",
290        /// A component's declarations.
291        PROPERTY = "Property",
292        EVENT = "Event",
293        STYLE = "Style",
294        CANVAS = "Canvas",
295        COLUMN = "Column",
296        ROW = "Row",
297        GRID = "Grid",
298        SCROLL_VIEW = "ScrollView",
299        LABEL = "Label",
300        BUTTON = "Button",
301        TOGGLE_BUTTON = "ToggleButton",
302        TEXT_BOX = "TextBox",
303        IMAGE = "Image",
304        SURFACE = "Surface",
305        SPACER = "Spacer",
306        PILL = "Pill",
307        STAT_BAR = "StatBar",
308        LIST_VIEW = "ListView",
309        ITEMS_CONTROL = "ItemsControl",
310        CONTENT_PRESENTER = "ContentPresenter",
311        /// Slots: an item shown, an inventory's, the container's.
312        ITEM_SLOT = "ItemSlot",
313        INV_SLOT = "InvSlot",
314        SLOT = "Slot",
315        PLAYER_INVENTORY = "PlayerInventory",
316        /// Which container slot is which, for a block that makes recipes.
317        MACHINE = "Machine",
318        /// A kind of recipe's layout on an inventory screen.
319        RECIPE_AREA = "RecipeArea",
320        /// A recipe layout's places.
321        RECIPE_SLOT = "RecipeSlot",
322        RECIPE_RESULT = "RecipeResult",
323        RECIPE_COUNT = "RecipeCount",
324    }
325
326    /// The fields and types of the game's item definitions (`items/<path>.json`, 1.21.4+).
327    item_def {
328        MODEL = "model",
329        TYPE = "type",
330        TINTS = "tints",
331        VALUE = "value",
332        /// A plain model.
333        PLAIN = "minecraft:model",
334        /// A tint of one colour.
335        CONSTANT = "minecraft:constant",
336    }
337
338    /// The fields and layers of the game's equipment assets (`equipment/<path>.json`, 1.21.4+).
339    equipment {
340        LAYERS = "layers",
341        TEXTURE = "texture",
342        /// Worn on the body: helmet, chestplate, boots.
343        HUMANOID = "humanoid",
344        /// Worn on the legs: leggings.
345        HUMANOID_LEGGINGS = "humanoid_leggings",
346    }
347
348    /// The fields of the game's model JSON (block and item models, block states' variants).
349    model {
350        PARENT = "parent",
351        TEXTURES = "textures",
352        ELEMENTS = "elements",
353        DISPLAY = "display",
354        FACES = "faces",
355        FROM = "from",
356        TO = "to",
357        UV = "uv",
358        ROTATION = "rotation",
359        TEXTURE = "texture",
360        /// A sprite item's (`item/generated`) first layer.
361        LAYER0 = "layer0",
362        /// The texture a block's particles take, and what a face falls back to.
363        PARTICLE = "particle",
364        /// A block state variant's model.
365        MODEL = "model",
366        /// An element's rotation.
367        ORIGIN = "origin",
368        AXIS = "axis",
369        ANGLE = "angle",
370        RESCALE = "rescale",
371        /// Block states: variants by property values, or parts applied when they match.
372        VARIANTS = "variants",
373        MULTIPART = "multipart",
374        WHEN = "when",
375        APPLY = "apply",
376        OR = "OR",
377        AND = "AND",
378    }
379
380    /// The fields of Yog's content files (`.yblock`, `.yitem`, `.ydef`) the tools read directly.
381    content_field {
382        ID = "id",
383        KIND = "kind",
384        MODEL = "model",
385        ITEM_MODEL = "item_model",
386        MATERIAL = "material",
387        ITEM = "item",
388        POTION = "potion",
389        EFFECTS = "effects",
390        EFFECT = "effect",
391        OF = "of",
392        COLOR = "color",
393    }
394
395    /// What a mod asks of this client (`client_op`, ABI 3.9) — what the
396    /// local player is and sees, and what it does: arguments and answers are JSON.
397    client_op {
398        /// Where it is and how it's doing: position, look, motion, health, food, dimension, slot.
399        STATE = "state",
400        /// Moves as if keys were held — `forward`, `strafe` in -1..1, `jump`, `sneak`, `sprint` — till `release`.
401        DRIVE = "drive",
402        /// Hands the movement keys back to the player.
403        RELEASE = "release",
404        /// Turns to `yaw`, `pitch` (degrees).
405        LOOK = "look",
406        /// Attacks entity `entity` (its UUID), or what the crosshair is on.
407        ATTACK = "attack",
408        /// Uses the held item (`off_hand` for the other hand).
409        USE = "use",
410        /// Keeps breaking the block at `x y z` from `face`; `stop` to give up.
411        MINE = "mine",
412        /// Places the held block against the block at `x y z`, on `face`.
413        PLACE = "place",
414        /// Selects hotbar slot `slot` (0–8).
415        SLOT = "slot",
416        /// The blocks among `ids` between `min` and `max`: `[x, y, z, id]` each.
417        SCAN = "scan",
418        /// The blocks between `min` and `max` for finding a way: a palette
419        /// (`id`, `solid`, `liquid`, `climbable`) and an index per block, x fastest.
420        AREA = "area",
421        /// The entities within `radius`: `uuid`, `type`, position, health, a player's `name`.
422        ENTITIES = "entities",
423        /// The player's inventory: `slot`, `item`, `count` each.
424        INVENTORY = "inventory",
425        /// Clicks slot `slot` of the open container (`button`, `mode` as the game's).
426        CLICK = "click",
427        /// Sends `text` to the chat (a command when it starts with `/`).
428        CHAT = "chat",
429    }
430
431    /// The attributes of `.yui` elements.
432    yui_attr {
433        ID = "id",
434        X = "x",
435        Y = "y",
436        W = "w",
437        H = "h",
438        MIN_W = "min_w",
439        MIN_H = "min_h",
440        FLEX = "flex",
441        DOCK = "dock",
442        GAP = "gap",
443        PADDING = "padding",
444        BG = "bg",
445        COLOR = "color",
446        ALIGN = "align",
447        HALIGN = "halign",
448        VALIGN = "valign",
449        MARGIN = "margin",
450        FONT_SCALE = "font_scale",
451        FONT = "font",
452        SHADOW = "shadow",
453        NO_WRAP = "no_wrap",
454        ELLIPSIS = "ellipsis",
455        FOCUS_STYLE = "focus_style",
456        FOCUS_COLOR = "focus_color",
457        ENABLED = "enabled",
458        ON_CLICK = "on_click",
459        TOOLTIP = "tooltip",
460        WIDTH = "width",
461        HEIGHT = "height",
462        ANCHOR = "anchor",
463        MODAL = "modal",
464        PAUSE = "pause",
465        LIVE = "live",
466        THEME = "theme",
467        TEXT = "text",
468        PLACEHOLDER = "placeholder",
469        MAX_LENGTH = "max_length",
470        ON_CHANGE = "on_change",
471        ON_SUBMIT = "on_submit",
472        ITEM = "item",
473        COUNT = "count",
474        INDEX = "index",
475        ACCEPTS = "accepts",
476        SOURCE = "source",
477        STRETCH = "stretch",
478        UV = "uv",
479        TINT = "tint",
480        KIND = "kind",
481        RESULT = "result",
482        PLACES = "places",
483        RECIPE_LAYOUT = "recipe_layout",
484        SLOT = "slot",
485        BLOCK = "block",
486        STYLE = "style",
487        BASED_ON = "based_on",
488        ORIENTATION = "orientation",
489        ROWS = "rows",
490        COLUMNS = "columns",
491        ROW_SPACING = "row_spacing",
492        COLUMN_SPACING = "column_spacing",
493        VALUE = "value",
494        SELECTED = "selected",
495        CHECKED = "checked",
496        ITEM_HEIGHT = "item_height",
497        SCROLL_KEY = "scroll_key",
498        VISIBLE = "visible",
499        GRID_ROW = "Grid.Row",
500        GRID_COLUMN = "Grid.Column",
501        GRID_ROWSPAN = "Grid.RowSpan",
502        GRID_COLUMNSPAN = "Grid.ColumnSpan",
503    }
504}
505
506macro_rules! formats {
507    ($( $(#[$doc:meta])* $variant:ident = $ext:expr, $noun:expr; )*) => {
508        /// Yog's file formats: each one's extension and what a file of it is
509        /// called in messages. What each looks like is `yog_formats::shape`.
510        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
511        pub enum Format { $( $(#[$doc])* $variant ),* }
512
513        impl Format {
514            /// Every format.
515            pub const ALL: &'static [Format] = &[$(Format::$variant),*];
516
517            /// Its extension, without the dot: `yblock`.
518            #[must_use]
519            pub const fn ext(self) -> &'static str {
520                match self { $(Format::$variant => $ext),* }
521            }
522
523            /// What one file of it is: `block`, `animation`.
524            #[must_use]
525            pub const fn noun(self) -> &'static str {
526                match self { $(Format::$variant => $noun),* }
527            }
528        }
529    };
530}
531
532formats! {
533    Block = "yblock", kind::BLOCK;
534    Item = "yitem", kind::ITEM;
535    Entity = "yentity", kind::ENTITY;
536    Recipe = "yrecipe", kind::RECIPE;
537    Book = "ybook", kind::BOOK;
538    /// A definition of some kind (`kind: "ladder"`).
539    Def = "ydef", "definition";
540    /// Named resources: strings, colours, sounds, numbers.
541    Res = "yres", "resources";
542    /// A screen or a component.
543    Ui = "yui", "screen";
544    Model = "ymodel", "model";
545    Anim = "yanim", "animation";
546    Font = "yfont", "font";
547    /// A texture: its picture as given and how it animates.
548    Texture = "ytexture", kind::TEXTURE;
549    /// A material: the textures a surface is drawn with, and how.
550    Material = "ymaterial", kind::MATERIAL;
551    /// Compiled Yog Data.
552    Data = "ydata", "compiled data";
553    /// A built mod.
554    Mod = "yog", "mod";
555}
556
557impl Format {
558    /// The format with extension `ext` (with or without the dot).
559    #[must_use]
560    pub fn of(ext: &str) -> Option<Format> {
561        let ext = ext.trim_start_matches('.');
562        Format::ALL.iter().copied().find(|f| f.ext() == ext)
563    }
564
565    /// The format of the file at `path`, by its extension.
566    #[must_use]
567    pub fn of_path(path: &std::path::Path) -> Option<Format> {
568        Format::of(path.extension()?.to_str()?)
569    }
570
571    /// Whether `path` is a file of this format.
572    #[must_use]
573    pub fn is(self, path: &std::path::Path) -> bool {
574        Format::of_path(path) == Some(self)
575    }
576
577    /// `name` with this format's extension: `stone` → `stone.yblock`.
578    #[must_use]
579    pub fn file(self, name: &str) -> String {
580        format!("{name}.{}", self.ext())
581    }
582}
583
584/// Texture `path`'s file under its namespace: `block/stone` → `textures/block/stone.png`.
585#[must_use]
586pub fn texture_file(path: &str) -> String {
587    format!("{}{path}{}", res::TEXTURES, res::PNG)
588}
589
590/// Model `path`'s file: `item/apple` → `models/item/apple.json`.
591#[must_use]
592pub fn model_file(path: &str) -> String {
593    format!("{}{path}{}", res::MODELS, res::JSON)
594}
595
596/// Block `path`'s states' file: `stone` → `blockstates/stone.json`.
597#[must_use]
598pub fn blockstate_file(path: &str) -> String {
599    format!("{}{path}{}", res::BLOCKSTATES, res::JSON)
600}
601
602/// The game's registry of content `kind` — what its tags' and data's
603/// folders are named: `entity` → `entity_type`, `biome` → `worldgen/biome`.
604#[must_use]
605pub fn registry_of(kind: &str) -> &str {
606    match kind {
607        kind::ENTITY => "entity_type",
608        kind::BIOME => "worldgen/biome",
609        other => other,
610    }
611}
612
613/// The kinds whose data and tag folders were plural before 1.21 (`items`, `loot_tables`).
614const PLURAL_BEFORE_1_21: &[&str] = &[kind::ITEM, kind::BLOCK, kind::ENTITY, kind::FLUID, kind::LOOT_TABLE, kind::ADVANCEMENT, kind::RECIPE];
615
616/// The folders of content `kind`'s data or tags, the current one first, then
617/// 1.20's plural one when it was named differently: `item` → `item`, `items`.
618#[must_use]
619pub fn registry_dirs(kind: &str) -> Vec<String> {
620    let dir = registry_of(kind);
621    let mut out = vec![dir.to_string()];
622    if PLURAL_BEFORE_1_21.contains(&kind) {
623        out.push(format!("{dir}s"));
624    }
625    out
626}
627
628/// Language `locale`'s file under a namespace: `lang/en_us.json`.
629#[must_use]
630pub fn lang_file(locale: &str) -> String {
631    format!("{}{locale}{}", res::LANG, res::JSON)
632}
633
634/// What the game's translation keys of content `kind` in namespace `ns` start with: `item.minecraft.`.
635#[must_use]
636pub fn lang_prefix(kind: &str, ns: &str) -> String {
637    format!("{kind}.{ns}.")
638}
639
640/// The game's translation key of content `kind` `id`: `item.minecraft.iron_ingot`, a path's `/` as `.`.
641#[must_use]
642pub fn lang_key(kind: &str, id: &str) -> String {
643    let (ns, path) = split(id);
644    format!("{}{}", lang_prefix(kind, ns), path.replace(sep::PATH, "."))
645}
646
647/// `ns:path`.
648pub fn join(ns: &str, path: &str) -> String {
649    format!("{ns}{}{path}", sep::NAMESPACE)
650}
651
652/// `id`'s namespace and path; a bare path is the game's.
653pub fn split(id: &str) -> (&str, &str) {
654    id.split_once(sep::NAMESPACE).unwrap_or((ns::MINECRAFT, id))
655}
656
657/// `id` with a namespace: the game's if it has none.
658pub fn qualify(id: &str) -> String {
659    qualify_in(ns::MINECRAFT, id)
660}
661
662/// `id` with a namespace: `ns` if it has none (what a mod writes bare is its own).
663pub fn qualify_in(ns: &str, id: &str) -> String {
664    if id.contains(sep::NAMESPACE) { id.to_string() } else { join(ns, id) }
665}
666
667/// The names for the other languages: generated, never edited by hand.
668pub mod export {
669    use super::ALL;
670
671    const HEADER: &str = "Generated by yog-ids from Yog-Mod-Loader/rust/crates/yog-ids — don't edit; regenerate with `cargo run -p yog-ids --bin yog-ids-export`.";
672
673    /// A TypeScript module: one `as const` object per group.
674    pub fn typescript() -> String {
675        let mut out = format!("// {HEADER}\n");
676        for (group, names) in ALL {
677            out.push_str(&format!("\nexport const {} = {{\n", camel(group)));
678            for (name, value) in *names {
679                out.push_str(&format!("  {name}: {value:?},\n"));
680            }
681            out.push_str("} as const;\n");
682        }
683        out.push_str("\n/** Yog's file formats: extension and noun (`Format`). */\nexport const formats = {\n");
684        for f in crate::Format::ALL {
685            out.push_str(&format!("  {}: {{ ext: {:?}, noun: {:?} }},\n", camel(&format!("{f:?}").to_lowercase()), f.ext(), f.noun()));
686        }
687        out.push_str("} as const;\n");
688        out
689    }
690
691    /// A Java class (`package`): a nested class of constants per group.
692    pub fn java(package: &str, class: &str) -> String {
693        let mut out = format!("// {HEADER}\npackage {package};\n\n/** The names Yog's code shares (yog-ids). */\npublic final class {class} {{\n    private {class}() {{}}\n");
694        for (group, names) in ALL {
695            out.push_str(&format!("\n    public static final class {} {{\n        private {}() {{}}\n\n", pascal(group), pascal(group)));
696            for (name, value) in *names {
697                out.push_str(&format!("        public static final String {name} = {value:?};\n"));
698            }
699            out.push_str("    }\n");
700        }
701        out.push_str("\n    /** Yog's file formats' extensions (`Format`). */\n    public static final class Ext {\n        private Ext() {}\n\n");
702        for f in crate::Format::ALL {
703            out.push_str(&format!("        public static final String {} = {:?};\n", format!("{f:?}").to_uppercase(), f.ext()));
704        }
705        out.push_str("    }\n}\n");
706        out
707    }
708
709    fn pascal(s: &str) -> String {
710        s.split('_').map(|w| w[..1].to_uppercase() + &w[1..]).collect()
711    }
712
713    fn camel(s: &str) -> String {
714        let p = pascal(s);
715        p[..1].to_lowercase() + &p[1..]
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722
723    #[test]
724    fn ids_are_composed_from_their_namespace() {
725        assert_eq!(yog::STATE, "yog:state");
726        assert_eq!(game::OVERWORLD, "minecraft:overworld");
727        assert_eq!(image_scheme::ITEM, kind::ITEM);
728        assert_eq!(split("stone"), ("minecraft", "stone"));
729        assert_eq!(qualify("mymod:x"), "mymod:x");
730        assert_eq!(Format::of(".yblock"), Some(Format::Block));
731        assert_eq!(registry_of(kind::BIOME), "worldgen/biome");
732        assert_eq!(Format::Anim.file("walk"), "walk.yanim");
733    }
734
735    /// The hosts' copy (`java-common`), when this crate is in its repository.
736    #[test]
737    fn the_java_names_are_current() {
738        let file = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../java-common/src/main/java/dev/yog/YogNames.java");
739        let Ok(java) = std::fs::read_to_string(&file) else { return };
740        assert!(
741            java == export::java("dev.yog", "YogNames"),
742            "{} is stale: cargo run -p yog-ids --bin yog-ids-export -- --java ../java-common/src/main/java/dev/yog dev.yog YogNames",
743            file.display()
744        );
745    }
746
747    #[test]
748    fn the_exports_name_every_group() {
749        let ts = export::typescript();
750        assert!(ts.contains("export const imageScheme = {") && ts.contains("  YOG: \"yog\","), "{ts}");
751        assert!(ts.contains("  block: { ext: \"yblock\", noun: \"block\" },"), "{ts}");
752        let java = export::java("dev.yog.generated", "YogNames");
753        assert!(java.contains("public static final class ImageScheme {") && java.contains("public static final String STATE = \"yog:state\";"), "{java}");
754    }
755}