Skip to main content

yog_data/tag/
mod.rs

1//! Tags of content: `@tag:item:minecraft:planks` — a reference of kind
2//! `tag` whose id is the content's kind, then the tag's id.
3
4use std::fmt;
5use std::marker::PhantomData;
6
7use substrate_data::{Id, Kind, Reader, Ref, Schema, Shape, Value, ValueRef};
8
9use crate::kinds;
10
11/// A tag's id (`item:minecraft:planks`) split: its content's kind and its own id.
12fn split(spelled: &str) -> Option<(&str, &str)> {
13    spelled.split_once(yog_ids::sep::NAMESPACE)
14}
15
16/// A tag of content of kind `K`: `@tag:item:minecraft:planks`.
17pub struct TagId<K> {
18    pub id: String,
19    kind: PhantomData<K>,
20}
21
22impl<K> TagId<K> {
23    pub fn new(id: impl Into<String>) -> Self {
24        Self {
25            id: id.into(),
26            kind: PhantomData,
27        }
28    }
29}
30
31impl<K> Clone for TagId<K> {
32    fn clone(&self) -> Self {
33        Self::new(self.id.clone())
34    }
35}
36
37impl<K> PartialEq for TagId<K> {
38    fn eq(&self, o: &Self) -> bool {
39        self.id == o.id
40    }
41}
42
43impl<K: Kind> fmt::Debug for TagId<K> {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(f, "@{}:{}:{}", kinds::Tag::NAME, K::NAME, self.id)
46    }
47}
48
49impl<K: Kind> Schema for TagId<K> {
50    fn shape() -> Shape {
51        substrate_data::schema::ref_shape::<kinds::Tag>()
52    }
53    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<Self> {
54        let tag = Id::<kinds::Tag>::read(v, cx)?;
55        match split(&tag.id) {
56            Some((kind, id)) if kind == K::NAME => Some(TagId::new(id)),
57            _ => {
58                cx.problem(format!(
59                    "`@{}:{}`: this takes a tag of {} — `@{}:{}:<id>`",
60                    kinds::Tag::NAME,
61                    tag.id,
62                    K::NAME,
63                    kinds::Tag::NAME,
64                    K::NAME
65                ));
66                None
67            }
68        }
69    }
70    fn write(&self) -> Value {
71        let spelled = format!("{}{}{}", K::NAME, yog_ids::sep::NAMESPACE, self.id);
72        Id::<kinds::Tag>::new(spelled).write()
73    }
74}
75
76/// Whether `v` is written as a tag.
77fn is_tag(v: &ValueRef<'_>) -> bool {
78    let tag = kinds::Tag::NAME;
79    match v {
80        ValueRef::Ref { kind, .. } => kind.as_str() == tag,
81        ValueRef::Str(s) => s
82            .strip_prefix(yog_ids::sep::TYPED)
83            .and_then(|rest| rest.strip_prefix(tag))
84            .is_some_and(|rest| rest.starts_with(yog_ids::sep::NAMESPACE)),
85        _ => false,
86    }
87}
88
89/// Content of kind `K` or a tag of it — a recipe ingredient.
90#[derive(Debug, Clone, PartialEq)]
91pub enum OneOrTag<K: Kind<Id = String>> {
92    One(Id<K>),
93    Tag(TagId<K>),
94}
95
96/// An item or an item tag: `"@item:minecraft:stick"`, `"@tag:item:minecraft:planks"`.
97pub type ItemIdOrTag = OneOrTag<kinds::Item>;
98
99impl<K: Kind<Id = String>> Schema for OneOrTag<K> {
100    fn shape() -> Shape {
101        Shape::Ref {
102            kinds: &[K::NAME, kinds::Tag::NAME],
103            id: String::shape,
104        }
105    }
106    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<Self> {
107        if let ValueRef::Str(s) = v
108            && let Some(tag) = s.strip_prefix(yog_ids::sep::TAG)
109        {
110            cx.problem(format!(
111                "`{s}`: write a tag as `@{}:{}:{tag}`",
112                kinds::Tag::NAME,
113                K::NAME
114            ));
115            return None;
116        }
117        if is_tag(v) {
118            TagId::read(v, cx).map(OneOrTag::Tag)
119        } else {
120            Id::read(v, cx).map(OneOrTag::One)
121        }
122    }
123    fn write(&self) -> Value {
124        match self {
125            OneOrTag::One(i) => i.write(),
126            OneOrTag::Tag(t) => t.write(),
127        }
128    }
129}
130
131/// Content of any kind or a tag of it: `@entity:minecraft:pig`,
132/// `@tag:entity:c:bosses` — what a tag names is its first part.
133#[derive(Debug, Clone, PartialEq)]
134pub enum AnyRefOrTag {
135    One(Ref),
136    /// The tag's content kind and its id.
137    Tag(String, String),
138}
139
140impl AnyRefOrTag {
141    /// The kind of content it names (`entity`).
142    pub fn kind(&self) -> &str {
143        match self {
144            AnyRefOrTag::One(r) => r.kind.as_str(),
145            AnyRefOrTag::Tag(kind, _) => kind,
146        }
147    }
148}
149
150impl fmt::Display for AnyRefOrTag {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        match self {
153            AnyRefOrTag::One(r) => r.fmt(f),
154            AnyRefOrTag::Tag(kind, id) => write!(f, "@{}:{kind}:{id}", kinds::Tag::NAME),
155        }
156    }
157}
158
159impl Schema for AnyRefOrTag {
160    fn shape() -> Shape {
161        Shape::Ref {
162            kinds: &[],
163            id: String::shape,
164        }
165    }
166    fn read(v: &ValueRef<'_>, cx: &mut Reader<'_>) -> Option<Self> {
167        let r = substrate_data::schema::read_ref(v, cx, &[])?;
168        if r.kind.as_str() != kinds::Tag::NAME {
169            return Some(AnyRefOrTag::One(r));
170        }
171        let Some((kind, id)) = split(r.id.as_str()) else {
172            cx.problem(format!("`{r}`: a tag is `@tag:<kind>:<id>`"));
173            return None;
174        };
175        Some(AnyRefOrTag::Tag(kind.to_owned(), id.to_owned()))
176    }
177    fn write(&self) -> Value {
178        match self {
179            AnyRefOrTag::One(r) => Value::Ref(r.clone()),
180            AnyRefOrTag::Tag(kind, id) => {
181                Id::<kinds::Tag>::new(format!("{kind}{}{id}", yog_ids::sep::NAMESPACE)).write()
182            }
183        }
184    }
185}