1use core::str;
5use serde::{Deserialize, Serialize};
6use std::{borrow::Borrow, fmt::Display, ops::Deref};
7
8#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
11#[repr(transparent)]
12pub struct Path(str);
13impl Path {
14 pub fn new(s: &str) -> Result<&Self, PathError> {
15 Self::from_str(s)
16 }
17
18 pub fn new_unchecked(s: &str) -> &Self {
19 Self::from_str_unchecked(s)
20 }
21}
22impl PathExt for Path {
23 type PathOwned = PathOwned;
24 type Path = Path;
25
26 fn validate(buf: &str) -> Result<(), PathError> {
27 if buf.is_empty() {
28 return Err(PathError::InvalidArgument);
29 }
30 Ok(())
31 }
32
33 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
34 PathOwned(buf)
35 }
36
37 fn from_str_unchecked(s: &str) -> &Self::Path {
39 unsafe { &*(s as *const str as *const Path) }
40 }
41
42 fn as_str(&self) -> &'_ str {
43 &self.0
44 }
45
46 fn has_root(&self) -> bool {
47 matches!(self.as_str().as_bytes().first(), Some(b'/'))
48 }
49}
50impl From<&Path> for String {
51 fn from(val: &Path) -> Self {
52 val.0.to_owned()
53 }
54}
55impl From<&Path> for PathOwned {
56 fn from(val: &Path) -> Self {
57 PathOwned(val.0.to_owned())
58 }
59}
60impl<'a> IntoIterator for &'a Path {
61 type Item = Component<'a>;
62 type IntoIter = Components<'a>;
63
64 fn into_iter(self) -> Self::IntoIter {
65 self.components()
66 }
67}
68impl AsRef<str> for Path {
69 fn as_ref(&self) -> &str {
70 &self.0
71 }
72}
73impl PartialEq<str> for Path {
74 fn eq(&self, other: &str) -> bool {
75 Self::from_str_unchecked(other) == self.as_path()
76 }
77}
78impl Display for Path {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.write_str(self.as_str())
81 }
82}
83impl ToOwned for Path {
84 type Owned = PathOwned;
85
86 fn to_owned(&self) -> Self::Owned {
87 PathOwned::from_owned_unchecked(self.as_str().to_owned())
88 }
89}
90impl PartialEq<PathOwned> for Path {
91 fn eq(&self, other: &PathOwned) -> bool {
92 other.as_path() == self.as_path()
93 }
94}
95impl AsRef<AbsolutePath> for Path {
96 fn as_ref(&self) -> &AbsolutePath {
97 AbsolutePath::from_str_unchecked(&self.0)
98 }
99}
100impl AsRef<RelativePath> for Path {
101 fn as_ref(&self) -> &RelativePath {
102 RelativePath::from_str_unchecked(&self.0)
103 }
104}
105impl AsRef<Path> for str {
106 fn as_ref(&self) -> &Path {
107 Path::from_str_unchecked(self)
108 }
109}
110impl PartialEq<AbsolutePath> for Path {
111 fn eq(&self, other: &AbsolutePath) -> bool {
112 AsRef::<AbsolutePath>::as_ref(&self) == other
113 }
114}
115impl PartialEq<RelativePath> for Path {
116 fn eq(&self, other: &RelativePath) -> bool {
117 AsRef::<RelativePath>::as_ref(&self) == other
118 }
119}
120
121#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
123#[repr(transparent)]
124pub struct PathOwned(String);
125impl PathOwned {
126 pub fn new(s: String) -> Result<Self, PathError> {
127 Self::from_owned(s)
128 }
129
130 pub fn new_unchecked(s: String) -> Self {
131 Self::from_owned_unchecked(s)
132 }
133}
134impl PathExt for PathOwned {
135 type PathOwned = PathOwned;
136 type Path = Path;
137
138 fn validate(buf: &str) -> Result<(), PathError> {
139 Self::Path::validate(buf)
140 }
141
142 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
143 PathOwned(buf)
144 }
145
146 fn from_str_unchecked(buf: &str) -> &Self::Path {
147 Self::Path::from_str_unchecked(buf)
148 }
149
150 fn as_str(&self) -> &'_ str {
151 &self.0
152 }
153
154 fn has_root(&self) -> bool {
155 self.as_path().has_root()
156 }
157}
158impl Deref for PathOwned {
159 type Target = Path;
160
161 fn deref(&self) -> &Self::Target {
162 Path::from_str_unchecked(&self.0)
163 }
164}
165impl AsRef<Path> for PathOwned {
166 fn as_ref(&self) -> &Path {
167 Path::from_str_unchecked(&self.0)
168 }
169}
170impl PartialEq<Path> for PathOwned {
171 fn eq(&self, other: &Path) -> bool {
172 other.as_path() == self.as_path()
173 }
174}
175impl Borrow<Path> for PathOwned {
176 fn borrow(&self) -> &Path {
177 self
178 }
179}
180impl AsRef<str> for PathOwned {
181 fn as_ref(&self) -> &str {
182 &self.0
183 }
184}
185impl PartialEq<str> for PathOwned {
186 fn eq(&self, other: &str) -> bool {
187 Self::from_str_unchecked(other) == self.as_path()
188 }
189}
190impl From<PathOwned> for String {
191 fn from(val: PathOwned) -> Self {
192 val.0
193 }
194}
195impl<'a> IntoIterator for &'a PathOwned {
196 type Item = Component<'a>;
197 type IntoIter = Components<'a>;
198
199 fn into_iter(self) -> Self::IntoIter {
200 self.components()
201 }
202}
203impl Display for PathOwned {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.write_str(self.as_str())
206 }
207}
208impl From<&AbsolutePath> for PathOwned {
209 fn from(value: &AbsolutePath) -> Self {
210 Self::new_unchecked(value.to_string())
211 }
212}
213impl From<AbsolutePathOwned> for PathOwned {
214 fn from(value: AbsolutePathOwned) -> Self {
215 Self::new_unchecked(value.0)
216 }
217}
218impl From<&RelativePath> for PathOwned {
219 fn from(value: &RelativePath) -> Self {
220 Self::new_unchecked(value.to_string())
221 }
222}
223impl From<RelativePathOwned> for PathOwned {
224 fn from(value: RelativePathOwned) -> Self {
225 Self::new_unchecked(value.0)
226 }
227}
228
229#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
231#[repr(transparent)]
232pub struct AbsolutePath(str);
233impl AbsolutePath {
234 pub fn new(s: &str) -> Result<&Self, PathError> {
235 Self::from_str(s)
236 }
237
238 pub fn new_unchecked(s: &str) -> &Self {
239 Self::from_str_unchecked(s)
240 }
241}
242impl PathExt for AbsolutePath {
243 type PathOwned = AbsolutePathOwned;
244 type Path = AbsolutePath;
245
246 fn validate(buf: &str) -> Result<(), PathError> {
247 if buf.is_empty() {
248 return Err(PathError::InvalidArgument);
249 }
250 if !matches!(buf.as_bytes().first(), Some(b'/')) {
251 return Err(PathError::InvalidArgument);
252 }
253 Ok(())
254 }
255
256 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
257 AbsolutePathOwned(buf)
258 }
259
260 fn from_str_unchecked(s: &str) -> &Self::Path {
262 unsafe { &*(s as *const str as *const Self::Path) }
263 }
264
265 fn as_str(&self) -> &'_ str {
266 &self.0
267 }
268
269 fn has_root(&self) -> bool {
270 true
271 }
272}
273impl From<&AbsolutePath> for String {
274 fn from(val: &AbsolutePath) -> Self {
275 val.0.to_owned()
276 }
277}
278impl From<&AbsolutePath> for AbsolutePathOwned {
279 fn from(val: &AbsolutePath) -> Self {
280 val.to_path()
281 }
282}
283impl<'a> IntoIterator for &'a AbsolutePath {
284 type Item = Component<'a>;
285 type IntoIter = Components<'a>;
286
287 fn into_iter(self) -> Self::IntoIter {
288 self.components()
289 }
290}
291impl ToOwned for AbsolutePath {
292 type Owned = AbsolutePathOwned;
293
294 fn to_owned(&self) -> Self::Owned {
295 AbsolutePathOwned::from_owned_unchecked(self.as_str().to_owned())
296 }
297}
298impl AsRef<str> for AbsolutePath {
299 fn as_ref(&self) -> &str {
300 self.as_str()
301 }
302}
303impl PartialEq<str> for AbsolutePath {
304 fn eq(&self, other: &str) -> bool {
305 Self::from_str_unchecked(other) == self.as_path()
306 }
307}
308impl Display for AbsolutePath {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 f.write_str(self.as_str())
311 }
312}
313impl PartialEq<AbsolutePathOwned> for AbsolutePath {
314 fn eq(&self, other: &AbsolutePathOwned) -> bool {
315 other.as_path() == self.as_path()
316 }
317}
318impl AsRef<AbsolutePath> for str {
319 fn as_ref(&self) -> &AbsolutePath {
320 AbsolutePath::from_str_unchecked(self)
321 }
322}
323
324#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
326#[repr(transparent)]
327pub struct AbsolutePathOwned(String);
328impl AbsolutePathOwned {
329 pub fn new(s: String) -> Result<Self, PathError> {
330 Self::from_owned(s)
331 }
332
333 pub fn new_unchecked(s: String) -> Self {
334 Self::from_owned_unchecked(s)
335 }
336}
337impl PathExt for AbsolutePathOwned {
338 type PathOwned = AbsolutePathOwned;
339 type Path = AbsolutePath;
340
341 fn validate(buf: &str) -> Result<(), PathError> {
342 Self::Path::validate(buf)
343 }
344
345 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
346 AbsolutePathOwned(buf)
347 }
348
349 fn from_str_unchecked(buf: &str) -> &Self::Path {
351 Self::Path::from_str_unchecked(buf)
352 }
353
354 fn as_str(&self) -> &'_ str {
355 &self.0
356 }
357
358 fn has_root(&self) -> bool {
359 self.as_path().has_root()
360 }
361}
362impl Deref for AbsolutePathOwned {
363 type Target = AbsolutePath;
364
365 fn deref(&self) -> &Self::Target {
366 AbsolutePath::from_str_unchecked(&self.0)
367 }
368}
369impl AsRef<AbsolutePath> for AbsolutePathOwned {
370 fn as_ref(&self) -> &AbsolutePath {
371 AbsolutePath::from_str_unchecked(&self.0)
372 }
373}
374impl PartialEq<AbsolutePath> for AbsolutePathOwned {
375 fn eq(&self, other: &AbsolutePath) -> bool {
376 self.0 == other.0
377 }
378}
379impl Borrow<AbsolutePath> for AbsolutePathOwned {
380 fn borrow(&self) -> &AbsolutePath {
381 self
382 }
383}
384impl TryFrom<String> for AbsolutePathOwned {
385 type Error = PathError;
386
387 fn try_from(value: String) -> Result<Self, Self::Error> {
388 Self::from_owned(value)
389 }
390}
391impl TryFrom<&str> for AbsolutePathOwned {
392 type Error = PathError;
393
394 fn try_from(value: &str) -> Result<Self, Self::Error> {
395 Self::from_str(value).map(|s| s.to_owned())
396 }
397}
398impl From<AbsolutePathOwned> for String {
399 fn from(val: AbsolutePathOwned) -> Self {
400 val.0
401 }
402}
403impl<'a> IntoIterator for &'a AbsolutePathOwned {
404 type Item = Component<'a>;
405 type IntoIter = Components<'a>;
406
407 fn into_iter(self) -> Self::IntoIter {
408 self.components()
409 }
410}
411impl AsRef<str> for AbsolutePathOwned {
412 fn as_ref(&self) -> &str {
413 &self.0
414 }
415}
416impl PartialEq<str> for AbsolutePathOwned {
417 fn eq(&self, other: &str) -> bool {
418 Self::from_str_unchecked(other) == self.as_path()
419 }
420}
421impl Display for AbsolutePathOwned {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 f.write_str(self.as_str())
424 }
425}
426
427#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
429#[repr(transparent)]
430pub struct RelativePath(str);
431impl PathExt for RelativePath {
432 type PathOwned = RelativePathOwned;
433 type Path = RelativePath;
434
435 fn validate(buf: &str) -> Result<(), PathError> {
436 if buf.is_empty() {
437 return Err(PathError::InvalidArgument);
438 }
439 if matches!(buf.as_bytes().first(), Some(b'/')) {
440 return Err(PathError::InvalidArgument);
441 }
442 Ok(())
443 }
444
445 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
446 RelativePathOwned(buf)
447 }
448
449 fn from_str_unchecked(s: &str) -> &Self::Path {
451 unsafe { &*(s as *const str as *const Self::Path) }
452 }
453
454 fn as_str(&self) -> &'_ str {
455 &self.0
456 }
457
458 fn has_root(&self) -> bool {
459 false
460 }
461}
462impl From<&RelativePath> for String {
463 fn from(val: &RelativePath) -> Self {
464 val.0.to_owned()
465 }
466}
467impl From<&RelativePath> for RelativePathOwned {
468 fn from(val: &RelativePath) -> Self {
469 val.to_path()
470 }
471}
472impl<'a> IntoIterator for &'a RelativePath {
473 type Item = Component<'a>;
474 type IntoIter = Components<'a>;
475
476 fn into_iter(self) -> Self::IntoIter {
477 self.components()
478 }
479}
480impl AsRef<str> for RelativePath {
481 fn as_ref(&self) -> &str {
482 &self.0
483 }
484}
485impl PartialEq<str> for RelativePath {
486 fn eq(&self, other: &str) -> bool {
487 Self::from_str_unchecked(other) == self.as_path()
488 }
489}
490impl Display for RelativePath {
491 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492 f.write_str(self.as_str())
493 }
494}
495impl ToOwned for RelativePath {
496 type Owned = RelativePathOwned;
497
498 fn to_owned(&self) -> Self::Owned {
499 RelativePathOwned::from_owned_unchecked(self.as_str().to_owned())
500 }
501}
502impl PartialEq<RelativePathOwned> for RelativePath {
503 fn eq(&self, other: &RelativePathOwned) -> bool {
504 other.as_path() == self.as_path()
505 }
506}
507impl AsRef<RelativePath> for str {
508 fn as_ref(&self) -> &RelativePath {
509 RelativePath::from_str_unchecked(self)
510 }
511}
512
513#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
515#[repr(transparent)]
516pub struct RelativePathOwned(String);
517impl PathExt for RelativePathOwned {
518 type PathOwned = RelativePathOwned;
519 type Path = RelativePath;
520
521 fn validate(buf: &str) -> Result<(), PathError> {
522 Self::Path::validate(buf)
523 }
524
525 fn from_owned_unchecked(buf: String) -> Self::PathOwned {
526 RelativePathOwned(buf)
527 }
528
529 fn from_str_unchecked(buf: &str) -> &Self::Path {
530 Self::Path::from_str_unchecked(buf)
531 }
532
533 fn as_str(&self) -> &'_ str {
534 &self.0
535 }
536
537 fn has_root(&self) -> bool {
538 self.as_path().has_root()
539 }
540}
541impl Deref for RelativePathOwned {
542 type Target = RelativePath;
543
544 fn deref(&self) -> &Self::Target {
545 RelativePath::from_str_unchecked(&self.0)
546 }
547}
548impl AsRef<RelativePath> for RelativePathOwned {
549 fn as_ref(&self) -> &RelativePath {
550 RelativePath::from_str_unchecked(&self.0)
551 }
552}
553impl PartialEq<RelativePath> for RelativePathOwned {
554 fn eq(&self, other: &RelativePath) -> bool {
555 other.as_path() == self.as_path()
556 }
557}
558impl Borrow<RelativePath> for RelativePathOwned {
559 fn borrow(&self) -> &RelativePath {
560 self
561 }
562}
563impl From<RelativePathOwned> for String {
564 fn from(val: RelativePathOwned) -> Self {
565 val.0
566 }
567}
568impl<'a> IntoIterator for &'a RelativePathOwned {
569 type Item = Component<'a>;
570 type IntoIter = Components<'a>;
571
572 fn into_iter(self) -> Self::IntoIter {
573 self.components()
574 }
575}
576impl AsRef<str> for RelativePathOwned {
577 fn as_ref(&self) -> &str {
578 &self.0
579 }
580}
581impl PartialEq<str> for RelativePathOwned {
582 fn eq(&self, other: &str) -> bool {
583 Self::from_str_unchecked(other) == self.as_path()
584 }
585}
586impl Display for RelativePathOwned {
587 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588 f.write_str(self.as_str())
589 }
590}
591
592pub struct Components<'a> {
594 path: &'a str,
595 has_root: bool,
596}
597impl<'a> Components<'a> {
598 fn parse_single_component<'b>(&self, comp: &'b str) -> Option<Component<'b>> {
599 match comp {
600 "." => Some(Component::CurDir),
601 ".." => Some(Component::ParentDir),
602 "" if self.has_root => Some(Component::RootDir),
603 "" if !self.path.is_empty() => Some(Component::CurDir), "" => None,
605 _ => Some(Component::Normal(comp)),
606 }
607 }
608
609 fn parse_next_component(&self) -> (usize, Option<Component<'a>>) {
610 let (extra, comp) = match self.path.as_bytes().iter().position(|b| is_sep_byte(*b)) {
611 None => (0, self.path),
612 Some(i) => (1, &self.path[..i]),
613 };
614 (comp.len() + extra, self.parse_single_component(comp))
615 }
616
617 pub fn into_vec_normal(self) -> Vec<String> {
619 self.filter_map(|component| match component {
620 Component::Normal(name) => Some(name.to_owned()),
621 _ => None,
622 })
623 .collect()
624 }
625}
626impl<'a> Iterator for Components<'a> {
627 type Item = Component<'a>;
628
629 fn next(&mut self) -> Option<Self::Item> {
630 let (index, comp) = self.parse_next_component();
631 self.path = &self.path[index..];
632 self.has_root = false;
633 comp
634 }
635}
636
637#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
639pub enum Component<'a> {
640 RootDir,
641 CurDir,
642 ParentDir,
643 Normal(&'a str),
644}
645impl<'a> Component<'a> {
646 pub fn as_string(&self) -> &'a str {
647 match self {
648 Component::RootDir => "/",
649 Component::CurDir => ".",
650 Component::ParentDir => "..",
651 Component::Normal(s) => s,
652 }
653 }
654
655 pub fn is_empty(&self) -> bool {
657 matches!(self, Component::Normal(s) if s.len() == 0)
658 }
659
660 pub fn len(&self) -> usize {
662 match self {
663 Component::RootDir => 1,
664 Component::CurDir => 1,
665 Component::ParentDir => 2,
666 Component::Normal(s) => s.len(),
667 }
668 }
669}
670
671#[derive(Debug, thiserror::Error)]
672pub enum PathError {
673 #[error("No root")]
674 NoRoot,
675
676 #[error("No parent: {0}")]
677 NoParent(PathOwned),
678
679 #[error("Invalid argument")]
680 InvalidArgument,
681}
682
683pub trait PathExt {
684 type PathOwned: PathExt;
685 type Path: PathExt + ?Sized;
686
687 fn validate(buf: &str) -> Result<(), PathError>;
688 fn from_owned_unchecked(buf: String) -> Self::PathOwned;
689 fn from_str_unchecked(buf: &str) -> &Self::Path;
690
691 fn from_owned(buf: String) -> Result<Self::PathOwned, PathError> {
692 Self::validate(&buf)?;
693 Ok(Self::from_owned_unchecked(buf))
694 }
695
696 fn from_str(buf: &str) -> Result<&Self::Path, PathError> {
697 Self::validate(buf)?;
698 Ok(Self::from_str_unchecked(buf))
699 }
700
701 fn as_path(&self) -> &Self::Path {
702 Self::from_str_unchecked(self.as_str())
703 }
704
705 fn to_path(&self) -> Self::PathOwned {
706 Self::from_owned_unchecked(self.as_str().to_owned())
707 }
708
709 fn as_str(&self) -> &str;
710
711 fn has_root(&self) -> bool;
712
713 fn components(&self) -> Components<'_> {
715 Components { path: self.as_str(), has_root: self.has_root() }
716 }
717
718 fn parent(&self) -> Option<&Self::Path> {
720 self.parent_and_file_name().map(|(p, _)| p)
721 }
722
723 fn parent_result(&self) -> Result<&Self::Path, PathError> {
725 self.parent()
726 .ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
727 }
728
729 fn parents(&self) -> impl Iterator<Item = &Self::Path> {
742 self.paths().take_while(|path| path.as_str().len() < self.as_str().len())
743 }
744
745 fn paths(&self) -> impl Iterator<Item = &Self::Path> {
759 self.components().scan((0_usize, self.as_str()), |(index, path), component| {
760 let end = *index + component.len();
761 let result = &path[0..end];
762 *index = if *index > 0 { end + 1 } else { end };
763 Some(Self::from_str_unchecked(result))
764 })
765 }
766
767 fn parent_and_file_name(&self) -> Option<(&Self::Path, &'_ str)> {
769 match self.components().last() {
770 Some(Component::Normal(name)) => {
771 let path = self.as_str();
772 let parent = match path.split_at(path.len() - name.len()) {
773 ("/", _) => "/",
774 (p, _) if p.len() > 1 => &p[0..p.len() - 1],
775 (p, _) => p,
776 };
777 Some((Self::from_str_unchecked(parent), name))
778 },
779 _ => None,
780 }
781 }
782
783 fn parent_and_file_name_result(&self) -> Result<(&Self::Path, &str), PathError> {
785 self.parent_and_file_name()
786 .ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
787 }
788
789 fn file_name(&self) -> Option<&str> {
791 self.parent_and_file_name().map(|(_, f)| f)
792 }
793
794 fn file_name_result(&self) -> Result<&str, PathError> {
796 self.file_name()
797 .ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
798 }
799
800 fn normalize(&self) -> Result<Self::PathOwned, PathError> {
802 Ok(Self::from_owned_unchecked(from_components(normalize_components(self.components())?)))
803 }
804
805 fn join<'a: 'b, 'b>(
807 &'a self,
808 other: impl IntoIterator<Item = Component<'b>>,
809 ) -> Result<Self::PathOwned, PathError> {
810 Ok(Self::from_owned_unchecked(join(self.components(), other)?))
811 }
812
813 fn join_path(&self, other: &str) -> Result<Self::PathOwned, PathError> {
815 let other_path = Path::from_str_unchecked(other);
816 self.join(other_path)
817 }
818}
819
820fn join<'a: 'b, 'b: 'a>(
821 a: impl IntoIterator<Item = Component<'a>>,
822 b: impl IntoIterator<Item = Component<'b>>,
823) -> Result<String, PathError> {
824 let components = a.into_iter().chain(b);
825 let normalized = normalize_components(components)?;
826 Ok(from_components(normalized))
827}
828
829fn normalize_components<'a>(
830 components: impl IntoIterator<Item = Component<'a>>,
831) -> Result<Vec<Component<'a>>, PathError> {
832 let mut stack: Vec<_> = components.into_iter().filter(|c| !matches!(c, Component::CurDir)).collect();
833 let mut index = 0;
834 while index < stack.len() {
835 match stack[index] {
836 Component::CurDir => {
837 stack.remove(index);
839 },
840 Component::RootDir => {
841 for _ in 0..index {
843 stack.remove(0);
844 }
845
846 index = 1;
848 },
849 Component::ParentDir => {
850 if index > 0 {
851 match stack[index - 1] {
853 Component::RootDir => return Err(PathError::NoRoot),
855 Component::ParentDir => {
857 index += 1;
858 continue;
859 },
860 _ => {},
861 }
862
863 stack.remove(index - 1);
865 stack.remove(index - 1);
866
867 index -= 1;
869 } else {
870 index += 1;
872 }
873 },
874 _ => {
875 index += 1;
877 },
878 }
879 }
880 Ok(stack)
881}
882
883fn from_components<'a>(components: impl IntoIterator<Item = Component<'a>>) -> String {
884 let result: String = components
885 .into_iter()
886 .scan(false, |state, c| {
887 let result_state = *state;
888 match &c {
889 Component::RootDir => {
890 *state = false;
891 },
892 _ => {
893 *state = true;
894 },
895 }
896 Some((result_state, c))
897 })
898 .flat_map(|(separator, c)| match separator {
899 false => ["", c.as_string()],
900 true => ["/", c.as_string()],
901 })
902 .collect();
903 result
904}
905
906#[inline]
907pub fn is_sep_byte(b: u8) -> bool {
908 b == b'/'
909}
910
911#[cfg(test)]
912mod tests {
913 use crate::{AbsolutePath, Component, Path, PathExt, RelativePath};
914
915 #[test]
916 fn test_components() {
917 let path = Path::from_str("/hello/world").unwrap();
918 let mut components = path.components();
919 assert_eq!(Some(Component::RootDir), components.next());
920 assert_eq!(Some(Component::Normal("hello")), components.next());
921 assert_eq!(Some(Component::Normal("world")), components.next());
922 assert_eq!(None, components.next());
923 }
924
925 #[test]
926 fn test_components_empty_component() {
927 let path = Path::from_str("/hello//world").unwrap();
928 let mut components = path.components();
929 assert_eq!(Some(Component::RootDir), components.next());
930 assert_eq!(Some(Component::Normal("hello")), components.next());
931 assert_eq!(Some(Component::CurDir), components.next());
932 assert_eq!(Some(Component::Normal("world")), components.next());
933 assert_eq!(None, components.next());
934 }
935
936 #[test]
937 fn test_components_empty() {
938 let path = Path::from_owned_unchecked("".to_owned());
939 let mut components = path.components();
940 assert_eq!(None, components.next());
941 }
942
943 #[test]
944 fn test_relative_components() {
945 let path = RelativePath::from_str("./hello/world").unwrap();
946 let mut components = path.components();
947 assert_eq!(Some(Component::CurDir), components.next());
948 assert_eq!(Some(Component::Normal("hello")), components.next());
949 assert_eq!(Some(Component::Normal("world")), components.next());
950 assert_eq!(None, components.next());
951 }
952
953 #[test]
954 fn test_absolute_components() {
955 let path = AbsolutePath::from_str("/hello/world").unwrap();
956 let mut components = path.components();
957 assert_eq!(Some(Component::RootDir), components.next());
958 assert_eq!(Some(Component::Normal("hello")), components.next());
959 assert_eq!(Some(Component::Normal("world")), components.next());
960 assert_eq!(None, components.next());
961 }
962
963 #[test]
964 fn test_parents() {
965 let path = Path::from_str_unchecked("/hello/world/test.zip");
966 let mut parents = path.parents();
967 assert_eq!(Some(Path::from_str_unchecked("/")), parents.next());
968 assert_eq!(Some(Path::from_str_unchecked("/hello")), parents.next());
969 assert_eq!(Some(Path::from_str_unchecked("/hello/world")), parents.next());
970 assert_eq!(None, parents.next());
971 }
972
973 #[test]
974 fn test_paths() {
975 let path = Path::from_str_unchecked("/hello/world/test.zip");
976 let mut paths = path.paths();
977 assert_eq!(Some(Path::from_str_unchecked("/")), paths.next());
978 assert_eq!(Some(Path::from_str_unchecked("/hello")), paths.next());
979 assert_eq!(Some(Path::from_str_unchecked("/hello/world")), paths.next());
980 assert_eq!(Some(Path::from_str_unchecked("/hello/world/test.zip")), paths.next());
981 assert_eq!(None, paths.next());
982 }
983
984 #[test]
985 fn test_normalize() {
986 fn normalize(s: &str) -> String {
987 Path::from_str(s).unwrap().normalize().unwrap().into()
988 }
989 assert_eq!("/hello/test", normalize("/hello/test"));
990 assert_eq!("test", normalize("hello/.././test"));
991 assert_eq!("/test/hello", normalize("/test//hello"));
992 assert_eq!("../test", normalize("../test"));
993 assert_eq!("/", normalize("/"));
994 assert_eq!("/", normalize("//"));
995 assert_eq!("/test", normalize("/test/"));
996 assert_eq!("/test", normalize("/test//"));
997 assert_eq!("../test", normalize("./../test"));
998 assert_eq!("../../test", normalize("./../../test"));
999 assert_eq!("../../../test", normalize("./../../../test"));
1000 }
1001
1002 #[test]
1003 fn test_file_name() {
1004 assert_eq!(Some("test"), Path::from_str("/hello/test").unwrap().file_name());
1005 assert_eq!(Some("test.zip"), Path::from_str("hello/.././test.zip").unwrap().file_name());
1006 assert_eq!(None, Path::from_str("hello/.././test.zip/..").unwrap().file_name());
1007 assert_eq!(None, Path::from_str("/").unwrap().file_name());
1008 }
1009
1010 #[test]
1011 fn test_parent() {
1012 assert_eq!(Some(Path::from_str_unchecked("/hello")), Path::from_str("/hello/test").unwrap().parent());
1013 assert_eq!(Some(Path::from_str_unchecked("/")), Path::from_str("/hello").unwrap().parent());
1014 assert_eq!(None, Path::from_str("/").unwrap().parent());
1015 }
1016
1017 #[test]
1018 fn test_parent_and_file_name() {
1019 assert_eq!(
1020 Some((Path::from_str_unchecked("/hello"), "test")),
1021 Path::from_str("/hello/test").unwrap().parent_and_file_name()
1022 );
1023 assert_eq!(
1024 Some((Path::from_str_unchecked("hello/../."), "test.zip")),
1025 Path::from_str("hello/.././test.zip").unwrap().parent_and_file_name()
1026 );
1027 assert_eq!(None, Path::from_str("hello/.././test.zip/..").unwrap().parent_and_file_name());
1028 assert_eq!(None, Path::from_str("/").unwrap().parent_and_file_name());
1029 assert_eq!(
1030 Some((Path::from_str_unchecked("/"), "test")),
1031 Path::from_str("/test").unwrap().parent_and_file_name()
1032 );
1033 }
1034
1035 #[test]
1036 fn test_join() {
1037 assert_eq!(
1038 "/hello/test/world",
1039 Path::from_str("/hello/test")
1040 .unwrap()
1041 .join(Path::from_str("world").unwrap())
1042 .unwrap()
1043 .as_str()
1044 );
1045 assert_eq!(
1046 "/world",
1047 Path::from_str("/hello/test")
1048 .unwrap()
1049 .join(Path::from_str("/world").unwrap())
1050 .unwrap()
1051 .as_str()
1052 );
1053 }
1054}