mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Generated
-1
@@ -223,7 +223,6 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"filetime",
|
||||
"glob",
|
||||
"once_cell",
|
||||
"onig",
|
||||
"predicates",
|
||||
|
||||
@@ -12,7 +12,6 @@ authors = ["uutils developers"]
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
clap = "2.34"
|
||||
glob = "0.3"
|
||||
walkdir = "2.3"
|
||||
regex = "1.5"
|
||||
once_cell = "1.10"
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
extern crate findutils;
|
||||
extern crate glob;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let strs: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2022 Tavian Barnes
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use onig::{self, Regex, RegexOptions, Syntax};
|
||||
|
||||
/// Parse a string as a POSIX Basic Regular Expression.
|
||||
fn parse_bre(expr: &str, options: RegexOptions) -> Result<Regex, onig::Error> {
|
||||
let bre = Syntax::posix_basic();
|
||||
Regex::with_options(expr, bre.options() | options, bre)
|
||||
}
|
||||
|
||||
/// Push a literal character onto a regex, escaping it if necessary.
|
||||
fn regex_push_literal(regex: &mut String, ch: char) {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_03
|
||||
if matches!(ch, '.' | '[' | '\\' | '*' | '^' | '$') {
|
||||
regex.push('\\');
|
||||
}
|
||||
regex.push(ch);
|
||||
}
|
||||
|
||||
/// Extracts a bracket expression from a glob.
|
||||
fn extract_bracket_expr(pattern: &str) -> Option<(String, &str)> {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01
|
||||
//
|
||||
// If an open bracket introduces a bracket expression as in XBD RE Bracket Expression,
|
||||
// except that the <exclamation-mark> character ( '!' ) shall replace the <circumflex>
|
||||
// character ( '^' ) in its role in a non-matching list in the regular expression notation,
|
||||
// it shall introduce a pattern bracket expression. A bracket expression starting with an
|
||||
// unquoted <circumflex> character produces unspecified results. Otherwise, '[' shall match
|
||||
// the character itself.
|
||||
//
|
||||
// To check for valid bracket expressions, we scan for the closing bracket and
|
||||
// attempt to parse that segment as a regex. If that fails, we treat the '['
|
||||
// literally.
|
||||
|
||||
let mut expr = "[".to_string();
|
||||
|
||||
let mut chars = pattern.chars();
|
||||
let mut next = chars.next();
|
||||
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 3. A non-matching list expression begins with a <circumflex> ( '^' ) ...
|
||||
//
|
||||
// (but in a glob, '!' is used instead of '^')
|
||||
if next == Some('!') {
|
||||
expr.push('^');
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 1. ... The <right-square-bracket> ( ']' ) shall lose its special meaning and represent
|
||||
// itself in a bracket expression if it occurs first in the list (after an initial
|
||||
// <circumflex> ( '^' ), if any).
|
||||
if next == Some(']') {
|
||||
expr.push(']');
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
while let Some(ch) = next {
|
||||
expr.push(ch);
|
||||
|
||||
match ch {
|
||||
'[' => {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 4. A collating symbol is a collating element enclosed within bracket-period
|
||||
// ( "[." and ".]" ) delimiters. ...
|
||||
//
|
||||
// 5. An equivalence class expression shall ... be expressed by enclosing any
|
||||
// one of the collating elements in the equivalence class within bracket-
|
||||
// equal ( "[=" and "=]" ) delimiters.
|
||||
//
|
||||
// 6. ... A character class expression is expressed as a character class name
|
||||
// enclosed within bracket- <colon> ( "[:" and ":]" ) delimiters.
|
||||
next = chars.next();
|
||||
if let Some(delim) = next {
|
||||
expr.push(delim);
|
||||
|
||||
if matches!(delim, '.' | '=' | ':') {
|
||||
let rest = chars.as_str();
|
||||
let end = rest.find([delim, ']'])? + 2;
|
||||
expr.push_str(&rest[..end]);
|
||||
chars = rest[end..].chars();
|
||||
}
|
||||
}
|
||||
}
|
||||
']' => {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
|
||||
//
|
||||
// 1. ... The <right-square-bracket> ( ']' ) shall ... terminate the bracket
|
||||
// expression, unless it appears in a collating symbol (such as "[.].]" ) or is
|
||||
// the ending <right-square-bracket> for a collating symbol, equivalence class,
|
||||
// or character class.
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
next = chars.next();
|
||||
}
|
||||
|
||||
if parse_bre(&expr, RegexOptions::REGEX_OPTION_NONE).is_ok() {
|
||||
Some((expr, chars.as_str()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a POSIX glob into a POSIX Basic Regular Expression
|
||||
fn glob_to_regex(pattern: &str) -> String {
|
||||
let mut regex = String::new();
|
||||
|
||||
let mut chars = pattern.chars();
|
||||
while let Some(ch) = chars.next() {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
|
||||
match ch {
|
||||
'?' => regex.push('.'),
|
||||
'*' => regex.push_str(".*"),
|
||||
'\\' => {
|
||||
if let Some(ch) = chars.next() {
|
||||
regex_push_literal(&mut regex, ch);
|
||||
} else {
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
|
||||
//
|
||||
// If pattern ends with an unescaped <backslash>, fnmatch() shall return a
|
||||
// non-zero value (indicating either no match or an error).
|
||||
//
|
||||
// Most implementations return FNM_NOMATCH in this case, so return a regex that
|
||||
// never matches.
|
||||
return "$.".to_string();
|
||||
}
|
||||
}
|
||||
'[' => {
|
||||
if let Some((expr, rest)) = extract_bracket_expr(chars.as_str()) {
|
||||
regex.push_str(&expr);
|
||||
chars = rest.chars();
|
||||
} else {
|
||||
regex_push_literal(&mut regex, ch);
|
||||
}
|
||||
}
|
||||
_ => regex_push_literal(&mut regex, ch),
|
||||
}
|
||||
}
|
||||
|
||||
regex
|
||||
}
|
||||
|
||||
/// An fnmatch()-style glob matcher.
|
||||
pub struct Pattern {
|
||||
regex: Regex,
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
/// Parse an fnmatch()-style glob.
|
||||
pub fn new(pattern: &str, caseless: bool) -> Self {
|
||||
let options = if caseless {
|
||||
RegexOptions::REGEX_OPTION_IGNORECASE
|
||||
} else {
|
||||
RegexOptions::REGEX_OPTION_NONE
|
||||
};
|
||||
|
||||
// As long as glob_to_regex() is correct, this should never fail
|
||||
let regex = parse_bre(&glob_to_regex(pattern), options).unwrap();
|
||||
Self { regex }
|
||||
}
|
||||
|
||||
/// Test if this patern matches a string.
|
||||
pub fn matches(&self, string: &str) -> bool {
|
||||
self.regex.is_match(string)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn literals() {
|
||||
assert_eq!(glob_to_regex(r"foo.bar"), r"foo\.bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regex_special() {
|
||||
assert_eq!(glob_to_regex(r"^foo.bar$"), r"\^foo\.bar\$");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wildcards() {
|
||||
assert_eq!(glob_to_regex(r"foo?bar*baz"), r"foo.bar.*baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escapes() {
|
||||
assert_eq!(glob_to_regex(r"fo\o\?bar\*baz\\"), r"foo?bar\*baz\\");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incomplete_escape() {
|
||||
assert_eq!(glob_to_regex(r"foo\"), r"$.")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_brackets() {
|
||||
assert_eq!(glob_to_regex(r"foo[bar][!baz]"), r"foo[bar][^baz]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complex_brackets() {
|
||||
assert_eq!(
|
||||
glob_to_regex(r"[!]!.*[\[.].][=]=][:space:]-]"),
|
||||
r"[^]!.*[\[.].][=]=][:space:]-]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_brackets() {
|
||||
assert_eq!(glob_to_regex(r"foo[bar[!baz"), r"foo\[bar\[!baz");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pattern_matches() {
|
||||
assert!(Pattern::new(r"foo*bar", false).matches("foo--bar"));
|
||||
|
||||
assert!(!Pattern::new(r"foo*bar", false).matches("bar--foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matches() {
|
||||
assert!(Pattern::new(r"foo*BAR", true).matches("FOO--bar"));
|
||||
|
||||
assert!(!Pattern::new(r"foo*BAR", true).matches("BAR--foo"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::io::{stderr, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
fn read_link_target(file_info: &DirEntry) -> Option<PathBuf> {
|
||||
match file_info.path().read_link() {
|
||||
Ok(target) => Some(target),
|
||||
Err(err) => {
|
||||
// If it's not a symlink, then it's not an error that should be
|
||||
// shown.
|
||||
if err.kind() != std::io::ErrorKind::InvalidInput {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error reading target of {}: {}",
|
||||
file_info.path().display(),
|
||||
err
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher makes a comparison of the link target against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct LinkNameMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl LinkNameMatcher {
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> LinkNameMatcher {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for LinkNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern.matches(&target.to_string_lossy())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
use std::io::ErrorKind;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::symlink;
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
||||
fn create_file_link() {
|
||||
#[cfg(unix)]
|
||||
if let Err(e) = symlink("abbbc", "test_data/links/link-f") {
|
||||
if e.kind() != ErrorKind::AlreadyExists {
|
||||
panic!("Failed to create sym link: {:?}", e);
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Err(e) = symlink_file("abbbc", "test_data/links/link-f") {
|
||||
if e.kind() != ErrorKind::AlreadyExists {
|
||||
panic!("Failed to create sym link: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_against_link_target() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = LinkNameMatcher::new("ab?bc", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matches_against_link_target() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = LinkNameMatcher::new("AbB?c", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@
|
||||
mod delete;
|
||||
mod empty;
|
||||
pub mod exec;
|
||||
mod glob;
|
||||
mod lname;
|
||||
mod logical_matchers;
|
||||
mod name;
|
||||
mod path;
|
||||
mod perm;
|
||||
mod printer;
|
||||
mod printf;
|
||||
@@ -28,10 +31,12 @@ use walkdir::DirEntry;
|
||||
use self::delete::DeleteMatcher;
|
||||
use self::empty::EmptyMatcher;
|
||||
use self::exec::SingleExecMatcher;
|
||||
use self::lname::LinkNameMatcher;
|
||||
use self::logical_matchers::{
|
||||
AndMatcherBuilder, FalseMatcher, ListMatcherBuilder, NotMatcher, TrueMatcher,
|
||||
};
|
||||
use self::name::{CaselessNameMatcher, NameMatcher};
|
||||
use self::name::NameMatcher;
|
||||
use self::path::PathMatcher;
|
||||
use self::perm::PermMatcher;
|
||||
use self::printer::{PrintDelimiter, Printer};
|
||||
use self::printf::Printf;
|
||||
@@ -279,19 +284,26 @@ fn build_matcher_tree(
|
||||
}
|
||||
"-true" => Some(TrueMatcher.into_box()),
|
||||
"-false" => Some(FalseMatcher.into_box()),
|
||||
"-name" | "-lname" => {
|
||||
"-lname" | "-ilname" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(NameMatcher::new(args[i], args[i - 1].starts_with("-l"))?.into_box())
|
||||
Some(LinkNameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-iname" | "-ilname" => {
|
||||
"-name" | "-iname" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(CaselessNameMatcher::new(args[i], args[i - 1].starts_with("-il"))?.into_box())
|
||||
Some(NameMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-path" | "-ipath" | "-wholename" | "-iwholename" => {
|
||||
if i >= args.len() - 1 {
|
||||
return Err(From::from(format!("missing argument to {}", args[i])));
|
||||
}
|
||||
i += 1;
|
||||
Some(PathMatcher::new(args[i], args[i - 1].starts_with("-i")).into_box())
|
||||
}
|
||||
"-regextype" => {
|
||||
if i >= args.len() - 1 {
|
||||
|
||||
+17
-126
@@ -4,109 +4,32 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use std::io::stderr;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use glob::Pattern;
|
||||
use glob::PatternError;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
fn read_link_target(file_info: &DirEntry) -> Option<PathBuf> {
|
||||
match file_info.path().read_link() {
|
||||
Ok(target) => Some(target),
|
||||
Err(err) => {
|
||||
// If it's not a symlink, then it's not an error that should be
|
||||
// shown.
|
||||
if err.kind() != std::io::ErrorKind::InvalidInput {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error reading target of {}: {}",
|
||||
file_info.path().display(),
|
||||
err
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher makes a case-sensitive comparison of the name against a
|
||||
/// shell wildcard pattern. See `glob::Pattern` for details on the exact
|
||||
/// syntax.
|
||||
/// This matcher makes a comparison of the name against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct NameMatcher {
|
||||
pattern: Pattern,
|
||||
match_link_target: bool,
|
||||
}
|
||||
|
||||
impl NameMatcher {
|
||||
pub fn new(pattern_string: &str, match_link_target: bool) -> Result<NameMatcher, PatternError> {
|
||||
let p = Pattern::new(pattern_string)?;
|
||||
Ok(NameMatcher {
|
||||
pattern: p,
|
||||
match_link_target,
|
||||
})
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> Self {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
if self.match_link_target {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern.matches(&target.to_string_lossy())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.pattern
|
||||
.matches(file_info.file_name().to_string_lossy().as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher makes a case-insensitive comparison of the name against a
|
||||
/// shell wildcard pattern. See `glob::Pattern` for details on the exact
|
||||
/// syntax.
|
||||
pub struct CaselessNameMatcher {
|
||||
pattern: Pattern,
|
||||
match_link_target: bool,
|
||||
}
|
||||
|
||||
impl CaselessNameMatcher {
|
||||
pub fn new(
|
||||
pattern_string: &str,
|
||||
match_link_target: bool,
|
||||
) -> Result<CaselessNameMatcher, PatternError> {
|
||||
let p = Pattern::new(&pattern_string.to_lowercase())?;
|
||||
Ok(CaselessNameMatcher {
|
||||
pattern: p,
|
||||
match_link_target,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Matcher for CaselessNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
if self.match_link_target {
|
||||
if let Some(target) = read_link_target(file_info) {
|
||||
self.pattern
|
||||
.matches(&target.to_string_lossy().to_lowercase())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
self.pattern
|
||||
.matches(&file_info.file_name().to_string_lossy().to_lowercase())
|
||||
}
|
||||
let name = file_info.file_name().to_string_lossy();
|
||||
self.pattern.matches(&name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
@@ -119,7 +42,7 @@ mod tests {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
||||
fn create_file_link() {
|
||||
#[cfg(unix)]
|
||||
@@ -139,7 +62,7 @@ mod tests {
|
||||
#[test]
|
||||
fn matching_with_wrong_case_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"A*C".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("A*C", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -147,7 +70,7 @@ mod tests {
|
||||
#[test]
|
||||
fn matching_with_right_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"abb?c".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("abb?c", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -155,7 +78,7 @@ mod tests {
|
||||
#[test]
|
||||
fn not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"shouldn't match".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("shouldn't match", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -165,31 +88,15 @@ mod tests {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = NameMatcher::new("link?f", false).unwrap();
|
||||
let matcher = NameMatcher::new("link?f", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_against_link_target_if_requested() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = NameMatcher::new("ab?bc", true).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_create_with_invalid_pattern() {
|
||||
let result = NameMatcher::new(&"a**c".to_string(), false);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matching_with_wrong_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"A*C".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("A*C", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -197,7 +104,7 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_matching_with_right_case_returns_true() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"abb?c".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("abb?c", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -205,7 +112,7 @@ mod tests {
|
||||
#[test]
|
||||
fn caseless_not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"shouldn't match".to_string(), false).unwrap();
|
||||
let matcher = NameMatcher::new("shouldn't match", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
@@ -215,24 +122,8 @@ mod tests {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = CaselessNameMatcher::new("linK?f", false).unwrap();
|
||||
let matcher = NameMatcher::new("linK?f", true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matches_against_link_target_if_requested() {
|
||||
create_file_link();
|
||||
|
||||
let link_f = get_dir_entry_for("test_data/links", "link-f");
|
||||
let matcher = CaselessNameMatcher::new("AbB?c", true).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&link_f, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_cant_create_with_invalid_pattern() {
|
||||
let result = CaselessNameMatcher::new(&"a**c".to_string(), false);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2017 Google Inc.
|
||||
//
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::glob::Pattern;
|
||||
use super::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher makes a comparison of the path against a shell wildcard
|
||||
/// pattern. See `glob::Pattern` for details on the exact syntax.
|
||||
pub struct PathMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl PathMatcher {
|
||||
pub fn new(pattern_string: &str, caseless: bool) -> Self {
|
||||
let pattern = Pattern::new(pattern_string, caseless);
|
||||
Self { pattern }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for PathMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
let path = file_info.path().to_string_lossy();
|
||||
self.pattern.matches(&path)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::find::matchers::tests::get_dir_entry_for;
|
||||
use crate::find::matchers::Matcher;
|
||||
use crate::find::tests::FakeDependencies;
|
||||
|
||||
// Variants of fix_up_slashes that properly escape the forward slashes for
|
||||
// being in a glob.
|
||||
#[cfg(windows)]
|
||||
fn fix_up_glob_slashes(re: &str) -> String {
|
||||
re.replace("/", "\\\\")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn fix_up_glob_slashes(re: &str) -> String {
|
||||
re.to_owned()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matching_against_whole_path() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/a*c"), false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matching_against_just_name() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new("a*c", false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matching_against_wrong_case() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/A*C"), false);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_matching() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = PathMatcher::new(&fix_up_glob_slashes("test_*/*/A*C"), true);
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ mod tests {
|
||||
// being in a regex.
|
||||
#[cfg(windows)]
|
||||
fn fix_up_regex_slashes(re: &str) -> String {
|
||||
re.replace("/", "\\\\")
|
||||
re.replace("/", r"\\")
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
|
||||
@@ -4,12 +4,5 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
extern crate glob;
|
||||
extern crate regex;
|
||||
extern crate walkdir;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempfile;
|
||||
|
||||
pub mod find;
|
||||
pub mod xargs;
|
||||
|
||||
@@ -26,6 +26,12 @@ LOG_FILE=bfs/tests.log
|
||||
PASS=$(sed -n "s/^tests passed: \(.*\)/\1/p" "$LOG_FILE" | head -n1)
|
||||
SKIP=$(sed -n "s/^tests skipped: \(.*\)/\1/p" "$LOG_FILE" | head -n1)
|
||||
FAIL=$(sed -n "s/^tests failed: \(.*\)/\1/p" "$LOG_FILE" | head -n1)
|
||||
|
||||
# Default any missing numbers to zero (e.g. no tests skipped)
|
||||
: ${PASS:=0}
|
||||
: ${SKIP:=0}
|
||||
: ${FAIL:=0}
|
||||
|
||||
TOTAL=$((PASS + SKIP + FAIL))
|
||||
if (( TOTAL <= 1 )); then
|
||||
echo "Error in the execution, failing early"
|
||||
|
||||
Reference in New Issue
Block a user