1use std::borrow::Borrow;
4use std::borrow::Cow;
5use std::fmt;
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::ops::Deref;
9use std::path::Component;
10use std::path::Path;
11use std::path::PathBuf;
12use std::str::FromStr;
13use std::sync::LazyLock;
14
15use ecow::EcoString;
16use thiserror::Error;
17
18const _TEMPLATE: &str = "@template";
21
22#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
29pub struct Id(EcoString);
30
31impl Id {
32 pub const SEPARATOR: &'static str = "/";
34
35 pub const TEMPLATE: &'static str = _TEMPLATE;
37}
38
39impl Id {
40 pub fn template() -> Self {
42 static TEMPLATE: LazyLock<Id> = LazyLock::new(|| Id(_TEMPLATE.into()));
43
44 TEMPLATE.clone()
45 }
46
47 pub fn new<S: Into<EcoString>>(string: S) -> Result<Self, ParseIdError> {
63 let id = string.into();
64 Self::validate(&id)?;
65
66 Ok(Self(id))
67 }
68
69 pub fn new_from_path<P: AsRef<Path>>(path: P) -> Result<Self, ParseIdError> {
83 fn inner(path: &Path) -> Result<Id, ParseIdError> {
84 let mut id = String::new();
85
86 for component in path.components() {
87 match component {
88 Component::Normal(comp) => {
89 if let Some(comp) = comp.to_str() {
90 Id::validate_component(comp)?;
91
92 if !id.is_empty() {
93 id.push_str(Id::SEPARATOR);
94 }
95
96 id.push_str(comp);
97 } else {
98 return Err(ParseIdError::InvalidFragment);
99 }
100 }
101 _ => return Err(ParseIdError::InvalidFragment),
102 }
103 }
104
105 Ok(Id(id.into()))
106 }
107
108 inner(path.as_ref())
109 }
110
111 pub unsafe fn new_unchecked(string: EcoString) -> Self {
116 debug_assert!(Self::is_valid(&string));
117 Self(string)
118 }
119}
120
121impl Id {
122 pub fn is_valid<S: AsRef<str>>(string: S) -> bool {
135 Self::validate(string).is_ok()
136 }
137
138 fn validate<S: AsRef<str>>(string: S) -> Result<(), ParseIdError> {
139 if string.as_ref() == Self::TEMPLATE {
140 return Ok(());
141 }
142
143 for fragment in string.as_ref().split(Self::SEPARATOR) {
144 Self::validate_component(fragment)?;
145 }
146
147 Ok(())
148 }
149
150 pub fn is_component_valid<S: AsRef<str>>(component: S) -> bool {
161 Self::validate_component(component).is_ok()
162 }
163
164 fn validate_component<S: AsRef<str>>(component: S) -> Result<(), ParseIdError> {
166 let component = component.as_ref();
167
168 if component.is_empty() {
169 return Err(ParseIdError::Empty);
170 }
171
172 let mut chars = component.chars().peekable();
173 if !chars.next().unwrap().is_ascii_alphabetic() {
174 return Err(ParseIdError::InvalidFragment);
175 }
176
177 if chars.peek().is_some()
178 && !chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
179 {
180 return Err(ParseIdError::InvalidFragment);
181 }
182
183 Ok(())
184 }
185}
186
187impl Id {
188 pub fn as_str(&self) -> &str {
190 self.0.as_str()
191 }
192
193 pub fn to_inner(&self) -> EcoString {
195 self.0.clone()
196 }
197
198 pub fn name(&self) -> &str {
209 self.components()
210 .next_back()
211 .expect("id is always non-empty")
212 }
213
214 pub fn module(&self) -> &str {
225 let mut c = self.components();
226 _ = c.next_back().expect("id is always non-empty");
227 c.rest
228 }
229
230 pub fn ancestors(&self) -> Ancestors<'_> {
245 Ancestors { rest: &self.0 }
246 }
247
248 pub fn components(&self) -> Components<'_> {
263 Components { rest: &self.0 }
264 }
265
266 pub fn to_path(&self) -> Cow<'_, Path> {
268 let s = self.as_str();
269
270 if Self::SEPARATOR == std::path::MAIN_SEPARATOR_STR {
271 Cow::Borrowed(Path::new(s))
272 } else {
273 Cow::Owned(PathBuf::from(
274 s.replace(Self::SEPARATOR, std::path::MAIN_SEPARATOR_STR),
275 ))
276 }
277 }
278}
279
280impl Id {
281 pub unsafe fn push_component_unchecked<S: AsRef<str>>(&mut self, component: S) {
286 let comp = component.as_ref();
287 self.0.push_str(Self::SEPARATOR);
288 self.0.push_str(comp);
289 }
290
291 pub fn push_component<S: AsRef<str>>(&mut self, component: S) -> Result<(), ParseIdError> {
293 let comp = component.as_ref();
294 Self::validate_component(comp)?;
295
296 unsafe {
298 self.push_component_unchecked(component);
299 }
300
301 Ok(())
302 }
303
304 pub fn push_path_component<P: AsRef<Path>>(
306 &mut self,
307 component: P,
308 ) -> Result<(), ParseIdError> {
309 self.push_component(
310 component
311 .as_ref()
312 .to_str()
313 .ok_or(ParseIdError::InvalidFragment)?,
314 )
315 }
316}
317
318impl Deref for Id {
319 type Target = str;
320
321 fn deref(&self) -> &Self::Target {
322 self.0.as_str()
323 }
324}
325
326impl AsRef<str> for Id {
327 fn as_ref(&self) -> &str {
328 self.0.as_str()
329 }
330}
331
332impl Borrow<str> for Id {
333 fn borrow(&self) -> &str {
334 self.0.as_str()
335 }
336}
337
338impl FromStr for Id {
339 type Err = ParseIdError;
340
341 fn from_str(s: &str) -> Result<Self, Self::Err> {
342 Self::new(s)
343 }
344}
345
346impl Debug for Id {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 Debug::fmt(self.as_str(), f)
349 }
350}
351
352impl Display for Id {
353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 Display::fmt(self.as_str(), f)
355 }
356}
357
358impl PartialEq<str> for Id {
359 fn eq(&self, other: &str) -> bool {
360 self.as_str() == other
361 }
362}
363
364impl PartialEq<Id> for str {
365 fn eq(&self, other: &Id) -> bool {
366 self == other.as_str()
367 }
368}
369
370impl PartialEq<String> for Id {
371 fn eq(&self, other: &String) -> bool {
372 self.as_str() == other
373 }
374}
375
376impl PartialEq<Id> for String {
377 fn eq(&self, other: &Id) -> bool {
378 self == other.as_str()
379 }
380}
381
382impl PartialEq<EcoString> for Id {
383 fn eq(&self, other: &EcoString) -> bool {
384 self.as_str() == other
385 }
386}
387
388impl PartialEq<Id> for EcoString {
389 fn eq(&self, other: &Id) -> bool {
390 self == other.as_str()
391 }
392}
393
394#[derive(Debug)]
396pub struct Ancestors<'id> {
397 rest: &'id str,
398}
399
400impl<'id> Iterator for Ancestors<'id> {
401 type Item = &'id str;
402
403 fn next(&mut self) -> Option<Self::Item> {
404 let ret = self.rest;
405 self.rest = self
406 .rest
407 .rsplit_once(Id::SEPARATOR)
408 .map(|(rest, _)| rest)
409 .unwrap_or("");
410
411 if ret.is_empty() {
412 return None;
413 }
414
415 Some(ret)
416 }
417}
418
419#[derive(Debug)]
421pub struct Components<'id> {
422 rest: &'id str,
423}
424
425impl<'id> Iterator for Components<'id> {
426 type Item = &'id str;
427
428 fn next(&mut self) -> Option<Self::Item> {
429 if self.rest.is_empty() {
430 return None;
431 }
432
433 let (c, rest) = self
434 .rest
435 .split_once(Id::SEPARATOR)
436 .unwrap_or((self.rest, ""));
437 self.rest = rest;
438
439 Some(c)
440 }
441}
442
443impl DoubleEndedIterator for Components<'_> {
444 fn next_back(&mut self) -> Option<Self::Item> {
445 if self.rest.is_empty() {
446 return None;
447 }
448
449 let (rest, c) = self
450 .rest
451 .rsplit_once(Id::SEPARATOR)
452 .unwrap_or(("", self.rest));
453 self.rest = rest;
454
455 Some(c)
456 }
457}
458
459#[derive(Debug, Error)]
464pub enum ParseIdError {
465 #[error("id contained an invalid fragment")]
467 InvalidFragment,
468
469 #[error("id contained empty or no fragments")]
471 Empty,
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 #[test]
479 fn test_ancestors() {
480 assert_eq!(
481 Id::new("a/b/c").unwrap().ancestors().collect::<Vec<_>>(),
482 ["a/b/c", "a/b", "a"]
483 );
484 }
485
486 #[test]
487 fn test_components() {
488 assert_eq!(
489 Id::new("a/b/c").unwrap().components().collect::<Vec<_>>(),
490 ["a", "b", "c"]
491 );
492 assert_eq!(
493 Id::new("a/b/c")
494 .unwrap()
495 .components()
496 .rev()
497 .collect::<Vec<_>>(),
498 ["c", "b", "a"]
499 );
500 }
501
502 #[test]
503 fn test_name() {
504 let tests = [("a/b/c", "c"), ("a/b", "b"), ("a", "a")];
505
506 for (id, name) in tests {
507 assert_eq!(Id(id.into()).name(), name);
508 }
509 }
510
511 #[test]
512 fn test_module() {
513 let tests = [("a/b/c", "a/b"), ("a/b", "a"), ("a", "")];
514
515 for (id, name) in tests {
516 assert_eq!(Id(id.into()).module(), name);
517 }
518 }
519
520 #[test]
521 fn test_str_invalid() {
522 assert!(Id::new("/a").is_err());
523 assert!(Id::new("a/").is_err());
524 assert!(Id::new("a//b").is_err());
525
526 assert!(Id::new("a ").is_err());
527 assert!(Id::new("1a").is_err());
528 assert!(Id::new("").is_err());
529 }
530}