Repository navigation
Expand file tree
/
Copy pathpath_utils.rs
More file actions
147 lines (126 loc) · 5.06 KB
/
Copy pathpath_utils.rs
File metadata and controls
147 lines (126 loc) · 5.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use os_str_bytes::{OsStrBytes, OsStrBytesExt};
use regex::bytes::Regex;
use std::ffi::{OsStr, OsString};
use std::path::{Path, PathBuf};
use std::sync::LazyLock;
static WINDOWS_PREFIX_RE: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^[a-zA-Z]:(.*)").expect("Bad regex."));
/// Compute the distance between two paths in terms of number of components to leave and to enter.
pub fn path_distances(from: &Path, to: &Path) -> (usize, usize) {
let from_components = from.components();
let to_components = to.components();
let from_length = from.components().count();
let to_length = to.components().count();
let common_path_len = from_components
.zip(to_components)
.take_while(|(a, b)| a == b)
.count();
let leave_count = from_length - common_path_len;
let enter_count = to_length - common_path_len;
(leave_count, enter_count)
}
/// Strip the suffix from path `path` if it ends with `suffix`, returning the remaining prefix.
/// Returns `None` if `path` does not end with `suffix`.
pub fn strip_suffix(path: &Path, suffix: &Path) -> Option<PathBuf> {
let mut x = path.components().rev();
let mut y = suffix.components().rev();
loop {
let rev_result = x.clone();
match (x.next(), y.next()) {
(Some(a), Some(b)) if a == b => (),
(Some(_) | None, Some(_)) => return None,
(_, None) => return Some(rev_result.rev().collect::<PathBuf>()),
}
}
}
/// Check if the path is absolute.
pub fn is_absolute(path: &Path) -> bool {
fn sanitize_path(path: &OsStr) -> OsString {
WINDOWS_PREFIX_RE
.captures(path.as_encoded_bytes())
.and_then(|caps| caps.get(1))
.and_then(|m| OsStr::from_io_bytes(m.as_bytes()))
.unwrap_or(path)
.to_owned()
}
let sanitised_path = sanitize_path(path.as_os_str());
sanitised_path.starts_with("/") || sanitised_path.starts_with("\\")
}
#[cfg(test)]
mod tests {
use super::{is_absolute, path_distances, strip_suffix};
use all_asserts::{assert_false, assert_true};
use std::path::{Path, PathBuf};
#[test]
fn test_strip_suffix_exact_match() {
let include_directive_content = Path::new("bar/baz.h");
let include_file_path = Path::new("/foo/bar/baz.h");
let result = strip_suffix(include_file_path, include_directive_content);
assert_eq!(result, Some(PathBuf::from("/foo")));
}
#[test]
fn test_strip_suffix_no_match() {
let include_directive_content = Path::new("baz/bar.h");
let include_file_path = Path::new("/foo/bar/baz.h");
let result = strip_suffix(include_file_path, include_directive_content);
assert_eq!(result, None);
}
#[test]
fn test_strip_suffix_suffix_longer_than_path() {
let include_directive_content = Path::new("foo/bar/baz.h");
let include_file_path = Path::new("baz.h");
let result = strip_suffix(include_file_path, include_directive_content);
assert_eq!(result, None);
}
#[test]
fn test_strip_suffix_suffix_is_file_name() {
let include_directive_content = Path::new("baz.h");
let include_file_path = Path::new("/foo/bar/baz.h");
let result = strip_suffix(include_file_path, include_directive_content);
assert_eq!(result, Some(PathBuf::from("/foo/bar")));
}
#[test]
fn test_path_distance_same_path() {
let from = Path::new("/foo/bar/baz");
let to = Path::new("/foo/bar/baz");
assert_eq!(path_distances(from, to), (0, 0));
}
#[test]
fn test_path_distance_subdir() {
let from = Path::new("/foo/bar");
let to = Path::new("/foo/bar/baz/qux");
// leave_count = 0, enter_count = 2
assert_eq!(path_distances(from, to), (0, 2));
}
#[test]
fn test_path_distance_parent_dir() {
let from = Path::new("/foo/bar/baz/qux");
let to = Path::new("/foo/bar");
// leave_count = 2, enter_count = 0
assert_eq!(path_distances(from, to), (2, 0));
}
#[test]
fn test_path_distance_different_roots() {
let from = Path::new("/foo/bar");
let to = Path::new("/baz/qux");
// common_path_len = 1 ("/"), leave_count = 2, enter_count = 2
assert_eq!(path_distances(from, to), (2, 2));
}
#[test]
fn test_is_absolute_unix_paths() {
assert_true!(is_absolute(Path::new("/usr/include")));
assert_true!(is_absolute(Path::new("/")));
assert_false!(is_absolute(Path::new("relative/path")));
assert_false!(is_absolute(Path::new("file.h")));
assert_false!(is_absolute(Path::new("../file.h")));
}
#[test]
fn test_is_absolute_windows_paths() {
assert_true!(is_absolute(Path::new("C:/Windows/System32")));
assert_true!(is_absolute(Path::new("D:\\")));
assert_false!(is_absolute(Path::new("E:Windows\\System32")));
assert_false!(is_absolute(Path::new("Windows\\System32")));
assert_false!(is_absolute(Path::new("file.h")));
assert_true!(is_absolute(Path::new(r"\\?\C:\Folder\File.txt")));
}
}