rpkg_rs/misc/
resource_id.rs1use crate::resource::runtime_resource_id::RuntimeResourceID;
13use lazy_regex::regex;
14use std::str::FromStr;
15use thiserror::Error;
16
17#[cfg(feature = "serde")]
18use serde::{Deserialize, Serialize};
19
20static CONSOLE_TAG: &str = "pc";
21
22#[derive(Error, Debug)]
23pub enum ResourceIDError {
24 #[error("Invalid format {}", _0)]
25 InvalidFormat(String),
26}
27
28#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct ResourceID {
31 uri: String,
32}
33
34impl FromStr for ResourceID {
35 type Err = ResourceIDError;
36
37 fn from_str(source: &str) -> Result<Self, Self::Err> {
38 let mut uri = source.to_ascii_lowercase();
39 uri.retain(|c| c as u8 > 0x1F);
40 let rid = Self { uri };
41
42 if !rid.is_valid() {
43 return Err(ResourceIDError::InvalidFormat("".to_string()));
44 };
45
46 Ok(Self {
47 uri: rid.uri.replace(format!("{}_", CONSOLE_TAG).as_str(), ""),
48 })
49 }
50}
51
52impl ResourceID {
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn create_derived(&self, parameters: &str, extension: &str) -> ResourceID {
70 let mut derived = format!("[{}]", self.uri);
71 if !parameters.is_empty() {
72 derived += format!("({})", parameters).as_str();
73 }
74 derived += ".";
75 if !extension.is_empty() {
76 derived += extension;
77 }
78
79 ResourceID { uri: derived }
80 }
81
82 pub fn create_aspect(&self, ids: Vec<&ResourceID>) -> ResourceID {
102 let mut rid = self.clone();
103 for id in ids {
104 rid.add_parameter(id.uri.as_str());
105 }
106 rid
107 }
108
109 pub fn add_parameter(&mut self, param: &str) {
110 let params = self.parameters();
111 let new_uri = if params.is_empty() {
112 match self.uri.rfind('.') {
113 Some(index) => {
114 let mut modified_string = self.uri.to_string();
115 modified_string.insert(index, '(');
116 modified_string.insert_str(index + 1, param);
117 modified_string.insert(index + param.len() + 1, ')');
118 modified_string
119 }
120 None => self.uri.to_string(), }
122 } else {
123 match self.uri.rfind(").") {
124 Some(index) => {
125 let mut modified_string = self.uri.to_string();
126 modified_string.insert(index, ',');
127 modified_string.insert_str(index + 1, param);
128 modified_string
129 }
130 None => self.uri.to_string(), }
132 };
133 self.uri = new_uri;
134 }
135
136 pub fn resource_path(&self) -> String {
139 let mut platform_uri = String::new();
140
141 if let Some(dot) = self.uri.rfind('.') {
142 platform_uri.push_str(&self.uri[..=dot]);
143 platform_uri.push_str("pc_");
144 platform_uri.push_str(&self.uri[dot + 1..]);
145 platform_uri
146 } else {
147 self.uri.clone()
148 }
149 }
150
151 pub fn inner_most_resource_path(&self) -> ResourceID {
164 let open_count = self.uri.chars().filter(|c| *c == '[').count();
165 if open_count == 1 {
166 return self.clone();
167 }
168
169 let parts = self.uri.splitn(open_count + 1, ']').collect::<Vec<&str>>();
170 let rid_str = format!("{}]{}", parts[0], parts[1])
171 .chars()
172 .skip(open_count - 1)
173 .collect::<String>();
174
175 match Self::from_str(rid_str.as_str()) {
176 Ok(r) => r,
177 Err(_) => self.clone(),
178 }
179 }
180
181 pub fn inner_resource_path(&self) -> ResourceID {
197 let open_count = self.uri.chars().filter(|c| *c == '[').count();
198 if open_count == 1 {
199 return self.clone();
200 }
201
202 let re = regex!(r"\[(.*?)][^]]*$");
203 if let Some(captures) = re.captures(&self.uri) {
204 if let Some(inner_string) = captures.get(1) {
205 if let Ok(rid) = ResourceID::from_str(inner_string.as_str()) {
206 return rid;
207 }
208 }
209 }
210 self.clone()
211 }
212
213 pub fn protocol(&self) -> Option<String> {
214 match self.uri.find(':') {
215 Some(n) => {
216 let protocol: String = self.uri.chars().take(n).collect();
217 Some(protocol.replace('[', ""))
218 }
219 None => None,
220 }
221 }
222
223 pub fn parameters(&self) -> Vec<String> {
224 let re = regex!(r"(.*)\((.*)\)\.(.*)");
225 if let Some(captures) = re.captures(self.uri.as_str()) {
226 if let Some(cap) = captures.get(2) {
227 return cap
228 .as_str()
229 .split(',')
230 .map(|s: &str| s.to_string())
231 .collect();
232 }
233 }
234 vec![]
235 }
236
237 pub fn path(&self) -> Option<String> {
238 let path: String = self.uri.chars().skip(1).collect();
239 if let Some(n) = path.rfind('/') {
240 let p: String = path.chars().take(n).collect();
241 if !p.contains('.') {
242 return Some(p);
243 }
244 }
245 None
246 }
247
248 pub fn is_empty(&self) -> bool {
249 self.uri.is_empty()
250 }
251
252 pub fn is_valid(&self) -> bool {
253 {
254 self.uri.starts_with('[')
255 && !self.uri.contains("unknown")
256 && !self.uri.contains('*')
257 && self.uri.contains(']')
258 }
259 }
260
261 pub fn into_rrid(self) -> RuntimeResourceID {
262 RuntimeResourceID::from_resource_id(&self)
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269 #[test]
270 fn test_parameters() -> Result<(), ResourceIDError> {
271 let mut resource_id = ResourceID::from_str(
272 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
273 )?;
274 resource_id.add_parameter("lmao");
275 assert_eq!(resource_id.resource_path(), "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass](lmao).pc_fx");
276 assert_eq!(resource_id.parameters(), ["lmao".to_string()]);
277
278 resource_id.add_parameter("lmao2");
279 assert_eq!(resource_id.resource_path(), "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass](lmao,lmao2).pc_fx");
280 Ok(())
281 }
282
283 #[test]
284 fn test_get_inner_most_resource_path() -> Result<(), ResourceIDError> {
285 let resource_id = ResourceID::from_str(
286 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
287 )?;
288 let inner_path = resource_id.inner_most_resource_path();
289 assert_eq!(
290 inner_path.resource_path(),
291 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
292 );
293
294 let resource_id = ResourceID::from_str("[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate")?;
295 let inner_path = resource_id.inner_most_resource_path();
296 assert_eq!(
297 inner_path.resource_path(),
298 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
299 );
300
301 let resource_id = ResourceID::from_str("[[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate](dx12).pc_mate")?;
302 let inner_path = resource_id.inner_most_resource_path();
303 assert_eq!(
304 inner_path.resource_path(),
305 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
306 );
307
308 Ok(())
309 }
310
311 #[test]
312 fn text_get_inner_resource_path() -> Result<(), ResourceIDError> {
313 let resource_id = ResourceID::from_str(
314 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx",
315 )?;
316 let inner_path = resource_id.inner_resource_path();
317 assert_eq!(
318 inner_path.resource_path(),
319 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
320 );
321
322 let resource_id = ResourceID::from_str("[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate")?;
323 let inner_path = resource_id.inner_resource_path();
324 assert_eq!(
325 inner_path.resource_path(),
326 "[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].pc_fx"
327 );
328
329 let resource_id = ResourceID::from_str("[[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).mate](dx12).pc_mate")?;
330 let inner_path = resource_id.inner_resource_path();
331 assert_eq!(inner_path.resource_path(), "[[assembly:/_pro/_test/usern/materialclasses/ball_of_water_b.materialclass].fx](dx11).pc_mate");
332 Ok(())
333 }
334}