mcfunction_debugger/generator/parser/command/
resource_location.rs1use serde::{Deserialize, Serialize};
20use std::{cmp::Ordering, convert::TryFrom, fmt::Display, hash::Hash};
21
22pub type ResourceLocation = ResourceLocationRef<String>;
23
24#[derive(Clone, Debug)]
25pub struct ResourceLocationRef<S: AsRef<str>> {
26 string: S,
27 namespace_len: usize,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum InvalidResourceLocation {
32 InvalidNamespace,
33 InvalidPath,
34}
35
36impl Display for InvalidResourceLocation {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 match self {
39 InvalidResourceLocation::InvalidNamespace => write!(f, "Invalid Namespace"),
40 InvalidResourceLocation::InvalidPath => write!(f, "Invalid Path"),
41 }
42 }
43}
44
45impl<'l> TryFrom<&'l str> for ResourceLocationRef<&'l str> {
46 type Error = InvalidResourceLocation;
47
48 fn try_from(string: &'l str) -> Result<Self, Self::Error> {
49 Self::try_from(string)
50 }
51}
52
53impl<'l> TryFrom<String> for ResourceLocationRef<String> {
54 type Error = InvalidResourceLocation;
55
56 fn try_from(string: String) -> Result<Self, Self::Error> {
57 Self::try_from(string)
58 }
59}
60
61fn is_valid_namespace_char(c: char) -> bool {
62 c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '-' || c == '.' || c == '_'
63}
64
65fn is_valid_path_char(c: char) -> bool {
66 c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '-' || c == '.' || c == '/' || c == '_'
67}
68
69impl<S: AsRef<str>> ResourceLocationRef<S> {
70 pub fn new(namespace: &str, path: &str) -> ResourceLocation {
71 ResourceLocationRef {
72 string: format!("{}:{}", namespace, path),
73 namespace_len: namespace.len(),
74 }
75 }
76
77 fn try_from(string: S) -> Result<ResourceLocationRef<S>, InvalidResourceLocation> {
78 let str = string.as_ref();
79 let (path, namespace_len) = if let Some((namespace, path)) = str.split_once(':') {
80 if !namespace.chars().all(is_valid_namespace_char) {
81 return Err(InvalidResourceLocation::InvalidNamespace);
82 }
83 (path, namespace.len())
84 } else {
85 (str, usize::MAX)
86 };
87
88 if !path.chars().all(is_valid_path_char) {
89 Err(InvalidResourceLocation::InvalidPath)
90 } else {
91 Ok(ResourceLocationRef {
92 string,
93 namespace_len,
94 })
95 }
96 }
97
98 pub fn namespace(&self) -> &str {
99 if self.namespace_len == usize::MAX {
100 "minecraft"
101 } else {
102 &self.string.as_ref()[..self.namespace_len]
103 }
104 }
105
106 pub fn path(&self) -> &str {
107 if self.namespace_len == usize::MAX {
108 self.string.as_ref()
109 } else {
110 &self.string.as_ref()[self.namespace_len + 1..]
111 }
112 }
113
114 pub fn to_owned(&self) -> ResourceLocation {
115 ResourceLocation {
116 string: self.string.as_ref().to_owned(),
117 namespace_len: self.namespace_len,
118 }
119 }
120
121 pub fn mcfunction_path(&self) -> String {
122 format!("{}/functions/{}.mcfunction", self.namespace(), self.path())
123 .replace('/', &std::path::MAIN_SEPARATOR.to_string())
124 }
125}
126
127impl ResourceLocation {
128 pub fn to_ref(&self) -> ResourceLocationRef<&str> {
129 ResourceLocationRef {
130 string: &self.string,
131 namespace_len: self.namespace_len,
132 }
133 }
134}
135
136impl Serialize for ResourceLocation {
137 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
138 where
139 S: serde::Serializer,
140 {
141 serializer.serialize_str(&self.string)
142 }
143}
144
145impl<'de> Deserialize<'de> for ResourceLocation {
146 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
147 where
148 D: serde::Deserializer<'de>,
149 {
150 let s = String::deserialize(deserializer)?;
151 TryFrom::try_from(s).map_err(serde::de::Error::custom)
152 }
153}
154
155impl<S: AsRef<str>> PartialEq for ResourceLocationRef<S> {
156 fn eq(&self, other: &Self) -> bool {
157 self.namespace() == other.namespace() && self.path() == other.path()
158 }
159}
160
161impl<S: AsRef<str>> Eq for ResourceLocationRef<S> {}
162
163impl<S: AsRef<str>> PartialOrd for ResourceLocationRef<S> {
164 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
165 Some(self.cmp(other))
166 }
167}
168
169impl<S: AsRef<str>> Ord for ResourceLocationRef<S> {
170 fn cmp(&self, other: &Self) -> Ordering {
171 self.namespace()
172 .cmp(other.namespace())
173 .then_with(|| self.path().cmp(other.path()))
174 }
175}
176
177impl<S: AsRef<str>> Hash for ResourceLocationRef<S> {
178 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
179 self.namespace().hash(state);
180 self.path().hash(state);
181 }
182}
183
184impl<'l> ResourceLocationRef<&'l str> {
185 pub fn parse(string: &'l str) -> Result<(Self, usize), String> {
186 const INVALID_ID: &str = "Invalid ID";
187
188 let len = string
189 .find(|c| !Self::is_allowed_in_resource_location(c))
190 .unwrap_or(string.len());
191 let resource_location = &string[..len];
192
193 let resource_location =
194 ResourceLocationRef::try_from(resource_location).map_err(|_| INVALID_ID.to_string())?;
195 Ok((resource_location, len))
196 }
197
198 fn is_allowed_in_resource_location(c: char) -> bool {
199 return c >= '0' && c <= '9'
200 || c >= 'a' && c <= 'z'
201 || c == '-'
202 || c == '.'
203 || c == '/'
204 || c == ':'
205 || c == '_';
206 }
207}
208
209impl<S: AsRef<str>> Display for ResourceLocationRef<S> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 self.string.as_ref().fmt(f)
212 }
213}