use crate::resource::runtime_resource_id::RuntimeResourceID;
use lazy_regex::regex;
use std::str::FromStr;
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
static CONSOLE_TAG: &str = "pc";
#[derive(Error, Debug)]
pub enum ResourceIDError {
#[error("Invalid format {}", _0)]
InvalidFormat(String),
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ResourceID {
uri: String,
}
impl FromStr for ResourceID {
type Err = ResourceIDError;
fn from_str(source: &str) -> Result<Self, Self::Err> {
let mut uri = source.to_ascii_lowercase();
uri.retain(|c| c as u8 > 0x1F);
let rid = Self { uri };
if !rid.is_valid() {
return Err(ResourceIDError::InvalidFormat("".to_string()));
};
Ok(Self {
uri: rid.uri.replace(format!("{CONSOLE_TAG}_").as_str(), ""),
})
}
}
impl ResourceID {
pub fn new() -> Self {
Self::default()
}
pub fn create_derived(&self, parameters: &str, extension: &str) -> ResourceID {
let mut derived = format!("[{}]", self.uri);
if !parameters.is_empty() {
derived += format!("({parameters})").as_str();
}
derived += ".";
if !extension.is_empty() {
derived += extension;
}
ResourceID { uri: derived }
}
pub fn create_aspect(&self, ids: Vec<&ResourceID>) -> ResourceID {
let mut rid = self.clone();
for id in ids {
rid.add_parameter(id.uri.as_str());
}
rid
}
pub fn add_parameter(&mut self, param: &str) {
let params = self.parameters();
let new_uri = if params.is_empty() {
match self.uri.rfind('.') {
Some(index) => {
let mut modified_string = self.uri.to_string();
modified_string.insert(index, '(');
modified_string.insert_str(index + 1, param);
modified_string.insert(index + param.len() + 1, ')');
modified_string
}
None => self.uri.to_string(), }
} else {
match self.uri.rfind(").") {
Some(index) => {
let mut modified_string = self.uri.to_string();
modified_string.insert(index, ',');
modified_string.insert_str(index + 1, param);
modified_string
}
None => self.uri.to_string(), }
};
self.uri = new_uri;
}
pub fn resource_path(&self) -> String {
let mut platform_uri = String::new();
if let Some(dot) = self.uri.rfind('.') {
platform_uri.push_str(&self.uri[..=dot]);
platform_uri.push_str("pc_");
platform_uri.push_str(&self.uri[dot + 1..]);
platform_uri
} else {
self.uri.clone()
}
}
pub fn inner_most_resource_path(&self) -> ResourceID {
let open_count = self.uri.chars().filter(|c| *c == '[').count();
if open_count == 1 {
return self.clone();
}
let parts = self.uri.splitn(open_count + 1, ']').collect::<Vec<&str>>();
let rid_str = format!("{}]{}", parts[0], parts[1])
.chars()
.skip(open_count - 1)
.collect::<String>();
match Self::from_str(rid_str.as_str()) {
Ok(r) => r,
Err(_) => self.clone(),
}
}
pub fn inner_resource_path(&self) -> ResourceID {
let open_count = self.uri.chars().filter(|c| *c == '[').count();
if open_count == 1 {
return self.clone();
}
let re = regex!(r"\[(.*?)][^]]*$");
if let Some(captures) = re.captures(&self.uri) {
if let Some(inner_string) = captures.get(1) {
if let Ok(rid) = ResourceID::from_str(inner_string.as_str()) {
return rid;
}
}
}
self.clone()
}
pub fn protocol(&self) -> Option<String> {
match self.uri.find(':') {
Some(n) => {
let protocol: String = self.uri.chars().take(n).collect();
Some(protocol.replace('[', ""))
}
None => None,
}
}
pub fn parameters(&self) -> Vec<String> {
let re = regex!(r"(.*)\((.*)\)\.(.*)");
if let Some(captures) = re.captures(self.uri.as_str()) {
if let Some(cap) = captures.get(2) {
return cap
.as_str()
.split(',')
.map(|s: &str| s.to_string())
.collect();
}
}
vec![]
}
pub fn path(&self) -> Option<String> {
let path: String = self.uri.chars().skip(1).collect();
if let Some(n) = path.rfind('/') {
let p: String = path.chars().take(n).collect();
if !p.contains('.') {
return Some(p);
}
}
None
}
pub fn is_empty(&self) -> bool {
self.uri.is_empty()
}
pub fn is_valid(&self) -> bool {
{
self.uri.starts_with('[')
&& !self.uri.contains("unknown")
&& !self.uri.contains('*')
&& self.uri.contains(']')
}
}
pub fn into_rrid(self) -> RuntimeResourceID {
RuntimeResourceID::from_resource_id(&self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parameters() -> Result<(), ResourceIDError> {
let mut resource_id = ResourceID::from_str(
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
)?;
resource_id.add_parameter("lmao");
assert_eq!(resource_id.resource_path(), "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass](lmao).pc_fx");
assert_eq!(resource_id.parameters(), ["lmao".to_string()]);
resource_id.add_parameter("lmao2");
assert_eq!(resource_id.resource_path(), "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass](lmao,lmao2).pc_fx");
Ok(())
}
#[test]
fn test_get_inner_most_resource_path() -> Result<(), ResourceIDError> {
let resource_id = ResourceID::from_str(
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
)?;
let inner_path = resource_id.inner_most_resource_path();
assert_eq!(
inner_path.resource_path(),
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
);
let resource_id = ResourceID::from_str("[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate")?;
let inner_path = resource_id.inner_most_resource_path();
assert_eq!(
inner_path.resource_path(),
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
);
let resource_id = ResourceID::from_str("[[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate](dx12).pc_mate")?;
let inner_path = resource_id.inner_most_resource_path();
assert_eq!(
inner_path.resource_path(),
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
);
Ok(())
}
#[test]
fn text_get_inner_resource_path() -> Result<(), ResourceIDError> {
let resource_id = ResourceID::from_str(
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
)?;
let inner_path = resource_id.inner_resource_path();
assert_eq!(
inner_path.resource_path(),
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
);
let resource_id = ResourceID::from_str("[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate")?;
let inner_path = resource_id.inner_resource_path();
assert_eq!(
inner_path.resource_path(),
"[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
);
let resource_id = ResourceID::from_str("[[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate](dx12).pc_mate")?;
let inner_path = resource_id.inner_resource_path();
assert_eq!(inner_path.resource_path(), "[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).pc_mate");
Ok(())
}
}