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
15#[derive(Clone, Debug, Eq, Hash, PartialEq)]
17pub struct RedoxScheme<'a>(Cow<'a, str>);
18
19impl<'a> RedoxScheme<'a> {
20 pub fn new<S: Into<Cow<'a, str>>>(scheme: S) -> Option<Self> {
22 let scheme = scheme.into();
23 if scheme.contains(&['\0', '/', ':']) {
25 return None;
26 }
27 Some(Self(scheme))
28 }
29
30 pub fn canonicalize_as_scheme<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
32 canonicalize_using_scheme_checked(self, path)
33 }
34
35 pub fn into_owned<'b>(self) -> RedoxScheme<'b> {
36 RedoxScheme(self.0.into_owned().into())
37 }
38}
39
40impl<'a> AsRef<str> for RedoxScheme<'a> {
41 fn as_ref(&self) -> &str {
42 self.0.as_ref()
43 }
44}
45
46impl<'a> fmt::Display for RedoxScheme<'a> {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "{}", self.0)
49 }
50}
51
52#[derive(Clone, Debug, Eq, Hash, PartialEq)]
54pub struct RedoxReference<'a>(Cow<'a, str>);
55
56impl<'a> RedoxReference<'a> {
57 pub fn new<S: Into<Cow<'a, str>>>(reference: S) -> Option<Self> {
61 let reference = reference.into();
62 if reference.as_bytes().iter().any(|&b| b == b'\0') {
66 return None;
67 }
68 Some(Self(reference))
69 }
70
71 pub fn new_from_c<S: Into<&'a CStr>>(reference: S) -> Option<Self> {
73 let reference = reference.into();
74 Some(Self(Cow::Borrowed(reference.to_str().ok()?)))
75 }
76
77 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxReference<'b>> {
82 let path = path.into();
83 Some(self.join_checked(RedoxReference::new(path)?))
84 }
85
86 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxReference<'b> {
88 if !path.is_relative() {
89 path
91 } else if path.0.is_empty() {
92 RedoxReference(self.0.to_string().into())
94 } else {
95 let mut reference = self.0.clone().into_owned();
97 if !reference.is_empty() && !reference.ends_with('/') {
98 reference.push('/');
99 }
100 reference.push_str(&path.0);
101 RedoxReference(reference.into())
102 }
103 }
104
105 pub fn canonical(self) -> Self {
111 let mut canonicalized = true;
112 let mut is_absolute = false;
113 let mut parts = [""; 4096]; let mut parts_len = 0;
115 for (i, part) in self.0.split('/').enumerate() {
116 if part.is_empty() {
117 if i == 0 {
118 is_absolute = true;
119 } else {
120 canonicalized = false;
121 }
122 } else if part == "." {
123 canonicalized = false;
124 } else if part == ".." {
125 if parts_len > 0 {
126 parts_len -= 1;
127 }
128 canonicalized = false;
129 } else {
130 if parts_len >= 4096 {
131 break;
132 }
133 parts[parts_len] = part;
134 parts_len += 1;
135 }
136 }
137
138 if canonicalized {
139 return self;
140 }
141 let mut string = String::with_capacity(self.0.len());
142 if is_absolute {
143 string.push('/');
144 }
145 for (i, part) in parts[..parts_len].iter().enumerate() {
146 if i > 0 {
147 string.push('/');
148 }
149 string.push_str(part);
150 }
151
152 Self(Cow::Owned(string))
154 }
155
156 pub fn canonical_max_upward(self, max_upward: usize) -> Option<(Self, usize)> {
163 let mut canonicalized = true;
164 let mut parts = [""; 4096]; let mut parts_len = 0;
166 let mut upward_depth = 0;
167 for (i, part) in self.0.split('/').enumerate() {
168 if part.is_empty() {
169 if i == 0 {
170 return None;
172 }
173 canonicalized = false;
174 } else if part == "." {
175 canonicalized = false;
176 } else if part == ".." {
177 if parts_len == 0 {
178 upward_depth += 1;
179 if upward_depth > max_upward {
180 return None;
182 }
183 } else {
184 parts_len -= 1;
185 }
186
187 canonicalized = false;
188 } else {
189 if parts_len + upward_depth >= 4096 {
190 break;
191 }
192 parts[parts_len] = part;
193 parts_len += 1;
194 }
195 }
196
197 if canonicalized {
198 return Some((self, max_upward + parts_len));
199 }
200 let mut string = String::with_capacity(self.0.len());
201 for _ in 0..upward_depth {
202 if !string.is_empty() {
203 string.push('/');
204 }
205 string.push_str("..");
206 }
207 for part in parts[..parts_len].iter() {
208 if !string.is_empty() {
209 string.push('/');
210 }
211 string.push_str(part);
212 }
213
214 Some((Self(string.into()), max_upward + parts_len - upward_depth))
216 }
217
218 pub fn to_relative(self) -> Self {
222 if self.is_relative() {
223 return self;
224 }
225 match self.0 {
226 Cow::Borrowed(s) => Self(s[1..].into()),
227 Cow::Owned(s) => Self(s[1..].to_string().into()),
228 }
229 }
230
231 pub fn is_canon(&self) -> bool {
233 (self.0.is_empty() || self.0 == "/")
234 || (!self.is_relative()
235 && self
236 .0
237 .split('/')
238 .skip(1)
239 .all(|seg| seg != ".." && seg != "." && seg != ""))
240 }
241
242 pub fn dirname<'b>(&self) -> RedoxReference<'b> {
244 self.join_checked(RedoxReference("..".into())).canonical()
246 }
247
248 pub fn is_relative(&self) -> bool {
250 !self.0.starts_with('/')
251 }
252
253 pub fn into_owned<'b>(self) -> RedoxReference<'b> {
255 RedoxReference(self.0.into_owned().into())
256 }
257}
258
259impl<'a> AsRef<str> for RedoxReference<'a> {
260 fn as_ref(&self) -> &str {
261 self.0.as_ref()
262 }
263}
264
265impl<'a> fmt::Display for RedoxReference<'a> {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267 write!(f, "{}", self.0)
268 }
269}
270
271impl<'a> From<RedoxReference<'a>> for Cow<'a, str> {
272 fn from(value: RedoxReference<'a>) -> Self {
273 value.0
274 }
275}
276
277#[derive(Clone, Debug, Eq, Hash, PartialEq)]
279pub enum RedoxPath<'a> {
280 Standard(RedoxReference<'a>),
282 Legacy(RedoxScheme<'a>, RedoxReference<'a>),
284}
285
286impl<'a> RedoxPath<'a> {
287 pub fn from_absolute<S: Into<Cow<'a, str>>>(path: S) -> Option<Self> {
291 Self::from_absolute_checked(path).and_then(|s| s.abs())
292 }
293
294 pub fn from_absolute_c<S: Into<&'a CStr>>(path: S) -> Option<Self> {
296 Self::from_absolute_c_checked(path).and_then(|s| s.abs())
297 }
298
299 pub fn from_reference(path: RedoxReference<'a>) -> Option<Self> {
301 RedoxStr::from(path).abs()
302 }
303
304 fn from_absolute_c_checked<S: Into<&'a CStr>>(path: S) -> Option<RedoxStr<'a>> {
306 let path = path.into();
307 if matches!(path.to_bytes_with_nul().first(), Some(b'/')) {
308 Some(RedoxStr::Absolute(Self::Standard(
310 RedoxReference::new_from_c(path)?,
311 )))
312 } else {
313 let s = path.to_str().ok()?;
315 Self::from_absolute_checked(s)
317 }
318 }
319
320 fn from_absolute_checked<S: Into<Cow<'a, str>>>(path: S) -> Option<RedoxStr<'a>> {
322 let path = path.into();
323 let r = if path.starts_with('/') {
324 Self::Standard(RedoxReference::new(path)?)
326 } else {
327 let mut parts = path.as_ref().splitn(2, ':');
328 let (first, second) = match (parts.next().and_then(RedoxScheme::new), parts.next()) {
329 (Some(a), Some(b)) => (a, b),
330 _ => return Some(RedoxStr::Relative(RedoxReference::new(path)?)),
332 };
333 let scheme = first.into_owned();
335 let reference = RedoxReference::new(second.to_string())?;
336 Self::Legacy(scheme, reference)
337 };
338 Some(RedoxStr::Absolute(r))
339 }
340
341 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxPath<'b>> {
346 let path = path.into();
347 Some(self.join_checked(RedoxReference::new(path)?))
348 }
349
350 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxPath<'b> {
352 if !path.is_relative() {
353 RedoxPath::Standard(path)
355 } else {
356 match self {
357 Self::Standard(reference) => RedoxPath::Standard(reference.join_checked(path)),
358 Self::Legacy(scheme, reference) => RedoxPath::Legacy(
359 RedoxScheme(scheme.0.to_string().into()),
360 reference.join_checked(path),
361 ),
362 }
363 }
364 }
365
366 pub fn canonical(self) -> Self {
368 match self {
369 Self::Standard(reference) => Self::Standard(reference.canonical()),
370 Self::Legacy(scheme, reference) => {
371 Self::Legacy(scheme, reference)
374 }
375 }
376 }
377
378 pub fn is_canon(&self) -> bool {
384 match self {
385 Self::Standard(reference) => reference.is_canon(),
386 Self::Legacy(_scheme, _reference) => true,
387 }
388 }
389
390 pub fn as_parts(&'a self) -> Option<(RedoxScheme<'a>, RedoxReference<'a>)> {
395 let (scheme, path) = self.get_scheme_prefixed(true)?;
396 Some((scheme, RedoxReference(path.into())))
397 }
398
399 pub fn get_scheme(&'a self) -> Option<RedoxScheme<'a>> {
402 Some(self.get_scheme_prefixed(false)?.0)
403 }
404
405 fn get_scheme_prefixed(&'a self, fallback: bool) -> Option<(RedoxScheme<'a>, &'a str)> {
406 const SCHEME_PREFIX_LENGTH: usize = "/scheme/".len();
407 match self {
408 Self::Standard(reference) => {
409 let mut parts = reference.0.split('/');
410 if parts.next() == Some("") && parts.next() == Some("scheme") {
411 match parts.next() {
412 Some(scheme_name) => {
413 let scheme_length = SCHEME_PREFIX_LENGTH + scheme_name.len() + 1;
415 let remainder = reference.0.get(scheme_length..).unwrap_or("");
416
417 return Some((RedoxScheme::new(scheme_name)?, remainder));
418 }
419 None => {
420 return Some((RedoxScheme(Cow::from("")), ""));
422 }
423 }
424 }
425 if !fallback {
426 return None;
427 }
428 let remainder = reference.0.get(1..).unwrap_or("");
430 return Some((RedoxScheme(Cow::from(DEFAULT_SCHEME)), remainder));
431 }
432 Self::Legacy(scheme, reference) => {
433 Some((scheme.clone(), reference.as_ref()))
435 }
436 }
437 }
438
439 pub fn matches_scheme(&self, other: &str) -> bool {
441 if let Some((scheme, _)) = self.get_scheme_prefixed(true) {
442 scheme.0 == other
443 } else {
444 false
445 }
446 }
447
448 pub fn is_scheme_category(&self, category: &str) -> bool {
450 if let Some((scheme, _)) = self.get_scheme_prefixed(true) {
451 let mut parts = scheme.0.splitn(2, '.');
452 if let Some(cat) = parts.next() {
453 cat == category && parts.next().is_some()
454 } else {
455 false
456 }
457 } else {
458 false
459 }
460 }
461
462 pub fn is_default_scheme(&self) -> bool {
464 self.matches_scheme(DEFAULT_SCHEME)
465 }
466
467 pub fn is_legacy(&self) -> bool {
469 matches!(self, RedoxPath::Legacy(_, _))
470 }
471
472 pub fn to_standard(self) -> Cow<'a, str> {
474 self.to_standard_checked().0
475 }
476
477 fn to_standard_checked(self) -> RedoxReference<'a> {
478 match self {
479 RedoxPath::Standard(reference) => reference,
480 RedoxPath::Legacy(scheme, reference) => {
481 RedoxReference(format!("/scheme/{}/{}", scheme.0, reference.0).into())
482 }
483 }
484 }
485
486 pub fn to_standard_canon(self) -> RedoxPath<'a> {
489 match self {
490 RedoxPath::Standard(reference) => RedoxPath::Standard(reference.canonical()),
491 RedoxPath::Legacy(scheme, reference) => {
492 canonicalize_using_scheme_checked(&scheme, RedoxStr::Relative(reference))
493 }
494 }
495 }
496
497 pub fn dirname<'b>(&self) -> RedoxPath<'b> {
499 match self {
500 RedoxPath::Standard(redox_reference) => RedoxPath::Standard(redox_reference.dirname()),
501 RedoxPath::Legacy(redox_scheme, redox_reference) => RedoxPath::Legacy(
502 RedoxScheme(redox_scheme.to_string().into()),
503 redox_reference.dirname(),
504 ),
505 }
506 }
507
508 pub fn to_reference(self) -> RedoxReference<'a> {
510 match self {
511 RedoxPath::Standard(redox_reference) => redox_reference,
512 RedoxPath::Legacy(_, redox_reference) => redox_reference,
513 }
514 }
515
516 pub fn as_reference(&self) -> &RedoxReference<'a> {
518 match self {
519 RedoxPath::Standard(redox_reference) => redox_reference,
520 RedoxPath::Legacy(_, redox_reference) => redox_reference,
521 }
522 }
523
524 pub fn canonicalize_as_cwd<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
526 canonicalize_using_cwd_checked(Some(self), path, false).unwrap()
528 }
529
530 pub fn canonicalize_as_cwd_standard<'b>(&self, path: RedoxStr<'b>) -> RedoxPath<'b> {
532 canonicalize_to_standard_checked(Some(self), path).unwrap()
534 }
535
536 pub fn canonicalize_as_cwd_with_max_upward<'b>(
538 &self,
539 relpath: RedoxReference<'b>,
540 max_upward: usize,
541 ) -> Option<(RedoxPath<'b>, usize)> {
542 let (relpath, max_upward) = relpath.canonical_max_upward(max_upward)?;
543 let newpath = self.join_checked(relpath);
544 Some((newpath, max_upward))
545 }
546
547 pub fn into_owned<'b>(self) -> RedoxPath<'b> {
549 match self {
550 RedoxPath::Standard(redox_reference) => {
551 RedoxPath::Standard(redox_reference.into_owned())
552 }
553 RedoxPath::Legacy(redox_scheme, redox_reference) => {
554 RedoxPath::Legacy(redox_scheme.into_owned(), redox_reference.into_owned())
555 }
556 }
557 }
558}
559
560impl<'a> fmt::Display for RedoxPath<'a> {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 match self {
563 RedoxPath::Standard(reference) => {
564 write!(f, "{}", reference.0)
565 }
566 RedoxPath::Legacy(scheme, reference) => {
567 write!(f, "{}:{}", scheme.0, reference.0)
568 }
569 }
570 }
571}
572
573impl<'a> From<RedoxPath<'a>> for Cow<'a, str> {
574 fn from(value: RedoxPath<'a>) -> Self {
575 match value {
576 RedoxPath::Standard(redox_reference) => redox_reference.0,
577 a => Cow::Owned(a.to_string()),
578 }
579 }
580}
581
582#[derive(Clone, Debug, Eq, Hash, PartialEq)]
584pub enum RedoxStr<'a> {
585 Absolute(RedoxPath<'a>),
586 Relative(RedoxReference<'a>),
587}
588
589impl<'a> RedoxStr<'a> {
590 pub fn new<S: Into<Cow<'a, str>>>(path: S) -> Option<RedoxStr<'a>> {
592 RedoxPath::from_absolute_checked(path)
593 }
594 pub fn new_c<S: Into<&'a CStr>>(path: S) -> Option<RedoxStr<'a>> {
596 RedoxPath::from_absolute_c_checked(path)
597 }
598 pub fn abs(self) -> Option<RedoxPath<'a>> {
600 match self {
601 RedoxStr::Absolute(redox_path) => Some(redox_path),
602 _ => None,
603 }
604 }
605 pub fn rel(self) -> Option<RedoxReference<'a>> {
607 match self {
608 RedoxStr::Relative(redox_reference) => Some(redox_reference),
609 _ => None,
610 }
611 }
612 pub fn is_canon(&self) -> bool {
614 match self {
615 RedoxStr::Absolute(redox_path) => redox_path.is_canon(),
616 RedoxStr::Relative(redox_reference) => redox_reference.is_canon(),
617 }
618 }
619 pub fn canonical(self) -> Self {
621 match self {
622 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.canonical()),
623 RedoxStr::Relative(redox_reference) => RedoxStr::from(redox_reference.canonical()),
624 }
625 }
626 pub fn dirname<'b>(&self) -> RedoxStr<'b> {
628 match self {
629 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.dirname()),
630 RedoxStr::Relative(redox_reference) => RedoxStr::from(redox_reference.dirname()),
631 }
632 }
633 pub fn is_empty(&self) -> bool {
635 match self {
636 RedoxStr::Absolute(_) => false, RedoxStr::Relative(redox_reference) => redox_reference.as_ref().is_empty(),
638 }
639 }
640 pub fn join<'b, S: Into<Cow<'b, str>>>(&self, path: S) -> Option<RedoxStr<'b>> {
642 match self {
643 RedoxStr::Absolute(redox_path) => Some(RedoxStr::from(redox_path.join(path)?)),
644 RedoxStr::Relative(redox_reference) => {
645 Some(RedoxStr::from(redox_reference.join(path)?))
646 }
647 }
648 }
649 pub fn join_checked<'b>(&self, path: RedoxReference<'b>) -> RedoxStr<'b> {
651 match self {
652 RedoxStr::Absolute(redox_path) => RedoxStr::from(redox_path.join_checked(path)),
653 RedoxStr::Relative(redox_reference) => {
654 RedoxStr::from(redox_reference.join_checked(path))
655 }
656 }
657 }
658 pub fn is_relative(&self) -> bool {
660 matches!(self, RedoxStr::Relative(_))
662 }
663
664 pub fn into_owned<'b>(self) -> RedoxStr<'b> {
666 match self {
667 RedoxStr::Absolute(redox_path) => RedoxStr::Absolute(redox_path.into_owned()),
668 RedoxStr::Relative(redox_reference) => RedoxStr::Relative(redox_reference.into_owned()),
669 }
670 }
671}
672
673impl<'a> From<RedoxPath<'a>> for RedoxStr<'a> {
674 fn from(value: RedoxPath<'a>) -> Self {
675 Self::Absolute(value)
676 }
677}
678
679impl<'a> From<RedoxReference<'a>> for RedoxStr<'a> {
680 fn from(value: RedoxReference<'a>) -> Self {
681 if !value.is_relative() {
683 RedoxStr::Absolute(RedoxPath::Standard(value))
684 } else {
685 RedoxStr::Relative(value)
686 }
687 }
688}
689impl<'a> From<RedoxStr<'a>> for Cow<'a, str> {
690 fn from(value: RedoxStr<'a>) -> Self {
691 match value {
692 RedoxStr::Absolute(redox_path) => redox_path.into(),
693 RedoxStr::Relative(redox_reference) => redox_reference.into(),
694 }
695 }
696}
697
698pub fn canonicalize_using_cwd<'a>(cwd_opt: Option<&'a str>, path: &'a str) -> Option<Cow<'a, str>> {
712 let cwd_opt = cwd_opt.and_then(|s| RedoxPath::from_absolute(Cow::Borrowed(s)));
713 let absolute = canonicalize_using_cwd_checked(
714 cwd_opt.as_ref(),
715 RedoxPath::from_absolute_checked(path)?,
716 false,
717 )?;
718 Some(absolute.into())
719}
720
721fn canonicalize_using_cwd_checked<'a, 'b>(
725 cwd_opt: Option<&RedoxPath<'a>>,
726 path: RedoxStr<'b>,
727 in_scheme_root: bool,
728) -> Option<RedoxPath<'b>> {
729 let absolute = match (path, in_scheme_root) {
730 (RedoxStr::Absolute(absolute), true) if absolute.get_scheme().is_some() => absolute,
731 (RedoxStr::Absolute(absolute), true) => {
732 cwd_opt?.join_checked(absolute.to_standard_checked().to_relative())
733 }
734 (RedoxStr::Absolute(absolute), false) => absolute,
735 (RedoxStr::Relative(relative), true) => cwd_opt?.join_checked(relative.canonical()),
736 (RedoxStr::Relative(relative), false) => cwd_opt?.join_checked(relative),
737 };
738 Some(absolute.canonical())
739}
740
741pub fn canonicalize_to_standard<'a>(
744 cwd_opt: Option<&'a str>,
745 path: &'a str,
746) -> Option<Cow<'a, str>> {
747 let absolute = match RedoxPath::from_absolute(path) {
748 Some(absolute) => absolute,
749 None => {
750 let absolute = RedoxPath::from_absolute(cwd_opt?)?;
751 absolute.join(path)?
752 }
753 };
754 Some(absolute.to_standard_canon().into())
755}
756
757fn canonicalize_to_standard_checked<'a, 'b>(
759 cwd_opt: Option<&RedoxPath<'a>>,
760 path: RedoxStr<'b>,
761) -> Option<RedoxPath<'b>> {
762 let path = match path {
763 RedoxStr::Absolute(absolute) => absolute,
764 RedoxStr::Relative(relative) => cwd_opt?.join_checked(relative),
765 }
766 .to_standard_canon();
767 Some(path)
768}
769
770pub fn canonicalize_using_scheme<'a>(scheme: &'a str, path: &'a str) -> Option<Cow<'a, str>> {
777 let path = RedoxPath::from_absolute_checked(path)?;
778 Some(canonicalize_using_scheme_checked(&RedoxScheme::new(scheme)?, path).into())
779}
780
781fn canonicalize_using_scheme_checked<'a, 'b>(
783 scheme: &RedoxScheme<'_>,
784 path: RedoxStr<'b>,
785) -> RedoxPath<'b> {
786 let path = match path {
787 RedoxStr::Absolute(s) => {
788 if !s.is_legacy() && s.matches_scheme(&scheme.0) {
789 return s;
792 }
793 RedoxStr::Absolute(s)
794 }
795 path => path,
796 };
797 canonicalize_using_cwd_checked(Some(&scheme_path_checked(&scheme)), path, true).unwrap()
799}
800
801pub fn canonicalize_using_cwd_with_max_upward<'a>(
805 cwd: &str,
806 relpath: &'a str,
807 max_upward: usize,
808) -> Option<(Cow<'a, str>, usize)> {
809 let cwd = RedoxStr::new(Cow::Borrowed(cwd))?;
811 let (absolute, max_upward) = canonicalize_using_cwd_with_max_upward_checked(
812 &cwd,
813 RedoxStr::new(relpath)?.rel()?,
814 max_upward,
815 )?;
816 Some((absolute.into(), max_upward))
817}
818
819fn canonicalize_using_cwd_with_max_upward_checked<'a, 'b>(
821 cwd: &RedoxStr<'a>,
822 relpath: RedoxReference<'b>,
823 max_upward: usize,
824) -> Option<(RedoxStr<'b>, usize)> {
825 let (relpath, max_upward) = relpath.canonical_max_upward(max_upward)?;
826 let relnew = cwd.join_checked(relpath);
827 Some((relnew, max_upward))
828}
829
830pub fn scheme_path(name: &str) -> Option<RedoxPath<'_>> {
834 let scheme = RedoxScheme::new(name)?;
835 Some(scheme_path_checked(&scheme))
836}
837
838fn scheme_path_checked<'a, 'b>(scheme: &'a RedoxScheme<'a>) -> RedoxPath<'b> {
840 if scheme.0.is_empty() {
841 return RedoxPath::Standard(RedoxReference("/scheme".into()));
842 }
843 RedoxPath::Standard(RedoxReference(format!("/scheme/{}", scheme.0).into()))
844}
845
846pub fn make_scheme_name<'a, 'b>(category: &'a str, detail: &'a str) -> Option<RedoxScheme<'b>> {
850 let name = format!("{}.{}", category, detail);
851 RedoxScheme::new(name)
852}
853
854#[cfg(test)]
855mod tests {
856 use super::*;
857 use alloc::format;
858
859 #[test]
861 fn test_absolute() {
862 let cwd_opt = None;
863 assert_eq!(
864 canonicalize_using_cwd(cwd_opt, "/"),
865 Some(Cow::Borrowed("/"))
866 );
867 assert_eq!(
868 canonicalize_using_cwd(cwd_opt, "/file"),
869 Some(Cow::Borrowed("/file"))
870 );
871 assert_eq!(
872 canonicalize_using_cwd(cwd_opt, "/folder/file"),
873 Some(Cow::Borrowed("/folder/file"))
874 );
875 assert_eq!(
876 canonicalize_using_cwd(cwd_opt, "/folder/file/"),
877 Some(Cow::Borrowed("/folder/file"))
878 );
879 assert_eq!(
880 canonicalize_using_cwd(cwd_opt, "/folder/file//"),
881 Some(Cow::Borrowed("/folder/file"))
882 );
883 assert_eq!(
884 canonicalize_using_cwd(cwd_opt, "/folder/../file"),
885 Some(Cow::Borrowed("/file"))
886 );
887 assert_eq!(
888 canonicalize_using_cwd(cwd_opt, "/folder/../.."),
889 Some(Cow::Borrowed("/"))
890 );
891 assert_eq!(
892 canonicalize_using_cwd(cwd_opt, "/folder/../../../.."),
893 Some(Cow::Borrowed("/"))
894 );
895 assert_eq!(
896 canonicalize_using_cwd(cwd_opt, "/.."),
897 Some(Cow::Borrowed("/"))
898 );
899 }
900
901 #[test]
903 fn test_new_relative() {
904 let cwd_opt = Some("/scheme/foo");
905 assert_eq!(
906 canonicalize_using_cwd(cwd_opt, "file"),
907 Some(Cow::Borrowed("/scheme/foo/file"))
908 );
909 assert_eq!(
910 canonicalize_using_cwd(cwd_opt, "folder/file"),
911 Some(Cow::Borrowed("/scheme/foo/folder/file"))
912 );
913 assert_eq!(
914 canonicalize_using_cwd(cwd_opt, "folder/../file"),
915 Some(Cow::Borrowed("/scheme/foo/file"))
916 );
917 assert_eq!(
918 canonicalize_using_cwd(cwd_opt, "folder/../.."),
919 Some(Cow::Borrowed("/scheme"))
920 );
921 assert_eq!(
922 canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
923 Some(Cow::Borrowed("/"))
924 );
925 assert_eq!(
926 canonicalize_using_cwd(cwd_opt, ".."),
927 Some(Cow::Borrowed("/scheme"))
928 );
929 }
930
931 #[test]
933 fn test_new_scheme() {
934 let cwd_opt = None;
935 assert_eq!(
936 canonicalize_using_cwd(cwd_opt, "/scheme/bar/"),
937 Some(Cow::Borrowed("/scheme/bar"))
938 );
939 assert_eq!(
940 canonicalize_using_cwd(cwd_opt, "/scheme/bar/file"),
941 Some(Cow::Borrowed("/scheme/bar/file"))
942 );
943 assert_eq!(
944 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/file"),
945 Some(Cow::Borrowed("/scheme/bar/folder/file"))
946 );
947 assert_eq!(
948 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../file"),
949 Some(Cow::Borrowed("/scheme/bar/file"))
950 );
951 assert_eq!(
952 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../.."),
953 Some(Cow::Borrowed("/scheme"))
954 );
955 assert_eq!(
956 canonicalize_using_cwd(cwd_opt, "/scheme/bar/folder/../../../.."),
957 Some(Cow::Borrowed("/"))
958 );
959 assert_eq!(
960 canonicalize_using_cwd(cwd_opt, "/scheme/bar/.."),
961 Some(Cow::Borrowed("/scheme"))
962 );
963
964 assert_eq!(
965 canonicalize_using_scheme("bar", ""),
966 Some(Cow::Borrowed("/scheme/bar"))
967 );
968 assert_eq!(
969 canonicalize_using_scheme("bar", "foo"),
970 Some(Cow::Borrowed("/scheme/bar/foo"))
971 );
972 assert_eq!(
973 canonicalize_using_scheme("foo", "/bar"),
974 Some(Cow::Borrowed("/scheme/foo/bar"))
975 );
976 assert_eq!(
977 canonicalize_using_scheme("bar", ".."),
978 Some(Cow::Borrowed("/scheme/bar"))
979 );
980 assert_eq!(
981 canonicalize_using_scheme("file", "/scheme/foo/bar"),
982 Some(Cow::Borrowed("/scheme/foo/bar"))
983 );
984 }
985
986 #[test]
988 fn test_old_relative() {
989 let cwd_opt = Some("foo:");
990 assert_eq!(
991 canonicalize_using_cwd(cwd_opt, "file"),
992 Some(Cow::Borrowed("foo:file"))
993 );
994 assert_eq!(
995 canonicalize_using_cwd(cwd_opt, "folder/file"),
996 Some(Cow::Borrowed("foo:folder/file"))
997 );
998 assert_eq!(
999 canonicalize_using_cwd(cwd_opt, "folder/../file"),
1000 Some(Cow::Borrowed("foo:folder/../file"))
1001 );
1002 assert_eq!(
1003 canonicalize_using_cwd(cwd_opt, "folder/../.."),
1004 Some(Cow::Borrowed("foo:folder/../.."))
1005 );
1006 assert_eq!(
1007 canonicalize_using_cwd(cwd_opt, "folder/../../../.."),
1008 Some(Cow::Borrowed("foo:folder/../../../.."))
1009 );
1010 assert_eq!(
1011 canonicalize_using_cwd(cwd_opt, ".."),
1012 Some(Cow::Borrowed("foo:.."))
1013 );
1014 }
1015
1016 #[test]
1018 fn test_old_scheme() {
1019 let cwd_opt = None;
1020 assert_eq!(
1021 canonicalize_using_cwd(cwd_opt, "bar:"),
1022 Some(Cow::Borrowed("bar:"))
1023 );
1024 assert_eq!(
1025 canonicalize_using_cwd(cwd_opt, "bar:file"),
1026 Some(Cow::Borrowed("bar:file"))
1027 );
1028 assert_eq!(
1029 canonicalize_using_cwd(cwd_opt, "bar:folder/file"),
1030 Some(Cow::Borrowed("bar:folder/file"))
1031 );
1032 assert_eq!(
1033 canonicalize_using_cwd(cwd_opt, "bar:folder/../file"),
1034 Some(Cow::Borrowed("bar:folder/../file"))
1035 );
1036 assert_eq!(
1037 canonicalize_using_cwd(cwd_opt, "bar:folder/../.."),
1038 Some(Cow::Borrowed("bar:folder/../.."))
1039 );
1040 assert_eq!(
1041 canonicalize_using_cwd(cwd_opt, "bar:folder/../../../.."),
1042 Some(Cow::Borrowed("bar:folder/../../../.."))
1043 );
1044 assert_eq!(
1045 canonicalize_using_cwd(cwd_opt, "bar:.."),
1046 Some(Cow::Borrowed("bar:.."))
1047 );
1048 }
1049
1050 #[test]
1052 fn test_max_upward() {
1053 assert_eq!(
1055 canonicalize_using_cwd_with_max_upward("/", "foo/bar", 0),
1056 Some((Cow::Borrowed("/foo/bar"), 2))
1057 );
1058 assert_eq!(
1059 canonicalize_using_cwd_with_max_upward("/foo/", "bar/../baz/qux", 0),
1060 Some((Cow::Borrowed("/foo/baz/qux"), 2))
1061 );
1062 assert_eq!(
1063 canonicalize_using_cwd_with_max_upward("foo/../bar", "baz/./../qux", 0),
1064 Some((Cow::Borrowed("foo/../bar/qux"), 1))
1065 );
1066 assert_eq!(
1067 canonicalize_using_cwd_with_max_upward("/foo", "../bar", 1),
1068 Some((Cow::Borrowed("/foo/../bar"), 1))
1069 );
1070 assert_eq!(
1071 canonicalize_using_cwd_with_max_upward("foo/..", "../bar/..", 2),
1072 Some((Cow::Borrowed("foo/../.."), 1))
1073 );
1074 assert_eq!(
1076 canonicalize_using_cwd_with_max_upward("foo:", "bar/baz", 0),
1077 Some((Cow::Borrowed("foo:bar/baz"), 2))
1078 );
1079 assert_eq!(
1080 canonicalize_using_cwd_with_max_upward("foo:", "bar/baz//../../", 0),
1081 Some((Cow::Borrowed("foo:"), 0))
1082 );
1083 assert_eq!(
1084 canonicalize_using_cwd_with_max_upward("foo:/bar/..", "baz/./qux/..", 0),
1085 Some((Cow::Borrowed("foo:/bar/../baz"), 1))
1086 );
1087 assert_eq!(
1089 canonicalize_using_cwd_with_max_upward("/foo", "../bar", 0),
1090 None
1091 );
1092 assert_eq!(
1093 canonicalize_using_cwd_with_max_upward("/foo", "/bar", 0),
1094 None
1095 );
1096 assert_eq!(
1097 canonicalize_using_cwd_with_max_upward("/foo/..", "../bar/..", 0),
1098 None
1099 );
1100 }
1101 #[test]
1103 fn test_cow_borrowing() {
1104 let cwd_opt = None;
1105 for (path, should_owned) in [
1106 ("/foo/bar/baz", false),
1107 ("/scheme/foo/bar/baz", false),
1108 ("foo/bar/baz", false), ("/scheme/foo/../baz", true),
1110 ("foo:bar", true),
1111 ] {
1112 assert_eq!(
1113 matches!(canonicalize_using_cwd(cwd_opt, path), Some(Cow::Owned(_))),
1114 should_owned
1115 );
1116 }
1117 }
1118 #[test]
1120 fn test_dirname() {
1121 for (a, b) in [
1122 ("/foo/bar/baz", "/foo/bar"),
1123 ("/baz/foo/bar/", "/baz/foo"),
1124 ("/", "/"),
1125 ("/scheme/foo", "/scheme"),
1126 ("foo:bar/baz/", "foo:bar"),
1127 ("foo:bar", "foo:"),
1128 ("foo:", "foo:"),
1129 ] {
1130 assert_eq!(
1131 RedoxPath::from_absolute(a).unwrap().dirname(),
1132 RedoxPath::from_absolute(b).unwrap()
1133 );
1134 }
1135 }
1136
1137 #[test]
1139 fn test_orbital_scheme() {
1140 for flag_str in &["", "abflrtu"] {
1141 for x in &[-1, 0, 1] {
1142 for y in &[-1, 0, 1] {
1143 for w in &[0, 1] {
1144 for h in &[0, 1] {
1145 for title in &[
1146 "",
1147 "title",
1148 "title/with/slashes",
1149 "title:with:colons",
1150 "title/../with/../dots/..",
1151 ] {
1152 let path = format!(
1153 "orbital:{}/{}/{}/{}/{}/{}",
1154 flag_str, x, y, w, h, title
1155 );
1156 assert_eq!(
1157 canonicalize_using_cwd(None, &path),
1158 Some((&path).into())
1159 );
1160 }
1161 }
1162 }
1163 }
1164 }
1165 }
1166 }
1167
1168 #[test]
1170 fn test_parts() {
1171 for (path, scheme, reference) in [
1172 ("/foo/bar/baz", "file", "foo/bar/baz"),
1173 ("/scheme/foo/bar/baz", "foo", "bar/baz"),
1174 ("/", "file", ""),
1175 ("/bar", "file", "bar"),
1176 ("/...", "file", "..."),
1177 ("//double/slash", "file", "/double/slash"),
1178 ("/ending/in/slash/", "file", "ending/in/slash/"),
1179 ("/contains/dot/.", "file", "contains/dot/."),
1180 ("/contains/dotdot/..", "file", "contains/dotdot/.."),
1181 ] {
1182 let redox_path = RedoxPath::from_absolute(path).unwrap();
1183 let parts = redox_path.as_parts();
1184 assert_eq!(
1185 (path, parts),
1186 (
1187 path,
1188 Some((
1189 RedoxScheme::new(scheme).unwrap(),
1190 RedoxReference::new(reference).unwrap()
1191 ))
1192 )
1193 );
1194 if path.starts_with("/scheme") {
1195 let to_string = format!("/scheme/{scheme}");
1196 let joined_path = RedoxPath::from_absolute(&to_string)
1197 .unwrap()
1198 .join(reference)
1199 .unwrap();
1200 assert_eq!(path, &format!("{joined_path}"));
1201 } else {
1202 assert_eq!(path, &format!("/{reference}"));
1203 }
1204 }
1205
1206 assert_eq!(RedoxPath::from_absolute("not/absolute"), None);
1208
1209 for path in ["/scheme/use:colon/"] {
1211 let redox_path = RedoxPath::from_absolute(path).unwrap();
1212 let parts = redox_path.as_parts();
1213 assert_eq!((path, parts), (path, None));
1214 }
1215 }
1216
1217 #[test]
1218 fn test_old_scheme_parts() {
1219 for (path, scheme, reference) in [
1220 ("foo:bar/baz", "foo", "bar/baz"),
1221 ("emptyref:", "emptyref", ""),
1222 (":emptyscheme", "", "emptyscheme"),
1223 ] {
1224 let redox_path = RedoxPath::from_absolute(path).unwrap();
1225 let parts = redox_path.as_parts();
1226 assert_eq!(
1227 (path, parts),
1228 (
1229 path,
1230 Some((
1231 RedoxScheme::new(scheme).unwrap(),
1232 RedoxReference::new(reference).unwrap()
1233 ))
1234 )
1235 );
1236 }
1237
1238 assert_eq!(RedoxPath::from_absolute("scheme/withslash:path"), None);
1240 assert_eq!(RedoxPath::from_absolute(""), None)
1242 }
1243
1244 #[test]
1245 fn test_matches() {
1246 assert!(RedoxPath::from_absolute("/scheme/foo")
1247 .unwrap()
1248 .matches_scheme("foo"));
1249 assert!(RedoxPath::from_absolute("/scheme/foo/bar")
1250 .unwrap()
1251 .matches_scheme("foo"));
1252 assert!(!RedoxPath::from_absolute("/scheme/foo")
1253 .unwrap()
1254 .matches_scheme("bar"));
1255 assert!(RedoxPath::from_absolute("foo:")
1256 .unwrap()
1257 .matches_scheme("foo"));
1258 assert!(RedoxPath::from_absolute(
1259 canonicalize_using_cwd(Some("/scheme/foo"), "bar").unwrap()
1260 )
1261 .unwrap()
1262 .matches_scheme("foo"));
1263 assert!(
1264 RedoxPath::from_absolute(canonicalize_using_cwd(Some("/foo"), "bar").unwrap())
1265 .unwrap()
1266 .matches_scheme("file")
1267 );
1268 assert!(RedoxPath::from_absolute(
1269 canonicalize_using_cwd(Some("/scheme"), "foo/bar").unwrap()
1270 )
1271 .unwrap()
1272 .matches_scheme("foo"));
1273 assert!(RedoxPath::from_absolute("foo:/bar")
1274 .unwrap()
1275 .matches_scheme("foo"));
1276 assert!(!RedoxPath::from_absolute("foo:/bar")
1277 .unwrap()
1278 .matches_scheme("bar"));
1279 assert!(RedoxPath::from_absolute("/scheme/file")
1280 .unwrap()
1281 .is_default_scheme());
1282 assert!(!RedoxPath::from_absolute("/scheme/foo")
1283 .unwrap()
1284 .is_default_scheme());
1285 assert!(RedoxPath::from_absolute("file:bar")
1286 .unwrap()
1287 .is_default_scheme());
1288 assert!(RedoxPath::from_absolute("file:")
1289 .unwrap()
1290 .is_default_scheme());
1291 assert!(!RedoxPath::from_absolute("foo:bar")
1292 .unwrap()
1293 .is_default_scheme());
1294 assert!(RedoxPath::from_absolute("foo:bar").unwrap().is_legacy());
1295 assert!(!RedoxPath::from_absolute("/foo/bar").unwrap().is_legacy());
1296 }
1297
1298 #[test]
1299 fn test_to_standard() {
1300 assert_eq!(
1301 &RedoxPath::from_absolute("foo:bar").unwrap().to_standard(),
1302 "/scheme/foo/bar"
1303 );
1304 assert_eq!(
1305 &RedoxPath::from_absolute("file:bar").unwrap().to_standard(),
1306 "/scheme/file/bar"
1307 );
1308 assert_eq!(
1309 &RedoxPath::from_absolute("/scheme/foo/bar")
1310 .unwrap()
1311 .to_standard(),
1312 "/scheme/foo/bar"
1313 );
1314 assert_eq!(
1315 &RedoxPath::from_absolute("/foo/bar").unwrap().to_standard(),
1316 "/foo/bar"
1317 );
1318 assert_eq!(
1319 &RedoxPath::from_absolute("foo:bar/../bar2")
1320 .unwrap()
1321 .to_standard_canon()
1322 .to_string(),
1323 "/scheme/foo/bar2"
1324 );
1325 assert_eq!(
1326 &RedoxPath::from_absolute("file:bar/./../bar2")
1327 .unwrap()
1328 .to_standard_canon()
1329 .to_string(),
1330 "/scheme/file/bar2"
1331 );
1332 assert_eq!(
1333 &RedoxPath::from_absolute("/scheme/file/bar/./../../foo/bar")
1334 .unwrap()
1335 .to_standard_canon()
1336 .to_string(),
1337 "/scheme/foo/bar"
1338 );
1339 assert_eq!(
1340 &RedoxPath::from_absolute("/foo/bar")
1341 .unwrap()
1342 .to_standard_canon()
1343 .to_string(),
1344 "/foo/bar"
1345 );
1346 assert_eq!(
1347 &canonicalize_to_standard(None, "/scheme/foo/bar").unwrap(),
1348 "/scheme/foo/bar"
1349 );
1350 assert_eq!(
1351 &canonicalize_to_standard(None, "foo:bar").unwrap(),
1352 "/scheme/foo/bar"
1353 );
1354 assert_eq!(
1355 &canonicalize_to_standard(None, "foo:bar/../..").unwrap(),
1356 "/scheme/foo"
1357 );
1358 assert_eq!(
1359 &canonicalize_to_standard(None, "/scheme/foo/bar/..").unwrap(),
1360 "/scheme/foo"
1361 );
1362 assert_eq!(
1363 &canonicalize_to_standard(None, "foo:bar/bar2/..").unwrap(),
1364 "/scheme/foo/bar"
1365 );
1366 }
1367
1368 #[test]
1369 fn test_scheme_path() {
1370 assert_eq!(
1371 scheme_path("foo"),
1372 Some(RedoxPath::from_absolute("/scheme/foo").unwrap())
1373 );
1374 assert_eq!(
1375 scheme_path(""),
1376 Some(RedoxPath::from_absolute("/scheme").unwrap())
1377 );
1378 assert_eq!(scheme_path("/foo"), None);
1379 assert_eq!(scheme_path("foo/bar"), None);
1380 assert_eq!(scheme_path("foo:"), None);
1381 }
1382
1383 #[test]
1384 fn test_category() {
1385 assert_eq!(
1386 make_scheme_name("foo", "bar"),
1387 Some(RedoxScheme::new("foo.bar").unwrap())
1388 );
1389 assert_eq!(
1390 RedoxPath::from_absolute(
1391 scheme_path(make_scheme_name("foo", "bar").unwrap().as_ref()).unwrap()
1392 )
1393 .unwrap(),
1394 RedoxPath::Standard(RedoxReference::new("/scheme/foo.bar").unwrap())
1395 );
1396 assert_eq!(make_scheme_name("foo", "/bar"), None);
1397 assert_eq!(make_scheme_name("foo", ":bar"), None);
1398 assert!(RedoxPath::from_absolute(
1399 scheme_path(make_scheme_name("foo", "bar").unwrap().as_ref()).unwrap()
1400 )
1401 .unwrap()
1402 .is_scheme_category("foo"));
1403 assert!(RedoxPath::from_absolute("/scheme/foo.bar/bar2")
1404 .unwrap()
1405 .is_scheme_category("foo"));
1406 assert!(!RedoxPath::from_absolute("/scheme/foo/bar")
1407 .unwrap()
1408 .is_scheme_category("foo"));
1409 assert!(!RedoxPath::from_absolute("/foo.bar/bar2")
1410 .unwrap()
1411 .is_scheme_category("foo"));
1412 }
1413}