2016-10-04 20:22:13 -04:00
|
|
|
use std::borrow::Cow;
|
2019-04-04 18:33:41 -04:00
|
|
|
|
2019-06-26 16:47:33 -04:00
|
|
|
use bstr::{ByteSlice, ByteVec};
|
2016-09-30 19:42:41 -04:00
|
|
|
|
|
|
|
/// The final component of the path, if it is a normal file.
|
|
|
|
///
|
2023-09-26 15:01:20 -04:00
|
|
|
/// If the path terminates in `.`, `..`, or consists solely of a root of
|
|
|
|
/// prefix, file_name will return None.
|
|
|
|
pub(crate) fn file_name<'a>(path: &Cow<'a, [u8]>) -> Option<Cow<'a, [u8]>> {
|
|
|
|
if path.last_byte().map_or(true, |b| b == b'.') {
|
2016-09-30 19:42:41 -04:00
|
|
|
return None;
|
|
|
|
}
|
2019-04-04 18:33:41 -04:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
2016-09-30 19:42:41 -04:00
|
|
|
}
|
2016-10-04 20:22:13 -04:00
|
|
|
|
|
|
|
/// 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
|
2022-04-24 17:00:39 +08:00
|
|
|
/// liberal. Specifically, the extension is:
|
2016-10-04 20:22:13 -04:00
|
|
|
///
|
|
|
|
/// * 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.
|
2023-09-26 15:01:20 -04:00
|
|
|
pub(crate) fn file_name_ext<'a>(
|
|
|
|
name: &Cow<'a, [u8]>,
|
|
|
|
) -> Option<Cow<'a, [u8]>> {
|
2016-10-04 20:22:13 -04:00
|
|
|
if name.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2019-04-05 20:40:39 -04:00
|
|
|
let last_dot_at = match name.rfind_byte(b'.') {
|
|
|
|
None => return None,
|
|
|
|
Some(i) => i,
|
globset: remove use of unsafe
This commit removes, in retrospect, a silly use of `unsafe`. In particular,
to extract a file name extension (distinct from how `std` implements it),
we were transmuting an OsStr to its underlying WTF-8 byte representation
and then searching that. This required `unsafe` and relied on an
undocumented std API, so it was a bad choice to make, but everything gets
sacrificed at the Alter of Performance.
The thing I didn't seem to realize at the time was that:
1. On Unix, you can already get the raw byte representation in a manner
that has zero cost.
2. On Windows, paths are already being encoded and copied every which
way. So doing a UTF-8 check and, in rare cases (for invalid UTF-8),
an extra copy, doesn't seem like that much more of an added expense.
Thus, rewrite the extension extraction using safe APIs. On Unix, this
should have identical performance characteristics as the previous
implementation. On Windows, we do pay a higher cost in the UTF-8
check, but Windows is already paying a similar cost a few times over
anyway.
2018-02-10 21:37:13 -05:00
|
|
|
};
|
2019-04-04 18:33:41 -04:00
|
|
|
Some(match *name {
|
globset: remove use of unsafe
This commit removes, in retrospect, a silly use of `unsafe`. In particular,
to extract a file name extension (distinct from how `std` implements it),
we were transmuting an OsStr to its underlying WTF-8 byte representation
and then searching that. This required `unsafe` and relied on an
undocumented std API, so it was a bad choice to make, but everything gets
sacrificed at the Alter of Performance.
The thing I didn't seem to realize at the time was that:
1. On Unix, you can already get the raw byte representation in a manner
that has zero cost.
2. On Windows, paths are already being encoded and copied every which
way. So doing a UTF-8 check and, in rare cases (for invalid UTF-8),
an extra copy, doesn't seem like that much more of an added expense.
Thus, rewrite the extension extraction using safe APIs. On Unix, this
should have identical performance characteristics as the previous
implementation. On Windows, we do pay a higher cost in the UTF-8
check, but Windows is already paying a similar cost a few times over
anyway.
2018-02-10 21:37:13 -05:00
|
|
|
Cow::Borrowed(name) => Cow::Borrowed(&name[last_dot_at..]),
|
2019-04-04 18:33:41 -04:00
|
|
|
Cow::Owned(ref name) => {
|
|
|
|
let mut name = name.clone();
|
|
|
|
name.drain_bytes(..last_dot_at);
|
globset: remove use of unsafe
This commit removes, in retrospect, a silly use of `unsafe`. In particular,
to extract a file name extension (distinct from how `std` implements it),
we were transmuting an OsStr to its underlying WTF-8 byte representation
and then searching that. This required `unsafe` and relied on an
undocumented std API, so it was a bad choice to make, but everything gets
sacrificed at the Alter of Performance.
The thing I didn't seem to realize at the time was that:
1. On Unix, you can already get the raw byte representation in a manner
that has zero cost.
2. On Windows, paths are already being encoded and copied every which
way. So doing a UTF-8 check and, in rare cases (for invalid UTF-8),
an extra copy, doesn't seem like that much more of an added expense.
Thus, rewrite the extension extraction using safe APIs. On Unix, this
should have identical performance characteristics as the previous
implementation. On Windows, we do pay a higher cost in the UTF-8
check, but Windows is already paying a similar cost a few times over
anyway.
2018-02-10 21:37:13 -05:00
|
|
|
Cow::Owned(name)
|
|
|
|
}
|
|
|
|
})
|
2016-10-04 20:22:13 -04:00
|
|
|
}
|
|
|
|
|
2016-10-10 19:16:52 -04:00
|
|
|
/// Normalizes a path to use `/` as a separator everywhere, even on platforms
|
|
|
|
/// that recognize other characters as separators.
|
|
|
|
#[cfg(unix)]
|
2023-09-26 15:01:20 -04:00
|
|
|
pub(crate) fn normalize_path(path: Cow<'_, [u8]>) -> Cow<'_, [u8]> {
|
2016-10-10 19:16:52 -04:00
|
|
|
// 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))]
|
2023-09-26 15:01:20 -04:00
|
|
|
pub(crate) fn normalize_path(mut path: Cow<[u8]>) -> Cow<[u8]> {
|
2016-10-10 19:16:52 -04:00
|
|
|
use std::path::is_separator;
|
|
|
|
|
|
|
|
for i in 0..path.len() {
|
2023-09-26 15:01:20 -04:00
|
|
|
if path[i] == b'/' || !is_separator(char::from(path[i])) {
|
2016-10-10 19:16:52 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
path.to_mut()[i] = b'/';
|
|
|
|
}
|
|
|
|
path
|
|
|
|
}
|
|
|
|
|
2016-10-04 20:22:13 -04:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2016-10-10 19:16:52 -04:00
|
|
|
use std::borrow::Cow;
|
2019-04-04 18:33:41 -04:00
|
|
|
|
2020-02-17 18:08:47 -05:00
|
|
|
use bstr::{ByteVec, B};
|
2016-10-04 20:22:13 -04:00
|
|
|
|
2016-10-10 19:16:52 -04:00
|
|
|
use super::{file_name_ext, normalize_path};
|
2016-10-04 20:22:13 -04:00
|
|
|
|
|
|
|
macro_rules! ext {
|
|
|
|
($name:ident, $file_name:expr, $ext:expr) => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2019-06-26 16:47:33 -04:00
|
|
|
let bs = Vec::from($file_name);
|
2019-04-04 18:33:41 -04:00
|
|
|
let got = file_name_ext(&Cow::Owned(bs));
|
|
|
|
assert_eq!($ext.map(|s| Cow::Borrowed(B(s))), got);
|
2016-10-04 20:22:13 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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>);
|
2016-10-10 19:16:52 -04:00
|
|
|
|
|
|
|
macro_rules! normalize {
|
|
|
|
($name:ident, $path:expr, $expected:expr) => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
2019-06-26 16:47:33 -04:00
|
|
|
let bs = Vec::from_slice($path);
|
2019-04-04 18:33:41 -04:00
|
|
|
let got = normalize_path(Cow::Owned(bs));
|
2016-10-10 19:16:52 -04:00
|
|
|
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");
|
2016-10-04 20:22:13 -04:00
|
|
|
}
|