]>
Commit | Line | Data |
---|---|---|
dfeec247 XL |
1 | use std::borrow::Cow; |
2 | ||
f035d41b | 3 | use bstr::{ByteSlice, ByteVec}; |
dfeec247 XL |
4 | |
5 | /// The final component of the path, if it is a normal file. | |
6 | /// | |
7 | /// If the path terminates in ., .., or consists solely of a root of prefix, | |
8 | /// file_name will return None. | |
f035d41b | 9 | pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> { |
dfeec247 XL |
10 | if path.is_empty() { |
11 | return None; | |
f035d41b | 12 | } else if path.last_byte() == Some(b'.') { |
dfeec247 XL |
13 | return None; |
14 | } | |
15 | let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0); | |
16 | Some(match *path { | |
17 | Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]), | |
18 | Cow::Owned(ref path) => { | |
19 | let mut path = path.clone(); | |
20 | path.drain_bytes(..last_slash); | |
21 | Cow::Owned(path) | |
22 | } | |
23 | }) | |
24 | } | |
25 | ||
26 | /// Return a file extension given a path's file name. | |
27 | /// | |
28 | /// Note that this does NOT match the semantics of std::path::Path::extension. | |
29 | /// Namely, the extension includes the `.` and matching is otherwise more | |
30 | /// liberal. Specifically, the extenion is: | |
31 | /// | |
32 | /// * None, if the file name given is empty; | |
33 | /// * None, if there is no embedded `.`; | |
34 | /// * Otherwise, the portion of the file name starting with the final `.`. | |
35 | /// | |
36 | /// e.g., A file name of `.rs` has an extension `.rs`. | |
37 | /// | |
38 | /// N.B. This is done to make certain glob match optimizations easier. Namely, | |
39 | /// a pattern like `*.rs` is obviously trying to match files with a `rs` | |
40 | /// extension, but it also matches files like `.rs`, which doesn't have an | |
41 | /// extension according to std::path::Path::extension. | |
f035d41b | 42 | pub fn file_name_ext<'a>(name: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> { |
dfeec247 XL |
43 | if name.is_empty() { |
44 | return None; | |
45 | } | |
46 | let last_dot_at = match name.rfind_byte(b'.') { | |
47 | None => return None, | |
48 | Some(i) => i, | |
49 | }; | |
50 | Some(match *name { | |
51 | Cow::Borrowed(name) => Cow::Borrowed(&name[last_dot_at..]), | |
52 | Cow::Owned(ref name) => { | |
53 | let mut name = name.clone(); | |
54 | name.drain_bytes(..last_dot_at); | |
55 | Cow::Owned(name) | |
56 | } | |
57 | }) | |
58 | } | |
59 | ||
60 | /// Normalizes a path to use `/` as a separator everywhere, even on platforms | |
61 | /// that recognize other characters as separators. | |
62 | #[cfg(unix)] | |
064997fb | 63 | pub fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> { |
dfeec247 XL |
64 | // UNIX only uses /, so we're good. |
65 | path | |
66 | } | |
67 | ||
68 | /// Normalizes a path to use `/` as a separator everywhere, even on platforms | |
69 | /// that recognize other characters as separators. | |
70 | #[cfg(not(unix))] | |
f035d41b | 71 | pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> { |
dfeec247 XL |
72 | use std::path::is_separator; |
73 | ||
74 | for i in 0..path.len() { | |
75 | if path[i] == b'/' || !is_separator(path[i] as char) { | |
76 | continue; | |
77 | } | |
78 | path.to_mut()[i] = b'/'; | |
79 | } | |
80 | path | |
81 | } | |
82 | ||
83 | #[cfg(test)] | |
84 | mod tests { | |
85 | use std::borrow::Cow; | |
86 | ||
f035d41b | 87 | use bstr::{ByteVec, B}; |
dfeec247 XL |
88 | |
89 | use super::{file_name_ext, normalize_path}; | |
90 | ||
91 | macro_rules! ext { | |
92 | ($name:ident, $file_name:expr, $ext:expr) => { | |
93 | #[test] | |
94 | fn $name() { | |
f035d41b | 95 | let bs = Vec::from($file_name); |
dfeec247 XL |
96 | let got = file_name_ext(&Cow::Owned(bs)); |
97 | assert_eq!($ext.map(|s| Cow::Borrowed(B(s))), got); | |
98 | } | |
99 | }; | |
100 | } | |
101 | ||
102 | ext!(ext1, "foo.rs", Some(".rs")); | |
103 | ext!(ext2, ".rs", Some(".rs")); | |
104 | ext!(ext3, "..rs", Some(".rs")); | |
105 | ext!(ext4, "", None::<&str>); | |
106 | ext!(ext5, "foo", None::<&str>); | |
107 | ||
108 | macro_rules! normalize { | |
109 | ($name:ident, $path:expr, $expected:expr) => { | |
110 | #[test] | |
111 | fn $name() { | |
f035d41b | 112 | let bs = Vec::from_slice($path); |
dfeec247 XL |
113 | let got = normalize_path(Cow::Owned(bs)); |
114 | assert_eq!($expected.to_vec(), got.into_owned()); | |
115 | } | |
116 | }; | |
117 | } | |
118 | ||
119 | normalize!(normal1, b"foo", b"foo"); | |
120 | normalize!(normal2, b"foo/bar", b"foo/bar"); | |
121 | #[cfg(unix)] | |
122 | normalize!(normal3, b"foo\\bar", b"foo\\bar"); | |
123 | #[cfg(not(unix))] | |
124 | normalize!(normal3, b"foo\\bar", b"foo/bar"); | |
125 | #[cfg(unix)] | |
126 | normalize!(normal4, b"foo\\bar/baz", b"foo\\bar/baz"); | |
127 | #[cfg(not(unix))] | |
128 | normalize!(normal4, b"foo\\bar/baz", b"foo/bar/baz"); | |
129 | } |