mcfunction_debugger/generator/parser/command/
resource_location.rs

1// Mcfunction-Debugger is a debugger for Minecraft's *.mcfunction files that does not require any
2// Minecraft mods.
3//
4// © Copyright (C) 2021-2024 Adrodoc <adrodoc55@googlemail.com> & Skagaros <skagaros@gmail.com>
5//
6// This file is part of Mcfunction-Debugger.
7//
8// Mcfunction-Debugger is free software: you can redistribute it and/or modify it under the terms of
9// the GNU General Public License as published by the Free Software Foundation, either version 3 of
10// the License, or (at your option) any later version.
11//
12// Mcfunction-Debugger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
13// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License along with Mcfunction-Debugger.
17// If not, see <http://www.gnu.org/licenses/>.
18
19use 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}