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
|
@@ -1,96 +1,192 @@
|
|
|
1
1
|
use std::borrow::Cow;
|
|
2
|
-
|
|
3
|
-
use
|
|
4
|
-
use
|
|
5
|
-
use
|
|
6
|
-
use
|
|
7
|
-
use path_parsing::{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
|
|
3
|
+
use crate::basename::basename;
|
|
4
|
+
use crate::chop_basename::chop_basename;
|
|
5
|
+
use crate::cleanpath_aggressive::with_separators;
|
|
6
|
+
use crate::dirname::dirname;
|
|
7
|
+
use crate::path_parsing::{Rules, SEP_BYTES};
|
|
8
|
+
use crate::prepend_prefix::prepend_prefix;
|
|
9
|
+
|
|
10
|
+
// Pathname's `cleanpath_conservative`
|
|
11
|
+
pub fn cleanpath_conservative(rules: Rules, path: &[u8]) -> Cow<'_, [u8]> {
|
|
12
|
+
let mut names: Vec<&[u8]> = vec![];
|
|
11
13
|
let mut prefix = path;
|
|
12
|
-
while let Some((
|
|
14
|
+
while let Some((p, base)) = chop_basename(rules, prefix) {
|
|
13
15
|
prefix = p;
|
|
14
|
-
if base !=
|
|
16
|
+
if base != b"." {
|
|
15
17
|
names.push(base);
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
|
|
22
|
-
// ```
|
|
23
|
-
//
|
|
24
|
-
if contains_sep(basename(&prefix, "").as_bytes()) {
|
|
25
|
-
let len = names.iter().rposition(|&c| c != "..").map_or(0, |pos| pos + 1);
|
|
20
|
+
let prefix = with_separators(rules, prefix);
|
|
21
|
+
if rules.contains_sep(basename(rules, &prefix, b"")) {
|
|
22
|
+
let len = names.iter().rposition(|&c| c != b"..").map_or(0, |pos| pos + 1);
|
|
26
23
|
names.truncate(len);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
if names.is_empty() {
|
|
27
|
+
return Cow::Owned(dirname(rules, &prefix).into_owned());
|
|
28
|
+
}
|
|
29
|
+
names.reverse();
|
|
30
|
+
if names.last() != Some(&&b".."[..]) && basename(rules, path, b"") == b"." {
|
|
31
|
+
names.push(b".");
|
|
32
|
+
}
|
|
33
|
+
let result = prepend_prefix(rules, &prefix, &names.join(SEP_BYTES)).into_owned();
|
|
34
|
+
match names.last() {
|
|
35
|
+
Some(&b".") | Some(&b"..") => Cow::Owned(result),
|
|
36
|
+
_ if has_trailing_separator(rules, path) => Cow::Owned(add_trailing_separator(rules, Cow::Owned(result)).into_owned()),
|
|
37
|
+
_ => Cow::Owned(result),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
// Pathname's `has_trailing_separator?`
|
|
42
|
+
pub fn has_trailing_separator(rules: Rules, path: &[u8]) -> bool {
|
|
43
|
+
match chop_basename(rules, path) {
|
|
44
|
+
Some((dirname, basename)) => dirname.len() + basename.len() < path.len(),
|
|
45
|
+
None => false,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Pathname's `add_trailing_separator`:
|
|
50
|
+
// `File.basename(path + 'a') == 'a' ? path : File.join(path, '')`
|
|
51
|
+
pub fn add_trailing_separator(rules: Rules, path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
|
|
52
|
+
let ends_a_path = if rules.is_dosish() {
|
|
53
|
+
basename(rules, &[&path[..], b"a"].concat(), b"") == b"a"
|
|
54
|
+
} else {
|
|
55
|
+
path.last().map_or(true, |&c| rules.is_sep(c))
|
|
56
|
+
};
|
|
57
|
+
if ends_a_path {
|
|
58
|
+
path
|
|
40
59
|
} else {
|
|
41
|
-
|
|
60
|
+
Cow::Owned(rules.join(&path, b""))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pathname's `del_trailing_separator`
|
|
65
|
+
pub fn del_trailing_separator(rules: Rules, path: &[u8]) -> Cow<'_, [u8]> {
|
|
66
|
+
if let Some((dirname, basename)) = chop_basename(rules, path) {
|
|
67
|
+
// The basename follows the dirname
|
|
68
|
+
return Cow::Borrowed(&path[..dirname.len() + basename.len()]);
|
|
69
|
+
}
|
|
70
|
+
match rules.last_non_sep_pos(path) {
|
|
71
|
+
// No trailing separators
|
|
72
|
+
_ if path.last().map_or(true, |&c| !rules.is_sep(c)) => Cow::Borrowed(path),
|
|
73
|
+
// `$` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/]`
|
|
74
|
+
last => {
|
|
75
|
+
let before = last.map_or(0, |pos| pos + 1);
|
|
76
|
+
let dir = dirname(rules, path);
|
|
77
|
+
let separators = dir.len() - dir.iter().rev().take_while(|&&c| rules.is_sep(c)).count();
|
|
78
|
+
Cow::Owned([&path[..before], &dir[separators..]].concat())
|
|
79
|
+
}
|
|
42
80
|
}
|
|
43
|
-
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[cfg(test)]
|
|
84
|
+
fn cleanpath_conservative_str(path: &str) -> String {
|
|
85
|
+
String::from_utf8(cleanpath_conservative(Rules::UNIX, path.as_bytes()).into_owned()).unwrap()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[cfg(test)]
|
|
89
|
+
fn windows_cleanpath_conservative(path: &str) -> String {
|
|
90
|
+
String::from_utf8(cleanpath_conservative(Rules::WINDOWS, path.as_bytes()).into_owned()).unwrap()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[cfg(test)]
|
|
94
|
+
fn del_str(rules: Rules, path: &str) -> String {
|
|
95
|
+
String::from_utf8(del_trailing_separator(rules, path.as_bytes()).into_owned()).unwrap()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[test]
|
|
99
|
+
fn it_keeps_the_whole_path_with_a_trailing_separator() {
|
|
100
|
+
assert_eq!(cleanpath_conservative_str("a/b/"), "a/b/");
|
|
101
|
+
assert_eq!(cleanpath_conservative_str("/a/b//"), "/a/b/");
|
|
102
|
+
assert_eq!(cleanpath_conservative_str("a/./b/"), "a/b/");
|
|
103
|
+
assert_eq!(cleanpath_conservative_str("a/b/../"), "a/b/..");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[test]
|
|
107
|
+
fn it_adds_trailing_separators() {
|
|
108
|
+
assert_eq!(add_trailing_separator(Rules::UNIX, Cow::Borrowed(&b""[..])), &b""[..]);
|
|
109
|
+
assert_eq!(add_trailing_separator(Rules::UNIX, Cow::Borrowed(&b"/"[..])), &b"/"[..]);
|
|
110
|
+
assert_eq!(add_trailing_separator(Rules::UNIX, Cow::Borrowed(&b"a"[..])), &b"a/"[..]);
|
|
111
|
+
assert_eq!(add_trailing_separator(Rules::UNIX, Cow::Borrowed(&b"a/"[..])), &b"a/"[..]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
fn it_detects_trailing_separators() {
|
|
116
|
+
assert!(!has_trailing_separator(Rules::UNIX, b""));
|
|
117
|
+
assert!(!has_trailing_separator(Rules::UNIX, b"/"));
|
|
118
|
+
assert!(!has_trailing_separator(Rules::UNIX, b"a"));
|
|
119
|
+
assert!(has_trailing_separator(Rules::UNIX, b"a/"));
|
|
120
|
+
assert!(has_trailing_separator(Rules::UNIX, b"/a//"));
|
|
44
121
|
}
|
|
45
122
|
|
|
46
123
|
#[test]
|
|
47
124
|
fn it_conservatively_cleans_the_path() {
|
|
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!(
|
|
72
|
-
assert_eq!(
|
|
73
|
-
assert_eq!(
|
|
74
|
-
assert_eq!(
|
|
75
|
-
assert_eq!(
|
|
76
|
-
assert_eq!(
|
|
77
|
-
assert_eq!(
|
|
78
|
-
assert_eq!(
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
125
|
+
assert_eq!(cleanpath_conservative_str("/"), "/");
|
|
126
|
+
assert_eq!(cleanpath_conservative_str(""), ".");
|
|
127
|
+
assert_eq!(cleanpath_conservative_str("."), ".");
|
|
128
|
+
assert_eq!(cleanpath_conservative_str(".."), "..");
|
|
129
|
+
assert_eq!(cleanpath_conservative_str("a"), "a");
|
|
130
|
+
assert_eq!(cleanpath_conservative_str("/."), "/");
|
|
131
|
+
assert_eq!(cleanpath_conservative_str("/.."), "/");
|
|
132
|
+
assert_eq!(cleanpath_conservative_str("/a"), "/a");
|
|
133
|
+
assert_eq!(cleanpath_conservative_str("./"), ".");
|
|
134
|
+
assert_eq!(cleanpath_conservative_str("../"), "..");
|
|
135
|
+
assert_eq!(cleanpath_conservative_str("a/"), "a/");
|
|
136
|
+
assert_eq!(cleanpath_conservative_str("a//b"), "a/b");
|
|
137
|
+
assert_eq!(cleanpath_conservative_str("a/."), "a/.");
|
|
138
|
+
assert_eq!(cleanpath_conservative_str("a/./"), "a/.");
|
|
139
|
+
assert_eq!(cleanpath_conservative_str("a/../"), "a/..");
|
|
140
|
+
assert_eq!(cleanpath_conservative_str("/a/."), "/a/.");
|
|
141
|
+
assert_eq!(cleanpath_conservative_str("./.."), "..");
|
|
142
|
+
assert_eq!(cleanpath_conservative_str("../."), "..");
|
|
143
|
+
assert_eq!(cleanpath_conservative_str("./../"), "..");
|
|
144
|
+
assert_eq!(cleanpath_conservative_str(".././"), "..");
|
|
145
|
+
assert_eq!(cleanpath_conservative_str("/./.."), "/");
|
|
146
|
+
assert_eq!(cleanpath_conservative_str("/../."), "/");
|
|
147
|
+
assert_eq!(cleanpath_conservative_str("/./../"), "/");
|
|
148
|
+
assert_eq!(cleanpath_conservative_str("/.././"), "/");
|
|
149
|
+
assert_eq!(cleanpath_conservative_str("a/b/c"), "a/b/c");
|
|
150
|
+
assert_eq!(cleanpath_conservative_str("./b/c"), "b/c");
|
|
151
|
+
assert_eq!(cleanpath_conservative_str("a/./c"), "a/c");
|
|
152
|
+
assert_eq!(cleanpath_conservative_str("a/b/."), "a/b/.");
|
|
153
|
+
assert_eq!(cleanpath_conservative_str("a/../."), "a/..");
|
|
154
|
+
assert_eq!(cleanpath_conservative_str("/../.././../a"), "/a");
|
|
155
|
+
assert_eq!(cleanpath_conservative_str("a/b/../../../../c/../d"), "a/b/../../../../c/../d");
|
|
156
|
+
|
|
157
|
+
assert_eq!(cleanpath_conservative_str("//"), "/");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn it_conservatively_cleans_windows_paths() {
|
|
162
|
+
assert_eq!(windows_cleanpath_conservative("c:\\foo\\bar"), "c:/foo/bar");
|
|
163
|
+
assert_eq!(windows_cleanpath_conservative("//"), "//");
|
|
164
|
+
assert_eq!(windows_cleanpath_conservative("a\\b\\"), "a/b/");
|
|
165
|
+
assert_eq!(windows_cleanpath_conservative("C:/a/../b/"), "C:/a/../b/");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
fn it_adds_trailing_separators_on_windows() {
|
|
170
|
+
let add = |path: &'static str| add_trailing_separator(Rules::WINDOWS, Cow::Borrowed(path.as_bytes())).into_owned();
|
|
171
|
+
assert_eq!(add("a"), b"a/");
|
|
172
|
+
assert_eq!(add("a\\"), b"a\\");
|
|
173
|
+
assert_eq!(add("C:"), b"C:");
|
|
174
|
+
assert_eq!(add("C:/"), b"C:/");
|
|
175
|
+
assert_eq!(add("//a/b"), b"//a/b/");
|
|
176
|
+
assert!(has_trailing_separator(Rules::WINDOWS, b"a\\"));
|
|
177
|
+
assert!(!has_trailing_separator(Rules::UNIX, b"a\\"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn it_deletes_trailing_separators() {
|
|
182
|
+
for &(path, expected) in &[("", ""), ("/", "/"), ("/a", "/a"), ("/a/", "/a"), ("/a//", "/a"),
|
|
183
|
+
(".", "."), ("./", "."), (".//", "."), ("///", "/"), ("///a/", "///a")] {
|
|
184
|
+
assert_eq!(del_str(Rules::UNIX, path), expected, "{:?}", path);
|
|
185
|
+
}
|
|
186
|
+
for &(path, expected) in &[("A:", "A:"), ("A:/", "A:/"), ("A://", "A:/"), ("A:.", "A:."), ("A:./", "A:."),
|
|
187
|
+
("A:.//", "A:."), ("//", "//"), ("//a", "//a"), ("//a/", "//a"), ("//a//", "//a"),
|
|
188
|
+
("//a/b", "//a/b"), ("//a/b/", "//a/b"), ("//a/b//", "//a/b"), ("//a/b/c", "//a/b/c"),
|
|
189
|
+
("//a/b/c/", "//a/b/c"), ("//a/b/c//", "//a/b/c"), ("a\\", "a")] {
|
|
190
|
+
assert_eq!(del_str(Rules::WINDOWS, path), expected, "{:?}", path);
|
|
191
|
+
}
|
|
96
192
|
}
|
data/src/dirname.rs
CHANGED
|
@@ -1,66 +1,130 @@
|
|
|
1
|
-
use std::
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
&path[..end + 1]
|
|
23
|
-
} else {
|
|
24
|
-
"/"
|
|
1
|
+
use std::borrow::Cow;
|
|
2
|
+
|
|
3
|
+
use crate::path_parsing::Rules;
|
|
4
|
+
|
|
5
|
+
// Ruby's `File.dirname`
|
|
6
|
+
pub fn dirname(rules: Rules, path: &[u8]) -> Cow<'_, [u8]> {
|
|
7
|
+
let mut name = 0;
|
|
8
|
+
let mut root = rules.skip_root(path);
|
|
9
|
+
if rules.is_dosish() {
|
|
10
|
+
// Keep a UNC prefix
|
|
11
|
+
if root > 1 && rules.is_sep(path[0]) {
|
|
12
|
+
name = root - 2;
|
|
13
|
+
root = name + rules.skip_prefix(&path[name..]);
|
|
14
|
+
}
|
|
15
|
+
} else if root > 1 {
|
|
16
|
+
// Leading separators become one
|
|
17
|
+
name = root - 1;
|
|
18
|
+
}
|
|
19
|
+
let end = rules.last_separator(&path[root..]).map_or(root, |pos| root + pos);
|
|
20
|
+
if end == name {
|
|
21
|
+
return Cow::Borrowed(b".");
|
|
25
22
|
}
|
|
23
|
+
let path = &path[name..];
|
|
24
|
+
let end = end - name;
|
|
25
|
+
let root = root - name;
|
|
26
|
+
if rules.has_drive_letter(path) {
|
|
27
|
+
if path.len() > 2 && rules.is_sep(path[2]) {
|
|
28
|
+
// "C:" and one separator, then the rest
|
|
29
|
+
let top = 2 + rules.skip_root(&path[2..]);
|
|
30
|
+
return Cow::Owned([&path[..3], &path[top..end.max(top)]].concat());
|
|
31
|
+
}
|
|
32
|
+
if root == 2 && end == 2 {
|
|
33
|
+
return Cow::Owned([&path[..2], b"."].concat());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
Cow::Borrowed(&path[..end])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[cfg(test)]
|
|
40
|
+
fn dirname_str(path: &str) -> String {
|
|
41
|
+
String::from_utf8(dirname(Rules::UNIX, path.as_bytes()).into_owned()).unwrap()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[cfg(test)]
|
|
45
|
+
fn windows_dirname(path: &str) -> String {
|
|
46
|
+
String::from_utf8(dirname(Rules::WINDOWS, path.as_bytes()).into_owned()).unwrap()
|
|
26
47
|
}
|
|
27
48
|
|
|
28
49
|
#[test]
|
|
29
50
|
fn absolute() {
|
|
30
|
-
assert_eq!(
|
|
51
|
+
assert_eq!(dirname_str("/a/b///c"), "/a/b");
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
#[test]
|
|
34
55
|
fn trailing_slashes_absolute() {
|
|
35
|
-
assert_eq!(
|
|
56
|
+
assert_eq!(dirname_str("/a/b///c//////"), "/a/b");
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
#[test]
|
|
39
60
|
fn relative() {
|
|
40
|
-
assert_eq!(
|
|
61
|
+
assert_eq!(dirname_str("b///c"), "b");
|
|
41
62
|
}
|
|
42
63
|
|
|
43
64
|
#[test]
|
|
44
65
|
fn trailing_slashes_relative() {
|
|
45
|
-
assert_eq!(
|
|
66
|
+
assert_eq!(dirname_str("b/c//"), "b");
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
#[test]
|
|
49
70
|
fn root() {
|
|
50
|
-
assert_eq!(
|
|
71
|
+
assert_eq!(dirname_str("//c"), "/");
|
|
51
72
|
}
|
|
52
73
|
|
|
53
74
|
#[test]
|
|
54
75
|
fn trailing_slashes_root() {
|
|
55
|
-
assert_eq!(
|
|
76
|
+
assert_eq!(dirname_str("//c//"), "/");
|
|
56
77
|
}
|
|
57
78
|
|
|
58
79
|
#[test]
|
|
59
80
|
fn trailing_slashes_relative_root() {
|
|
60
|
-
assert_eq!(
|
|
81
|
+
assert_eq!(dirname_str("c//"), ".");
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
#[test]
|
|
64
85
|
fn returns_dot_for_empty_string() {
|
|
65
|
-
assert_eq!(
|
|
86
|
+
assert_eq!(dirname_str(""), ".");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn only_separators() {
|
|
91
|
+
assert_eq!(dirname_str("/"), "/");
|
|
92
|
+
assert_eq!(dirname_str("///"), "/");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[test]
|
|
96
|
+
fn non_utf8() {
|
|
97
|
+
assert_eq!(dirname(Rules::UNIX, b"\xff\xfe/a").as_ref(), b"\xff\xfe");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn windows_separators() {
|
|
102
|
+
assert_eq!(windows_dirname("a\\b"), "a");
|
|
103
|
+
assert_eq!(windows_dirname("a\\b\\"), "a");
|
|
104
|
+
assert_eq!(windows_dirname("a/b\\c"), "a/b");
|
|
105
|
+
assert_eq!(dirname_str("a\\b"), ".");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn windows_drive_letters() {
|
|
110
|
+
assert_eq!(windows_dirname("C:"), "C:.");
|
|
111
|
+
assert_eq!(windows_dirname("C:a"), "C:.");
|
|
112
|
+
assert_eq!(windows_dirname("C:/"), "C:/");
|
|
113
|
+
assert_eq!(windows_dirname("C:/a"), "C:/");
|
|
114
|
+
assert_eq!(windows_dirname("C://a"), "C:/");
|
|
115
|
+
assert_eq!(windows_dirname("C:/a/b"), "C:/a");
|
|
116
|
+
assert_eq!(windows_dirname("C://a//b"), "C:/a");
|
|
117
|
+
assert_eq!(windows_dirname("C:a/b"), "C:a");
|
|
118
|
+
assert_eq!(dirname_str("C:"), ".");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
fn windows_unc() {
|
|
123
|
+
assert_eq!(windows_dirname("//"), "//");
|
|
124
|
+
assert_eq!(windows_dirname("//a"), "//a");
|
|
125
|
+
assert_eq!(windows_dirname("//a/b"), "//a/b");
|
|
126
|
+
assert_eq!(windows_dirname("//a/b/c"), "//a/b");
|
|
127
|
+
assert_eq!(windows_dirname("//a/b/c/d"), "//a/b/c");
|
|
128
|
+
assert_eq!(windows_dirname("///a"), "//a");
|
|
129
|
+
assert_eq!(dirname_str("//a/b"), "/a");
|
|
66
130
|
}
|
data/src/extname.rs
CHANGED
|
@@ -1,25 +1,155 @@
|
|
|
1
|
-
use std::
|
|
2
|
-
use path_parsing::{SEP, find_last_non_sep_pos};
|
|
1
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
use crate::path_parsing::Rules;
|
|
4
|
+
|
|
5
|
+
// Ruby 2.7 changed `File.extname` to return an extension of only a dot
|
|
6
|
+
// (`"foo."` on Unix, `"foo./"` on both Unix and Windows, where trailing dots
|
|
7
|
+
// aren't part of a file name) instead of `""`. `FasterPath` follows the Ruby
|
|
8
|
+
// it is loaded into; see `Init_faster_pathname`.
|
|
9
|
+
static TRAILING_DOT_IS_EXTNAME: AtomicBool = AtomicBool::new(false);
|
|
10
|
+
|
|
11
|
+
pub fn set_trailing_dot_is_extname(value: bool) {
|
|
12
|
+
TRAILING_DOT_IS_EXTNAME.store(value, Ordering::Relaxed);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Ruby's `File.extname`
|
|
16
|
+
pub fn extname(rules: Rules, path: &[u8]) -> &[u8] {
|
|
17
|
+
extname_with(rules, path, TRAILING_DOT_IS_EXTNAME.load(Ordering::Relaxed))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Ruby's `ruby_enc_find_extname`
|
|
21
|
+
fn extname_with(rules: Rules, path: &[u8], trailing_dot_is_extname: bool) -> &[u8] {
|
|
22
|
+
let end = path.len();
|
|
23
|
+
// The last component
|
|
24
|
+
let mut p = match rules.last_separator(path) {
|
|
25
|
+
Some(pos) => {
|
|
26
|
+
let mut p = pos;
|
|
27
|
+
while p < end && rules.is_sep(path[p]) {
|
|
28
|
+
p += 1;
|
|
19
29
|
}
|
|
20
|
-
|
|
21
|
-
_ => {}
|
|
30
|
+
p
|
|
22
31
|
}
|
|
32
|
+
None => 0,
|
|
23
33
|
};
|
|
24
|
-
|
|
34
|
+
let name = p;
|
|
35
|
+
|
|
36
|
+
// Leading dots don't start an extension
|
|
37
|
+
while p < end && path[p] == b'.' {
|
|
38
|
+
p += 1;
|
|
39
|
+
}
|
|
40
|
+
let mut dot = None;
|
|
41
|
+
while p < end {
|
|
42
|
+
let c = path[p];
|
|
43
|
+
if c == b'.' || (rules.is_dosish() && c == b' ') {
|
|
44
|
+
if !rules.is_dosish() {
|
|
45
|
+
dot = Some(p);
|
|
46
|
+
} else {
|
|
47
|
+
// NTFS ignores trailing dots and spaces
|
|
48
|
+
let last = p;
|
|
49
|
+
let mut last_dot = last;
|
|
50
|
+
p += 1;
|
|
51
|
+
while p < end && (path[p] == b'.' || path[p] == b' ') {
|
|
52
|
+
if path[p] == b'.' {
|
|
53
|
+
last_dot = p;
|
|
54
|
+
}
|
|
55
|
+
p += 1;
|
|
56
|
+
}
|
|
57
|
+
if p >= end || rules.is_ads(path[p]) {
|
|
58
|
+
p = last;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
if c == b'.' || last_dot > last {
|
|
62
|
+
dot = Some(last_dot);
|
|
63
|
+
}
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
} else if rules.is_ads(c) || rules.is_sep(c) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
p += 1;
|
|
70
|
+
}
|
|
71
|
+
match dot {
|
|
72
|
+
Some(dot) if dot != name && dot + 1 == p => if trailing_dot_is_extname { &path[dot..p] } else { b"" },
|
|
73
|
+
Some(dot) if dot != name => &path[dot..p],
|
|
74
|
+
_ => b"",
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[cfg(test)]
|
|
79
|
+
fn extname_str(path: &str, trailing_dot_is_extname: bool) -> &str {
|
|
80
|
+
std::str::from_utf8(extname_with(Rules::UNIX, path.as_bytes(), trailing_dot_is_extname)).unwrap()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[cfg(test)]
|
|
84
|
+
fn windows_extname(path: &str) -> &str {
|
|
85
|
+
std::str::from_utf8(extname_with(Rules::WINDOWS, path.as_bytes(), true)).unwrap()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn it_finds_the_extension() {
|
|
90
|
+
for &trailing_dot in &[false, true] {
|
|
91
|
+
assert_eq!(extname_str("", trailing_dot), "");
|
|
92
|
+
assert_eq!(extname_str("/", trailing_dot), "");
|
|
93
|
+
assert_eq!(extname_str("a.rb", trailing_dot), ".rb");
|
|
94
|
+
assert_eq!(extname_str("a.rb/", trailing_dot), ".rb");
|
|
95
|
+
assert_eq!(extname_str("a.rb//", trailing_dot), ".rb");
|
|
96
|
+
assert_eq!(extname_str("a/b.c.d", trailing_dot), ".d");
|
|
97
|
+
assert_eq!(extname_str("a..b", trailing_dot), ".b");
|
|
98
|
+
assert_eq!(extname_str(".a.b", trailing_dot), ".b");
|
|
99
|
+
assert_eq!(extname_str("a/.rb", trailing_dot), "");
|
|
100
|
+
assert_eq!(extname_str("a.b/c", trailing_dot), "");
|
|
101
|
+
assert_eq!(extname_str(".bashrc", trailing_dot), "");
|
|
102
|
+
assert_eq!(extname_str("..foo", trailing_dot), "");
|
|
103
|
+
assert_eq!(extname_str("a/..b", trailing_dot), "");
|
|
104
|
+
assert_eq!(extname_str("...a", trailing_dot), "");
|
|
105
|
+
assert_eq!(extname_str("a.rb ", trailing_dot), ".rb ");
|
|
106
|
+
assert_eq!(extname_str("a\\b.c", trailing_dot), ".c");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[test]
|
|
111
|
+
fn only_dots() {
|
|
112
|
+
for &trailing_dot in &[false, true] {
|
|
113
|
+
assert_eq!(extname_str(".", trailing_dot), "");
|
|
114
|
+
assert_eq!(extname_str("..", trailing_dot), "");
|
|
115
|
+
assert_eq!(extname_str("....", trailing_dot), "");
|
|
116
|
+
assert_eq!(extname_str("/.", trailing_dot), "");
|
|
117
|
+
assert_eq!(extname_str(".a/", trailing_dot), "");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
fn trailing_dot() {
|
|
123
|
+
assert_eq!(extname_str("foo.", false), "");
|
|
124
|
+
assert_eq!(extname_str(".foo.", false), "");
|
|
125
|
+
assert_eq!(extname_str("foo..", false), "");
|
|
126
|
+
assert_eq!(extname_str("a./", false), "");
|
|
127
|
+
assert_eq!(extname_str("foo.", true), ".");
|
|
128
|
+
assert_eq!(extname_str(".foo.", true), ".");
|
|
129
|
+
assert_eq!(extname_str("foo..", true), ".");
|
|
130
|
+
assert_eq!(extname_str("a./", true), ".");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn non_utf8() {
|
|
135
|
+
assert_eq!(extname_with(Rules::UNIX, b"\xff.\xfe", false), b".\xfe");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[test]
|
|
139
|
+
fn windows() {
|
|
140
|
+
assert_eq!(windows_extname("a.rb"), ".rb");
|
|
141
|
+
assert_eq!(windows_extname("a.b\\c"), "");
|
|
142
|
+
assert_eq!(windows_extname("a\\b.c"), ".c");
|
|
143
|
+
assert_eq!(windows_extname("a.rb\\"), ".rb");
|
|
144
|
+
assert_eq!(windows_extname("foo."), "");
|
|
145
|
+
assert_eq!(windows_extname("foo.rb."), ".rb");
|
|
146
|
+
assert_eq!(windows_extname("foo.rb "), ".rb");
|
|
147
|
+
assert_eq!(windows_extname("foo.rb. ."), ".rb");
|
|
148
|
+
assert_eq!(windows_extname("foo.rb::$DATA"), ".rb");
|
|
149
|
+
assert_eq!(windows_extname("foo.rb::$DATA.bar"), ".rb");
|
|
150
|
+
assert_eq!(windows_extname("foo .ext"), ".ext");
|
|
151
|
+
assert_eq!(windows_extname("foo. .ext"), ".ext");
|
|
152
|
+
assert_eq!(windows_extname("foo.ext .ext"), ".ext");
|
|
153
|
+
assert_eq!(windows_extname("foo .ext "), ".ext");
|
|
154
|
+
assert_eq!(windows_extname(".bashrc"), "");
|
|
25
155
|
}
|