fallow_types/
path_util.rs1use std::path::{Component, Path};
14
15pub fn is_absolute_path_any_platform(path: &Path) -> bool {
21 if path.is_absolute() {
22 return true;
23 }
24 if matches!(path.components().next(), Some(Component::RootDir)) {
25 return true;
26 }
27 looks_like_windows_drive_absolute(path.as_os_str().as_encoded_bytes())
28}
29
30pub fn looks_like_windows_absolute_path(value: &str) -> bool {
35 looks_like_windows_drive_absolute(value.as_bytes())
36}
37
38#[must_use]
45pub fn display_relative(root: &Path, path: &Path) -> String {
46 path.strip_prefix(root)
47 .unwrap_or(path)
48 .display()
49 .to_string()
50 .replace('\\', "/")
51}
52
53fn looks_like_windows_drive_absolute(bytes: &[u8]) -> bool {
54 bytes.len() >= 3
55 && bytes[0].is_ascii_alphabetic()
56 && bytes[1] == b':'
57 && matches!(bytes[2], b'/' | b'\\')
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use std::path::PathBuf;
64
65 #[test]
66 fn display_relative_strips_root_prefix() {
67 assert_eq!(
68 display_relative(Path::new("/project"), Path::new("/project/src/a.ts")),
69 "src/a.ts"
70 );
71 }
72
73 #[test]
74 fn display_relative_keeps_path_outside_root() {
75 assert_eq!(
76 display_relative(Path::new("/project"), Path::new("/other/a.ts")),
77 "/other/a.ts"
78 );
79 }
80
81 #[test]
82 fn display_relative_normalises_backslashes() {
83 assert_eq!(
84 display_relative(Path::new("/project"), Path::new(r"packages\ui\a.ts")),
85 "packages/ui/a.ts"
86 );
87 }
88
89 #[test]
90 fn display_relative_of_root_is_empty() {
91 assert_eq!(
92 display_relative(Path::new("/project"), Path::new("/project")),
93 ""
94 );
95 }
96
97 #[test]
98 fn posix_style_root_is_absolute_on_any_platform() {
99 assert!(is_absolute_path_any_platform(Path::new(
100 "/project/src/a.ts"
101 )));
102 assert!(is_absolute_path_any_platform(Path::new("/foo")));
103 assert!(is_absolute_path_any_platform(Path::new("/")));
104 }
105
106 #[test]
107 fn windows_drive_letter_is_absolute_on_any_platform() {
108 assert!(is_absolute_path_any_platform(Path::new(
109 "C:\\project\\src\\a.ts"
110 )));
111 assert!(is_absolute_path_any_platform(Path::new(
112 "C:/project/src/a.ts"
113 )));
114 assert!(is_absolute_path_any_platform(Path::new("d:/foo")));
115 }
116
117 #[test]
118 fn relative_paths_return_false() {
119 assert!(!is_absolute_path_any_platform(Path::new("src/a.ts")));
120 assert!(!is_absolute_path_any_platform(Path::new("./src/a.ts")));
121 assert!(!is_absolute_path_any_platform(Path::new("../parent/a.ts")));
122 assert!(!is_absolute_path_any_platform(Path::new("a.ts")));
123 assert!(!is_absolute_path_any_platform(Path::new("")));
124 }
125
126 #[cfg_attr(miri, ignore)]
127 #[test]
128 fn host_absolute_works_through_is_absolute() {
129 let cwd = std::env::current_dir().expect("current_dir");
130 assert!(is_absolute_path_any_platform(&cwd));
131 }
132
133 #[test]
134 fn looks_like_windows_absolute_path_recognises_drive_shapes() {
135 assert!(looks_like_windows_absolute_path("C:\\foo"));
136 assert!(looks_like_windows_absolute_path("c:/foo"));
137 assert!(looks_like_windows_absolute_path("Z:/very/deep/path.ts"));
138 assert!(!looks_like_windows_absolute_path("/foo"));
139 assert!(!looks_like_windows_absolute_path("src/foo"));
140 assert!(!looks_like_windows_absolute_path("C:"));
141 assert!(!looks_like_windows_absolute_path("CC:/foo"));
142 assert!(!looks_like_windows_absolute_path(""));
143 }
144
145 #[test]
146 fn drive_prefix_path_string_is_absolute_via_os_str_bytes() {
147 let p = PathBuf::from("E:/source/map.js");
148 assert!(is_absolute_path_any_platform(&p));
149 }
150}