Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
#[cfg(test)]
doc_comment::doctest!("../README.md");

use std::cmp;
use std::cmp::Ordering;
use std::error::Error;
use std::ffi::OsString;
Expand Down Expand Up @@ -209,18 +208,20 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternE
// make sure that the pattern is valid first, else early return with error
let _ = Pattern::new(pattern)?;

let mut components = Path::new(pattern).components().peekable();
loop {
match components.peek() {
Some(&Component::Prefix(..)) | Some(&Component::RootDir) => {
components.next();
let mut root_len = 0;
for component in Path::new(pattern).components() {
match component {
Component::Prefix(prefix) => root_len += prefix.as_os_str().len(),
Component::RootDir => {
// UNC prefixes have an implicit root separator. Only count a
// separator if it is actually present in the original pattern.
if pattern[root_len..].starts_with(path::is_separator) {
root_len += 1;
}
}
_ => break,
}
}
let rest = components.map(|s| s.as_os_str()).collect::<PathBuf>();
let normalized_pattern = Path::new(pattern).iter().collect::<PathBuf>();
let root_len = normalized_pattern.to_str().unwrap().len() - rest.to_str().unwrap().len();
let root = if root_len > 0 {
Some(Path::new(&pattern[..root_len]))
} else {
Expand All @@ -244,8 +245,7 @@ pub fn glob_with(pattern: &str, options: MatchOptions) -> Result<Paths, PatternE
let scope = PathWrapper::from_path(scope);

let mut dir_patterns = Vec::new();
let components =
pattern[cmp::min(root_len, pattern.len())..].split_terminator(path::is_separator);
let components = pattern[root_len..].split_terminator(path::is_separator);

for component in components {
dir_patterns.push(Pattern::new(component)?);
Expand Down Expand Up @@ -1146,6 +1146,50 @@ mod test {
assert!(glob("abc[def").err().unwrap().pos == 3);
}

#[cfg(windows)]
#[test]
fn test_unc_root_patterns() {
// UNC prefixes have an implicit root separator, even when there is no
// separator after the share in the original pattern (issue #134).
for (pattern, root, remaining, require_dir) in [
(r"\\*.custom*\*", r"\\*.custom*\*", vec![""], false),
(r"\\*中文*\*", r"\\*中文*\*", vec![""], false),
(r"\\*\*", r"\\*\*", vec![""], false),
("//*.custom*/*", "//*.custom*/*", vec![""], false),
(r"\\*.custom*\*\", r"\\*.custom*\*\", vec![""], true),
(r"\\*中文*\*\*.rs", r"\\*中文*\*\", vec!["*.rs"], false),
(
r"\\*\*\\中文\*.rs",
r"\\*\*\",
vec!["", "中文", "*.rs"],
false,
),
] {
let paths = glob(pattern).unwrap();
assert_eq!(paths.scope.unwrap().path, Path::new(root), "{pattern}");
assert_eq!(
paths
.dir_patterns
.iter()
.map(Pattern::as_str)
.collect::<Vec<_>>(),
remaining,
"{pattern}"
);
assert_eq!(paths.require_dir, require_dir, "{pattern}");
}
}

#[cfg(windows)]
#[test]
fn test_unsupported_verbatim_unc_patterns() {
for pattern in [r"\\?\UNC\*\*", r"\\?\UNC\*\*\*.rs"] {
let mut paths = glob(pattern).unwrap();
assert!(paths.scope.is_none());
assert!(paths.next().is_none());
}
}

// this test assumes that there is a /root directory and that
// the user running this test is not root or otherwise doesn't
// have permission to read its contents
Expand Down
36 changes: 36 additions & 0 deletions tests/glob-std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,40 @@ fn main() {
)
);
}

// Absolute patterns preserve Unicode components and redundant separators.
mk_file("中文", true);
mk_file("中文/一.rs", false);
let absolute_root = root.path().to_str().unwrap();
let expected = vec![root.path().join("中文/一.rs")];
for pattern in [
format!("{absolute_root}/中文/*.rs"),
format!("{absolute_root}//./中文//*.rs"),
] {
assert_eq!(glob_vec(&pattern), expected);
}

#[cfg(windows)]
{
let forward_root = absolute_root.replace('\\', "/");
let repeated_root = format!("{}\\\\{}", &absolute_root[..2], &absolute_root[3..]);
let verbatim_root = root.path().canonicalize().unwrap();
for pattern in [
format!("{absolute_root}\\中文\\*.rs"),
format!("{forward_root}/中文/*.rs"),
format!("{repeated_root}\\中文\\*.rs"),
] {
assert_eq!(glob_vec(&pattern), expected);
}
assert_eq!(
glob_vec(&format!("{}\\中文\\*.rs", verbatim_root.display())),
vec![verbatim_root.join("中文/一.rs")]
);
// A path rooted on the current drive, without a drive prefix.
let rooted = &absolute_root[2..];
assert_eq!(
glob_vec(&format!("{rooted}\\中文\\*.rs")),
vec![PathBuf::from(rooted).join("中文/一.rs")]
);
}
}