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 prefix,
8/// file_name will return None.
9pub 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.
42pub 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)]
63pub 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 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)]
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