globset/
pathutil.rs

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(true, |b| b == b'.') {
11        return None;
12    }
13    let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
14    Some(match *path {
15        Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]),
16        Cow::Owned(ref path) => {
17            let mut path = 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 = 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)]
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}