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