use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, convert::TryFrom, fmt::Display, hash::Hash};
pub type ResourceLocation = ResourceLocationRef<String>;
#[derive(Clone, Debug)]
pub struct ResourceLocationRef<S: AsRef<str>> {
string: S,
namespace_len: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InvalidResourceLocation {
InvalidNamespace,
InvalidPath,
}
impl Display for InvalidResourceLocation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InvalidResourceLocation::InvalidNamespace => write!(f, "Invalid Namespace"),
InvalidResourceLocation::InvalidPath => write!(f, "Invalid Path"),
}
}
}
impl<'l> TryFrom<&'l str> for ResourceLocationRef<&'l str> {
type Error = InvalidResourceLocation;
fn try_from(string: &'l str) -> Result<Self, Self::Error> {
Self::try_from(string)
}
}
impl<'l> TryFrom<String> for ResourceLocationRef<String> {
type Error = InvalidResourceLocation;
fn try_from(string: String) -> Result<Self, Self::Error> {
Self::try_from(string)
}
}
fn is_valid_namespace_char(c: char) -> bool {
c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '-' || c == '.' || c == '_'
}
fn is_valid_path_char(c: char) -> bool {
c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c == '-' || c == '.' || c == '/' || c == '_'
}
impl<S: AsRef<str>> ResourceLocationRef<S> {
pub fn new(namespace: &str, path: &str) -> ResourceLocation {
ResourceLocationRef {
string: format!("{}:{}", namespace, path),
namespace_len: namespace.len(),
}
}
fn try_from(string: S) -> Result<ResourceLocationRef<S>, InvalidResourceLocation> {
let str = string.as_ref();
let (path, namespace_len) = if let Some((namespace, path)) = str.split_once(':') {
if !namespace.chars().all(is_valid_namespace_char) {
return Err(InvalidResourceLocation::InvalidNamespace);
}
(path, namespace.len())
} else {
(str, usize::MAX)
};
if !path.chars().all(is_valid_path_char) {
Err(InvalidResourceLocation::InvalidPath)
} else {
Ok(ResourceLocationRef {
string,
namespace_len,
})
}
}
pub fn namespace(&self) -> &str {
if self.namespace_len == usize::MAX {
"minecraft"
} else {
&self.string.as_ref()[..self.namespace_len]
}
}
pub fn path(&self) -> &str {
if self.namespace_len == usize::MAX {
self.string.as_ref()
} else {
&self.string.as_ref()[self.namespace_len + 1..]
}
}
pub fn to_owned(&self) -> ResourceLocation {
ResourceLocation {
string: self.string.as_ref().to_owned(),
namespace_len: self.namespace_len,
}
}
pub fn mcfunction_path(&self) -> String {
format!("{}/functions/{}.mcfunction", self.namespace(), self.path())
.replace('/', &std::path::MAIN_SEPARATOR.to_string())
}
}
impl ResourceLocation {
pub fn to_ref(&self) -> ResourceLocationRef<&str> {
ResourceLocationRef {
string: &self.string,
namespace_len: self.namespace_len,
}
}
}
impl Serialize for ResourceLocation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.string)
}
}
impl<'de> Deserialize<'de> for ResourceLocation {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
TryFrom::try_from(s).map_err(serde::de::Error::custom)
}
}
impl<S: AsRef<str>> PartialEq for ResourceLocationRef<S> {
fn eq(&self, other: &Self) -> bool {
self.namespace() == other.namespace() && self.path() == other.path()
}
}
impl<S: AsRef<str>> Eq for ResourceLocationRef<S> {}
impl<S: AsRef<str>> PartialOrd for ResourceLocationRef<S> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<S: AsRef<str>> Ord for ResourceLocationRef<S> {
fn cmp(&self, other: &Self) -> Ordering {
self.namespace()
.cmp(other.namespace())
.then_with(|| self.path().cmp(other.path()))
}
}
impl<S: AsRef<str>> Hash for ResourceLocationRef<S> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.namespace().hash(state);
self.path().hash(state);
}
}
impl<'l> ResourceLocationRef<&'l str> {
pub fn parse(string: &'l str) -> Result<(Self, usize), String> {
const INVALID_ID: &str = "Invalid ID";
let len = string
.find(|c| !Self::is_allowed_in_resource_location(c))
.unwrap_or(string.len());
let resource_location = &string[..len];
let resource_location =
ResourceLocationRef::try_from(resource_location).map_err(|_| INVALID_ID.to_string())?;
Ok((resource_location, len))
}
fn is_allowed_in_resource_location(c: char) -> bool {
return c >= '0' && c <= '9'
|| c >= 'a' && c <= 'z'
|| c == '-'
|| c == '.'
|| c == '/'
|| c == ':'
|| c == '_';
}
}
impl<S: AsRef<str>> Display for ResourceLocationRef<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.string.as_ref().fmt(f)
}
}