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 prefix, 8 /// file_name will return None. 9 pub 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 = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0); 16 Some(match *path { 17 Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]), 18 Cow::Owned(ref path) => { 19 let mut path = 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. 42 pub 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 = 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)] 63 pub 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 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)] 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