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.
@@ -1,96 +1,192 @@
1
1
  use std::borrow::Cow;
2
- use std::path::MAIN_SEPARATOR;
3
- use prepend_prefix::prepend_prefix;
4
- use basename::basename;
5
- use dirname::dirname;
6
- use chop_basename::chop_basename;
7
- use path_parsing::{SEP_STR, contains_sep};
8
-
9
- pub fn cleanpath_conservative(path: &str) -> Cow<str> {
10
- let mut names: Vec<&str> = vec![];
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((ref p, ref base)) = chop_basename(&prefix) {
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
- // // Windows Feature
19
- //
20
- // ```ruby
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
- let last_name = match names.first() {
30
- Some(&name) => name,
31
- None => return dirname(&prefix).into(),
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
- if last_name != ".." && basename(&path, "") == "." {
35
- names.reverse();
36
- names.push(".");
37
- } else if last_name != "." && last_name != ".." &&
38
- chop_basename(path).map(|(a, b)| a.len() + b.len()).unwrap() < path.len() {
39
- return format!("{}{}", last_name, MAIN_SEPARATOR).into();
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
- names.reverse();
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
- prepend_prefix(&prefix, &names.join(&SEP_STR))
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!(cleanpath_conservative("/"), "/");
49
- assert_eq!(cleanpath_conservative(""), ".");
50
- assert_eq!(cleanpath_conservative("."), ".");
51
- assert_eq!(cleanpath_conservative(".."), "..");
52
- assert_eq!(cleanpath_conservative("a"), "a");
53
- assert_eq!(cleanpath_conservative("/."), "/");
54
- assert_eq!(cleanpath_conservative("/.."), "/");
55
- assert_eq!(cleanpath_conservative("/a"), "/a");
56
- assert_eq!(cleanpath_conservative("./"), ".");
57
- assert_eq!(cleanpath_conservative("../"), "..");
58
- assert_eq!(cleanpath_conservative("a/"), "a/");
59
- assert_eq!(cleanpath_conservative("a//b"), "a/b");
60
- assert_eq!(cleanpath_conservative("a/."), "a/.");
61
- assert_eq!(cleanpath_conservative("a/./"), "a/.");
62
- assert_eq!(cleanpath_conservative("a/../"), "a/..");
63
- assert_eq!(cleanpath_conservative("/a/."), "/a/.");
64
- assert_eq!(cleanpath_conservative("./.."), "..");
65
- assert_eq!(cleanpath_conservative("../."), "..");
66
- assert_eq!(cleanpath_conservative("./../"), "..");
67
- assert_eq!(cleanpath_conservative(".././"), "..");
68
- assert_eq!(cleanpath_conservative("/./.."), "/");
69
- assert_eq!(cleanpath_conservative("/../."), "/");
70
- assert_eq!(cleanpath_conservative("/./../"), "/");
71
- assert_eq!(cleanpath_conservative("/.././"), "/");
72
- assert_eq!(cleanpath_conservative("a/b/c"), "a/b/c");
73
- assert_eq!(cleanpath_conservative("./b/c"), "b/c");
74
- assert_eq!(cleanpath_conservative("a/./c"), "a/c");
75
- assert_eq!(cleanpath_conservative("a/b/."), "a/b/.");
76
- assert_eq!(cleanpath_conservative("a/../."), "a/..");
77
- assert_eq!(cleanpath_conservative("/../.././../a"), "/a");
78
- assert_eq!(cleanpath_conservative("a/b/../../../../c/../d"), "a/b/../../../../c/../d");
79
-
80
- // Future Windows Support
81
- //
82
- // DOSISH = File::ALT_SEPARATOR != nil
83
- // DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:."
84
- // DOSISH_UNC = File.dirname("//") == "//"
85
- //
86
- //
87
- // if DOSISH
88
- // assert_eq!(cleanpath_conservative, 'c:/foo/bar', 'c:\\foo\\bar')
89
- // end
90
- //
91
- // if DOSISH_UNC
92
- // assert_eq!(cleanpath_conservative, '//', '//')
93
- // else
94
- // assert_eq!(cleanpath_conservative, '/', '//')
95
- // end
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::str;
2
- use path_parsing::{find_last_sep_pos, find_last_non_sep_pos};
3
-
4
- pub fn dirname(path: &str) -> &str {
5
- let bytes = path.as_bytes();
6
- let mut last_slash_pos = match find_last_sep_pos(bytes) {
7
- Some(pos) => pos,
8
- _ => return ".",
9
- };
10
- // Skip trailing slashes.
11
- if last_slash_pos == bytes.len() - 1 {
12
- let last_non_slash_pos = match find_last_non_sep_pos(&bytes[..last_slash_pos]) {
13
- Some(pos) => pos,
14
- _ => return "/"
15
- };
16
- last_slash_pos = match find_last_sep_pos(&bytes[..last_non_slash_pos]) {
17
- Some(pos) => pos,
18
- _ => return "."
19
- };
20
- };
21
- if let Some(end) = find_last_non_sep_pos(&bytes[..last_slash_pos]) {
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!(dirname("/a/b///c"), "/a/b");
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!(dirname("/a/b///c//////"), "/a/b");
56
+ assert_eq!(dirname_str("/a/b///c//////"), "/a/b");
36
57
  }
37
58
 
38
59
  #[test]
39
60
  fn relative() {
40
- assert_eq!(dirname("b///c"), "b");
61
+ assert_eq!(dirname_str("b///c"), "b");
41
62
  }
42
63
 
43
64
  #[test]
44
65
  fn trailing_slashes_relative() {
45
- assert_eq!(dirname("b/c//"), "b");
66
+ assert_eq!(dirname_str("b/c//"), "b");
46
67
  }
47
68
 
48
69
  #[test]
49
70
  fn root() {
50
- assert_eq!(dirname("//c"), "/");
71
+ assert_eq!(dirname_str("//c"), "/");
51
72
  }
52
73
 
53
74
  #[test]
54
75
  fn trailing_slashes_root() {
55
- assert_eq!(dirname("//c//"), "/");
76
+ assert_eq!(dirname_str("//c//"), "/");
56
77
  }
57
78
 
58
79
  #[test]
59
80
  fn trailing_slashes_relative_root() {
60
- assert_eq!(dirname("c//"), ".");
81
+ assert_eq!(dirname_str("c//"), ".");
61
82
  }
62
83
 
63
84
  #[test]
64
85
  fn returns_dot_for_empty_string() {
65
- assert_eq!(dirname(""), ".");
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::str;
2
- use path_parsing::{SEP, find_last_non_sep_pos};
1
+ use std::sync::atomic::{AtomicBool, Ordering};
3
2
 
4
- pub fn extname(path: &str) -> &str {
5
- let end = match find_last_non_sep_pos(path.as_bytes()) {
6
- Some(pos) => pos + 1,
7
- _ => return "",
8
- };
9
- let bytes = &path.as_bytes()[..end];
10
- for (pos, c) in bytes.iter().enumerate().rev() {
11
- match *c {
12
- b'.' => {
13
- let prev = bytes.get(pos - 1);
14
- if pos == end - 1 || prev == None || prev == Some(&SEP) {
15
- return "";
16
- } else {
17
- return &path[pos..end]
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
- SEP => return "",
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
  }