1use std::borrow::Cow;
2
3use 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
8/// prefix, file_name will return None.
9pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
10 if path.last_byte().map_or(default:true, |b: u8| b == b'.') {
11 return None;
12 }
13 let last_slash: usize = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(default:0);
14 Some(match *path {
15 Cow::Borrowed(path: &[u8]) => Cow::Borrowed(&path[last_slash..]),
16 Cow::Owned(ref path: &Vec) => {
17 let mut path: Vec = path.clone();
18 path.drain_bytes(..last_slash);
19 Cow::Owned(path)
20 }
21 })
22}
23
24/// Return a file extension given a path's file name.
25///
26/// Note that this does NOT match the semantics of std::path::Path::extension.
27/// Namely, the extension includes the `.` and matching is otherwise more
28/// liberal. Specifically, the extension is:
29///
30/// * None, if the file name given is empty;
31/// * None, if there is no embedded `.`;
32/// * Otherwise, the portion of the file name starting with the final `.`.
33///
34/// e.g., A file name of `.rs` has an extension `.rs`.
35///
36/// N.B. This is done to make certain glob match optimizations easier. Namely,
37/// a pattern like `*.rs` is obviously trying to match files with a `rs`
38/// extension, but it also matches files like `.rs`, which doesn't have an
39/// extension according to std::path::Path::extension.
40pub(crate) fn file_name_ext<'a>(
41 name: &Cow<'a, [u8]>,
42) -> 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)]
63pub(crate) 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))]
71pub(crate) 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(char::from(path[i])) {
76 continue;
77 }
78 path.to_mut()[i] = b'/';
79 }
80 path
81}
82
83#[cfg(test)]
84mod 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