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