1#![no_std]
2
3extern crate alloc;
4
5use alloc::{
6 borrow::Cow,
7 format,
8 string::{String, ToString},
9};
10use core::{ffi::CStr, fmt};
11
12const DEFAULT_SCHEME: &str = "file";
14
15pub const PATH_MAX: usize = 4096;
17
18#[derive(Clone, Debug, Eq, Hash, PartialEq)]
20pub struct RedoxScheme<'a>(Cow<'a, str>);
21
22impl<'a> RedoxScheme<'a> {
23 pub fn new<S: Into<Cow<'a, str>>>(scheme: S) -> Option<Self> {
25 let scheme = scheme.into();
26 if !scheme.is_ascii()
30 || scheme
31 .as_bytes()
32 .iter()
33 .any(|b| matches!(b, b'\0' | b'/' | b':'))
34 {
35 return None;
36 }
37 Some(Self(scheme))
38 }
39
40 pub fn canonicalize_as_scheme<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
42 canonicalize_using_scheme_checked(self, path)
43 }
44
45 pub fn into_owned<'b>(self) -> RedoxScheme<'b> {
46 RedoxScheme(self.0.into_owned().into())
47 }
48}
49
50impl<'a> AsRef<str> for RedoxScheme<'a> {
51 fn as_ref(&self) -> &str {
52 self.0.as_ref()
53 }
54}
55
56impl<'a> fmt::Display for RedoxScheme<'a> {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}", self.0)
59 }
60}
61
62#[derive(Clone, Debug, Eq, Hash, PartialEq)]
64pub struct RedoxReference<'a>(Cow<'a, str>);
65
66impl<'a> RedoxReference<'a> {
67 pub fn new<S: Into<Cow<'a, str>>>(reference: S) -> Option<Self> {
71 let reference = reference.into();
72 #[cfg(not(feature = "simd"))]
75 if reference.as_bytes().iter().any(|&b| b == b'\0') {
76 return None;
77 }
78 #[cfg(feature = "simd")]
79 if memchr::memchr(b'\0', reference.as_bytes()).is_some() {
80 return None;
81 }
82 Some(Self(reference))
83 }
84
85 pub fn new_from_c<S: Into<&'a CStr>>(reference: S) -> Option<Self> {
87 let reference = reference.into();
88 #[cfg(feature = "simd")]
89 let s = simdutf8::basic::from_utf8(reference.to_bytes()).ok();
90 #[cfg(not(feature = "simd"))]
91 let s = reference.to_str().ok();
92 Some(Self(Cow::Borrowed(s?)))
93 }
94
95 pub fn new_from_buf(path: &'a [u8], len: usize) -> Option<Self> {
97 let buf = path.get(..len)?;
98 #[cfg(not(feature = "simd"))]
99 let s = str::from_utf8(buf).ok()?;
100 #[cfg(feature = "simd")]
101 let s = simdutf8::basic::from_utf8(buf).ok()?;
102 Self::new(s)
103 }
104
105 pub unsafe fn new_unchecked<S: Into<Cow<'a, str>>>(reference: S) -> Self {
107 Self(reference.into())
108 }
109
110 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxReference<'b>> {
115 let path = path.into();
116 Some(self.join_checked(RedoxReference::new(path)?))
117 }
118
119 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxReference<'b> {
121 if !path.is_relative() {
122 path
124 } else if path.0.is_empty() {
125 RedoxReference(self.0.to_string().into())
127 } else {
128 let mut reference = self.0.clone().into_owned();
130 if !reference.is_empty() && !reference.ends_with('/') {
131 reference.push('/');
132 }
133 reference.push_str(&path.0);
134 RedoxReference(reference.into())
135 }
136 }
137
138 pub fn canonical(self) -> Self {
144 let mut canonicalized = true;
145 let mut is_absolute = false;
146 let mut parts = [""; PATH_MAX];
147 let mut parts_len = 0;
148 for (i, part) in self.0.split('/').enumerate() {
149 if part.is_empty() {
150 if i == 0 {
151 is_absolute = true;
152 } else {
153 canonicalized = false;
154 }
155 } else if part == "." {
156 canonicalized = false;
157 } else if part == ".." {
158 if parts_len > 0 {
159 parts_len -= 1;
160 }
161 canonicalized = false;
162 } else {
163 if parts_len >= PATH_MAX {
164 break;
165 }
166 parts[parts_len] = part;
167 parts_len += 1;
168 }
169 }
170
171 if canonicalized {
172 return self;
173 }
174 let mut string = String::with_capacity(self.0.len());
175 if is_absolute {
176 string.push('/');
177 }
178 for (i, part) in parts[..parts_len].iter().enumerate() {
179 if i > 0 {
180 string.push('/');
181 }
182 string.push_str(part);
183 }
184
185 Self(Cow::Owned(string))
187 }
188
189 pub fn canonical_max_upward(self, max_upward: usize) -> Option<(Self, usize)> {
196 let mut canonicalized = true;
197 let mut parts = [""; PATH_MAX];
198 let mut parts_len = 0;
199 let mut upward_depth = 0;
200 for (i, part) in self.0.split('/').enumerate() {
201 if part.is_empty() {
202 if i == 0 {
203 return None;
205 }
206 canonicalized = false;
207 } else if part == "." {
208 canonicalized = false;
209 } else if part == ".." {
210 if parts_len == 0 {
211 upward_depth += 1;
212 if upward_depth > max_upward {
213 return None;
215 }
216 } else {
217 parts_len -= 1;
218 }
219
220 canonicalized = false;
221 } else {
222 if parts_len + upward_depth >= PATH_MAX {
223 break;
224 }
225 parts[parts_len] = part;
226 parts_len += 1;
227 }
228 }
229
230 if canonicalized {
231 return Some((self, max_upward + parts_len));
232 }
233 let mut string = String::with_capacity(self.0.len());
234 for _ in 0..upward_depth {
235 if !string.is_empty() {
236 string.push('/');
237 }
238 string.push_str("..");
239 }
240 for part in parts[..parts_len].iter() {
241 if !string.is_empty() {
242 string.push('/');
243 }
244 string.push_str(part);
245 }
246
247 Some((Self(string.into()), max_upward + parts_len - upward_depth))
249 }
250
251 pub fn to_relative(self) -> Self {
255 if self.is_relative() {
256 return self;
257 }
258 match self.0 {
259 Cow::Borrowed(s) => Self(s[1..].into()),
260 Cow::Owned(s) => Self(s[1..].to_string().into()),
261 }
262 }
263
264 pub fn is_canon(&self) -> bool {
266 (self.0.is_empty() || self.0 == "/")
267 || (!self.is_relative()
268 && self
269 .0
270 .split('/')
271 .skip(1)
272 .all(|seg| seg != ".." && seg != "." && seg != ""))
273 }
274
275 pub fn dirname<'b>(&self) -> RedoxReference<'b> {
277 self.dirname_split().0.into_owned()
278 }
279
280 pub fn dirname_split(&'a self) -> (RedoxReference<'a>, Option<RedoxReference<'a>>) {
282 let mut slice = &*self.0;
283 let mut was_upward = false;
284 loop {
285 let Some((dir, file)) = slice.rsplit_once('/') else {
287 let file = if matches!(slice, "." | "..") {
288 None
289 } else {
290 Some(RedoxReference(slice.into()))
291 };
292 return (RedoxReference("".into()), file);
293 };
294 slice = dir;
295 was_upward = match file {
296 "" | "." => false,
297 ".." => true,
298 _ if was_upward => false,
299 file => {
300 return (
301 RedoxReference(slice.into()),
302 Some(RedoxReference(file.into())),
303 );
304 }
305 };
306 if slice.is_empty() {
307 return (RedoxReference("/".into()), None);
308 }
309 }
310 }
311
312 pub fn is_relative(&self) -> bool {
314 !self.0.starts_with('/')
315 }
316
317 pub fn into_owned<'b>(self) -> RedoxReference<'b> {
319 RedoxReference(self.0.into_owned().into())
320 }
321}
322
323impl<'a> AsRef<str> for RedoxReference<'a> {
324 fn as_ref(&self) -> &str {
325 self.0.as_ref()
326 }
327}
328
329impl<'a> fmt::Display for RedoxReference<'a> {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 write!(f, "{}", self.0)
332 }
333}
334
335impl<'a> From<RedoxReference<'a>> for Cow<'a, str> {
336 fn from(value: RedoxReference<'a>) -> Self {
337 value.0
338 }
339}
340
341#[derive(Clone, Debug, Eq, Hash, PartialEq)]
343pub enum RedoxPath<'a> {
344 Standard(RedoxReference<'a>),
346 Legacy(RedoxScheme<'a>, RedoxReference<'a>),
348}
349
350impl<'a> RedoxPath<'a> {
351 pub fn from_absolute<S: Into<Cow<'a, str>>>(path: S) -> Option<Self> {
355 RedoxStr::new(path).and_then(|s| s.abs())
356 }
357
358 pub fn from_absolute_c<S: Into<&'a CStr>>(path: S) -> Option<Self> {
360 RedoxStr::new_from_c(path).and_then(|s| s.abs())
361 }
362
363 pub fn from_absolute_buf(path: &'a [u8], len: usize) -> Option<Self> {
365 RedoxStr::new_from_buf(path, len).and_then(|s| s.abs())
366 }
367
368 pub fn from_reference(path: RedoxReference<'a>) -> Option<Self> {
370 RedoxStr::from(path).abs()
371 }
372
373 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxPath<'b>> {
378 let path = path.into();
379 Some(self.join_checked(RedoxReference::new(path)?))
380 }
381
382 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxPath<'b> {
384 if !path.is_relative() {
385 RedoxPath::Standard(path)
387 } else {
388 match self {
389 Self::Standard(reference) => RedoxPath::Standard(reference.join_checked(path)),
390 Self::Legacy(scheme, reference) => RedoxPath::Legacy(
391 RedoxScheme(scheme.0.to_string().into()),
392 reference.join_checked(path),
393 ),
394 }
395 }
396 }
397
398 pub fn canonical(self) -> Self {
400 match self {
401 Self::Standard(reference) => Self::Standard(reference.canonical()),
402 Self::Legacy(scheme, reference) => {
403 Self::Legacy(scheme, reference)
406 }
407 }
408 }
409
410 pub fn is_canon(&self) -> bool {
416 match self {
417 Self::Standard(reference) => reference.is_canon(),
418 Self::Legacy(_scheme, _reference) => true,
419 }
420 }
421
422 pub fn as_parts(&'a self) -> Option<(RedoxScheme<'a>, RedoxReference<'a>)> {
427 let (scheme, path) = self.get_scheme_prefixed(true)?;
428 Some((scheme, RedoxReference(path.into())))
429 }
430
431 pub fn get_scheme(&'a self) -> Option<RedoxScheme<'a>> {
434 Some(self.get_scheme_prefixed(false)?.0)
435 }
436
437 fn get_scheme_prefixed(&'a self, fallback: bool) -> Option<(RedoxScheme<'a>, &'a str)> {
438 const SCHEME_PREFIX_LENGTH: usize = "/scheme/".len();
439 match self {
440 Self::Standard(reference) => {
441 let mut parts = reference.0.split('/');
443 if parts.next() == Some("") && parts.next() == Some("scheme") {
444 match parts.next() {
445 Some(scheme_name) => {
446 let scheme_length = SCHEME_PREFIX_LENGTH + scheme_name.len() + 1;
448 let remainder = reference.0.get(scheme_length..).unwrap_or("");
449
450 return Some((RedoxScheme::new(scheme_name)?, remainder));
451 }
452 None => {
453 return Some((RedoxScheme(Cow::from("")), ""));
455 }
456 }
457 }
458 if !fallback {
459 return None;
460 }
461 let remainder = reference.0.get(1..).unwrap_or("");
463 return Some((RedoxScheme(Cow::from(DEFAULT_SCHEME)), remainder));
464 }
465 Self::Legacy(scheme, reference) => {
466 Some((scheme.clone(), reference.as_ref()))
468 }
469 }
470 }
471
472 pub fn matches_scheme(&self, other: &str) -> bool {
474 if let Some((scheme, _)) = self.get_scheme_prefixed(true) {
475 scheme.0 == other
476 } else {
477 false
478 }
479 }
480
481 pub fn is_scheme_category(&self, category: &str) -> bool {
483 if let Some((scheme, _)) = self.get_scheme_prefixed(true) {
484 let mut parts = scheme.0.splitn(2, '.');
485 if let Some(cat) = parts.next() {
486 cat == category && parts.next().is_some()
487 } else {
488 false
489 }
490 } else {
491 false
492 }
493 }
494
495 pub fn is_default_scheme(&self) -> bool {
497 self.matches_scheme(DEFAULT_SCHEME)
498 }
499
500 pub fn is_legacy(&self) -> bool {
502 matches!(self, RedoxPath::Legacy(_, _))
503 }
504
505 pub fn to_standard(self) -> RedoxReference<'a> {
507 match self {
508 RedoxPath::Standard(reference) => reference,
509 RedoxPath::Legacy(scheme, reference) => {
510 RedoxReference(format!("/scheme/{}/{}", scheme.0, reference.0).into())
511 }
512 }
513 }
514
515 pub fn to_standard_canon(self) -> RedoxPath<'a> {
518 match self {
519 RedoxPath::Standard(reference) => RedoxPath::Standard(reference.canonical()),
520 RedoxPath::Legacy(scheme, reference) => {
521 canonicalize_using_scheme_checked(&scheme, RedoxStr::Relative(reference))
522 }
523 }
524 }
525
526 pub fn dirname<'b>(&self) -> RedoxPath<'b> {
528 match self {
529 RedoxPath::Standard(redox_reference) => RedoxPath::Standard(redox_reference.dirname()),
530 RedoxPath::Legacy(redox_scheme, redox_reference) => RedoxPath::Legacy(
531 RedoxScheme(redox_scheme.to_string().into()),
532 redox_reference.dirname(),
533 ),
534 }
535 }
536
537 pub fn dirname_split(&'a self) -> (RedoxPath<'a>, Option<RedoxReference<'a>>) {
539 match self {
540 RedoxPath::Standard(redox_reference) => {
541 let (dir, name) = redox_reference.dirname_split();
542 (RedoxPath::Standard(dir), name)
543 }
544 RedoxPath::Legacy(redox_scheme, redox_reference) => {
545 let (dir, name) = redox_reference.dirname_split();
546 (RedoxPath::Legacy(redox_scheme.clone(), dir), name)
547 }
548 }
549 }
550
551 pub fn to_reference(self) -> RedoxReference<'a> {
553 match self {
554 RedoxPath::Standard(redox_reference) => redox_reference,
555 RedoxPath::Legacy(_, redox_reference) => redox_reference,
556 }
557 }
558
559 pub fn as_reference(&self) -> &RedoxReference<'a> {
561 match self {
562 RedoxPath::Standard(redox_reference) => redox_reference,
563 RedoxPath::Legacy(_, redox_reference) => redox_reference,
564 }
565 }
566
567 pub fn canonicalize_as_cwd<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
569 canonicalize_using_cwd_checked(Some(self), path, false).unwrap()
571 }
572
573 pub fn canonicalize_as_cwd_standard<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
575 canonicalize_to_standard_checked(Some(self), path).unwrap()
577 }
578
579 pub fn canonicalize_as_cwd_with_max_upward<'b>(
581 &self,
582 relpath: RedoxReference<'b>,
583 max_upward: usize,
584 ) -> Option<(RedoxPath<'b>, usize)> {
585 let (relpath, max_upward) = relpath.canonical_max_upward(max_upward)?;
586 let newpath = self.join_checked(relpath);
587 Some((newpath, max_upward))
588 }
589
590 pub fn into_owned<'b>(self) -> RedoxPath<'b> {
592 match self {
593 RedoxPath::Standard(redox_reference) => {
594 RedoxPath::Standard(redox_reference.into_owned())
595 }
596 RedoxPath::Legacy(redox_scheme, redox_reference) => {
597 RedoxPath::Legacy(redox_scheme.into_owned(), redox_reference.into_owned())
598 }
599 }
600 }
601}
602
603impl<'a> fmt::Display for RedoxPath<'a> {
604 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
605 match self {
606 RedoxPath::Standard(reference) => {
607 write!(f, "{}", reference.0)
608 }
609 RedoxPath::Legacy(scheme, reference) => {
610 write!(f, "{}:{}", scheme.0, reference.0)
611 }
612 }
613 }
614}
615
616impl<'a> From<RedoxPath<'a>> for Cow<'a, str> {
617 fn from(value: RedoxPath<'a>) -> Self {
618 match value {
619 RedoxPath::Standard(redox_reference) => redox_reference.0,
620 a => Cow::Owned(a.to_string()),
621 }
622 }
623}
624
625#[derive(Clone, Debug, Eq, Hash, PartialEq)]
627pub enum RedoxStr<'a> {
628 Absolute(RedoxPath<'a>),
629 Relative(RedoxReference<'a>),
630}
631
632impl<'a> RedoxStr<'a> {
633 pub fn new<S: Into<Cow<'a, str>>>(path: S) -> Option<Self> {
635 let path = path.into();
636 let r = if path.starts_with('/') {
637 RedoxPath::Standard(RedoxReference::new(path)?)
639 } else {
640 if let Some((scheme, reference)) = Self::split_scheme(&path) {
641 RedoxPath::Legacy(scheme, RedoxReference::new(reference)?)
643 } else {
644 return Some(RedoxStr::Relative(RedoxReference::new(path)?));
646 }
647 };
648 Some(RedoxStr::Absolute(r))
649 }
650
651 pub fn new_from_c<S: Into<&'a CStr>>(path: S) -> Option<Self> {
653 let path = path.into();
654 if matches!(path.to_bytes_with_nul().first(), Some(b'/')) {
655 Some(RedoxStr::Absolute(RedoxPath::Standard(
657 RedoxReference::new_from_c(path)?,
658 )))
659 } else {
660 #[cfg(feature = "simd")]
662 let s = simdutf8::basic::from_utf8(path.to_bytes()).ok()?;
663 #[cfg(not(feature = "simd"))]
664 let s = path.to_str().ok()?;
665 unsafe { Some(RedoxStr::new_unchecked(s)) }
667 }
668 }
669
670 pub fn new_from_buf(path: &'a [u8], len: usize) -> Option<RedoxStr<'a>> {
672 let buf = path.get(..len)?;
673 #[cfg(not(feature = "simd"))]
674 let s = str::from_utf8(buf).ok()?;
675 #[cfg(feature = "simd")]
676 let s = simdutf8::basic::from_utf8(buf).ok()?;
677 Self::new(s)
678 }
679
680 pub unsafe fn new_unchecked<S: Into<Cow<'a, str>>>(path: S) -> Self {
682 let path = path.into();
683 let r = if path.starts_with('/') {
684 RedoxPath::Standard(RedoxReference(path))
686 } else {
687 if let Some((scheme, reference)) = Self::split_scheme(&path) {
688 RedoxPath::Legacy(scheme, RedoxReference(reference))
690 } else {
691 return RedoxStr::Relative(RedoxReference(path));
693 }
694 };
695 RedoxStr::Absolute(r)
696 }
697
698 fn split_scheme(path: &Cow<'a, str>) -> Option<(RedoxScheme<'a>, Cow<'a, str>)> {
700 #[cfg(not(feature = "simd"))]
701 let i = {
702 let mut i = 0;
703 for &byte in path.as_bytes().iter() {
704 if byte == b'/' {
705 return None;
706 }
707 if byte == b':' {
708 break;
709 }
710 i += 1;
711 }
712 i
713 };
714 #[cfg(feature = "simd")]
715 let i = {
716 let path_b = path.as_bytes();
717 let chr = memchr::memchr2(b'/', b':', path_b);
718 match chr {
719 Some(c) if path_b.get(c).filter(|&&b| b == b':').is_some() => c,
720 _ => return None,
721 }
722 };
723 let (scheme, path): (Cow<'a, str>, Cow<'a, str>) = match path {
724 Cow::Borrowed(s) => (s.get(..i)?.into(), s.get(i + 1..)?.into()),
725 Cow::Owned(s) => (
726 s.get(..i)?.to_string().into(),
727 s.get(i + 1..)?.to_string().into(),
728 ),
729 };
730 Some((RedoxScheme::new(scheme)?, path))
731 }
732
733 pub fn as_abs(&'a self) -> Option<&'a RedoxPath<'a>> {
735 match self {
736 RedoxStr::Absolute(redox_path) => Some(redox_path),
737 _ => None,
738 }
739 }
740 pub fn as_rel(&'a self) -> Option<&'a RedoxReference<'a>> {
742 match self {
743 RedoxStr::Relative(redox_reference) => Some(redox_reference),
744 _ => None,
745 }
746 }
747 pub fn abs(self) -> Option<RedoxPath<'a>> {
749 match self {
750 RedoxStr::Absolute(redox_path) => Some(redox_path),
751 _ => None,
752 }
753 }
754 pub fn rel(self) -> Option<RedoxReference<'a>> {
756 match self {
757 RedoxStr::Relative(redox_reference) => Some(redox_reference),
758 _ => None,
759 }
760 }
761 pub fn is_canon(&self) -> bool {
763 match self {
764 RedoxStr::Absolute(redox_path) => redox_path.is_canon(),
765 RedoxStr::Relative(redox_reference) => redox_reference.is_canon(),
766 }
767 }
768 pub fn canonical(self) -> Self {
770 match self {
771 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.canonical()),
772 RedoxStr::Relative(redox_reference) => RedoxStr::from(redox_reference.canonical()),
773 }
774 }
775 pub fn to_standard(self) -> RedoxReference<'a> {
777 match self {
778 RedoxStr::Absolute(redox_path) => redox_path.to_standard(),
779 RedoxStr::Relative(redox_reference) => redox_reference,
780 }
781 }
782 pub fn dirname<'b>(&self) -> RedoxStr<'b> {
784 match self {
785 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.dirname()),
786 RedoxStr::Relative(redox_reference) => RedoxStr::from(redox_reference.dirname()),
787 }
788 }
789 pub fn dirname_split(&'a self) -> (RedoxStr<'a>, Option<RedoxReference<'a>>) {
791 match self {
792 RedoxStr::Absolute(redox_path) => {
793 let (dir, name) = redox_path.dirname_split();
794 (RedoxStr::from(dir), name)
795 }
796 RedoxStr::Relative(redox_reference) => {
797 let (dir, name) = redox_reference.dirname_split();
798 (RedoxStr::from(dir), name)
799 }
800 }
801 }
802 pub fn is_empty(&self) -> bool {
804 match self {
805 RedoxStr::Absolute(_) => false, RedoxStr::Relative(redox_reference) => redox_reference.as_ref().is_empty(),
807 }
808 }
809 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxStr<'b>> {
811 match self {
812 RedoxStr::Absolute(redox_path) => Some(RedoxStr::from(redox_path.join(path)?)),
813 RedoxStr::Relative(redox_reference) => {
814 Some(RedoxStr::from(redox_reference.join(path)?))
815 }
816 }
817 }
818 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxStr<'b> {
820 match self {
821 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.join_checked(path)),
822 RedoxStr::Relative(redox_reference) => {
823 RedoxStr::from(redox_reference.join_checked(path))
824 }
825 }
826 }
827 pub fn is_relative(&self) -> bool {
829 matches!(self, RedoxStr::Relative(_))
831 }
832
833 pub fn into_owned<'b>(self) -> RedoxStr<'b> {
835 match self {
836 RedoxStr::Absolute(redox_path) => RedoxStr::Absolute(redox_path.into_owned()),
837 RedoxStr::Relative(redox_reference) => RedoxStr::Relative(redox_reference.into_owned()),
838 }
839 }
840}
841
842impl<'a> From<RedoxPath<'a>> for RedoxStr<'a> {
843 fn from(value: RedoxPath<'a>) -> Self {
844 Self::Absolute(value)
845 }
846}
847
848impl<'a> From<RedoxReference<'a>> for RedoxStr<'a> {
849 fn from(value: RedoxReference<'a>) -> Self {
850 if !value.is_relative() {
852 RedoxStr::Absolute(RedoxPath::Standard(value))
853 } else {
854 RedoxStr::Relative(value)
855 }
856 }
857}
858impl<'a> From<RedoxStr<'a>> for Cow<'a, str> {
859 fn from(value: RedoxStr<'a>) -> Self {
860 match value {
861 RedoxStr::Absolute(redox_path) => redox_path.into(),
862 RedoxStr::Relative(redox_reference) => redox_reference.into(),
863 }
864 }
865}
866
867pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&'a str>, path: &'a str) -> Option<Cow<'a, str>> {
881 let cwd_opt = cwd_opt.and_then(|s| RedoxPath::from_absolute(Cow::Borrowed(s)));
882 let absolute = canonicalize_using_cwd_checked(cwd_opt.as_ref(), RedoxStr::new(path)?, false)?;
883 Some(absolute.into())
884}
885
886fn canonicalize_using_cwd_checked<'a, 'b>(
890 cwd_opt: Option<&RedoxPath<'a>>,
891 path: RedoxStr<'b>,
892 in_scheme_root: bool,
893) -> Option<RedoxPath<'b>> {
894 let absolute = match (path, in_scheme_root) {
895 (RedoxStr::Absolute(absolute), true) if absolute.get_scheme().is_some() => absolute,
896 (RedoxStr::Absolute(absolute), true) => {
897 cwd_opt?.join_checked(absolute.to_standard().to_relative())
898 }
899 (RedoxStr::Absolute(absolute), false) => absolute,
900 (RedoxStr::Relative(relative), true) => cwd_opt?.join_checked(relative.canonical()),
901 (RedoxStr::Relative(relative), false) => cwd_opt?.join_checked(relative),
902 };
903 Some(absolute.canonical())
904}
905
906pub fn canonicalize_to_standard<'a>(
909 cwd_opt: Option<&'a str>,
910 path: &'a str,
911) -> Option<Cow<'a, str>> {
912 let absolute = match RedoxPath::from_absolute(path) {
913 Some(absolute) => absolute,
914 None => {
915 let absolute = RedoxPath::from_absolute(cwd_opt?)?;
916 absolute.join(path)?
917 }
918 };
919 Some(absolute.to_standard_canon().into())
920}
921
922fn canonicalize_to_standard_checked<'a, 'b>(
924 cwd_opt: Option<&RedoxPath<'a>>,
925 path: RedoxStr<'b>,
926) -> Option<RedoxPath<'b>> {
927 let path = match path {
928 RedoxStr::Absolute(absolute) => absolute,
929 RedoxStr::Relative(relative) => cwd_opt?.join_checked(relative),
930 }
931 .to_standard_canon();
932 Some(path)
933}
934
935pub fn canonicalize_using_scheme<'a>(scheme: &'a str, path: &'a str) -> Option<Cow<'a, str>> {
942 let path = RedoxStr::new(path)?;
943 Some(canonicalize_using_scheme_checked(&RedoxScheme::new(scheme)?, path).into())
944}
945
946fn canonicalize_using_scheme_checked<'a, 'b>(
948 scheme: &RedoxScheme<'_>,
949 path: RedoxStr<'b>,
950) -> RedoxPath<'b> {
951 let path = match path {
952 RedoxStr::Absolute(s) => {
953 if !s.is_legacy() && s.matches_scheme(&scheme.0) {
954 return s;
957 }
958 RedoxStr::Absolute(s)
959 }
960 path => path,
961 };
962 canonicalize_using_cwd_checked(Some(&scheme_path_checked(&scheme)), path, true).unwrap()
964}
965
966pub fn canonicalize_using_cwd_with_max_upward<'a>(
970 cwd: &str,
971 relpath: &'a str,
972 max_upward: usize,
973) -> Option<(Cow<'a, str>, usize)> {
974 let cwd = RedoxStr::new(Cow::Borrowed(cwd))?;
976 let (absolute, max_upward) = canonicalize_using_cwd_with_max_upward_checked(
977 &cwd,
978 RedoxStr::new(relpath)?.rel()?,
979 max_upward,
980 )?;
981 Some((absolute.into(), max_upward))
982}
983
984fn canonicalize_using_cwd_with_max_upward_checked<'a, 'b>(
986 cwd: &RedoxStr<'a>,
987 relpath: RedoxReference<'b>,
988 max_upward: usize,
989) -> Option<(RedoxStr<'b>, usize)> {
990 let (relpath, max_upward) = relpath.canonical_max_upward(max_upward)?;
991 let relnew = cwd.join_checked(relpath);
992 Some((relnew, max_upward))
993}
994
995pub fn scheme_path(name: &str) -> Option<RedoxPath<'_>> {
999 let scheme = RedoxScheme::new(name)?;
1000 Some(scheme_path_checked(&scheme))
1001}
1002
1003fn scheme_path_checked<'a, 'b>(scheme: &'a RedoxScheme<'a>) -> RedoxPath<'b> {
1005 if scheme.0.is_empty() {
1006 return RedoxPath::Standard(RedoxReference("/scheme".into()));
1007 }
1008 RedoxPath::Standard(RedoxReference(format!("/scheme/{}", scheme.0).into()))
1009}
1010
1011pub fn make_scheme_name<'a, 'b>(category: &'a str, detail: &'a str) -> Option<RedoxScheme<'b>> {
1015 let name = format!("{}.{}", category, detail);
1016 RedoxScheme::new(name)
1017}
1018
1019#[cfg(test)]
1020mod tests {
1021 use super::*;
1022 use alloc::format;
1023
1024 #[test]
1026 fn test_absolute() {
1027 let cwd_opt = None;
1028 assert_eq!(
1029 canonicalize_using_cwd(cwd_opt, "/"),
1030 Some(Cow::Borrowed("/"))
1031 );
1032 assert_eq!(
1033 canonicalize_using_cwd(cwd_opt, "/file"),
1034 Some(Cow::Borrowed("/file"))
1035 );
1036 assert_eq!(
1037 canonicalize_using_cwd(cwd_opt, "/folder/file"),
1038 Some(Cow::Borrowed("/folder/file"))
1039 );
1040 assert_eq!(
1041 canonicalize_using_cwd(cwd_opt, "/folder/file/"),
1042 Some(Cow::Borrowed("/folder/file"))
1043 );
1044 assert_eq!(
1045 canonicalize_using_cwd(cwd_opt, "/folder/file//"),
1046 Some(Cow::Borrowed("/folder/file"))
1047 );
1048 assert_eq!(
1049 canonicalize_using_cwd(cwd_opt, "/folder/../file"),
1050 Some(Cow::Borrowed("/file"))
1051 );
1052 assert_eq!(
1053 canonicalize_using_cwd(cwd_opt, "/folder/../.."),
1054 Some(Cow::Borrowed("/"))
1055 );
1056 assert_eq!(
1057 canonicalize_using_cwd(cwd_opt, "/folder/../../../.."),
1058 Some(Cow::Borrowed("/"))
1059 );
1060 assert_eq!(
1061 canonicalize_using_cwd(cwd_opt, "/.."),
1062 Some(Cow::Borrowed("/"))
1063 );
1064 }
1065
1066 #[test]
1068 fn test_new_relative() {
1069 let cwd_opt = Some("/scheme/foo");
1070 assert_eq!(
1071 canonicalize_using_cwd(cwd_opt, "file"),
1072 Some(Cow::Borrowed("/scheme/foo/file"))
1073 );
1074 assert_eq!(
1075 canonicalize_using_cwd(cwd_opt, "folder/file"),
1076 Some(Cow::Borrowed("/scheme/foo/folder/file"))
1077 );
1078 assert_eq!(
1079 canonicalize_using_cwd(cwd_opt, "folder/../file"),
1080 Some(Cow::Borrowed("/scheme/foo/file"))
1081 );
1082 assert_eq!(
1083 canonicalize_using_cwd(cwd_opt, "folder/../.."),
1084 Some(Cow::Borrowed("/scheme"))
1085 );
1086 assert_eq!(
1087 canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
1088 Some(Cow::Borrowed("/"))
1089 );
1090 assert_eq!(
1091 canonicalize_using_cwd(cwd_opt, ".."),
1092 Some(Cow::Borrowed("/scheme"))
1093 );
1094 }
1095
1096 #[test]
1098 fn test_new_scheme() {
1099 let cwd_opt = None;
1100 assert_eq!(
1101 canonicalize_using_cwd(cwd_opt, "/scheme/bar/"),
1102 Some(Cow::Borrowed("/scheme/bar"))
1103 );
1104 assert_eq!(
1105 canonicalize_using_cwd(cwd_opt, "/scheme/bar/file"),
1106 Some(Cow::Borrowed("/scheme/bar/file"))
1107 );
1108 assert_eq!(
1109 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/file"),
1110 Some(Cow::Borrowed("/scheme/bar/folder/file"))
1111 );
1112 assert_eq!(
1113 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../file"),
1114 Some(Cow::Borrowed("/scheme/bar/file"))
1115 );
1116 assert_eq!(
1117 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../.."),
1118 Some(Cow::Borrowed("/scheme"))
1119 );
1120 assert_eq!(
1121 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../../../.."),
1122 Some(Cow::Borrowed("/"))
1123 );
1124 assert_eq!(
1125 canonicalize_using_cwd(cwd_opt, "/scheme/bar/.."),
1126 Some(Cow::Borrowed("/scheme"))
1127 );
1128
1129 assert_eq!(
1130 canonicalize_using_scheme("bar", ""),
1131 Some(Cow::Borrowed("/scheme/bar"))
1132 );
1133 assert_eq!(
1134 canonicalize_using_scheme("bar", "foo"),
1135 Some(Cow::Borrowed("/scheme/bar/foo"))
1136 );
1137 assert_eq!(
1138 canonicalize_using_scheme("foo", "/bar"),
1139 Some(Cow::Borrowed("/scheme/foo/bar"))
1140 );
1141 assert_eq!(
1142 canonicalize_using_scheme("bar", ".."),
1143 Some(Cow::Borrowed("/scheme/bar"))
1144 );
1145 assert_eq!(
1146 canonicalize_using_scheme("file", "/scheme/foo/bar"),
1147 Some(Cow::Borrowed("/scheme/foo/bar"))
1148 );
1149 }
1150
1151 #[test]
1153 fn test_old_relative() {
1154 let cwd_opt = Some("foo:");
1155 assert_eq!(
1156 canonicalize_using_cwd(cwd_opt, "file"),
1157 Some(Cow::Borrowed("foo:file"))
1158 );
1159 assert_eq!(
1160 canonicalize_using_cwd(cwd_opt, "folder/file"),
1161 Some(Cow::Borrowed("foo:folder/file"))
1162 );
1163 assert_eq!(
1164 canonicalize_using_cwd(cwd_opt, "folder/../file"),
1165 Some(Cow::Borrowed("foo:folder/../file"))
1166 );
1167 assert_eq!(
1168 canonicalize_using_cwd(cwd_opt, "folder/../.."),
1169 Some(Cow::Borrowed("foo:folder/../.."))
1170 );
1171 assert_eq!(
1172 canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
1173 Some(Cow::Borrowed("foo:folder/../../../.."))
1174 );
1175 assert_eq!(
1176 canonicalize_using_cwd(cwd_opt, ".."),
1177 Some(Cow::Borrowed("foo:.."))
1178 );
1179 }
1180
1181 #[test]
1183 fn test_old_scheme() {
1184 let cwd_opt = None;
1185 assert_eq!(
1186 canonicalize_using_cwd(cwd_opt, "bar:"),
1187 Some(Cow::Borrowed("bar:"))
1188 );
1189 assert_eq!(
1190 canonicalize_using_cwd(cwd_opt, "bar:file"),
1191 Some(Cow::Borrowed("bar:file"))
1192 );
1193 assert_eq!(
1194 canonicalize_using_cwd(cwd_opt, "bar:folder/file"),
1195 Some(Cow::Borrowed("bar:folder/file"))
1196 );
1197 assert_eq!(
1198 canonicalize_using_cwd(cwd_opt, "bar:folder/../file"),
1199 Some(Cow::Borrowed("bar:folder/../file"))
1200 );
1201 assert_eq!(
1202 canonicalize_using_cwd(cwd_opt, "bar:folder/../.."),
1203 Some(Cow::Borrowed("bar:folder/../.."))
1204 );
1205 assert_eq!(
1206 canonicalize_using_cwd(cwd_opt, "bar:folder/../../../.."),
1207 Some(Cow::Borrowed("bar:folder/../../../.."))
1208 );
1209 assert_eq!(
1210 canonicalize_using_cwd(cwd_opt, "bar:.."),
1211 Some(Cow::Borrowed("bar:.."))
1212 );
1213 }
1214
1215 #[test]
1217 fn test_max_upward() {
1218 assert_eq!(
1220 canonicalize_using_cwd_with_max_upward("/", "foo/bar", 0),
1221 Some((Cow::Borrowed("/foo/bar"), 2))
1222 );
1223 assert_eq!(
1224 canonicalize_using_cwd_with_max_upward("/foo/", "bar/../baz/qux", 0),
1225 Some((Cow::Borrowed("/foo/baz/qux"), 2))
1226 );
1227 assert_eq!(
1228 canonicalize_using_cwd_with_max_upward("foo/../bar", "baz/./../qux", 0),
1229 Some((Cow::Borrowed("foo/../bar/qux"), 1))
1230 );
1231 assert_eq!(
1232 canonicalize_using_cwd_with_max_upward("/foo", "../bar", 1),
1233 Some((Cow::Borrowed("/foo/../bar"), 1))
1234 );
1235 assert_eq!(
1236 canonicalize_using_cwd_with_max_upward("foo/..", "../bar/..", 2),
1237 Some((Cow::Borrowed("foo/../.."), 1))
1238 );
1239 assert_eq!(
1241 canonicalize_using_cwd_with_max_upward("foo:", "bar/baz", 0),
1242 Some((Cow::Borrowed("foo:bar/baz"), 2))
1243 );
1244 assert_eq!(
1245 canonicalize_using_cwd_with_max_upward("foo:", "bar/baz//../../", 0),
1246 Some((Cow::Borrowed("foo:"), 0))
1247 );
1248 assert_eq!(
1249 canonicalize_using_cwd_with_max_upward("foo:/bar/..", "baz/./qux/..", 0),
1250 Some((Cow::Borrowed("foo:/bar/../baz"), 1))
1251 );
1252 assert_eq!(
1254 canonicalize_using_cwd_with_max_upward("/foo", "../bar", 0),
1255 None
1256 );
1257 assert_eq!(
1258 canonicalize_using_cwd_with_max_upward("/foo", "/bar", 0),
1259 None
1260 );
1261 assert_eq!(
1262 canonicalize_using_cwd_with_max_upward("/foo/..", "../bar/..", 0),
1263 None
1264 );
1265 }
1266 #[test]
1268 fn test_cow_borrowing() {
1269 let cwd_opt = None;
1270 for (path, should_owned) in [
1271 ("/foo/bar/baz", false),
1272 ("/scheme/foo/bar/baz", false),
1273 ("foo/bar/baz", false), ("/scheme/foo/../baz", true),
1275 ("foo:bar", true),
1276 ] {
1277 assert_eq!(
1278 matches!(canonicalize_using_cwd(cwd_opt, path), Some(Cow::Owned(_))),
1279 should_owned
1280 );
1281 }
1282 }
1283 #[test]
1285 fn test_dirname() {
1286 for (a, b) in [
1287 ("/foo/bar/baz", "/foo/bar"),
1288 ("/baz/foo/bar/", "/baz/foo"),
1289 ("/", "/"),
1290 ("/scheme/foo", "/scheme"),
1291 ("foo:bar/baz/", "foo:bar"),
1292 ("foo:bar", "foo:"),
1293 ("foo:", "foo:"),
1294 ] {
1295 assert_eq!(
1296 RedoxPath::from_absolute(a).unwrap().dirname(),
1297 RedoxPath::from_absolute(b).unwrap()
1298 );
1299 }
1300 for (a, b) in [
1301 ("foo/bar/.", "foo"),
1302 ("./foo/bar/..", "."),
1303 ("./.", ""),
1304 (".", ""),
1305 ("", ""),
1306 ] {
1307 assert_eq!(
1308 RedoxStr::new(a).unwrap().dirname(),
1309 RedoxStr::new(b).unwrap()
1310 );
1311 }
1312 }
1313
1314 #[test]
1316 fn test_orbital_scheme() {
1317 for flag_str in &["", "abflrtu"] {
1318 for x in &[-1, 0, 1] {
1319 for y in &[-1, 0, 1] {
1320 for w in &[0, 1] {
1321 for h in &[0, 1] {
1322 for title in &[
1323 "",
1324 "title",
1325 "title/with/slashes",
1326 "title:with:colons",
1327 "title/../with/../dots/..",
1328 ] {
1329 let path = format!(
1330 "orbital:{}/{}/{}/{}/{}/{}",
1331 flag_str, x, y, w, h, title
1332 );
1333 assert_eq!(
1334 canonicalize_using_cwd(None, &path),
1335 Some((&path).into())
1336 );
1337 }
1338 }
1339 }
1340 }
1341 }
1342 }
1343 }
1344
1345 #[test]
1347 fn test_parts() {
1348 for (path, scheme, reference) in [
1349 ("/foo/bar/baz", "file", "foo/bar/baz"),
1350 ("/scheme/foo/bar/baz", "foo", "bar/baz"),
1351 ("/", "file", ""),
1352 ("/bar", "file", "bar"),
1353 ("/...", "file", "..."),
1354 ("//double/slash", "file", "/double/slash"),
1355 ("/ending/in/slash/", "file", "ending/in/slash/"),
1356 ("/contains/dot/.", "file", "contains/dot/."),
1357 ("/contains/dotdot/..", "file", "contains/dotdot/.."),
1358 ] {
1359 let redox_path = RedoxPath::from_absolute(path).unwrap();
1360 let parts = redox_path.as_parts();
1361 assert_eq!(
1362 (path, parts),
1363 (
1364 path,
1365 Some((
1366 RedoxScheme::new(scheme).unwrap(),
1367 RedoxReference::new(reference).unwrap()
1368 ))
1369 )
1370 );
1371 if path.starts_with("/scheme") {
1372 let to_string = format!("/scheme/{scheme}");
1373 let joined_path = RedoxPath::from_absolute(&to_string)
1374 .unwrap()
1375 .join(reference)
1376 .unwrap();
1377 assert_eq!(path, &format!("{joined_path}"));
1378 } else {
1379 assert_eq!(path, &format!("/{reference}"));
1380 }
1381 }
1382
1383 assert_eq!(RedoxPath::from_absolute("not/absolute"), None);
1385
1386 for path in ["/scheme/use:colon/", "/scheme/βeta/"] {
1388 let redox_path = RedoxPath::from_absolute(path).unwrap();
1389 let parts = redox_path.as_parts();
1390 assert_eq!((path, parts), (path, None));
1391 }
1392 }
1393
1394 #[test]
1395 fn test_old_scheme_parts() {
1396 for (path, scheme, reference) in [
1397 ("foo:bar/baz", "foo", "bar/baz"),
1398 ("emptyref:", "emptyref", ""),
1399 (":emptyscheme", "", "emptyscheme"),
1400 ] {
1401 let redox_path = RedoxPath::from_absolute(path).unwrap();
1402 let parts = redox_path.as_parts();
1403 assert_eq!(
1404 (path, parts),
1405 (
1406 path,
1407 Some((
1408 RedoxScheme::new(scheme).unwrap(),
1409 RedoxReference::new(reference).unwrap()
1410 ))
1411 )
1412 );
1413 }
1414
1415 assert_eq!(RedoxPath::from_absolute("scheme/withslash:path"), None);
1417 assert_eq!(RedoxPath::from_absolute(""), None)
1419 }
1420
1421 #[test]
1422 fn test_matches() {
1423 assert!(RedoxPath::from_absolute("/scheme/foo")
1424 .unwrap()
1425 .matches_scheme("foo"));
1426 assert!(RedoxPath::from_absolute("/scheme/foo/bar")
1427 .unwrap()
1428 .matches_scheme("foo"));
1429 assert!(!RedoxPath::from_absolute("/scheme/foo")
1430 .unwrap()
1431 .matches_scheme("bar"));
1432 assert!(RedoxPath::from_absolute("foo:")
1433 .unwrap()
1434 .matches_scheme("foo"));
1435 assert!(RedoxPath::from_absolute(
1436 canonicalize_using_cwd(Some("/scheme/foo"), "bar").unwrap()
1437 )
1438 .unwrap()
1439 .matches_scheme("foo"));
1440 assert!(
1441 RedoxPath::from_absolute(canonicalize_using_cwd(Some("/foo"), "bar").unwrap())
1442 .unwrap()
1443 .matches_scheme("file")
1444 );
1445 assert!(RedoxPath::from_absolute(
1446 canonicalize_using_cwd(Some("/scheme"), "foo/bar").unwrap()
1447 )
1448 .unwrap()
1449 .matches_scheme("foo"));
1450 assert!(RedoxPath::from_absolute("foo:/bar")
1451 .unwrap()
1452 .matches_scheme("foo"));
1453 assert!(!RedoxPath::from_absolute("foo:/bar")
1454 .unwrap()
1455 .matches_scheme("bar"));
1456 assert!(RedoxPath::from_absolute("/scheme/file")
1457 .unwrap()
1458 .is_default_scheme());
1459 assert!(!RedoxPath::from_absolute("/scheme/foo")
1460 .unwrap()
1461 .is_default_scheme());
1462 assert!(RedoxPath::from_absolute("file:bar")
1463 .unwrap()
1464 .is_default_scheme());
1465 assert!(RedoxPath::from_absolute("file:")
1466 .unwrap()
1467 .is_default_scheme());
1468 assert!(!RedoxPath::from_absolute("foo:bar")
1469 .unwrap()
1470 .is_default_scheme());
1471 assert!(RedoxPath::from_absolute("foo:bar").unwrap().is_legacy());
1472 assert!(!RedoxPath::from_absolute("/foo/bar").unwrap().is_legacy());
1473 }
1474
1475 #[test]
1476 fn test_to_standard() {
1477 assert_eq!(
1478 RedoxPath::from_absolute("foo:bar")
1479 .unwrap()
1480 .to_standard()
1481 .as_ref(),
1482 "/scheme/foo/bar"
1483 );
1484 assert_eq!(
1485 RedoxPath::from_absolute("file:bar")
1486 .unwrap()
1487 .to_standard()
1488 .as_ref(),
1489 "/scheme/file/bar"
1490 );
1491 assert_eq!(
1492 RedoxPath::from_absolute("/scheme/foo/bar")
1493 .unwrap()
1494 .to_standard()
1495 .as_ref(),
1496 "/scheme/foo/bar"
1497 );
1498 assert_eq!(
1499 RedoxPath::from_absolute("/foo/bar")
1500 .unwrap()
1501 .to_standard()
1502 .as_ref(),
1503 "/foo/bar"
1504 );
1505 assert_eq!(
1506 &RedoxPath::from_absolute("foo:bar/../bar2")
1507 .unwrap()
1508 .to_standard_canon()
1509 .to_string(),
1510 "/scheme/foo/bar2"
1511 );
1512 assert_eq!(
1513 &RedoxPath::from_absolute("file:bar/./../bar2")
1514 .unwrap()
1515 .to_standard_canon()
1516 .to_string(),
1517 "/scheme/file/bar2"
1518 );
1519 assert_eq!(
1520 &RedoxPath::from_absolute("/scheme/file/bar/./../../foo/bar")
1521 .unwrap()
1522 .to_standard_canon()
1523 .to_string(),
1524 "/scheme/foo/bar"
1525 );
1526 assert_eq!(
1527 &RedoxPath::from_absolute("/foo/bar")
1528 .unwrap()
1529 .to_standard_canon()
1530 .to_string(),
1531 "/foo/bar"
1532 );
1533 assert_eq!(
1534 &canonicalize_to_standard(None, "/scheme/foo/bar").unwrap(),
1535 "/scheme/foo/bar"
1536 );
1537 assert_eq!(
1538 &canonicalize_to_standard(None, "foo:bar").unwrap(),
1539 "/scheme/foo/bar"
1540 );
1541 assert_eq!(
1542 &canonicalize_to_standard(None, "foo:bar/../..").unwrap(),
1543 "/scheme/foo"
1544 );
1545 assert_eq!(
1546 &canonicalize_to_standard(None, "/scheme/foo/bar/..").unwrap(),
1547 "/scheme/foo"
1548 );
1549 assert_eq!(
1550 &canonicalize_to_standard(None, "foo:bar/bar2/..").unwrap(),
1551 "/scheme/foo/bar"
1552 );
1553 }
1554
1555 #[test]
1556 fn test_scheme_path() {
1557 assert_eq!(
1558 scheme_path("foo"),
1559 Some(RedoxPath::from_absolute("/scheme/foo").unwrap())
1560 );
1561 assert_eq!(
1562 scheme_path(""),
1563 Some(RedoxPath::from_absolute("/scheme").unwrap())
1564 );
1565 assert_eq!(scheme_path("/foo"), None);
1566 assert_eq!(scheme_path("foo/bar"), None);
1567 assert_eq!(scheme_path("foo:"), None);
1568 }
1569
1570 #[test]
1571 fn test_category() {
1572 assert_eq!(
1573 make_scheme_name("foo", "bar"),
1574 Some(RedoxScheme::new("foo.bar").unwrap())
1575 );
1576 assert_eq!(
1577 RedoxPath::from_absolute(
1578 scheme_path(make_scheme_name("foo", "bar").unwrap().as_ref()).unwrap()
1579 )
1580 .unwrap(),
1581 RedoxPath::Standard(RedoxReference::new("/scheme/foo.bar").unwrap())
1582 );
1583 assert_eq!(make_scheme_name("foo", "/bar"), None);
1584 assert_eq!(make_scheme_name("foo", ":bar"), None);
1585 assert!(RedoxPath::from_absolute(
1586 scheme_path(make_scheme_name("foo", "bar").unwrap().as_ref()).unwrap()
1587 )
1588 .unwrap()
1589 .is_scheme_category("foo"));
1590 assert!(RedoxPath::from_absolute("/scheme/foo.bar/bar2")
1591 .unwrap()
1592 .is_scheme_category("foo"));
1593 assert!(!RedoxPath::from_absolute("/scheme/foo/bar")
1594 .unwrap()
1595 .is_scheme_category("foo"));
1596 assert!(!RedoxPath::from_absolute("/foo.bar/bar2")
1597 .unwrap()
1598 .is_scheme_category("foo"));
1599 }
1600}