blocky_nbt/tag/
mod.rs

1mod kind;
2mod index;
3mod parser;
4
5pub use kind::*;
6pub use index::*;
7pub use parser::*;
8
9#[cfg(feature = "preserve-order")]
10pub use indexmap::IndexMap as Map;
11#[cfg(not(feature = "preserve-order"))]
12pub use std::collections::HashMap as Map;
13
14use regex::Regex;
15use std::fmt;
16
17lazy_static! {
18    static ref SIMPLE_PATTERN: Regex = Regex::new("^[A-Za-z0-9._+-]+$").unwrap();
19}
20
21#[derive(Debug, PartialEq, Clone)]
22pub enum Tag {
23    End,
24    Byte(i8),
25    Short(i16),
26    Int(i32),
27    Long(i64),
28    Float(f32),
29    Double(f64),
30    ByteArray(Vec<i8>),
31    String(String),
32    List(Vec<Self>),
33    Compound(Map<String, Self>),
34    IntArray(Vec<i32>),
35    LongArray(Vec<i64>),
36}
37
38macro_rules! into_tag {
39    ($input:ty, $output:ident) => {
40        impl Into<Tag> for $input {
41            fn into(self) -> Tag {
42                Tag::$output(self)
43            }
44        }
45
46        impl Into<$input> for Tag {
47            fn into(self) -> $input {
48                match self {
49                    Tag::$output(value) => value,
50                    _ => panic!("cannot convert tag"),
51                }
52            }
53        }
54
55        impl<'b> Into<$input> for &'b Tag {
56            fn into(self) -> $input {
57                match self {
58                    Tag::$output(value) => value.to_owned(),
59                    _ => panic!("cannot convert tag"),
60                }
61            }
62        }
63    };
64}
65
66macro_rules! into_tags {
67    { $({ $input:ty, $output:ident }),* $(,)* } => {
68        $(into_tag!($input, $output);)*
69    };
70}
71
72into_tags! {
73    { i8, Byte },
74    { i16, Short },
75    { i32, Int },
76    { i64, Long },
77
78    { f32, Float },
79    { f64, Double },
80
81    { String, String },
82
83    { Vec<i8>, ByteArray },
84    { Vec<i32>, IntArray },
85    { Vec<i64>, LongArray },
86}
87
88impl Tag {
89    pub fn kind(&self) -> Kind {
90        match self {
91            Self::End => Kind::End,
92            Self::Byte(_) => Kind::Byte,
93            Self::Short(_) => Kind::Short,
94            Self::Int(_) => Kind::Int,
95            Self::Long(_) => Kind::Long,
96            Self::Float(_) => Kind::Float,
97            Self::Double(_) => Kind::Double,
98            Self::ByteArray(_) => Kind::ByteArray,
99            Self::String(_) => Kind::String,
100            Self::List(v) => Kind::List(
101                v.get(0)
102                    .map(|tag| tag.kind().id())
103                    .unwrap_or(Kind::End.id()),
104            ),
105            Self::Compound(_) => Kind::Compound,
106            Self::IntArray(_) => Kind::IntArray,
107            Self::LongArray(_) => Kind::LongArray,
108        }
109    }
110
111    pub fn get<I: Index>(&self, index: I) -> Option<&Self> {
112        index.index_into(self)
113    }
114
115    pub fn get_mut<I: Index>(&mut self, index: I) -> Option<&mut Self> {
116        index.index_into_mut(self)
117    }
118
119    pub fn insert<I: Index>(&mut self, index: I, value: Self) {
120        *index.index_or_insert(self) = value;
121    }
122}
123
124fn quote_and_escape(s: &str) -> String {
125    let mut builder = String::new();
126    let mut quote_chr = None;
127
128    for chr in s.chars() {
129        if chr == '\\' {
130            builder.push('\\');
131        } else if chr == '"' || chr == '\'' {
132            if quote_chr.is_none() {
133                quote_chr = Some(if chr == '"' { '\'' } else { '"' });
134            }
135
136            if quote_chr.is_some() && quote_chr.unwrap() == chr {
137                builder.push('\\');
138            }
139        }
140
141        builder.push(chr);
142    }
143
144    let quote_chr = quote_chr.unwrap_or('"');
145
146    builder.insert(0, quote_chr);
147    builder.push(quote_chr);
148    builder
149}
150
151impl fmt::Display for Tag {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        write!(f, "{}", match self {
154            Self::End => panic!("cannot convert end to string"),
155
156            Self::Byte(value) => format!("{}b", *value),
157            Self::Short(value) => format!("{}s", *value),
158            Self::Int(value) => format!("{}", *value),
159            Self::Long(value) => format!("{}L", *value),
160
161            Self::Float(value) => format!("{}f", *value),
162            Self::Double(value) => format!("{}d", *value),
163
164            Self::String(s) => quote_and_escape(&s),
165
166            Self::List(v) => {
167                let mut items = vec![];
168
169                for tag in v {
170                    items.push(tag.to_string());
171                }
172
173                format!("[{}]", items.join(","))
174            },
175
176            Self::Compound(m) => {
177                let mut items = vec![];
178
179                for (name, tag) in m {
180                    let value = tag.to_string();
181                    let key = if SIMPLE_PATTERN.is_match(&name) {
182                        name.clone()
183                    } else {
184                        quote_and_escape(&name)
185                    };
186
187                    items.push(format!("{}:{}", key, value));
188                }
189
190                format!("{{{}}}", items.join(","))
191            },
192
193            Self::ByteArray(v) => {
194                let mut items = vec![];
195
196                for value in v {
197                    items.push(Tag::Byte(*value).to_string());
198                }
199
200                format!("[B;{}]", items.join(","))
201            },
202            Self::IntArray(v) => {
203                let mut items = vec![];
204
205                for value in v {
206                    items.push(Tag::Int(*value).to_string());
207                }
208
209                format!("[I;{}]", items.join(","))
210            },
211            Self::LongArray(v) => {
212                let mut items = vec![];
213
214                for value in v {
215                    items.push(Tag::Long(*value).to_string());
216                }
217
218                format!("[L;{}]", items.join(","))
219            },
220        })
221    }
222}
223
224#[macro_export]
225macro_rules! tag {
226    ($s:expr) => {
227        $crate::Nbt::parse(format!("{}", $s))
228            .expect("failed to parse tag")
229    };
230}