mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
refactor: add tests for Pattern::match_path (#1837)
This commit is contained in:
parent
56f9a6a1eb
commit
96bcd820b8
3 changed files with 128 additions and 4 deletions
2
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
2
.github/DISCUSSION_TEMPLATE/1-q-a.yml
vendored
|
|
@ -48,7 +48,7 @@ body:
|
|||
id: validations
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you have completed the following
|
||||
description: Before submitting the post, please make sure you have completed the following
|
||||
options:
|
||||
- label: I have searched the existing discussions/issues
|
||||
required: true
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ pub struct Pattern {
|
|||
inner: globset::GlobMatcher,
|
||||
is_dir: bool,
|
||||
is_star: bool,
|
||||
#[cfg(windows)]
|
||||
sep_lit: bool,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
|
|
@ -19,7 +21,20 @@ impl Pattern {
|
|||
|
||||
#[inline]
|
||||
pub fn match_path(&self, path: impl AsRef<Path>, is_dir: bool) -> bool {
|
||||
is_dir == self.is_dir && (self.is_star || self.inner.is_match(path))
|
||||
if is_dir != self.is_dir {
|
||||
return false;
|
||||
} else if self.is_star {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
let path = if self.sep_lit {
|
||||
yazi_shared::fs::backslash_to_slash(path.as_ref())
|
||||
} else {
|
||||
std::borrow::Cow::Borrowed(path.as_ref())
|
||||
};
|
||||
|
||||
self.inner.is_match(path)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -35,16 +50,23 @@ impl FromStr for Pattern {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let a = s.trim_start_matches("\\s");
|
||||
let b = a.trim_end_matches('/');
|
||||
let sep_lit = b.contains('/');
|
||||
|
||||
let inner = GlobBuilder::new(b)
|
||||
.case_insensitive(a.len() == s.len())
|
||||
.literal_separator(true)
|
||||
.literal_separator(sep_lit)
|
||||
.backslash_escape(false)
|
||||
.empty_alternates(true)
|
||||
.build()?
|
||||
.compile_matcher();
|
||||
|
||||
Ok(Self { inner, is_dir: b.len() < a.len(), is_star: b == "*" })
|
||||
Ok(Self {
|
||||
inner,
|
||||
is_dir: b.len() < a.len(),
|
||||
is_star: b == "*",
|
||||
#[cfg(windows)]
|
||||
sep_lit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -53,3 +75,84 @@ impl TryFrom<String> for Pattern {
|
|||
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> { Self::from_str(s.as_str()) }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn matches(glob: &str, path: &str) -> bool {
|
||||
Pattern::from_str(glob).unwrap().match_path(path, false)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_unix() {
|
||||
// Wildcard
|
||||
assert!(matches("*", "/foo"));
|
||||
assert!(matches("*", "/foo/bar"));
|
||||
assert!(matches("**", "foo"));
|
||||
assert!(matches("**", "/foo"));
|
||||
assert!(matches("**", "/foo/bar"));
|
||||
|
||||
// Filename
|
||||
assert!(matches("*.md", "foo.md"));
|
||||
assert!(matches("*.md", "/foo.md"));
|
||||
assert!(matches("*.md", "/foo/bar.md"));
|
||||
|
||||
// 1-star
|
||||
assert!(matches("/*", "/foo"));
|
||||
assert!(matches("/*/*.md", "/foo/bar.md"));
|
||||
|
||||
// 2-star
|
||||
assert!(matches("/**", "/foo"));
|
||||
assert!(matches("/**", "/foo/bar"));
|
||||
assert!(matches("**/**", "/foo"));
|
||||
assert!(matches("**/**", "/foo/bar"));
|
||||
assert!(matches("/**/*", "/foo"));
|
||||
assert!(matches("/**/*", "/foo/bar"));
|
||||
|
||||
// Failures
|
||||
assert!(!matches("/*/*", "/foo"));
|
||||
assert!(!matches("/*/*.md", "/foo.md"));
|
||||
assert!(!matches("/*", "/foo/bar"));
|
||||
assert!(!matches("/*.md", "/foo/bar.md"));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_windows() {
|
||||
// Wildcard
|
||||
assert!(matches("*", r#"C:\foo"#));
|
||||
assert!(matches("*", r#"C:\foo\bar"#));
|
||||
assert!(matches("**", r#"foo"#));
|
||||
assert!(matches("**", r#"C:\foo"#));
|
||||
assert!(matches("**", r#"C:\foo\bar"#));
|
||||
|
||||
// Filename
|
||||
assert!(matches("*.md", r#"foo.md"#));
|
||||
assert!(matches("*.md", r#"C:\foo.md"#));
|
||||
assert!(matches("*.md", r#"C:\foo\bar.md"#));
|
||||
|
||||
// 1-star
|
||||
assert!(matches(r#"C:/*"#, r#"C:\foo"#));
|
||||
assert!(matches(r#"C:/*/*.md"#, r#"C:\foo\bar.md"#));
|
||||
|
||||
// 2-star
|
||||
assert!(matches(r#"C:/**"#, r#"C:\foo"#));
|
||||
assert!(matches(r#"C:/**"#, r#"C:\foo\bar"#));
|
||||
assert!(matches(r#"**/**"#, r#"C:\foo"#));
|
||||
assert!(matches(r#"**/**"#, r#"C:\foo\bar"#));
|
||||
assert!(matches(r#"C:/**/*"#, r#"C:\foo"#));
|
||||
assert!(matches(r#"C:/**/*"#, r#"C:\foo\bar"#));
|
||||
|
||||
// Drive letter
|
||||
assert!(matches(r#"*:/*"#, r#"C:\foo"#));
|
||||
assert!(matches(r#"*:/**/*.md"#, r#"C:\foo\bar.md"#));
|
||||
|
||||
// Failures
|
||||
assert!(!matches(r#"C:/*/*"#, r#"C:\foo"#));
|
||||
assert!(!matches(r#"C:/*/*.md"#, r#"C:\foo.md"#));
|
||||
assert!(!matches(r#"C:/*"#, r#"C:\foo\bar"#));
|
||||
assert!(!matches(r#"C:/*.md"#, r#"C:\foo\bar.md"#));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,27 @@ pub fn path_relative_to<'a>(path: &'a Path, root: &Path) -> Cow<'a, Path> {
|
|||
Cow::from(buf)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn backslash_to_slash(p: &Path) -> Cow<Path> {
|
||||
let bytes = p.as_os_str().as_encoded_bytes();
|
||||
|
||||
// Fast path to skip if there are no backslashes
|
||||
let skip_len = bytes.iter().take_while(|&&b| b != b'\\').count();
|
||||
if skip_len >= bytes.len() {
|
||||
return Cow::Borrowed(p);
|
||||
}
|
||||
|
||||
let (skip, rest) = bytes.split_at(skip_len);
|
||||
let mut out = Vec::new();
|
||||
out.try_reserve_exact(bytes.len()).unwrap_or_else(|_| panic!());
|
||||
out.extend(skip);
|
||||
|
||||
for &b in rest {
|
||||
out.push(if b == b'\\' { b'/' } else { b });
|
||||
}
|
||||
Cow::Owned(PathBuf::from(unsafe { OsString::from_encoded_bytes_unchecked(out) }))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue