1use std::borrow::Cow;
23use bstr::{ByteSlice, ByteVec};
45/// 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]>> {
10if path.last_byte().map_or(true, |b| b == b'.') {
11return None;
12 }
13let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
14Some(match *path {
15 Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]),
16 Cow::Owned(ref path) => {
17let mut path = path.clone();
18 path.drain_bytes(..last_slash);
19 Cow::Owned(path)
20 }
21 })
22}
2324/// 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]>> {
43if name.is_empty() {
44return None;
45 }
46let last_dot_at = match name.rfind_byte(b'.') {
47None => return None,
48Some(i) => i,
49 };
50Some(match *name {
51 Cow::Borrowed(name) => Cow::Borrowed(&name[last_dot_at..]),
52 Cow::Owned(ref name) => {
53let mut name = name.clone();
54 name.drain_bytes(..last_dot_at);
55 Cow::Owned(name)
56 }
57 })
58}
5960/// 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.
65path
66}
6768/// 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]> {
72use std::path::is_separator;
7374for i in 0..path.len() {
75if path[i] == b'/' || !is_separator(char::from(path[i])) {
76continue;
77 }
78 path.to_mut()[i] = b'/';
79 }
80 path
81}
8283#[cfg(test)]
84mod tests {
85use std::borrow::Cow;
8687use bstr::{ByteVec, B};
8889use super::{file_name_ext, normalize_path};
9091macro_rules! ext {
92 ($name:ident, $file_name:expr, $ext:expr) => {
93#[test]
94fn $name() {
95let bs = Vec::from($file_name);
96let got = file_name_ext(&Cow::Owned(bs));
97assert_eq!($ext.map(|s| Cow::Borrowed(B(s))), got);
98 }
99 };
100 }
101102ext!(ext1, "foo.rs", Some(".rs"));
103ext!(ext2, ".rs", Some(".rs"));
104ext!(ext3, "..rs", Some(".rs"));
105ext!(ext4, "", None::<&str>);
106ext!(ext5, "foo", None::<&str>);
107108macro_rules! normalize {
109 ($name:ident, $path:expr, $expected:expr) => {
110#[test]
111fn $name() {
112let bs = Vec::from_slice($path);
113let got = normalize_path(Cow::Owned(bs));
114assert_eq!($expected.to_vec(), got.into_owned());
115 }
116 };
117 }
118119normalize!(normal1, b"foo", b"foo");
120normalize!(normal2, b"foo/bar", b"foo/bar");
121#[cfg(unix)]
122normalize!(normal3, b"foo\\bar", b"foo\\bar");
123#[cfg(not(unix))]
124normalize!(normal3, b"foo\\bar", b"foo/bar");
125#[cfg(unix)]
126normalize!(normal4, b"foo\\bar/baz", b"foo\\bar/baz");
127#[cfg(not(unix))]
128normalize!(normal4, b"foo\\bar/baz", b"foo/bar/baz");
129}