faster_path 0.3.10 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Cargo.lock +19 -37
- data/Cargo.toml +5 -6
- data/Gemfile +3 -7
- data/README.md +94 -30
- data/Rakefile +11 -2
- data/faster_path.gemspec +6 -3
- data/lib/faster_path/version.rb +1 -1
- data/lib/faster_path.rb +4 -2
- data/src/basename.rs +166 -69
- data/src/chop_basename.rs +84 -21
- data/src/cleanpath_aggressive.rs +87 -71
- data/src/cleanpath_conservative.rs +175 -79
- data/src/dirname.rs +96 -32
- data/src/extname.rs +150 -20
- data/src/lib.rs +54 -104
- data/src/path_parsing.rs +282 -21
- data/src/pathname.rs +296 -256
- data/src/plus.rs +90 -74
- data/src/prepend_prefix.rs +11 -13
- data/src/relative_path_from.rs +72 -48
- data/src/ruby.rs +530 -0
- metadata +17 -18
- data/src/debug.rs +0 -29
- data/src/helpers.rs +0 -51
- data/src/memrnchr.rs +0 -70
- data/src/pathname_sys.rs +0 -41
data/src/basename.rs
CHANGED
|
@@ -1,146 +1,243 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
use
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if let Some(last_slash_pos) = find_last_sep_pos(bytes) {
|
|
12
|
-
if last_slash_pos == right - 1 {
|
|
13
|
-
if let Some(pos) = find_last_non_sep_pos(&bytes[..last_slash_pos]) {
|
|
14
|
-
right = pos + 1;
|
|
15
|
-
} else {
|
|
16
|
-
return "/";
|
|
17
|
-
}
|
|
18
|
-
if let Some(pos) = find_last_sep_pos(&bytes[..right]) {
|
|
19
|
-
left = pos + 1;
|
|
20
|
-
}
|
|
21
|
-
} else {
|
|
22
|
-
left = last_slash_pos + 1;
|
|
23
|
-
}
|
|
1
|
+
use memchr::memrchr;
|
|
2
|
+
|
|
3
|
+
use crate::path_parsing::{Rules, SEP_BYTES};
|
|
4
|
+
|
|
5
|
+
// Ruby's `File.basename(path, ext)`.
|
|
6
|
+
pub fn basename<'a>(rules: Rules, path: &'a [u8], ext: &[u8]) -> &'a [u8] {
|
|
7
|
+
let (name, base_len) = last_component(rules, path);
|
|
8
|
+
match base_len {
|
|
9
|
+
Some(base_len) => &name[..ext_end(rules, name, base_len, ext)],
|
|
10
|
+
None => name,
|
|
24
11
|
}
|
|
25
|
-
&path[left..left + ext_end(&bytes[left..right], ext)]
|
|
26
12
|
}
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
// Ruby's `ruby_enc_find_basename`: the last component of the path, without
|
|
15
|
+
// trailing separators (and on Windows, trailing dots, spaces and `:stream`),
|
|
16
|
+
// and where that component ends without its extension. A path that is only
|
|
17
|
+
// separators and prefixes has no extension: its "component" is a separator,
|
|
18
|
+
// or "" for a drive letter.
|
|
19
|
+
pub fn last_component(rules: Rules, path: &[u8]) -> (&[u8], Option<usize>) {
|
|
20
|
+
if path.is_empty() {
|
|
21
|
+
return (path, None);
|
|
31
22
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
match
|
|
35
|
-
Some(
|
|
36
|
-
|
|
23
|
+
if !rules.is_dosish() {
|
|
24
|
+
// On Unix this is just the last name, or the last separator.
|
|
25
|
+
let end = match rules.last_non_sep_pos(path) {
|
|
26
|
+
Some(pos) => pos + 1,
|
|
27
|
+
None => return (&path[path.len() - 1..], None),
|
|
37
28
|
};
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
// Paths are short: a plain scan beats `memrchr`'s setup here.
|
|
30
|
+
let start = path[..end].iter().rposition(|&c| c == b'/').map_or(0, |pos| pos + 1);
|
|
31
|
+
let component = &path[start..end];
|
|
32
|
+
return (component, Some(base_len(component)));
|
|
33
|
+
}
|
|
34
|
+
let root = rules.skip_prefix(path);
|
|
35
|
+
let mut name = root;
|
|
36
|
+
while name < path.len() && rules.is_sep(path[name]) {
|
|
37
|
+
name += 1;
|
|
38
|
+
}
|
|
39
|
+
if name == path.len() {
|
|
40
|
+
if name != root {
|
|
41
|
+
// The last separator
|
|
42
|
+
return (&path[name - 1..name], None);
|
|
43
|
+
}
|
|
44
|
+
if path[name - 1] == b':' {
|
|
45
|
+
// A drive letter
|
|
46
|
+
return (&path[name..], None);
|
|
47
|
+
}
|
|
48
|
+
// A UNC prefix
|
|
49
|
+
return (SEP_BYTES, None);
|
|
40
50
|
}
|
|
41
|
-
|
|
51
|
+
let start = match rules.last_separator(&path[name..]) {
|
|
52
|
+
Some(pos) => {
|
|
53
|
+
let mut start = name + pos;
|
|
54
|
+
while rules.is_sep(path[start]) {
|
|
55
|
+
start += 1;
|
|
56
|
+
}
|
|
57
|
+
start
|
|
58
|
+
}
|
|
59
|
+
None => name,
|
|
60
|
+
};
|
|
61
|
+
let rest = &path[start..];
|
|
62
|
+
let component = &rest[..rules.ntfs_tail(rest)];
|
|
63
|
+
(component, Some(base_len(component)))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Where the name ends without its extension: at its last dot, unless that
|
|
67
|
+
// is one of its leading dots.
|
|
68
|
+
fn base_len(component: &[u8]) -> usize {
|
|
69
|
+
let dots = component.iter().take_while(|&&c| c == b'.').count();
|
|
70
|
+
component[dots..].iter().rposition(|&c| c == b'.').map_or(component.len(), |pos| dots + pos)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Ruby's `rmext`: where `component` ends without the extension `ext`, or
|
|
74
|
+
// its length if `ext` isn't there. `ext` is an extension to remove, ".*"
|
|
75
|
+
// for any extension, or a character followed by `*` to remove everything
|
|
76
|
+
// from the last occurrence of that character.
|
|
77
|
+
pub fn ext_end(rules: Rules, component: &[u8], base_len: usize, ext: &[u8]) -> usize {
|
|
78
|
+
let len = component.len();
|
|
79
|
+
let end = match *ext {
|
|
80
|
+
[] => 0,
|
|
81
|
+
[b'.', b'*'] => base_len,
|
|
82
|
+
[c, b'*'] => memrchr(c, component).unwrap_or(len),
|
|
83
|
+
_ if len < ext.len() => len,
|
|
84
|
+
_ => {
|
|
85
|
+
let start = len - ext.len();
|
|
86
|
+
if rules.same_path(&component[start..], ext) { start } else { 0 }
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
if end == 0 { len } else { end }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#[cfg(test)]
|
|
93
|
+
fn basename_str<'a>(path: &'a str, ext: &str) -> &'a str {
|
|
94
|
+
std::str::from_utf8(basename(Rules::UNIX, path.as_bytes(), ext.as_bytes())).unwrap()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[cfg(test)]
|
|
98
|
+
fn windows_basename<'a>(path: &'a str, ext: &str) -> &'a str {
|
|
99
|
+
std::str::from_utf8(basename(Rules::WINDOWS, path.as_bytes(), ext.as_bytes())).unwrap()
|
|
42
100
|
}
|
|
43
101
|
|
|
44
102
|
#[test]
|
|
45
103
|
fn non_dot_asterisk_ext() {
|
|
46
104
|
// This is undocumented Ruby functionality. We match it in case some code out there relies on it.
|
|
47
|
-
assert_eq!(
|
|
48
|
-
assert_eq!(
|
|
49
|
-
assert_eq!(
|
|
50
|
-
assert_eq!(
|
|
105
|
+
assert_eq!(basename_str("abc", "b*"), "a");
|
|
106
|
+
assert_eq!(basename_str("abc", "abc"), "abc");
|
|
107
|
+
assert_eq!(basename_str("abc", "a*"), "abc");
|
|
108
|
+
assert_eq!(basename_str("playlist", "l*"), "play");
|
|
51
109
|
// Treated as literal "*":
|
|
52
|
-
assert_eq!(
|
|
53
|
-
assert_eq!(
|
|
110
|
+
assert_eq!(basename_str("playlist", "yl*"), "playlist");
|
|
111
|
+
assert_eq!(basename_str("playl*", "yl*"), "pla");
|
|
54
112
|
}
|
|
55
113
|
|
|
56
114
|
#[test]
|
|
57
115
|
fn empty() {
|
|
58
|
-
assert_eq!(
|
|
59
|
-
assert_eq!(
|
|
60
|
-
assert_eq!(
|
|
116
|
+
assert_eq!(basename_str("", ""), "");
|
|
117
|
+
assert_eq!(basename_str("", ".*"), "");
|
|
118
|
+
assert_eq!(basename_str("", ".a"), "");
|
|
61
119
|
}
|
|
62
120
|
|
|
63
121
|
#[test]
|
|
64
122
|
fn sep() {
|
|
65
|
-
assert_eq!(
|
|
66
|
-
assert_eq!(
|
|
123
|
+
assert_eq!(basename_str("/", ""), "/");
|
|
124
|
+
assert_eq!(basename_str("//", ""), "/");
|
|
67
125
|
}
|
|
68
126
|
|
|
69
127
|
#[test]
|
|
70
128
|
fn trailing_dot() {
|
|
71
|
-
assert_eq!(
|
|
72
|
-
assert_eq!(
|
|
73
|
-
assert_eq!(
|
|
129
|
+
assert_eq!(basename_str("file.test.", ""), "file.test.");
|
|
130
|
+
assert_eq!(basename_str("file.test.", "."), "file.test");
|
|
131
|
+
assert_eq!(basename_str("file.test.", ".*"), "file.test");
|
|
74
132
|
}
|
|
75
133
|
|
|
76
134
|
#[test]
|
|
77
135
|
fn trailing_dot_dot() {
|
|
78
|
-
assert_eq!(
|
|
79
|
-
assert_eq!(
|
|
136
|
+
assert_eq!(basename_str("a..", ".."), "a");
|
|
137
|
+
assert_eq!(basename_str("a..", ".*"), "a.");
|
|
80
138
|
}
|
|
81
139
|
|
|
82
140
|
#[test]
|
|
83
141
|
fn dot() {
|
|
84
|
-
assert_eq!(
|
|
85
|
-
assert_eq!(
|
|
86
|
-
assert_eq!(
|
|
142
|
+
assert_eq!(basename_str(".", ""), ".");
|
|
143
|
+
assert_eq!(basename_str(".", "."), ".");
|
|
144
|
+
assert_eq!(basename_str(".", ".*"), ".");
|
|
87
145
|
}
|
|
88
146
|
|
|
89
147
|
#[test]
|
|
90
148
|
fn dot_dot() {
|
|
91
|
-
assert_eq!(
|
|
92
|
-
assert_eq!(
|
|
93
|
-
assert_eq!(
|
|
94
|
-
assert_eq!(
|
|
149
|
+
assert_eq!(basename_str("..", ""), "..");
|
|
150
|
+
assert_eq!(basename_str("..", ".*"), "..");
|
|
151
|
+
assert_eq!(basename_str("..", ".."), "..");
|
|
152
|
+
assert_eq!(basename_str("..", "..."), "..");
|
|
95
153
|
}
|
|
96
154
|
|
|
97
155
|
#[test]
|
|
98
156
|
fn non_dot_ext() {
|
|
99
|
-
assert_eq!(
|
|
157
|
+
assert_eq!(basename_str("abc", "bc"), "a");
|
|
100
158
|
}
|
|
101
159
|
|
|
102
160
|
#[test]
|
|
103
161
|
fn basename_eq_ext() {
|
|
104
|
-
assert_eq!(
|
|
105
|
-
assert_eq!(
|
|
162
|
+
assert_eq!(basename_str(".x", ".x"), ".x");
|
|
163
|
+
assert_eq!(basename_str(".x", ".*"), ".x");
|
|
106
164
|
}
|
|
107
165
|
|
|
108
166
|
#[test]
|
|
109
167
|
fn absolute() {
|
|
110
|
-
assert_eq!(
|
|
168
|
+
assert_eq!(basename_str("/a/b///c", ""), "c");
|
|
111
169
|
}
|
|
112
170
|
|
|
113
171
|
#[test]
|
|
114
172
|
fn trailing_slashes_absolute() {
|
|
115
|
-
assert_eq!(
|
|
173
|
+
assert_eq!(basename_str("/a/b///c//////", ""), "c");
|
|
116
174
|
}
|
|
117
175
|
|
|
118
176
|
#[test]
|
|
119
177
|
fn relative() {
|
|
120
|
-
assert_eq!(
|
|
178
|
+
assert_eq!(basename_str("b///c", ""), "c");
|
|
121
179
|
}
|
|
122
180
|
|
|
123
181
|
#[test]
|
|
124
182
|
fn trailing_slashes_relative() {
|
|
125
|
-
assert_eq!(
|
|
183
|
+
assert_eq!(basename_str("b/c//", ""), "c");
|
|
126
184
|
}
|
|
127
185
|
|
|
128
186
|
#[test]
|
|
129
187
|
fn root() {
|
|
130
|
-
assert_eq!(
|
|
188
|
+
assert_eq!(basename_str("//c", ""), "c");
|
|
131
189
|
}
|
|
132
190
|
|
|
133
191
|
#[test]
|
|
134
192
|
fn trailing_slashes_root() {
|
|
135
|
-
assert_eq!(
|
|
193
|
+
assert_eq!(basename_str("//c//", ""), "c");
|
|
136
194
|
}
|
|
137
195
|
|
|
138
196
|
#[test]
|
|
139
197
|
fn trailing_slashes_relative_root() {
|
|
140
|
-
assert_eq!(
|
|
198
|
+
assert_eq!(basename_str("c//", ""), "c");
|
|
141
199
|
}
|
|
142
200
|
|
|
143
201
|
#[test]
|
|
144
202
|
fn edge_case_all_seps() {
|
|
145
|
-
assert_eq!("/",
|
|
203
|
+
assert_eq!("/", basename_str("///", ".*"));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn ext_compared_like_ruby() {
|
|
208
|
+
assert_eq!(basename_str("..", "."), ".");
|
|
209
|
+
assert_eq!(basename_str("a.rb", ".RB"), "a.rb");
|
|
210
|
+
assert_eq!(windows_basename("a.rb", ".RB"), "a");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[test]
|
|
214
|
+
fn windows_separators() {
|
|
215
|
+
assert_eq!(windows_basename("a\\b", ""), "b");
|
|
216
|
+
assert_eq!(windows_basename("a\\b\\", ""), "b");
|
|
217
|
+
assert_eq!(windows_basename("a/b\\c", ""), "c");
|
|
218
|
+
assert_eq!(windows_basename("\\", ""), "\\");
|
|
219
|
+
assert_eq!(basename_str("a\\b", ""), "a\\b");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn windows_prefixes() {
|
|
224
|
+
assert_eq!(windows_basename("C:", ""), "");
|
|
225
|
+
assert_eq!(windows_basename("C:/", ""), "/");
|
|
226
|
+
assert_eq!(windows_basename("C:/a", ""), "a");
|
|
227
|
+
assert_eq!(windows_basename("C:a", ""), "a");
|
|
228
|
+
assert_eq!(windows_basename("//a/b", ""), "/");
|
|
229
|
+
assert_eq!(windows_basename("//a/b/", ""), "/");
|
|
230
|
+
assert_eq!(windows_basename("//a/b/c", ""), "c");
|
|
231
|
+
assert_eq!(basename_str("//a/b", ""), "b");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#[test]
|
|
235
|
+
fn windows_ntfs_names() {
|
|
236
|
+
assert_eq!(windows_basename("foo.test ", ""), "foo.test");
|
|
237
|
+
assert_eq!(windows_basename("foo.test.", ""), "foo.test");
|
|
238
|
+
assert_eq!(windows_basename("foo.test::$DATA", ""), "foo.test");
|
|
239
|
+
assert_eq!(windows_basename("foo.test ", ".test"), "foo");
|
|
240
|
+
assert_eq!(windows_basename("foo.test.", ".*"), "foo");
|
|
241
|
+
assert_eq!(windows_basename("foo.test::$DATA", ".*"), "foo");
|
|
242
|
+
assert_eq!(windows_basename("...", ""), "...");
|
|
146
243
|
}
|
data/src/chop_basename.rs
CHANGED
|
@@ -1,30 +1,93 @@
|
|
|
1
|
-
use
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
use memchr::memmem;
|
|
2
|
+
|
|
3
|
+
use crate::basename::basename;
|
|
4
|
+
use crate::path_parsing::{offset_in, Rules};
|
|
5
|
+
|
|
6
|
+
// Pathname's `chop_basename`: the path split before its basename, unless
|
|
7
|
+
// the basename is empty or a separator.
|
|
8
|
+
pub fn chop_basename(rules: Rules, path: &[u8]) -> Option<(&[u8], &[u8])> {
|
|
9
|
+
let base = basename(rules, path, b"");
|
|
10
|
+
if base.is_empty() || (base.len() == 1 && rules.is_sep(base[0])) {
|
|
9
11
|
return None;
|
|
10
12
|
}
|
|
11
|
-
|
|
13
|
+
// `path.rindex(base)`. On Unix, nothing after the basename can contain it.
|
|
14
|
+
let start = if rules.is_dosish() { memmem::rfind(path, base)? } else { offset_in(path, base) };
|
|
15
|
+
Some((&path[..start], &path[start..start + base.len()]))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Pathname's `relative?`: whether chopping every basename off leaves nothing.
|
|
19
|
+
pub fn is_relative(rules: Rules, path: &[u8]) -> bool {
|
|
20
|
+
if !rules.is_dosish() {
|
|
21
|
+
return path.first() != Some(&b'/');
|
|
22
|
+
}
|
|
23
|
+
let mut prefix = path;
|
|
24
|
+
while let Some((rest, _)) = chop_basename(rules, prefix) {
|
|
25
|
+
prefix = rest;
|
|
26
|
+
}
|
|
27
|
+
prefix.is_empty()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[cfg(test)]
|
|
31
|
+
fn chop_basename_str(input: &str) -> Option<(&str, &str)> {
|
|
32
|
+
chop_basename(Rules::UNIX, input.as_bytes()).map(|(dirname, basename)| {
|
|
33
|
+
(std::str::from_utf8(dirname).unwrap(), std::str::from_utf8(basename).unwrap())
|
|
34
|
+
})
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
#[test]
|
|
15
38
|
fn it_chops_the_basename_and_dirname() {
|
|
16
|
-
assert_eq!(
|
|
17
|
-
assert_eq!(
|
|
18
|
-
assert_eq!(
|
|
19
|
-
assert_eq!(
|
|
20
|
-
assert_eq!(
|
|
21
|
-
assert_eq!(
|
|
22
|
-
assert_eq!(
|
|
23
|
-
assert_eq!(
|
|
24
|
-
assert_eq!(
|
|
25
|
-
assert_eq!(
|
|
26
|
-
assert_eq!(
|
|
39
|
+
assert_eq!(chop_basename_str(""), None );
|
|
40
|
+
assert_eq!(chop_basename_str("/"), None );
|
|
41
|
+
assert_eq!(chop_basename_str("."), Some(("", ".")) );
|
|
42
|
+
assert_eq!(chop_basename_str("asdf/asdf"), Some(("asdf/", "asdf")) );
|
|
43
|
+
assert_eq!(chop_basename_str("asdf.txt"), Some(("", "asdf.txt")) );
|
|
44
|
+
assert_eq!(chop_basename_str("asdf/"), Some(("", "asdf")) );
|
|
45
|
+
assert_eq!(chop_basename_str("/asdf/"), Some(("/", "asdf")) );
|
|
46
|
+
assert_eq!(chop_basename_str("a///b"), Some(("a///", "b")) );
|
|
47
|
+
assert_eq!(chop_basename_str("a///b//"), Some(("a///", "b")) );
|
|
48
|
+
assert_eq!(chop_basename_str("/a///b//"), Some(("/a///", "b")) );
|
|
49
|
+
assert_eq!(chop_basename_str("/a///b//"), Some(("/a///", "b")) );
|
|
50
|
+
|
|
51
|
+
assert_eq!(chop_basename_str("./../..///.../..//"), Some(("./../..///.../", "..")));
|
|
52
|
+
}
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
#[test]
|
|
55
|
+
fn it_chops_non_utf8_paths() {
|
|
56
|
+
assert_eq!(chop_basename(Rules::UNIX, b"\xff/\xfe\xfd/"), Some((&b"\xff/"[..], &b"\xfe\xfd"[..])));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#[cfg(test)]
|
|
60
|
+
fn windows_chop(input: &str) -> Option<(&str, &str)> {
|
|
61
|
+
chop_basename(Rules::WINDOWS, input.as_bytes()).map(|(dirname, basename)| {
|
|
62
|
+
(std::str::from_utf8(dirname).unwrap(), std::str::from_utf8(basename).unwrap())
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[test]
|
|
67
|
+
fn it_chops_windows_paths() {
|
|
68
|
+
assert_eq!(windows_chop("a\\b"), Some(("a\\", "b")));
|
|
69
|
+
assert_eq!(windows_chop("a\\b\\"), Some(("a\\", "b")));
|
|
70
|
+
assert_eq!(windows_chop("C:"), None);
|
|
71
|
+
assert_eq!(windows_chop("C:/"), None);
|
|
72
|
+
assert_eq!(windows_chop("C:/a"), Some(("C:/", "a")));
|
|
73
|
+
assert_eq!(windows_chop("C:a"), Some(("C:", "a")));
|
|
74
|
+
assert_eq!(windows_chop("//a/b"), None);
|
|
75
|
+
assert_eq!(windows_chop("//a/b/c"), Some(("//a/b/", "c")));
|
|
76
|
+
assert_eq!(windows_chop("a."), Some(("", "a")));
|
|
29
77
|
}
|
|
30
78
|
|
|
79
|
+
#[test]
|
|
80
|
+
fn it_knows_relative_paths() {
|
|
81
|
+
for &rules in &[Rules::UNIX, Rules::WINDOWS] {
|
|
82
|
+
assert!(is_relative(rules, b""));
|
|
83
|
+
assert!(is_relative(rules, b"a"));
|
|
84
|
+
assert!(is_relative(rules, b"a/b"));
|
|
85
|
+
assert!(!is_relative(rules, b"/"));
|
|
86
|
+
assert!(!is_relative(rules, b"/a"));
|
|
87
|
+
}
|
|
88
|
+
assert!(is_relative(Rules::UNIX, b"C:/a"));
|
|
89
|
+
assert!(is_relative(Rules::UNIX, b"\\a"));
|
|
90
|
+
for path in &["A:", "A:/", "A:/a", "//", "//a", "//a/", "//a/b", "//a/b/", "//a/b/c", "\\a"] {
|
|
91
|
+
assert!(!is_relative(Rules::WINDOWS, path.as_bytes()), "{}", path);
|
|
92
|
+
}
|
|
93
|
+
}
|
data/src/cleanpath_aggressive.rs
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
use std::borrow::Cow;
|
|
2
|
-
use prepend_prefix::prepend_prefix;
|
|
3
|
-
use basename::basename;
|
|
4
|
-
use chop_basename::chop_basename;
|
|
5
|
-
use path_parsing::{SEP_STR, contains_sep};
|
|
6
2
|
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
use crate::basename::basename;
|
|
4
|
+
use crate::chop_basename::chop_basename;
|
|
5
|
+
use crate::path_parsing::{Rules, SEP_BYTES};
|
|
6
|
+
use crate::prepend_prefix::prepend_prefix;
|
|
7
|
+
|
|
8
|
+
// Pathname's `cleanpath_aggressive`
|
|
9
|
+
pub fn cleanpath_aggressive(rules: Rules, path: &[u8]) -> Cow<'_, [u8]> {
|
|
10
|
+
let mut names: Vec<&[u8]> = vec![];
|
|
9
11
|
let mut prefix = path;
|
|
10
|
-
while let Some((
|
|
12
|
+
while let Some((p, base)) = chop_basename(rules, prefix) {
|
|
11
13
|
prefix = p;
|
|
12
|
-
match base
|
|
13
|
-
"." => {}
|
|
14
|
-
".." => names.push(base),
|
|
14
|
+
match base {
|
|
15
|
+
b"." => {}
|
|
16
|
+
b".." => names.push(base),
|
|
15
17
|
_ => {
|
|
16
|
-
if names.last() == Some(
|
|
18
|
+
if names.last() == Some(&&b".."[..]) {
|
|
17
19
|
names.pop();
|
|
18
20
|
} else {
|
|
19
21
|
names.push(base);
|
|
@@ -21,72 +23,86 @@ pub fn cleanpath_aggressive(path: &str) -> Cow<str> {
|
|
|
21
23
|
}
|
|
22
24
|
}
|
|
23
25
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
28
|
-
// ```
|
|
29
|
-
//
|
|
30
|
-
if contains_sep(basename(&prefix, "").as_bytes()) {
|
|
31
|
-
let len = names.iter().rposition(|&c| c != "..").map_or(0, |pos| pos + 1);
|
|
26
|
+
let prefix = with_separators(rules, prefix);
|
|
27
|
+
if rules.contains_sep(basename(rules, &prefix, b"")) {
|
|
28
|
+
let len = names.iter().rposition(|&c| c != b"..").map_or(0, |pos| pos + 1);
|
|
32
29
|
names.truncate(len);
|
|
33
30
|
}
|
|
34
31
|
names.reverse();
|
|
35
|
-
prepend_prefix(&prefix, &names.join(
|
|
32
|
+
let path = prepend_prefix(rules, &prefix, &names.join(SEP_BYTES));
|
|
33
|
+
Cow::Owned(path.into_owned())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// `pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR)`
|
|
37
|
+
pub fn with_separators(rules: Rules, prefix: &[u8]) -> Cow<'_, [u8]> {
|
|
38
|
+
if rules.is_dosish() && prefix.contains(&b'\\') {
|
|
39
|
+
Cow::Owned(prefix.iter().map(|&c| if c == b'\\' { b'/' } else { c }).collect())
|
|
40
|
+
} else {
|
|
41
|
+
Cow::Borrowed(prefix)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[cfg(test)]
|
|
46
|
+
fn cleanpath_aggressive_str(path: &str) -> String {
|
|
47
|
+
String::from_utf8(cleanpath_aggressive(Rules::UNIX, path.as_bytes()).into_owned()).unwrap()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[cfg(test)]
|
|
51
|
+
fn windows_cleanpath_aggressive(path: &str) -> String {
|
|
52
|
+
String::from_utf8(cleanpath_aggressive(Rules::WINDOWS, path.as_bytes()).into_owned()).unwrap()
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
#[test]
|
|
39
56
|
fn it_aggressively_cleans_the_path() {
|
|
40
|
-
assert_eq!(
|
|
41
|
-
assert_eq!(
|
|
42
|
-
assert_eq!(
|
|
43
|
-
assert_eq!(
|
|
44
|
-
assert_eq!(
|
|
45
|
-
assert_eq!(
|
|
46
|
-
assert_eq!(
|
|
47
|
-
assert_eq!(
|
|
48
|
-
assert_eq!(
|
|
49
|
-
assert_eq!(
|
|
50
|
-
assert_eq!(
|
|
51
|
-
assert_eq!(
|
|
52
|
-
assert_eq!(
|
|
53
|
-
assert_eq!(
|
|
54
|
-
assert_eq!(
|
|
55
|
-
assert_eq!(
|
|
56
|
-
assert_eq!(
|
|
57
|
-
assert_eq!(
|
|
58
|
-
assert_eq!(
|
|
59
|
-
assert_eq!(
|
|
60
|
-
assert_eq!(
|
|
61
|
-
assert_eq!(
|
|
62
|
-
assert_eq!(
|
|
63
|
-
assert_eq!(
|
|
64
|
-
assert_eq!(
|
|
65
|
-
assert_eq!(
|
|
66
|
-
assert_eq!(
|
|
67
|
-
assert_eq!(
|
|
68
|
-
assert_eq!(
|
|
69
|
-
assert_eq!(
|
|
70
|
-
assert_eq!(
|
|
71
|
-
assert_eq!(
|
|
57
|
+
assert_eq!(cleanpath_aggressive_str("/") , "/");
|
|
58
|
+
assert_eq!(cleanpath_aggressive_str("") , ".");
|
|
59
|
+
assert_eq!(cleanpath_aggressive_str(".") , ".");
|
|
60
|
+
assert_eq!(cleanpath_aggressive_str("..") , "..");
|
|
61
|
+
assert_eq!(cleanpath_aggressive_str("a") , "a");
|
|
62
|
+
assert_eq!(cleanpath_aggressive_str("/.") , "/");
|
|
63
|
+
assert_eq!(cleanpath_aggressive_str("/..") , "/");
|
|
64
|
+
assert_eq!(cleanpath_aggressive_str("/a") , "/a");
|
|
65
|
+
assert_eq!(cleanpath_aggressive_str("./") , ".");
|
|
66
|
+
assert_eq!(cleanpath_aggressive_str("../") , "..");
|
|
67
|
+
assert_eq!(cleanpath_aggressive_str("a/") , "a");
|
|
68
|
+
assert_eq!(cleanpath_aggressive_str("a//b") , "a/b");
|
|
69
|
+
assert_eq!(cleanpath_aggressive_str("a/.") , "a");
|
|
70
|
+
assert_eq!(cleanpath_aggressive_str("a/./") , "a");
|
|
71
|
+
assert_eq!(cleanpath_aggressive_str("a/..") , ".");
|
|
72
|
+
assert_eq!(cleanpath_aggressive_str("a/../") , ".");
|
|
73
|
+
assert_eq!(cleanpath_aggressive_str("/a/.") , "/a");
|
|
74
|
+
assert_eq!(cleanpath_aggressive_str("./..") , "..");
|
|
75
|
+
assert_eq!(cleanpath_aggressive_str("../.") , "..");
|
|
76
|
+
assert_eq!(cleanpath_aggressive_str("./../") , "..");
|
|
77
|
+
assert_eq!(cleanpath_aggressive_str(".././") , "..");
|
|
78
|
+
assert_eq!(cleanpath_aggressive_str("/./..") , "/");
|
|
79
|
+
assert_eq!(cleanpath_aggressive_str("/../.") , "/");
|
|
80
|
+
assert_eq!(cleanpath_aggressive_str("/./../") , "/");
|
|
81
|
+
assert_eq!(cleanpath_aggressive_str("/.././") , "/");
|
|
82
|
+
assert_eq!(cleanpath_aggressive_str("a/b/c") , "a/b/c");
|
|
83
|
+
assert_eq!(cleanpath_aggressive_str("./b/c") , "b/c");
|
|
84
|
+
assert_eq!(cleanpath_aggressive_str("a/./c") , "a/c");
|
|
85
|
+
assert_eq!(cleanpath_aggressive_str("a/b/.") , "a/b");
|
|
86
|
+
assert_eq!(cleanpath_aggressive_str("a/../.") , ".");
|
|
87
|
+
assert_eq!(cleanpath_aggressive_str("/../.././../a") , "/a");
|
|
88
|
+
assert_eq!(cleanpath_aggressive_str("a/b/../../../../c/../d"), "../../d");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
fn it_cleans_like_ruby_on_unix() {
|
|
93
|
+
assert_eq!(cleanpath_aggressive_str("///"), "/");
|
|
94
|
+
assert_eq!(cleanpath_aggressive_str("///a"), "/a");
|
|
95
|
+
assert_eq!(cleanpath_aggressive_str("///.."), "/");
|
|
96
|
+
assert_eq!(cleanpath_aggressive_str("///."), "/");
|
|
97
|
+
assert_eq!(cleanpath_aggressive_str("///a/../.."), "/");
|
|
72
98
|
}
|
|
73
99
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// defassert(:cleanpath_aggressive, '/', '///')
|
|
84
|
-
// defassert(:cleanpath_aggressive, '/a', '///a')
|
|
85
|
-
// defassert(:cleanpath_aggressive, '/', '///..')
|
|
86
|
-
// defassert(:cleanpath_aggressive, '/', '///.')
|
|
87
|
-
// defassert(:cleanpath_aggressive, '/', '///a/../..')
|
|
88
|
-
// end
|
|
89
|
-
//
|
|
90
|
-
// if DOSISH
|
|
91
|
-
// defassert(:cleanpath_aggressive, 'c:/foo/bar', 'c:\\foo\\bar')
|
|
92
|
-
// end
|
|
100
|
+
#[test]
|
|
101
|
+
fn it_cleans_like_ruby_on_windows() {
|
|
102
|
+
assert_eq!(windows_cleanpath_aggressive("//a/b/c/"), "//a/b/c");
|
|
103
|
+
assert_eq!(windows_cleanpath_aggressive("c:\\foo\\bar"), "c:/foo/bar");
|
|
104
|
+
assert_eq!(windows_cleanpath_aggressive("c:\\foo\\..\\..\\bar"), "c:/bar");
|
|
105
|
+
assert_eq!(windows_cleanpath_aggressive("a\\.\\b"), "a/b");
|
|
106
|
+
assert_eq!(windows_cleanpath_aggressive("C:"), "C:.");
|
|
107
|
+
assert_eq!(windows_cleanpath_aggressive("C:a/../b"), "C:b");
|
|
108
|
+
}
|