summaryrefslogtreecommitdiffstats
path: root/crates/globset/src/pathutil.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/globset/src/pathutil.rs')
-rw-r--r--crates/globset/src/pathutil.rs129
1 files changed, 129 insertions, 0 deletions
diff --git a/crates/globset/src/pathutil.rs b/crates/globset/src/pathutil.rs
new file mode 100644
index 00000000..26b496d3
--- /dev/null
+++ b/crates/globset/src/pathutil.rs
@@ -0,0 +1,129 @@
+use std::borrow::Cow;
+
+use bstr::{ByteSlice, ByteVec};
+
+/// The final component of the path, if it is a normal file.
+///
+/// If the path terminates in ., .., or consists solely of a root of prefix,
+/// file_name will return None.
+pub fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
+ if path.is_empty() {
+ return None;
+ } else if path.last_byte() == Some(b'.') {
+ return None;
+ }
+ let last_slash = path.rfind_byte(b'/').map(|i| i + 1).unwrap_or(0);
+ Some(match *path {
+ Cow::Borrowed(path) => Cow::Borrowed(&path[last_slash..]),
+ Cow::Owned(ref path) => {
+ let mut path = path.clone();
+ path.drain_bytes(..last_slash);
+ Cow::Owned(path)
+ }
+ })
+}
+
+/// Return a file extension given a path's file name.
+///
+/// Note that this does NOT match the semantics of std::path::Path::extension.
+/// Namely, the extension includes the `.` and matching is otherwise more
+/// liberal. Specifically, the extenion is:
+///
+/// * None, if the file name given is empty;
+/// * None, if there is no embedded `.`;
+/// * Otherwise, the portion of the file name starting with the final `.`.
+///
+/// e.g., A file name of `.rs` has an extension `.rs`.
+///
+/// N.B. This is done to make certain glob match optimizations easier. Namely,
+/// a pattern like `*.rs` is obviously trying to match files with a `rs`
+/// extension, but it also matches files like `.rs`, which doesn't have an
+/// extension according to std::path::Path::extension.
+pub fn file_name_ext<'a>(name: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
+ if name.is_empty() {
+ return None;
+ }
+ let last_dot_at = match name.rfind_byte(b'.') {
+ None => return None,
+ Some(i) => i,
+ };
+ Some(match *name {
+ Cow::Borrowed(name) => Cow::Borrowed(&name[last_dot_at..]),
+ Cow::Owned(ref name) => {
+ let mut name = name.clone();
+ name.drain_bytes(..last_dot_at);
+ Cow::Owned(name)
+ }
+ })
+}
+
+/// Normalizes a path to use `/` as a separator everywhere, even on platforms
+/// that recognize other characters as separators.
+#[cfg(unix)]
+pub fn normalize_path(path: Cow<[u8]>) -> Cow<[u8]> {
+ // UNIX only uses /, so we're good.
+ path
+}
+
+/// Normalizes a path to use `/` as a separator everywhere, even on platforms
+/// that recognize other characters as separators.
+#[cfg(not(unix))]
+pub fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
+ use std::path::is_separator;
+
+ for i in 0..path.len() {
+ if path[i] == b'/' || !is_separator(path[i] as char) {
+ continue;
+ }
+ path.to_mut()[i] = b'/';
+ }
+ path
+}
+
+#[cfg(test)]
+mod tests {
+ use std::borrow::Cow;
+
+ use bstr::{ByteVec, B};
+
+ use super::{file_name_ext, normalize_path};
+
+ macro_rules! ext {
+ ($name:ident, $file_name:expr, $ext:expr) => {
+ #[test]
+ fn $name() {
+ let bs = Vec::from($file_name);
+ let got = file_name_ext(&Cow::Owned(bs));
+ assert_eq!($ext.map(|s| Cow::Borrowed(B(s))), got);
+ }
+ };
+ }
+
+ ext!(ext1, "foo.rs", Some(".rs"));
+ ext!(ext2, ".rs", Some(".rs"));
+ ext!(ext3, "..rs", Some(".rs"));
+ ext!(ext4, "", None::<&str>);
+ ext!(ext5, "foo", None::<&str>);
+
+ macro_rules! normalize {
+ ($name:ident, $path:expr, $expected:expr) => {
+ #[test]
+ fn $name() {
+ let bs = Vec::from_slice($path);
+ let got = normalize_path(Cow::Owned(bs));
+ assert_eq!($expected.to_vec(), got.into_owned());
+ }
+ };
+ }
+
+ normalize!(normal1, b"foo", b"foo");
+ normalize!(normal2, b"foo/bar", b"foo/bar");
+ #[cfg(unix)]
+ normalize!(normal3, b"foo\\bar", b"foo\\bar");
+ #[cfg(not(unix))]
+ normalize!(normal3, b"foo\\bar", b"foo/bar");
+ #[cfg(unix)]
+ normalize!(normal4, b"foo\\bar/baz", b"foo\\bar/baz");
+ #[cfg(not(unix))]
+ normalize!(normal4, b"foo\\bar/baz", b"foo/bar/baz");
+}