1 use std::borrow::Cow;
2 
3 use bstr::{ByteSlice, ByteVec};
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.
9 pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
10     if path.is_empty() {
11         return None;
12     } else if path.last_byte() == Some(b'.') {
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.
42 pub fn file_name_ext<'a>(name: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
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)]
63 pub fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
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))]
71 pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
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 
87     use bstr::{ByteVec, B};
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() {
95                 let bs = Vec::from($file_name);
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() {
112                 let bs = Vec::from_slice($path);
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 }
130