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: usize = path.rfind_byte(b'/' ).map(|i| i + 1).unwrap_or(default:0); |
16 | Some(match *path { |
17 | Cow::Borrowed(path: &[u8]) => Cow::Borrowed(&path[last_slash..]), |
18 | Cow::Owned(ref path: &Vec) => { |
19 | let mut path: Vec = 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: usize = match name.rfind_byte(b'.' ) { |
47 | None => return None, |
48 | Some(i: usize) => i, |
49 | }; |
50 | Some(match *name { |
51 | Cow::Borrowed(name: &[u8]) => Cow::Borrowed(&name[last_dot_at..]), |
52 | Cow::Owned(ref name: &Vec) => { |
53 | let mut name: Vec = 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 | |