1#![doc = include_str!("../README.md")]
2
3use std::borrow::{Borrow, Cow};
4use std::path::PathBuf;
5
6#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
10pub struct Path<'a> {
11 path: Cow<'a, str>,
12}
13
14impl<'a> Path<'a> {
15 pub const fn new(s: &'a str) -> Self {
17 Self {
18 path: Cow::Borrowed(s),
19 }
20 }
21
22 pub fn into_owned(self) -> Path<'static> {
24 Path {
25 path: Cow::Owned(self.path.into_owned()),
26 }
27 }
28
29 pub fn into_std(&self) -> &std::path::Path {
31 std::path::Path::new::<str>(self.path.as_ref())
32 }
33
34 pub fn as_str(&self) -> &str {
36 &self.path
37 }
38
39 pub fn is_dir(&self) -> bool {
41 std::path::Path::new(self.path.as_ref()).is_dir()
42 }
43
44 pub fn basename(&self) -> Path<'_> {
47 self.split().1
48 }
49
50 pub fn dirname(&self) -> Path<'_> {
53 self.split().0
54 }
55
56 pub fn exists(&self) -> bool {
58 let path: &str = &self.path;
59 PathBuf::from(path).exists()
60 }
61
62 pub fn has_root(&self) -> bool {
64 self.path.starts_with('/') && !self.has_app_defined()
65 }
66
67 pub fn has_app_defined(&self) -> bool {
69 self.path.starts_with("//") && (self.path.len() == 2 || &self.path[2..3] != "/")
70 }
71
72 pub fn is_abs(&self) -> bool {
74 self.has_root() || self.has_app_defined()
75 }
76
77 pub fn is_normal(&self) -> bool {
80 let start = if self.path.starts_with("//") {
81 2
82 } else if self.path.starts_with('/') {
83 1
84 } else {
85 0
86 };
87 if self.path[start..].is_empty() {
88 return start > 0;
89 }
90 let limit = if self.path[start..].ends_with('/') {
91 self.path.len() - 1
92 } else {
93 self.path.len()
94 };
95 let components: Vec<_> = self.path[start..limit].split('/').collect();
96 let mut parent_allowed = start == 0;
97 for component in components {
98 if parent_allowed {
99 if matches!(component, "." | "") {
100 return false;
101 }
102 parent_allowed = component == "..";
103 } else if matches!(component, ".." | "." | "") {
104 return false;
105 }
106 }
107 true
108 }
109
110 pub fn join<'b, 'c>(&self, with: impl Into<Path<'b>>) -> Path<'c>
113 where
114 'a: 'c,
115 'b: 'c,
116 {
117 let with = with.into();
118 if with.is_abs() {
119 with.clone()
120 } else {
121 Path::from(format!("{}/{}", self.path, with.path))
122 }
123 }
124
125 pub fn strip_prefix<'b>(&self, prefix: impl Into<Path<'b>>) -> Option<Path> {
128 let prefix = prefix.into();
129 if self.has_root() && !prefix.has_root() {
134 return None;
135 }
136 if self.has_app_defined() && !prefix.has_app_defined() {
137 return None;
138 }
139 let mut path = self.path[..].trim_start_matches('/');
140 let mut prefix = prefix.path[..].trim_start_matches('/');
141 loop {
142 if let Some(prefix_slash) = prefix.find('/') {
143 let path_slash = path.find('/')?;
144 if prefix[..prefix_slash] != path[..path_slash] {
145 return None;
146 }
147 path = path[path_slash + 1..].trim_start_matches('/');
148 prefix = prefix[prefix_slash + 1..].trim_start_matches('/');
149 } else if prefix == path {
150 return Some(Path::new("."));
151 } else if let Some(path) = path.strip_prefix(prefix) {
152 let path = path.trim_start_matches('/');
153 if path.is_empty() {
154 return Some(Path::new("."));
155 } else {
156 return Some(Path::new(path));
157 }
158 } else if prefix.starts_with("./") {
159 prefix = prefix[2..].trim_start_matches('/');
160 } else if path.starts_with("./") {
161 path = path[2..].trim_start_matches('/');
162 } else if prefix.is_empty() || prefix == "." {
163 if path.is_empty() {
164 return Some(Path::new("."));
165 } else {
166 return Some(Path::new(path));
167 }
168 }
169 }
170 }
171
172 pub fn split(&self) -> (Path, Path) {
174 if let Some(index) = self.path.rfind('/') {
175 let dirname = if index == 0 {
176 Path::new("/")
177 } else if index == 1 && self.path.starts_with("//") {
178 Path::new("//")
179 } else if self.path[..index].chars().all(|c| c == '/') {
180 Path::new("/")
181 } else {
182 Path::new(self.path[..index].trim_end_matches('/'))
183 };
184 let basename = if index + 1 == self.path.len() {
185 Path::new(".")
186 } else {
187 Path::new(&self.path[index + 1..])
188 };
189 (dirname, basename)
190 } else {
191 (Path::new("."), Path::new(&self.path))
192 }
193 }
194
195 pub fn components(&self) -> impl Iterator<Item = Component<'_>> {
198 let mut components = vec![];
199 let mut limit = self.path.len();
200 while let Some(slash) = self.path[..limit].rfind('/') {
201 if slash + 1 == limit {
202 components.push(Component::CurDir);
203 } else if &self.path[slash + 1..limit] == ".." {
204 components.push(Component::ParentDir);
205 } else if &self.path[slash + 1..limit] == "." {
206 components.push(Component::CurDir);
207 } else {
208 components.push(Component::Normal(Path::new(&self.path[slash + 1..limit])));
209 }
210 if slash == 0 {
211 components.push(Component::RootDir);
212 limit = 0;
213 } else if slash == 1 && self.path.starts_with("//") {
214 components.push(Component::AppDefined);
215 limit = 0;
216 } else if self.path[..slash].chars().all(|c| c == '/') {
217 components.push(Component::RootDir);
218 limit = 0;
219 } else {
220 limit = slash;
221 while limit > 0 && self.path[..limit].ends_with('/') {
222 limit -= 1;
223 }
224 }
225 }
226 if limit > 0 {
227 if &self.path[..limit] == ".." {
228 components.push(Component::ParentDir);
229 } else if &self.path[..limit] == "." {
230 components.push(Component::CurDir);
231 } else {
232 components.push(Component::Normal(Path::new(&self.path[..limit])));
233 }
234 }
235 components.reverse();
236 components.into_iter()
237 }
238
239 pub fn cwd() -> Option<Path<'a>> {
242 Path::try_from(std::env::current_dir().ok()?).ok()
243 }
244}
245
246impl AsRef<std::ffi::OsStr> for Path<'_> {
247 fn as_ref(&self) -> &std::ffi::OsStr {
248 let path: &std::ffi::OsStr = self.as_str().as_ref();
249 path
250 }
251}
252
253impl AsRef<std::path::Path> for Path<'_> {
254 fn as_ref(&self) -> &std::path::Path {
255 std::path::Path::new(self.as_str())
256 }
257}
258
259impl Borrow<std::path::Path> for Path<'_> {
260 fn borrow(&self) -> &std::path::Path {
261 std::path::Path::new(self.as_str())
262 }
263}
264
265impl std::fmt::Debug for Path<'_> {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
267 write!(f, "{:?}", self.path)
268 }
269}
270
271impl std::fmt::Display for Path<'_> {
272 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
273 write!(f, "{}", self.path)
274 }
275}
276
277impl From<String> for Path<'_> {
278 fn from(s: String) -> Self {
279 Self {
280 path: Cow::Owned(s),
281 }
282 }
283}
284
285impl<'a> From<Path<'a>> for String {
286 fn from(path: Path<'a>) -> Self {
287 path.path.into_owned()
288 }
289}
290
291impl<'a> From<&'a String> for Path<'a> {
292 fn from(s: &'a String) -> Self {
293 Self {
294 path: Cow::Borrowed(s),
295 }
296 }
297}
298
299impl<'a> From<&'a str> for Path<'a> {
300 fn from(s: &'a str) -> Self {
301 Self {
302 path: Cow::Borrowed(s),
303 }
304 }
305}
306
307impl<'a> From<&'a Path<'a>> for &'a str {
308 fn from(path: &'a Path<'a>) -> Self {
309 &path.path
310 }
311}
312
313impl<'a> TryFrom<&'a std::path::Path> for Path<'a> {
314 type Error = std::str::Utf8Error;
315
316 fn try_from(p: &'a std::path::Path) -> Result<Self, Self::Error> {
317 Ok(Self {
318 path: Cow::Borrowed(<&str>::try_from(p.as_os_str())?),
319 })
320 }
321}
322
323impl TryFrom<std::path::PathBuf> for Path<'_> {
324 type Error = std::str::Utf8Error;
325
326 fn try_from(p: std::path::PathBuf) -> Result<Self, Self::Error> {
327 Ok(Self {
328 path: Cow::Owned(<&str>::try_from(p.as_os_str())?.to_string()),
329 })
330 }
331}
332
333impl TryFrom<std::ffi::OsString> for Path<'_> {
334 type Error = std::str::Utf8Error;
335
336 fn try_from(p: std::ffi::OsString) -> Result<Self, Self::Error> {
337 Ok(Self {
338 path: Cow::Owned(<&str>::try_from(p.as_os_str())?.to_string()),
339 })
340 }
341}
342
343impl<'a> From<Path<'a>> for std::path::PathBuf {
344 fn from(path: Path<'a>) -> Self {
345 PathBuf::from(path.path.to_string())
346 }
347}
348
349#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
353pub enum Component<'a> {
354 RootDir,
356 AppDefined,
358 CurDir,
360 ParentDir,
362 Normal(Path<'a>),
364}
365
366#[cfg(test)]
369mod tests {
370 use super::{Component, Path};
371
372 struct TestCase<'a> {
373 path: Path<'a>,
374 basename: Path<'a>,
375 dirname: Path<'a>,
376 is_abs: bool,
377 is_normal: bool,
378 components: &'a str,
379 }
380
381 static TEST_CASES: &[TestCase] = &[
382 TestCase {
383 path: Path::new("//"),
384 basename: Path::new("."),
385 dirname: Path::new("//"),
386 is_abs: true,
387 is_normal: true,
388 components: "AC",
389 },
390 TestCase {
391 path: Path::new("//."),
392 basename: Path::new("."),
393 dirname: Path::new("//"),
394 is_abs: true,
395 is_normal: false,
396 components: "AC",
397 },
398 TestCase {
399 path: Path::new("//.."),
400 basename: Path::new(".."),
401 dirname: Path::new("//"),
402 is_abs: true,
403 is_normal: false,
404 components: "AP",
405 },
406 TestCase {
407 path: Path::new("//foo"),
408 basename: Path::new("foo"),
409 dirname: Path::new("//"),
410 is_abs: true,
411 is_normal: true,
412 components: "AN",
413 },
414 TestCase {
415 path: Path::new("/./"),
416 basename: Path::new("."),
417 dirname: Path::new("/."),
418 is_abs: true,
419 is_normal: false,
420 components: "RCC",
421 },
422 TestCase {
423 path: Path::new("/./."),
424 basename: Path::new("."),
425 dirname: Path::new("/."),
426 is_abs: true,
427 is_normal: false,
428 components: "RCC",
429 },
430 TestCase {
431 path: Path::new("/./.."),
432 basename: Path::new(".."),
433 dirname: Path::new("/."),
434 is_abs: true,
435 is_normal: false,
436 components: "RCP",
437 },
438 TestCase {
439 path: Path::new("/./foo"),
440 basename: Path::new("foo"),
441 dirname: Path::new("/."),
442 is_abs: true,
443 is_normal: false,
444 components: "RCN",
445 },
446 TestCase {
447 path: Path::new("/../"),
448 basename: Path::new("."),
449 dirname: Path::new("/.."),
450 is_abs: true,
451 is_normal: false,
452 components: "RPC",
453 },
454 TestCase {
455 path: Path::new("/../."),
456 basename: Path::new("."),
457 dirname: Path::new("/.."),
458 is_abs: true,
459 is_normal: false,
460 components: "RPC",
461 },
462 TestCase {
463 path: Path::new("/../.."),
464 basename: Path::new(".."),
465 dirname: Path::new("/.."),
466 is_abs: true,
467 is_normal: false,
468 components: "RPP",
469 },
470 TestCase {
471 path: Path::new("/../foo"),
472 basename: Path::new("foo"),
473 dirname: Path::new("/.."),
474 is_abs: true,
475 is_normal: false,
476 components: "RPN",
477 },
478 TestCase {
479 path: Path::new("/foo/"),
480 basename: Path::new("."),
481 dirname: Path::new("/foo"),
482 is_abs: true,
483 is_normal: true,
484 components: "RNC",
485 },
486 TestCase {
487 path: Path::new("/foo/."),
488 basename: Path::new("."),
489 dirname: Path::new("/foo"),
490 is_abs: true,
491 is_normal: false,
492 components: "RNC",
493 },
494 TestCase {
495 path: Path::new("/foo/.."),
496 basename: Path::new(".."),
497 dirname: Path::new("/foo"),
498 is_abs: true,
499 is_normal: false,
500 components: "RNP",
501 },
502 TestCase {
503 path: Path::new("/foo/foo"),
504 basename: Path::new("foo"),
505 dirname: Path::new("/foo"),
506 is_abs: true,
507 is_normal: true,
508 components: "RNN",
509 },
510 TestCase {
511 path: Path::new(".//"),
512 basename: Path::new("."),
513 dirname: Path::new("."),
514 is_abs: false,
515 is_normal: false,
516 components: "CC",
517 },
518 TestCase {
519 path: Path::new(".//."),
520 basename: Path::new("."),
521 dirname: Path::new("."),
522 is_abs: false,
523 is_normal: false,
524 components: "CC",
525 },
526 TestCase {
527 path: Path::new(".//.."),
528 basename: Path::new(".."),
529 dirname: Path::new("."),
530 is_abs: false,
531 is_normal: false,
532 components: "CP",
533 },
534 TestCase {
535 path: Path::new(".//foo"),
536 basename: Path::new("foo"),
537 dirname: Path::new("."),
538 is_abs: false,
539 is_normal: false,
540 components: "CN",
541 },
542 TestCase {
543 path: Path::new("././"),
544 basename: Path::new("."),
545 dirname: Path::new("./."),
546 is_abs: false,
547 is_normal: false,
548 components: "CCC",
549 },
550 TestCase {
551 path: Path::new("././."),
552 basename: Path::new("."),
553 dirname: Path::new("./."),
554 is_abs: false,
555 is_normal: false,
556 components: "CCC",
557 },
558 TestCase {
559 path: Path::new("././.."),
560 basename: Path::new(".."),
561 dirname: Path::new("./."),
562 is_abs: false,
563 is_normal: false,
564 components: "CCP",
565 },
566 TestCase {
567 path: Path::new("././foo"),
568 basename: Path::new("foo"),
569 dirname: Path::new("./."),
570 is_abs: false,
571 is_normal: false,
572 components: "CCN",
573 },
574 TestCase {
575 path: Path::new("./../"),
576 basename: Path::new("."),
577 dirname: Path::new("./.."),
578 is_abs: false,
579 is_normal: false,
580 components: "CPC",
581 },
582 TestCase {
583 path: Path::new("./../."),
584 basename: Path::new("."),
585 dirname: Path::new("./.."),
586 is_abs: false,
587 is_normal: false,
588 components: "CPC",
589 },
590 TestCase {
591 path: Path::new("./../.."),
592 basename: Path::new(".."),
593 dirname: Path::new("./.."),
594 is_abs: false,
595 is_normal: false,
596 components: "CPP",
597 },
598 TestCase {
599 path: Path::new("./../foo"),
600 basename: Path::new("foo"),
601 dirname: Path::new("./.."),
602 is_abs: false,
603 is_normal: false,
604 components: "CPN",
605 },
606 TestCase {
607 path: Path::new("./foo/"),
608 basename: Path::new("."),
609 dirname: Path::new("./foo"),
610 is_abs: false,
611 is_normal: false,
612 components: "CNC",
613 },
614 TestCase {
615 path: Path::new("./foo/."),
616 basename: Path::new("."),
617 dirname: Path::new("./foo"),
618 is_abs: false,
619 is_normal: false,
620 components: "CNC",
621 },
622 TestCase {
623 path: Path::new("./foo/.."),
624 basename: Path::new(".."),
625 dirname: Path::new("./foo"),
626 is_abs: false,
627 is_normal: false,
628 components: "CNP",
629 },
630 TestCase {
631 path: Path::new("./foo/foo"),
632 basename: Path::new("foo"),
633 dirname: Path::new("./foo"),
634 is_abs: false,
635 is_normal: false,
636 components: "CNN",
637 },
638 TestCase {
639 path: Path::new("..//"),
640 basename: Path::new("."),
641 dirname: Path::new(".."),
642 is_abs: false,
643 is_normal: false,
644 components: "PC",
645 },
646 TestCase {
647 path: Path::new("..//."),
648 basename: Path::new("."),
649 dirname: Path::new(".."),
650 is_abs: false,
651 is_normal: false,
652 components: "PC",
653 },
654 TestCase {
655 path: Path::new("..//.."),
656 basename: Path::new(".."),
657 dirname: Path::new(".."),
658 is_abs: false,
659 is_normal: false,
660 components: "PP",
661 },
662 TestCase {
663 path: Path::new("..//foo"),
664 basename: Path::new("foo"),
665 dirname: Path::new(".."),
666 is_abs: false,
667 is_normal: false,
668 components: "PN",
669 },
670 TestCase {
671 path: Path::new(".././"),
672 basename: Path::new("."),
673 dirname: Path::new("../."),
674 is_abs: false,
675 is_normal: false,
676 components: "PCC",
677 },
678 TestCase {
679 path: Path::new(".././."),
680 basename: Path::new("."),
681 dirname: Path::new("../."),
682 is_abs: false,
683 is_normal: false,
684 components: "PCC",
685 },
686 TestCase {
687 path: Path::new(".././.."),
688 basename: Path::new(".."),
689 dirname: Path::new("../."),
690 is_abs: false,
691 is_normal: false,
692 components: "PCP",
693 },
694 TestCase {
695 path: Path::new(".././foo"),
696 basename: Path::new("foo"),
697 dirname: Path::new("../."),
698 is_abs: false,
699 is_normal: false,
700 components: "PCN",
701 },
702 TestCase {
703 path: Path::new("../../"),
704 basename: Path::new("."),
705 dirname: Path::new("../.."),
706 is_abs: false,
707 is_normal: true,
708 components: "PPC",
709 },
710 TestCase {
711 path: Path::new("../../."),
712 basename: Path::new("."),
713 dirname: Path::new("../.."),
714 is_abs: false,
715 is_normal: false,
716 components: "PPC",
717 },
718 TestCase {
719 path: Path::new("../../.."),
720 basename: Path::new(".."),
721 dirname: Path::new("../.."),
722 is_abs: false,
723 is_normal: true,
724 components: "PPP",
725 },
726 TestCase {
727 path: Path::new("../../foo"),
728 basename: Path::new("foo"),
729 dirname: Path::new("../.."),
730 is_abs: false,
731 is_normal: true,
732 components: "PPN",
733 },
734 TestCase {
735 path: Path::new("../foo/"),
736 basename: Path::new("."),
737 dirname: Path::new("../foo"),
738 is_abs: false,
739 is_normal: true,
740 components: "PNC",
741 },
742 TestCase {
743 path: Path::new("../foo/."),
744 basename: Path::new("."),
745 dirname: Path::new("../foo"),
746 is_abs: false,
747 is_normal: false,
748 components: "PNC",
749 },
750 TestCase {
751 path: Path::new("../foo/.."),
752 basename: Path::new(".."),
753 dirname: Path::new("../foo"),
754 is_abs: false,
755 is_normal: false,
756 components: "PNP",
757 },
758 TestCase {
759 path: Path::new("../foo/foo"),
760 basename: Path::new("foo"),
761 dirname: Path::new("../foo"),
762 is_abs: false,
763 is_normal: true,
764 components: "PNN",
765 },
766 TestCase {
767 path: Path::new("foo//"),
768 basename: Path::new("."),
769 dirname: Path::new("foo"),
770 is_abs: false,
771 is_normal: false,
772 components: "NC",
773 },
774 TestCase {
775 path: Path::new("foo//."),
776 basename: Path::new("."),
777 dirname: Path::new("foo"),
778 is_abs: false,
779 is_normal: false,
780 components: "NC",
781 },
782 TestCase {
783 path: Path::new("foo//.."),
784 basename: Path::new(".."),
785 dirname: Path::new("foo"),
786 is_abs: false,
787 is_normal: false,
788 components: "NP",
789 },
790 TestCase {
791 path: Path::new("foo//foo"),
792 basename: Path::new("foo"),
793 dirname: Path::new("foo"),
794 is_abs: false,
795 is_normal: false,
796 components: "NN",
797 },
798 TestCase {
799 path: Path::new("foo/./"),
800 basename: Path::new("."),
801 dirname: Path::new("foo/."),
802 is_abs: false,
803 is_normal: false,
804 components: "NCC",
805 },
806 TestCase {
807 path: Path::new("foo/./."),
808 basename: Path::new("."),
809 dirname: Path::new("foo/."),
810 is_abs: false,
811 is_normal: false,
812 components: "NCC",
813 },
814 TestCase {
815 path: Path::new("foo/./.."),
816 basename: Path::new(".."),
817 dirname: Path::new("foo/."),
818 is_abs: false,
819 is_normal: false,
820 components: "NCP",
821 },
822 TestCase {
823 path: Path::new("foo/./foo"),
824 basename: Path::new("foo"),
825 dirname: Path::new("foo/."),
826 is_abs: false,
827 is_normal: false,
828 components: "NCN",
829 },
830 TestCase {
831 path: Path::new("foo/../"),
832 basename: Path::new("."),
833 dirname: Path::new("foo/.."),
834 is_abs: false,
835 is_normal: false,
836 components: "NPC",
837 },
838 TestCase {
839 path: Path::new("foo/../."),
840 basename: Path::new("."),
841 dirname: Path::new("foo/.."),
842 is_abs: false,
843 is_normal: false,
844 components: "NPC",
845 },
846 TestCase {
847 path: Path::new("foo/../.."),
848 basename: Path::new(".."),
849 dirname: Path::new("foo/.."),
850 is_abs: false,
851 is_normal: false,
852 components: "NPP",
853 },
854 TestCase {
855 path: Path::new("foo/../foo"),
856 basename: Path::new("foo"),
857 dirname: Path::new("foo/.."),
858 is_abs: false,
859 is_normal: false,
860 components: "NPN",
861 },
862 TestCase {
863 path: Path::new("foo/foo/"),
864 basename: Path::new("."),
865 dirname: Path::new("foo/foo"),
866 is_abs: false,
867 is_normal: true,
868 components: "NNC",
869 },
870 TestCase {
871 path: Path::new("foo/foo/."),
872 basename: Path::new("."),
873 dirname: Path::new("foo/foo"),
874 is_abs: false,
875 is_normal: false,
876 components: "NNC",
877 },
878 TestCase {
879 path: Path::new("foo/foo/.."),
880 basename: Path::new(".."),
881 dirname: Path::new("foo/foo"),
882 is_abs: false,
883 is_normal: false,
884 components: "NNP",
885 },
886 TestCase {
887 path: Path::new("foo/foo/foo"),
888 basename: Path::new("foo"),
889 dirname: Path::new("foo/foo"),
890 is_abs: false,
891 is_normal: true,
892 components: "NNN",
893 },
894 ];
895
896 #[test]
897 fn basename() {
898 for tc in TEST_CASES.iter() {
899 assert_eq!(tc.basename, tc.path.basename(), "path: {:?}", tc.path);
900 }
901 }
902
903 #[test]
904 fn dirname() {
905 for tc in TEST_CASES.iter() {
906 assert_eq!(tc.dirname, tc.path.dirname(), "path: {:?}", tc.path);
907 }
908 }
909
910 #[test]
911 fn is_abs() {
912 for tc in TEST_CASES.iter() {
913 assert_eq!(tc.is_abs, tc.path.is_abs(), "path: {:?}", tc.path);
914 }
915 }
916
917 #[test]
918 fn is_normal() {
919 for tc in TEST_CASES.iter() {
920 assert_eq!(tc.is_normal, tc.path.is_normal(), "path: {:?}", tc.path);
921 }
922 }
923
924 #[test]
925 fn strip_prefix() {
926 for tc in TEST_CASES.iter() {
927 assert_eq!(
928 Some(tc.basename.clone()),
929 tc.path.strip_prefix(tc.dirname.clone()),
930 "path: {:?}",
931 tc.path
932 );
933 }
934 }
935
936 #[test]
937 fn split() {
938 for tc in TEST_CASES.iter() {
939 let (dirname, basename) = tc.path.split();
940 assert_eq!(tc.basename, basename, "path: {:?}", tc.path);
941 assert_eq!(tc.dirname, dirname, "path: {:?}", tc.path);
942 }
943 }
944
945 #[test]
946 fn components() {
947 fn component_to_char(c: Component) -> char {
948 match c {
949 Component::AppDefined => 'A',
950 Component::RootDir => 'R',
951 Component::CurDir => 'C',
952 Component::ParentDir => 'P',
953 Component::Normal(_) => 'N',
954 }
955 }
956 for tc in TEST_CASES.iter() {
957 let components: Vec<_> = tc.path.components().collect();
958 assert_eq!(
959 tc.components.chars().count(),
960 components.len(),
961 "path: {:?}",
962 tc.path
963 );
964 for (lhs, rhs) in std::iter::zip(tc.components.chars(), components.into_iter()) {
965 assert_eq!(lhs, component_to_char(rhs), "path: {:?}", tc.path);
966 }
967 }
968 }
969
970 #[test]
971 fn components_as_basename_dirname() {
972 for tc in TEST_CASES.iter() {
973 let mut components: Vec<_> = tc.path.components().collect();
974 fn basename_to_component(path: Path) -> Component {
975 if path.path == "." {
976 Component::CurDir
977 } else if path.path == ".." {
978 Component::ParentDir
979 } else {
980 Component::Normal(path)
981 }
982 }
983 assert_eq!(
984 Some(basename_to_component(tc.basename.clone())),
985 components.pop()
986 );
987 }
988 }
989}