1use std::{
4 borrow::Cow,
5 path::{Component, Path, PathBuf, MAIN_SEPARATOR, MAIN_SEPARATOR_STR},
6};
7
8use crate::utils::module::{export_default, ModuleInfo};
9use rquickjs::{
10 function::Opt,
11 module::{Declarations, Exports, ModuleDef},
12 prelude::{Func, Rest},
13 Ctx, Object, Result,
14};
15
16pub struct PathModule;
17
18#[cfg(windows)]
19const DELIMITER: char = ';';
20#[cfg(not(windows))]
21const DELIMITER: char = ':';
22
23#[cfg(windows)]
24pub const CURRENT_DIR_STR: &str = ".\\";
25
26#[cfg(windows)]
27const FORWARD_SLASH_STR: &str = "/";
28
29#[cfg(not(windows))]
30pub const CURRENT_DIR_STR: &str = "./";
31
32#[cfg(windows)]
33use memchr::{memchr, memchr2, memchr2_iter};
34
35#[cfg(windows)]
36pub fn replace_backslash(path: impl Into<String>) -> String {
37 let mut path = path.into();
38 let bytes = unsafe { path.as_bytes_mut() };
39
40 let mut start = 0;
41 while let Some(pos) = memchr(b'\\', &bytes[start..]) {
42 bytes[start + pos] = b'/';
43 start += pos + 1;
44 }
45 path
46}
47
48#[cfg(not(windows))]
49pub fn replace_backslash(path: impl Into<String>) -> String {
50 path.into().replace('\\', "/")
51}
52
53#[cfg(windows)]
54fn find_next_separator(s: &str) -> Option<usize> {
55 memchr2(b'\\', b'/', s.as_bytes())
56}
57
58#[cfg(not(windows))]
59fn find_next_separator(s: &str) -> Option<usize> {
60 s.find(MAIN_SEPARATOR)
61}
62
63#[cfg(windows)]
64fn find_last_sep(path: &str) -> Option<usize> {
65 memchr2_iter(b'\\', b'/', path.as_bytes()).next_back()
66}
67
68#[cfg(not(windows))]
69fn find_last_sep(path: &str) -> Option<usize> {
70 path.rfind(MAIN_SEPARATOR)
71}
72
73pub fn dirname<'a, P: Into<Cow<'a, str>>>(path: P) -> String {
74 let path = path.into();
75 let len = path.len();
76
77 if len == 0 {
78 return ".".into();
79 }
80
81 let bytes = path.as_bytes();
82
83 #[cfg(windows)]
84 {
85 if len == 1 {
86 return if is_sep(bytes[0]) {
87 path.into_owned()
88 } else {
89 ".".to_string()
90 };
91 }
92
93 let (root_end, offset) = if is_sep(bytes[0]) {
95 if is_sep(bytes[1]) {
96 parse_unc_root(bytes, len).unwrap_or((1, 1))
98 } else {
99 (1, 1)
100 }
101 } else if bytes.len() > 1 && is_drive_letter(bytes[0]) && bytes[1] == b':' {
102 let r = if len > 2 && is_sep(bytes[2]) { 3 } else { 2 };
103 (r, r)
104 } else {
105 (0, 0)
106 };
107
108 let end = find_dirname_end(bytes, offset);
110
111 match end {
112 Some(e) => &path[..e],
113 None if root_end > 0 => &path[..root_end],
114 None => ".",
115 }
116 .into()
117 }
118
119 #[cfg(not(windows))]
120 {
121 if len == 1 {
122 return if bytes[0] == b'/' {
123 path.into_owned()
124 } else {
125 ".".into()
126 };
127 }
128
129 let has_root = bytes[0] == b'/';
130 let end = find_dirname_end(bytes, 1);
131
132 match end {
133 Some(e) if has_root && e == 1 => "//",
134 Some(e) => &path[..e],
135 None if has_root => "/",
136 None => ".",
137 }
138 .into()
139 }
140}
141
142#[cfg(windows)]
143fn is_sep(c: u8) -> bool {
144 c == b'/' || c == b'\\'
145}
146
147#[cfg(windows)]
148fn is_drive_letter(c: u8) -> bool {
149 c.is_ascii_alphabetic()
150}
151
152#[cfg(windows)]
153fn parse_unc_root(bytes: &[u8], len: usize) -> Option<(usize, usize)> {
154 let mut j = 2;
155 while j < len && !is_sep(bytes[j]) {
157 j += 1;
158 }
159 if j >= len || j == 2 {
160 return None;
161 }
162 while j < len && is_sep(bytes[j]) {
164 j += 1;
165 }
166 if j >= len {
167 return None;
168 }
169 let share_start = j;
170 while j < len && !is_sep(bytes[j]) {
172 j += 1;
173 }
174 if j == share_start {
175 return None;
176 }
177 if j == len {
178 return None;
179 } Some((j + 1, j + 1))
181}
182
183fn find_dirname_end(bytes: &[u8], offset: usize) -> Option<usize> {
184 let mut matched_slash = true;
185 for i in (offset..bytes.len()).rev() {
186 #[cfg(windows)]
187 let is_separator = is_sep(bytes[i]);
188 #[cfg(not(windows))]
189 let is_separator = bytes[i] == b'/';
190
191 if is_separator {
192 if !matched_slash {
193 return Some(i);
194 }
195 } else {
196 matched_slash = false;
197 }
198 }
199 None
200}
201
202pub fn name_extname(path: &str) -> (&str, &str) {
203 let path = strip_last_sep(path);
204 let sep_pos = find_last_sep(path);
205
206 let path = match sep_pos {
207 Some(idx) => &path[idx + 1..],
208 None => path,
209 };
210 if path.starts_with('.') {
211 return (path, "");
212 }
213 match path.rfind('.') {
214 Some(idx) => path.split_at(idx),
215 None => (path, ""),
216 }
217}
218
219fn strip_last_sep(path: &str) -> &str {
220 if ends_with_sep(path) {
221 &path[..path.len() - 1]
222 } else {
223 path
224 }
225}
226
227pub fn basename(path: String, suffix: Opt<String>) -> String {
228 #[cfg(windows)]
229 {
230 if path.is_empty() || path == MAIN_SEPARATOR_STR || path == FORWARD_SLASH_STR {
231 return String::from("");
232 }
233 }
234 #[cfg(not(windows))]
235 {
236 if path.is_empty() || path == MAIN_SEPARATOR_STR {
237 return String::from("");
238 }
239 }
240
241 let (base, ext) = name_extname(&path);
242 let mut name = [base, ext].concat();
243 if let Some(suffix) = suffix.0 {
244 if let Some(location) = name.rfind(&suffix) {
245 name.truncate(location);
246 return name;
247 }
248 }
249 name
250}
251
252fn extname(path: String) -> String {
253 let (_, ext) = name_extname(&path);
254 ext.to_string()
255}
256
257fn format(obj: Object) -> String {
258 let dir: String = obj.get("dir").unwrap_or_default();
259 let root: String = obj.get("root").unwrap_or_default();
260 let base: String = obj.get("base").unwrap_or_default();
261 let name: String = obj.get("name").unwrap_or_default();
262 let ext: String = obj.get("ext").unwrap_or_default();
263
264 let mut path = String::new();
265 if !dir.is_empty() {
266 path.push_str(&dir);
267 if !ends_with_sep(&dir) {
268 path.push(MAIN_SEPARATOR);
269 }
270 } else if !root.is_empty() {
271 path.push_str(&root);
272 if !ends_with_sep(&root) {
273 path.push(MAIN_SEPARATOR);
274 }
275 }
276 if !base.is_empty() {
277 path.push_str(&base);
278 } else {
279 path.push_str(&name);
280 if !ext.is_empty() {
281 if !ext.starts_with('.') {
282 path.push('.');
283 }
284 path.push_str(&ext);
285 }
286 }
287 path
288}
289
290fn parse(ctx: Ctx, path_str: String) -> Result<Object> {
291 let obj = Object::new(ctx)?;
292 let path = Path::new(&path_str);
293 let parent = path
294 .parent()
295 .map(|p| p.to_str().unwrap())
296 .unwrap_or_default();
297 let filename = path
298 .file_name()
299 .map(|n| n.to_str().unwrap())
300 .unwrap_or_default();
301
302 let (name, extension) = name_extname(filename);
303
304 let root = path
305 .components()
306 .next()
307 .and_then(|c| match c {
308 Component::Prefix(prefix) => prefix.as_os_str().to_str(),
309 Component::RootDir => c.as_os_str().to_str(),
310 _ => Some(""),
311 })
312 .unwrap_or_default();
313
314 obj.set("root", root)?;
315 obj.set("dir", parent)?;
316 obj.set("base", [name, extension].concat())?;
317 obj.set("ext", extension)?;
318 obj.set("name", name)?;
319
320 Ok(obj)
321}
322
323fn join(parts: Rest<String>) -> String {
324 join_path(parts.0.iter())
325}
326
327pub fn join_path<S, I>(parts: I) -> String
328where
329 S: AsRef<str>,
330 I: IntoIterator<Item = S>,
331{
332 join_path_with_separator(parts, false)
333}
334
335pub fn join_path_with_separator<S, I>(parts: I, force_posix_sep: bool) -> String
336where
337 S: AsRef<str>,
338 I: IntoIterator<Item = S>,
339{
340 let parts_vec: Vec<S> = parts.into_iter().collect();
342 let likely_max_size = parts_vec
345 .iter()
346 .map(|p| p.as_ref().len() + 1)
347 .sum::<usize>()
348 + 10;
349 let result = String::with_capacity(likely_max_size);
350 join_resolve_path(parts_vec, false, result, PathBuf::new(), force_posix_sep)
351}
352
353pub fn resolve_path<S, I>(parts: I) -> Result<String>
354where
355 S: AsRef<str>,
356 I: IntoIterator<Item = S>,
357{
358 resolve_path_with_separator(parts, false)
359}
360
361pub fn resolve_path_with_separator<S, I>(parts: I, force_posix_sep: bool) -> Result<String>
362where
363 S: AsRef<str>,
364 I: IntoIterator<Item = S>,
365{
366 let cwd = std::env::current_dir()?;
367
368 let mut result = cwd.clone().into_os_string().into_string().unwrap();
369 if !result.ends_with(MAIN_SEPARATOR) {
371 result.push(MAIN_SEPARATOR);
372 }
373 #[cfg(windows)]
374 {
375 if force_posix_sep {
376 result = result.replace(MAIN_SEPARATOR, FORWARD_SLASH_STR);
377 }
378 }
379 Ok(join_resolve_path(parts, true, result, cwd, force_posix_sep))
380}
381
382pub fn relative<F, T>(from: F, to: T) -> Result<String>
383where
384 F: AsRef<str>,
385 T: AsRef<str>,
386{
387 let from_ref = from.as_ref();
388 let to_ref = to.as_ref();
389 if from_ref == to_ref {
390 return Ok("".into());
391 }
392
393 let mut abs_from = None;
394
395 if !is_absolute(from_ref) {
396 abs_from = Some(
397 std::env::current_dir()?.to_string_lossy().to_string() + MAIN_SEPARATOR_STR + from_ref,
398 );
399 }
400
401 let mut abs_to = None;
402
403 if !is_absolute(to_ref) {
404 abs_to = Some(
405 std::env::current_dir()?.to_string_lossy().to_string() + MAIN_SEPARATOR_STR + to_ref,
406 );
407 }
408
409 let from_ref = abs_from.as_deref().unwrap_or(from_ref);
410 let to_ref = abs_to.as_deref().unwrap_or(to_ref);
411
412 let mut from_index = 0;
413 let mut to_index = 0;
414 while from_index < from_ref.len() && to_index < to_ref.len() {
416 let from_next = find_next_separator(&from_ref[from_index..])
417 .unwrap_or(from_ref.len() - from_index)
418 + from_index;
419 let to_next =
420 find_next_separator(&to_ref[to_index..]).unwrap_or(to_ref.len() - to_index) + to_index;
421 if from_ref[from_index..from_next] != to_ref[to_index..to_next] {
422 break;
423 }
424 from_index = from_next + 1; to_index = to_next + 1; }
427 let mut relative = String::new();
428 while from_index < from_ref.len() {
430 let from_next = find_next_separator(&from_ref[from_index..])
431 .unwrap_or(from_ref.len() - from_index)
432 + from_index;
433 if !relative.is_empty() {
434 relative.push(MAIN_SEPARATOR);
435 }
436 relative.push_str("..");
437 from_index = from_next + 1; }
439 while to_index < to_ref.len() {
441 let to_next =
442 find_next_separator(&to_ref[to_index..]).unwrap_or(to_ref.len() - to_index) + to_index;
443 if !relative.is_empty() {
444 relative.push(MAIN_SEPARATOR);
445 }
446 let component = &to_ref[to_index..to_next];
447 if component != "." {
448 relative.push_str(component);
449 }
450 to_index = to_next + 1; }
452 Ok(if relative.is_empty() {
453 ".".into()
454 } else {
455 relative
456 })
457}
458
459fn join_resolve_path<S, I>(
460 parts: I,
461 resolve: bool,
462 mut result: String,
463 cwd: PathBuf,
464 force_posix_sep: bool,
465) -> String
466where
467 S: AsRef<str>,
468 I: IntoIterator<Item = S>,
469{
470 let (sep, sep_str) = if force_posix_sep {
471 ('/', "/")
472 } else {
473 (MAIN_SEPARATOR, MAIN_SEPARATOR_STR)
474 };
475
476 let mut resolve_cow: Cow<str>;
477 let mut empty = true;
478 let mut prefix_len = 0;
479
480 let mut index_stack = Vec::with_capacity(16);
481
482 if ends_with_sep(&result) && result.len() > 1 {
484 result.truncate(result.len() - 1);
485 }
486
487 for part in parts {
488 let mut part_ref: &str = part.as_ref();
489 let mut start = 0;
490 if resolve {
491 if cfg!(not(windows)) {
492 if part_ref.starts_with(MAIN_SEPARATOR) {
493 empty = false;
494 result = MAIN_SEPARATOR.into();
495 start = 1;
496 }
497 } else {
498 let starts_with_sep = starts_with_sep(part_ref);
499 if starts_with_sep {
500 let (prefix, _) = get_path_prefix(&cwd);
501 prefix_len = prefix.len();
502 result = prefix;
503 empty = false;
504 result.push(sep);
505 } else {
506 let path_buf: PathBuf = PathBuf::from(part_ref);
507 if path_buf.is_absolute() {
508 empty = false;
509 let (prefix, mut components) = get_path_prefix(&path_buf);
510 if !prefix.is_empty() {
511 components.next(); }
513 prefix_len = prefix.len();
514 result = prefix;
515 result.push(sep);
516 resolve_cow = components
517 .map(|comp| comp.as_os_str().to_str().unwrap_or_default()) .collect::<Vec<&str>>() .join(sep_str)
520 .into();
521 part_ref = resolve_cow.as_ref();
522 }
523 }
524 }
525 } else if starts_with_sep(part_ref) && empty {
526 empty = false;
527 result.push(sep);
528 start = 1;
529 }
530
531 while start < part_ref.len() {
532 let end = find_next_separator(&part_ref[start..]).map_or(part_ref.len(), |i| i + start);
533 match &part_ref[start..end] {
534 ".." => {
535 if let Some(last_index) = index_stack.pop() {
536 result.truncate(last_index);
537 } else if empty {
538 if let Some(last_index) = find_last_sep(&result) {
539 result.truncate(last_index);
540 }
541 }
542 },
543 "" | "." => {
544 },
546 sub_part => {
547 let len = result.len();
548 if !result.ends_with(sep) && !result.is_empty() {
549 result.push(sep);
550 }
551 result.push_str(sub_part);
552 result.push(sep);
553 index_stack.push(len);
554 },
555 }
556 start = end + 1;
557 }
558 }
559
560 if result.len() > prefix_len + 1 && ends_with_sep(&result) {
561 result.truncate(result.len() - 1);
562 }
563
564 result
565}
566
567pub fn resolve(path: Rest<String>) -> Result<String> {
568 resolve_path(path.iter())
569}
570
571fn get_path_prefix(cwd: &Path) -> (String, std::iter::Peekable<std::path::Components<'_>>) {
572 let mut components = cwd.components().peekable();
573
574 let prefix = if let Some(Component::Prefix(prefix)) = components.peek() {
575 prefix.as_os_str().to_str().unwrap().to_string()
576 } else {
577 "".into()
578 };
579
580 (prefix, components)
581}
582
583pub fn normalize<P: AsRef<str>>(path: P) -> String {
584 join_path([path].iter())
585}
586
587#[allow(dead_code)] fn starts_with_sep(path: &str) -> bool {
589 matches!(path.as_bytes().first().unwrap_or(&0), b'/' | b'\\')
590}
591
592#[cfg(windows)]
593pub fn ends_with_sep(path: &str) -> bool {
594 matches!(path.as_bytes().last().unwrap_or(&0), b'/' | b'\\')
595}
596
597#[cfg(not(windows))]
598pub fn ends_with_sep(path: &str) -> bool {
599 path.ends_with(MAIN_SEPARATOR)
600}
601
602#[cfg(windows)]
603pub fn is_absolute(path: &str) -> bool {
604 starts_with_sep(path) || PathBuf::from(path).is_absolute()
605}
606
607#[cfg(not(windows))]
608pub fn is_absolute(path: &str) -> bool {
609 path.starts_with(MAIN_SEPARATOR)
610}
611
612impl ModuleDef for PathModule {
613 fn declare(declare: &Declarations) -> Result<()> {
614 declare.declare("basename")?;
615 declare.declare("dirname")?;
616 declare.declare("extname")?;
617 declare.declare("format")?;
618 declare.declare("parse")?;
619 declare.declare("join")?;
620 declare.declare("resolve")?;
621 declare.declare("relative")?;
622 declare.declare("normalize")?;
623 declare.declare("isAbsolute")?;
624 declare.declare("delimiter")?;
625 declare.declare("sep")?;
626
627 declare.declare("default")?;
628 Ok(())
629 }
630
631 fn evaluate<'js>(ctx: &Ctx<'js>, exports: &Exports<'js>) -> Result<()> {
632 export_default(ctx, exports, |default| {
633 default.set("dirname", Func::from(dirname::<String>))?;
634 default.set("basename", Func::from(basename))?;
635 default.set("extname", Func::from(extname))?;
636 default.set("format", Func::from(format))?;
637 default.set("parse", Func::from(parse))?;
638 default.set("join", Func::from(join))?;
639 default.set("relative", Func::from(relative::<String, String>))?;
640 default.set("resolve", Func::from(resolve))?;
641 default.set("normalize", Func::from(normalize::<String>))?;
642 default.set("isAbsolute", Func::from(|s: String| is_absolute(&s)))?;
643 default.prop("delimiter", DELIMITER.to_string())?;
644 default.prop("sep", MAIN_SEPARATOR.to_string())?;
645 Ok(())
646 })
647 }
648}
649
650impl From<PathModule> for ModuleInfo<PathModule> {
651 fn from(val: PathModule) -> Self {
652 ModuleInfo {
653 name: "path",
654 module: val,
655 }
656 }
657}
658
659#[cfg(test)]
660mod tests {
661 use std::{env::set_current_dir, sync::Mutex};
662
663 static THREAD_LOCK: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
664
665 use once_cell::sync::Lazy;
666
667 use super::*;
668
669 #[test]
670 fn test_relative() {
671 let _shared = THREAD_LOCK.lock().unwrap();
672 let cwd = std::env::current_dir().expect("unable to get current working directory");
673 set_current_dir("/").expect("unable to set working directory to /");
674
675 assert_eq!(
676 relative("a/b/c", "b/c").unwrap(),
677 "../../../b/c".replace('/', MAIN_SEPARATOR_STR)
678 );
679 assert_eq!(
680 relative("/data/orandea/test/aaa", "/data/orandea/impl/bbb").unwrap(),
681 "../../impl/bbb".replace('/', MAIN_SEPARATOR_STR)
682 );
683 assert_eq!(
684 relative("/a/b/c", "/a/d").unwrap(),
685 "../../d".replace('/', MAIN_SEPARATOR_STR)
686 );
687 assert_eq!(relative("/a/b/c", "/a/b/c/d").unwrap(), "d");
688 assert_eq!(relative("/a/b/c", "/a/b/c").unwrap(), "");
689
690 assert_eq!(
691 relative("a/b", "a/b/c/d").unwrap(),
692 "c/d".replace('/', MAIN_SEPARATOR_STR)
693 );
694 assert_eq!(
695 relative("a/b/c", "b/c").unwrap(),
696 "../../../b/c".replace('/', MAIN_SEPARATOR_STR)
697 );
698
699 set_current_dir(cwd).expect("unable to set working directory back");
700 }
701
702 #[test]
703 fn test_dirname() {
704 assert_eq!(dirname("/usr/local/bin".to_string()), "/usr/local");
705 assert_eq!(dirname("/usr/local/".to_string()), "/usr");
706 assert_eq!(dirname("usr/local/bin".to_string()), "usr/local");
707 assert_eq!(dirname("/".to_string()), "/");
708 assert_eq!(dirname("".to_string()), ".");
709 }
710
711 #[test]
712 fn test_basename() {
713 assert_eq!(basename("/usr/local/bin".to_string(), Opt(None)), "bin");
714 assert_eq!(
715 basename("/usr/local/bin.txt".to_string(), Opt(None)),
716 "bin.txt"
717 );
718 assert_eq!(
719 basename(
720 "/usr/local/bin.txt".to_string(),
721 Opt(Some(".txt".to_string()))
722 ),
723 "bin"
724 );
725 assert_eq!(basename("".to_string(), Opt(None)), "");
726 assert_eq!(basename("/".to_string(), Opt(None)), "");
727 }
728
729 #[test]
730 fn test_extname() {
731 assert_eq!(extname("/usr/local/bin.txt".to_string()), ".txt");
732 assert_eq!(extname("/usr/local/bin".to_string()), "");
733 assert_eq!(extname("file.tar.gz".to_string()), ".gz");
734 assert_eq!(extname(".bashrc".to_string()), "");
735 assert_eq!(extname("".to_string()), "");
736 }
737
738 #[test]
739 fn test_join() {
740 assert_eq!(
742 join_path(["/usr", "local", "bin"].iter()),
743 "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR)
744 );
745 assert_eq!(
746 join_path(["/usr", "/local", "bin"].iter()),
747 "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR)
748 );
749 assert_eq!(
750 join_path(["usr", "local", "bin"].iter()),
751 "usr/local/bin".replace('/', MAIN_SEPARATOR_STR)
752 );
753 assert_eq!(join_path(["", "bin"].iter()), "bin");
754
755 assert_eq!(
757 join_path(["/usr", "..", "local", "bin"].iter()),
758 "/local/bin".replace('/', MAIN_SEPARATOR_STR)
759 ); assert_eq!(
761 join_path([".", "usr", "local"]),
762 "usr/local".replace('/', MAIN_SEPARATOR_STR)
763 ); assert_eq!(
765 join_path(["/usr", ".", "bin"].iter()),
766 "/usr/bin".replace('/', MAIN_SEPARATOR_STR)
767 ); assert_eq!(
769 join_path(["usr", "local", "bin", ".."].iter()),
770 "usr/local".replace('/', MAIN_SEPARATOR_STR)
771 ); assert_eq!(
773 join_path(["/usr", "local", "", "bin"].iter()),
774 "/usr/local/bin".replace('/', MAIN_SEPARATOR_STR)
775 ); assert_eq!(
777 join_path(["/usr", "local", ".hidden"].iter()),
778 "/usr/local/.hidden".replace('/', MAIN_SEPARATOR_STR)
779 ); }
781
782 #[test]
783 fn test_resolve_path() {
784 let _shared = THREAD_LOCK.lock().unwrap();
785 let prefix = if cfg!(windows) {
786 if let Some(Component::Prefix(prefix)) =
787 std::env::current_dir().unwrap().components().next()
788 {
789 prefix.as_os_str().to_str().unwrap().to_string()
790 } else {
791 "".into()
792 }
793 } else {
794 "".into()
795 };
796
797 assert_eq!(
798 resolve_path(["", "foo/bar"].iter()).unwrap(),
799 std::env::current_dir()
800 .unwrap()
801 .join("foo/bar".replace('/', MAIN_SEPARATOR_STR))
802 .to_string_lossy()
803 .to_string()
804 );
805
806 assert_eq!(
808 resolve_path(["/"].iter()).unwrap(),
809 prefix.clone() + MAIN_SEPARATOR_STR
810 );
811
812 assert_eq!(
814 resolve_path(["/foo/bar", "../baz"].iter()).unwrap(),
815 prefix.clone() + &"/foo/baz".replace('/', MAIN_SEPARATOR_STR)
816 );
817 assert_eq!(
818 resolve_path(["/foo/bar", "./baz"].iter()).unwrap(),
819 prefix.clone() + &"/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR)
820 );
821 assert_eq!(
822 resolve_path(["foo/bar", "/baz"].iter()).unwrap(),
823 prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR)
824 );
825
826 assert_eq!(
828 resolve_path(["/foo", "bar", ".", "baz"].iter()).unwrap(),
829 prefix.clone() + &"/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR)
830 ); assert_eq!(
832 resolve_path(["/foo", "bar", "..", "baz"].iter()).unwrap(),
833 prefix.clone() + &"/foo/baz".replace('/', MAIN_SEPARATOR_STR)
834 ); assert_eq!(
836 resolve_path(["/foo", "bar", "../..", "baz"].iter()).unwrap(),
837 prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR)
838 ); assert_eq!(
840 resolve_path(["/foo", "bar", ".hidden"].iter()).unwrap(),
841 prefix.clone() + &"/foo/bar/.hidden".replace('/', MAIN_SEPARATOR_STR)
842 ); assert_eq!(
844 resolve_path(["/foo", ".", "bar", "."].iter()).unwrap(),
845 prefix.clone() + &"/foo/bar".replace('/', MAIN_SEPARATOR_STR)
846 ); assert_eq!(
848 resolve_path(["/foo", "..", "..", "bar"].iter()).unwrap(),
849 prefix.clone() + &"/bar".replace('/', MAIN_SEPARATOR_STR)
850 ); assert_eq!(
852 resolve_path(["/foo/bar", "/..", "baz"].iter()).unwrap(),
853 prefix.clone() + &"/baz".replace('/', MAIN_SEPARATOR_STR)
854 ); assert_eq!(
857 resolve_path(["../foo"].iter()).unwrap(),
858 std::env::current_dir()
859 .unwrap()
860 .parent()
861 .unwrap()
862 .join("foo".replace('/', MAIN_SEPARATOR_STR))
863 .to_string_lossy()
864 .to_string()
865 ); assert_eq!(
868 resolve_path(["../".repeat(32)].iter()).unwrap(),
869 prefix.clone()
870 ); }
872
873 #[test]
874 fn test_normalize() {
875 assert_eq!(
876 normalize("/foo//bar//baz"),
877 "/foo/bar/baz".replace('/', MAIN_SEPARATOR_STR)
878 );
879 assert_eq!(
880 normalize("/foo/./bar/../baz"),
881 "/foo/baz".replace('/', MAIN_SEPARATOR_STR)
882 );
883 assert_eq!(
884 normalize("foo/bar/"),
885 "foo/bar".replace('/', MAIN_SEPARATOR_STR)
886 );
887 assert_eq!(normalize("./foo"), "foo");
888 }
889
890 #[test]
891 fn test_is_absolute() {
892 assert!(is_absolute("/usr/local/bin"));
893 assert!(!is_absolute("usr/local/bin"));
894 #[cfg(windows)]
895 assert!(is_absolute("C:\\Program Files")); assert!(!is_absolute("./local/bin"));
897 }
898
899 #[test]
900 fn test_replace_backslash() {
901 assert_eq!(replace_backslash("C:\\Program Files"), "C:/Program Files");
902 assert_eq!(replace_backslash("/usr/local/bin"), "/usr/local/bin");
903 assert_eq!(replace_backslash("C:\\Users\\User\\"), "C:/Users/User/");
904 }
905}