mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Initial dump of my code (#1)
* Initial checkin. Supports -name, -name -print and a subset of -type Has unit tests, but no system tests * Corrected usage text * Added ability to replace stdout with a fake. Moved and_matcher to logical_matchers * Significant refactoring to allow tests for the arg-parsing code * Added tests for the argument parsing code. * Added support for or operator * Added change missing form last commit * Added AUTHORS and (MIT) LICENSE * Added support for lists (i.e. the comma operand) * Added support for parentheses * Cleaned up names of some Or/And/List matcher methods * Added support for depth-first searching * Added initial support for max_depth, min_depth and depth arguments. * Cleaned up a surfeit of Box::new's and change the [Or|And|List]Matchers to use builders, so submatchers can't be added later on. * Added support for -a * Added support for prune * Added prune * Refactored the way we fake stdoutput. Will allowing for easy expansion for stderr, clocks etc. * Renamed side effects to matcherio * Removed leftover tracing println call * Tidied up use statements * Switched to using walkdir * Added support for -newer * renamed new_sideeffetcs to new_matcher_io * Implemented ctime, atime and mtime * merged name_matcher and caseless_name_matcher into one module * Added support for -size * Minor tweaks based on code review * Switched Config struct to implement Default * Replaced try! with ? * Added copyright headers
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
target
|
||||
Cargo.lock
|
||||
.gitignore
|
||||
.project
|
||||
.cargo
|
||||
.settings
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Names should be added to this file like so:
|
||||
# # Name or Organization <email address>
|
||||
#
|
||||
# Google Inc. <opensource@google.com>
|
||||
Generated
+162
@@ -0,0 +1,162 @@
|
||||
[root]
|
||||
name = "findutils"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"walkdir 1.0.7 (git+https://github.com/mcharsley/walkdir?rev=dffefcf8db97a331a0f81d120e8aa20c1b36251e)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread-id"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "1.0.7"
|
||||
source = "git+https://github.com/mcharsley/walkdir?rev=dffefcf8db97a331a0f81d120e8aa20c1b36251e#dffefcf8db97a331a0f81d120e8aa20c1b36251e"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum libc 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "684f330624d8c3784fb9558ca46c4ce488073a8d22450415c5eb4f4cfb0d11b5"
|
||||
"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
|
||||
"checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d"
|
||||
"checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01"
|
||||
"checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457"
|
||||
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
|
||||
"checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
|
||||
"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum walkdir 1.0.7 (git+https://github.com/mcharsley/walkdir?rev=dffefcf8db97a331a0f81d120e8aa20c1b36251e)" = "<none>"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.1.0"
|
||||
authors = ["mcharsley <mcharsley@google.com>"]
|
||||
|
||||
[dependencies]
|
||||
glob = "0.2"
|
||||
# Current release of walkdir doesn't support depth-first operation. While we
|
||||
# wait for https://github.com/BurntSushi/walkdir/pull/19 to be accepted and a
|
||||
# new release made, let's access my branch directly.
|
||||
walkdir = { git = "https://github.com/mcharsley/walkdir", rev = "dffefcf8db97a331a0f81d120e8aa20c1b36251e" }
|
||||
tempdir = "0.3"
|
||||
regex = "0.2"
|
||||
|
||||
[[bin]]
|
||||
name = "find"
|
||||
path = "src/find/main.rs"
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) Jordi Boggiano
|
||||
Copyright (c) Google Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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.
|
||||
|
||||
extern crate glob;
|
||||
extern crate findutils;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let strs: Vec<&str> = args.iter().map(|s| s.as_ref()).collect();
|
||||
let deps = findutils::find::StandardDependencies::new();
|
||||
std::process::exit(findutils::find::find_main(&strs, &deps));
|
||||
}
|
||||
@@ -0,0 +1,481 @@
|
||||
// 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.
|
||||
|
||||
//! This modules contains the matchers used for combining other matchers and
|
||||
//! performing boolean logic on them (and a couple of trivial always-true and
|
||||
//! always-false matchers). The design is strongly tied to the precedence rules
|
||||
//! when parsing command-line options (e.g. "-foo -o -bar -baz" is equivalent
|
||||
//! to "-foo -o ( -bar -baz )", not "( -foo -o -bar ) -baz").
|
||||
use std::error::Error;
|
||||
use std::iter::Iterator;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher contains a collection of other matchers. A file only matches
|
||||
/// if it matches ALL the contained sub-matchers. For sub-matchers that have
|
||||
/// side effects, the side effects occur in the same order as the sub-matchers
|
||||
/// were pushed into the collection.
|
||||
pub struct AndMatcher {
|
||||
submatchers: Vec<Box<Matcher>>,
|
||||
}
|
||||
|
||||
impl AndMatcher {
|
||||
pub fn new(submatchers: Vec<Box<Matcher>>) -> AndMatcher {
|
||||
AndMatcher { submatchers: submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Matcher for AndMatcher {
|
||||
/// Returns true if all sub-matchers return true. Short-circuiting does take
|
||||
/// place. If the nth sub-matcher returns false, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
self.submatchers.iter().all(|ref x| x.matches(dir_entry, matcher_io))
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
self.submatchers.iter().any(|ref x| x.has_side_effects())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AndMatcherBuilder {
|
||||
submatchers: Vec<Box<Matcher>>,
|
||||
}
|
||||
|
||||
impl AndMatcherBuilder {
|
||||
pub fn new() -> AndMatcherBuilder {
|
||||
AndMatcherBuilder { submatchers: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn new_and_condition(&mut self, matcher: Box<Matcher>) {
|
||||
self.submatchers.push(matcher);
|
||||
}
|
||||
|
||||
/// Builds a Matcher: consuming the builder in the process.
|
||||
pub fn build(mut self) -> Box<Matcher> {
|
||||
// special case. If there's only one submatcher, just return that directly
|
||||
if self.submatchers.len() == 1 {
|
||||
// safe to unwrap: we've just checked the size
|
||||
return self.submatchers.pop().unwrap();
|
||||
}
|
||||
let matcher = Box::new(AndMatcher::new(self.submatchers));
|
||||
self.submatchers = Vec::new();
|
||||
matcher
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// This matcher contains a collection of other matchers. A file matches
|
||||
/// if it matches any of the contained sub-matchers. For sub-matchers that have
|
||||
/// side effects, the side effects occur in the same order as the sub-matchers
|
||||
/// were pushed into the collection.
|
||||
pub struct OrMatcher {
|
||||
submatchers: Vec<Box<Matcher>>,
|
||||
}
|
||||
|
||||
impl OrMatcher {
|
||||
pub fn new(submatchers: Vec<Box<Matcher>>) -> OrMatcher {
|
||||
OrMatcher { submatchers: submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Matcher for OrMatcher {
|
||||
/// Returns true if any sub-matcher returns true. Short-circuiting does take
|
||||
/// place. If the nth sub-matcher returns true, then we immediately return
|
||||
/// and don't make any further calls.
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
self.submatchers.iter().any(|ref x| x.matches(dir_entry, matcher_io))
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
self.submatchers.iter().any(|ref x| x.has_side_effects())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OrMatcherBuilder {
|
||||
submatchers: Vec<AndMatcherBuilder>,
|
||||
}
|
||||
|
||||
impl OrMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<Matcher>) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers.last_mut().unwrap().new_and_condition(matcher);
|
||||
}
|
||||
|
||||
pub fn new_or_condition(&mut self, arg: &str) -> Result<(), Box<Error>> {
|
||||
if self.submatchers.last().unwrap().submatchers.is_empty() {
|
||||
return Err(From::from(format!("invalid expression; you have used a binary operator \
|
||||
'{}' with nothing before it.",
|
||||
arg)));
|
||||
}
|
||||
self.submatchers.push(AndMatcherBuilder::new());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> OrMatcherBuilder {
|
||||
let mut o = OrMatcherBuilder { submatchers: Vec::new() };
|
||||
o.submatchers.push(AndMatcherBuilder::new());
|
||||
o
|
||||
}
|
||||
|
||||
/// Builds a Matcher: consuming the builder in the process.
|
||||
pub fn build(mut self) -> Box<Matcher> {
|
||||
// Special case: if there's only one submatcher, just return that directly
|
||||
if self.submatchers.len() == 1 {
|
||||
// safe to unwrap: we've just checked the size
|
||||
return self.submatchers.pop().unwrap().build();
|
||||
}
|
||||
let mut submatchers = vec![];
|
||||
for x in self.submatchers {
|
||||
submatchers.push(x.build());
|
||||
}
|
||||
Box::new(OrMatcher::new(submatchers))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// This matcher contains a collection of other matchers. In contrast to
|
||||
/// OrMatcher and AndMatcher, all the submatcher objects are called regardless
|
||||
/// of the results of previous submatchers. This is primarily used for
|
||||
/// submatchers with side-effects. For such sub-matchers the side effects occur
|
||||
/// in the same order as the sub-matchers were pushed into the collection.
|
||||
pub struct ListMatcher {
|
||||
submatchers: Vec<Box<Matcher>>,
|
||||
}
|
||||
|
||||
impl ListMatcher {
|
||||
pub fn new(submatchers: Vec<Box<Matcher>>) -> ListMatcher {
|
||||
ListMatcher { submatchers: submatchers }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Matcher for ListMatcher {
|
||||
/// Calls matches on all submatcher objects, with no short-circuiting.
|
||||
/// Returns the result of the call to the final submatcher
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
let mut rc = false;
|
||||
for ref matcher in &self.submatchers {
|
||||
rc = matcher.matches(dir_entry, matcher_io);
|
||||
}
|
||||
rc
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
self.submatchers.iter().any(|ref x| x.has_side_effects())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListMatcherBuilder {
|
||||
submatchers: Vec<OrMatcherBuilder>,
|
||||
}
|
||||
|
||||
impl ListMatcherBuilder {
|
||||
pub fn new_and_condition(&mut self, matcher: Box<Matcher>) {
|
||||
// safe to unwrap. submatchers always has at least one member
|
||||
self.submatchers.last_mut().unwrap().new_and_condition(matcher);
|
||||
}
|
||||
|
||||
pub fn new_or_condition(&mut self, arg: &str) -> Result<(), Box<Error>> {
|
||||
self.submatchers.last_mut().unwrap().new_or_condition(arg)
|
||||
}
|
||||
|
||||
pub fn check_new_and_condition(&mut self) -> Result<(), Box<Error>> {
|
||||
{
|
||||
let child_or_matcher = &self.submatchers.last().unwrap();
|
||||
let grandchild_and_matcher = &child_or_matcher.submatchers.last().unwrap();
|
||||
|
||||
if grandchild_and_matcher.submatchers.is_empty() {
|
||||
return Err(From::from("invalid expression; you have used a binary operator '-a' \
|
||||
with nothing before it."));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new_list_condition(&mut self) -> Result<(), Box<Error>> {
|
||||
{
|
||||
let child_or_matcher = &self.submatchers.last().unwrap();
|
||||
let grandchild_and_matcher = &child_or_matcher.submatchers.last().unwrap();
|
||||
|
||||
if grandchild_and_matcher.submatchers.is_empty() {
|
||||
return Err(From::from("invalid expression; you have used a binary operator ',' \
|
||||
with nothing before it."));
|
||||
}
|
||||
}
|
||||
self.submatchers.push(OrMatcherBuilder::new());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> ListMatcherBuilder {
|
||||
let mut o = ListMatcherBuilder { submatchers: Vec::new() };
|
||||
o.submatchers.push(OrMatcherBuilder::new());
|
||||
o
|
||||
}
|
||||
|
||||
/// Builds a Matcher: consuming the builder in the process.
|
||||
pub fn build(mut self) -> Box<Matcher> {
|
||||
// Special case: if there's only one submatcher, just return that directly
|
||||
if self.submatchers.len() == 1 {
|
||||
// safe to unwrap: we've just checked the size
|
||||
return self.submatchers.pop().unwrap().build();
|
||||
}
|
||||
let mut submatchers = vec![];
|
||||
for x in self.submatchers {
|
||||
submatchers.push(x.build());
|
||||
}
|
||||
Box::new(ListMatcher::new(submatchers))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A simple matcher that always matches.
|
||||
pub struct TrueMatcher;
|
||||
|
||||
impl TrueMatcher {
|
||||
pub fn new_box() -> Box<Matcher> {
|
||||
Box::new(TrueMatcher {})
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for TrueMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple matcher that never matches.
|
||||
pub struct FalseMatcher;
|
||||
|
||||
impl Matcher for FalseMatcher {
|
||||
fn matches(&self, _dir_entry: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl FalseMatcher {
|
||||
pub fn new_box() -> Box<Matcher> {
|
||||
Box::new(FalseMatcher {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Matcher that wraps another matcher and inverts matching criteria.
|
||||
pub struct NotMatcher {
|
||||
submatcher: Box<Matcher>,
|
||||
}
|
||||
|
||||
impl NotMatcher {
|
||||
pub fn new(submatcher: Box<Matcher>) -> NotMatcher {
|
||||
NotMatcher { submatcher: submatcher }
|
||||
}
|
||||
|
||||
pub fn new_box(submatcher: Box<Matcher>) -> Box<NotMatcher> {
|
||||
Box::new(NotMatcher::new(submatcher))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NotMatcher {
|
||||
fn matches(&self, dir_entry: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
!self.submatcher.matches(dir_entry, matcher_io)
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
self.submatcher.has_side_effects()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use walkdir::DirEntry;
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use super::*;
|
||||
use find::matchers::{Matcher, MatcherIO};
|
||||
use find::tests::FakeDependencies;
|
||||
|
||||
/// Simple Matcher impl that has side effects
|
||||
pub struct HasSideEffects {}
|
||||
|
||||
impl Matcher for HasSideEffects {
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSideEffects {
|
||||
pub fn new_box() -> Box<Matcher> {
|
||||
Box::new(HasSideEffects {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn and_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = AndMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning true
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// start with one matcher returning false
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_or_condition("-o").unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let mut builder = ListMatcherBuilder::new();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
// result should always match that of the last pushed submatcher
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_list_condition().unwrap();
|
||||
builder.new_and_condition(FalseMatcher::new_box());
|
||||
assert!(!builder.build().matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn true_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = TrueMatcher {};
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = FalseMatcher {};
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn and_has_side_effects_works() {
|
||||
let mut builder = AndMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = AndMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_has_side_effects_works() {
|
||||
let mut builder = OrMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = OrMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_has_side_effects_works() {
|
||||
let mut builder = ListMatcherBuilder::new();
|
||||
|
||||
// start with one matcher with no side effects false
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
assert!(!builder.build().has_side_effects());
|
||||
|
||||
builder = ListMatcherBuilder::new();
|
||||
builder.new_and_condition(TrueMatcher::new_box());
|
||||
builder.new_and_condition(HasSideEffects::new_box());
|
||||
assert!(builder.build().has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn true_has_side_effects_works() {
|
||||
let matcher = TrueMatcher {};
|
||||
assert!(!matcher.has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn false_has_side_effects_works() {
|
||||
let matcher = FalseMatcher {};
|
||||
assert!(!matcher.has_side_effects());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matches_works() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let not_true = NotMatcher::new(TrueMatcher::new_box());
|
||||
let not_false = NotMatcher::new(FalseMatcher::new_box());
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!not_true.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert!(not_false.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_has_side_effects_works() {
|
||||
let has_fx = NotMatcher::new(HasSideEffects::new_box());
|
||||
let hasnt_fx = NotMatcher::new(FalseMatcher::new_box());
|
||||
assert!(has_fx.has_side_effects());
|
||||
assert!(!hasnt_fx.has_side_effects());
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,139 @@
|
||||
// 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 glob::Pattern;
|
||||
use glob::PatternError;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher makes a case-sensitive comparison of the name against a
|
||||
/// shell wildcard pattern. See glob::Pattern for details on the exact
|
||||
/// syntax.
|
||||
pub struct NameMatcher {
|
||||
pattern: Pattern,
|
||||
}
|
||||
|
||||
impl NameMatcher {
|
||||
pub fn new(pattern_string: &str) -> Result<NameMatcher, PatternError> {
|
||||
let p = Pattern::new(pattern_string)?;
|
||||
Ok(NameMatcher { pattern: p })
|
||||
}
|
||||
|
||||
pub fn new_box(pattern_string: &str) -> Result<Box<Matcher>, PatternError> {
|
||||
Ok(Box::new(NameMatcher::new(pattern_string)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
return self.pattern.matches(file_info.file_name().to_string_lossy().as_ref());
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl CaselessNameMatcher {
|
||||
pub fn new(pattern_string: &str) -> Result<CaselessNameMatcher, PatternError> {
|
||||
let p = Pattern::new(&pattern_string.to_lowercase())?;
|
||||
Ok(CaselessNameMatcher { pattern: p })
|
||||
}
|
||||
|
||||
pub fn new_box(pattern_string: &str) -> Result<Box<Matcher>, PatternError> {
|
||||
Ok(Box::new(CaselessNameMatcher::new(pattern_string)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Matcher for CaselessNameMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
return self.pattern
|
||||
.matches(file_info.file_name().to_string_lossy().to_lowercase().as_ref());
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use find::matchers::Matcher;
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
|
||||
|
||||
#[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()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[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()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = NameMatcher::new(&"should't match".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_create_with_invalid_pattern() {
|
||||
let result = NameMatcher::new(&"a**c".to_string());
|
||||
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()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[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()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_not_matching_returns_false() {
|
||||
let abbbc = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let matcher = CaselessNameMatcher::new(&"should't match".to_string()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(!matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caseless_cant_create_with_invalid_pattern() {
|
||||
let result = CaselessNameMatcher::new(&"a**c".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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 find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher just prints the name of the file to stdout.
|
||||
pub struct Printer;
|
||||
|
||||
impl Printer {
|
||||
pub fn new() -> Printer {
|
||||
Printer {}
|
||||
}
|
||||
|
||||
pub fn new_box() -> Box<Matcher> {
|
||||
Box::new(Printer::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for Printer {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
writeln!(matcher_io.deps.get_output().borrow_mut(),
|
||||
"{}",
|
||||
file_info.path().to_string_lossy())
|
||||
.unwrap();
|
||||
true
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::matchers::Matcher;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prints() {
|
||||
let abbbc = get_dir_entry_for("./test_data/simple", "abbbc");
|
||||
|
||||
let matcher = Printer::new();
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&abbbc, &mut deps.new_matcher_io()));
|
||||
assert_eq!("./test_data/simple/abbbc\n", deps.get_output_as_string());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// 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 find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher checks the type of the file.
|
||||
pub struct PruneMatcher;
|
||||
|
||||
impl PruneMatcher {
|
||||
pub fn new() -> PruneMatcher {
|
||||
PruneMatcher {}
|
||||
}
|
||||
|
||||
pub fn new_box() -> Box<Matcher> {
|
||||
Box::new(PruneMatcher::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for PruneMatcher {
|
||||
fn matches(&self, _: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
matcher_io.mark_current_dir_to_be_skipped();
|
||||
return true;
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use find::matchers::Matcher;
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn file_type_matcher() {
|
||||
let dir = get_dir_entry_for("test_data", "simple");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
assert!(!matcher_io.should_skip_current_dir());
|
||||
let matcher = PruneMatcher::new();
|
||||
assert!(matcher.matches(&dir, &mut matcher_io));
|
||||
assert!(matcher_io.should_skip_current_dir());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// 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::error::Error;
|
||||
use std::io::{stderr, Write};
|
||||
use std::str::FromStr;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{ComparableValue, Matcher, MatcherIO};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Unit {
|
||||
Byte,
|
||||
TwoByteWord,
|
||||
Block,
|
||||
KibiByte,
|
||||
MebiByte,
|
||||
GibiByte,
|
||||
}
|
||||
|
||||
impl FromStr for Unit {
|
||||
type Err = Box<Error>;
|
||||
fn from_str(s: &str) -> Result<Unit, Box<Error>> {
|
||||
Ok(match s {
|
||||
"c" => Unit::Byte,
|
||||
"w" => Unit::TwoByteWord,
|
||||
"" | "b" => Unit::Block,
|
||||
"k" => Unit::KibiByte,
|
||||
"M" => Unit::MebiByte,
|
||||
"G" => Unit::GibiByte,
|
||||
_ => {
|
||||
return Err(From::from(format!("Invalid suffix {} for -size. Only allowed \
|
||||
values are <nothing>, b, c, w, k, M or G",
|
||||
s)));
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn byte_size_to_unit_size(unit: Unit, byte_size: u64) -> u64 {
|
||||
// Short circuit (to avoid a overflow error when subtracting 1 later on)
|
||||
if byte_size == 0 {
|
||||
return 0;
|
||||
}
|
||||
let bits_to_shift = match unit {
|
||||
Unit::Byte => 0,
|
||||
Unit::TwoByteWord => 1,
|
||||
Unit::Block => 9,
|
||||
Unit::KibiByte => 10,
|
||||
Unit::MebiByte => 20,
|
||||
Unit::GibiByte => 30,
|
||||
};
|
||||
// Skip pointless arithmetic.
|
||||
if bits_to_shift == 0 {
|
||||
return byte_size;
|
||||
}
|
||||
// We want to round up (e.g. 1 byte - 1024 bytes = 1k.
|
||||
// 1025 bytes to 2048 bytes = 2k etc.
|
||||
((byte_size - 1) >> bits_to_shift) + 1
|
||||
}
|
||||
|
||||
/// Matcher that checks whether a file's size if {less than | equal to | more than}
|
||||
/// N units in size.
|
||||
pub struct SizeMatcher {
|
||||
value_to_match: ComparableValue,
|
||||
unit: Unit,
|
||||
}
|
||||
|
||||
impl SizeMatcher {
|
||||
pub fn new(value_to_match: ComparableValue,
|
||||
suffix_string: &str)
|
||||
-> Result<SizeMatcher, Box<Error>> {
|
||||
Ok(SizeMatcher {
|
||||
unit: suffix_string.parse()?,
|
||||
value_to_match: value_to_match,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_box(value_to_match: ComparableValue,
|
||||
suffix_string: &str)
|
||||
-> Result<Box<Matcher>, Box<Error>> {
|
||||
Ok(Box::new(SizeMatcher::new(value_to_match, suffix_string)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for SizeMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => {
|
||||
self.value_to_match
|
||||
.matches(byte_size_to_unit_size(self.unit, metadata.len()))
|
||||
}
|
||||
Err(e) => {
|
||||
writeln!(&mut stderr(),
|
||||
"Error getting file size for {}: {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use find::matchers::{ComparableValue, Matcher};
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
// need to explicitly use non-pub members
|
||||
use super::{byte_size_to_unit_size, Unit};
|
||||
|
||||
#[test]
|
||||
fn test_byte_size_to_unit_size() {
|
||||
assert_eq!(byte_size_to_unit_size(Unit::KibiByte, 0), 0);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::KibiByte, 1), 1);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::KibiByte, 1024), 1);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::KibiByte, 1025), 2);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::Byte, 1025), 1025);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::TwoByteWord, 1025), 513);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::Block, 1025), 3);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::KibiByte, 1025), 2);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::MebiByte, 1024 * 1024 + 1), 2);
|
||||
assert_eq!(byte_size_to_unit_size(Unit::GibiByte, 1024 * 1024 * 1024 + 1),
|
||||
2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_from_string() {
|
||||
assert_eq!(byte_size_to_unit_size("c".parse().unwrap(), 2), 2);
|
||||
assert_eq!(byte_size_to_unit_size("w".parse().unwrap(), 3), 2);
|
||||
assert_eq!(byte_size_to_unit_size("b".parse().unwrap(), 513), 2);
|
||||
assert_eq!(byte_size_to_unit_size("".parse().unwrap(), 513), 2);
|
||||
assert_eq!(byte_size_to_unit_size("k".parse().unwrap(), 1025), 2);
|
||||
assert_eq!(byte_size_to_unit_size("M".parse().unwrap(), 1024 * 1024 + 1),
|
||||
2);
|
||||
assert_eq!(byte_size_to_unit_size("G".parse().unwrap(), 2024 * 1024 * 1024 + 1),
|
||||
2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_matcher_bad_unit() {
|
||||
if let Err(e) = SizeMatcher::new(ComparableValue::EqualTo(2), "xyz") {
|
||||
assert!(e.description().contains("Invalid suffix") && e.description().contains("xyz"),
|
||||
"bad description: {}",
|
||||
e);
|
||||
} else {
|
||||
panic!("parsing a unit string should fail");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_matcher() {
|
||||
let file_info = get_dir_entry_for("./test_data/size", "512bytes");
|
||||
|
||||
let equal_to_2_blocks = SizeMatcher::new(ComparableValue::EqualTo(2), "b").unwrap();
|
||||
let equal_to_1_blocks = SizeMatcher::new(ComparableValue::EqualTo(1), "b").unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(!equal_to_2_blocks.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"512-byte file should not match size of 2 blocks");
|
||||
assert!(equal_to_1_blocks.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"512-byte file should match size of 1 block");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// 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;
|
||||
use std::error::Error;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{stderr, Write};
|
||||
use std::time::SystemTime;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{ComparableValue, Matcher, MatcherIO};
|
||||
|
||||
const SECONDS_PER_DAY: i64 = 60 * 60 * 24;
|
||||
|
||||
/// This matcher checks whether a file is newer than the file the matcher is initialized with.
|
||||
pub struct NewerMatcher {
|
||||
given_modification_time: SystemTime,
|
||||
}
|
||||
|
||||
impl NewerMatcher {
|
||||
pub fn new(path_to_file: &str) -> Result<NewerMatcher, Box<Error>> {
|
||||
let f = File::open(path_to_file)?;
|
||||
let metadata = f.metadata()?;
|
||||
Ok(NewerMatcher { given_modification_time: metadata.modified()? })
|
||||
}
|
||||
|
||||
pub fn new_box(path_to_file: &str) -> Result<Box<Matcher>, Box<Error>> {
|
||||
Ok(Box::new(NewerMatcher::new(path_to_file)?))
|
||||
}
|
||||
|
||||
/// Impementation of matches that returns a result, allowing use to use try!
|
||||
/// to deal with the errors.
|
||||
fn matches_impl(&self, file_info: &DirEntry) -> Result<bool, Box<Error>> {
|
||||
let this_time = file_info.metadata()?.modified()?;
|
||||
// duration_since returns an Ok duration if this_time <= given_modification_time
|
||||
// and returns an Err (with a duration) otherwise. So if this_time >
|
||||
// given_modification_time (in which case we want to return true) then
|
||||
// duration_since will return an error.
|
||||
Ok(self.given_modification_time.duration_since(this_time).is_err())
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for NewerMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
match self.matches_impl(file_info) {
|
||||
Err(e) => {
|
||||
writeln!(&mut stderr(),
|
||||
"Error getting modification time for {}: {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
e)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
Ok(t) => t,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FileTimeType {
|
||||
Accessed,
|
||||
Created,
|
||||
Modified,
|
||||
}
|
||||
|
||||
impl FileTimeType {
|
||||
fn get_file_time(self, metadata: Metadata) -> std::io::Result<SystemTime> {
|
||||
match self {
|
||||
FileTimeType::Accessed => metadata.accessed(),
|
||||
FileTimeType::Created => metadata.created(),
|
||||
FileTimeType::Modified => metadata.modified(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This matcher checks whether a file's accessed|creation|modification time is
|
||||
/// {less than | exactly | more than} N days old.
|
||||
pub struct FileTimeMatcher {
|
||||
days: ComparableValue,
|
||||
file_time_type: FileTimeType,
|
||||
}
|
||||
|
||||
impl Matcher for FileTimeMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
match self.matches_impl(file_info, matcher_io.now()) {
|
||||
Err(e) => {
|
||||
writeln!(&mut stderr(),
|
||||
"Error getting {:?} time for {}: {}",
|
||||
self.file_time_type,
|
||||
file_info.path().to_string_lossy(),
|
||||
e)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
Ok(t) => t,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl FileTimeMatcher {
|
||||
/// Impementation of matches that returns a result, allowing use to use try!
|
||||
/// to deal with the errors.
|
||||
fn matches_impl(&self, file_info: &DirEntry, now: SystemTime) -> Result<bool, Box<Error>> {
|
||||
let this_time = self.file_time_type.get_file_time(file_info.metadata()?)?;
|
||||
let mut is_negative = false;
|
||||
// durations can't be negative. So duration_since returns a duration
|
||||
// wrapped in an error if now < this_time.
|
||||
let age = match now.duration_since(this_time) {
|
||||
Ok(duration) => duration,
|
||||
Err(e) => {
|
||||
is_negative = true;
|
||||
e.duration()
|
||||
}
|
||||
};
|
||||
let age_in_seconds: i64 = age.as_secs() as i64 * if is_negative { -1 } else { 1 };
|
||||
// rust division truncates towards zero (see
|
||||
// https://github.com/rust-lang/rust/blob/master/src/libcore/ops.rs#L580 )
|
||||
// so a simple age_in_seconds / SECONDS_PER_DAY gives the wrong answer
|
||||
// for negative ages: a file whose age is 1 second in the future needs to
|
||||
// count as -1 day old, not 0.
|
||||
let age_in_days = age_in_seconds / SECONDS_PER_DAY + if is_negative { -1 } else { 0 };
|
||||
Ok(self.days.imatches(age_in_days))
|
||||
}
|
||||
|
||||
pub fn new(file_time_type: FileTimeType, days: ComparableValue) -> FileTimeMatcher {
|
||||
FileTimeMatcher {
|
||||
file_time_type: file_time_type,
|
||||
days: days,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_box(file_time_type: FileTimeType, days: ComparableValue) -> Box<Matcher> {
|
||||
Box::new(FileTimeMatcher::new(file_time_type, days))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{Read, Write};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use tempdir::TempDir;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{ComparableValue, Matcher};
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn newer_matcher() {
|
||||
// this file should already exist
|
||||
let old_file = get_dir_entry_for("test_data", "simple");
|
||||
|
||||
let temp_dir = TempDir::new("newer_matcher").unwrap();
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
// this has just been created, so should be newer
|
||||
let new_file_name = "newFile";
|
||||
File::create(temp_dir.path().join(new_file_name)).expect("create temp file");
|
||||
|
||||
let new_file = get_dir_entry_for(&temp_dir_path, &new_file_name);
|
||||
|
||||
let matcher_for_new =
|
||||
NewerMatcher::new(&temp_dir.path().join(new_file_name).to_string_lossy()).unwrap();
|
||||
let matcher_for_old = NewerMatcher::new(&old_file.path().to_string_lossy()).unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
assert!(!matcher_for_new.matches(&old_file, &mut deps.new_matcher_io()),
|
||||
"old_file shouldn't be newer than new_dir");
|
||||
assert!(matcher_for_old.matches(&new_file, &mut deps.new_matcher_io()),
|
||||
"new_file should be newer than old_dir");
|
||||
assert!(!matcher_for_old.matches(&old_file, &mut deps.new_matcher_io()),
|
||||
"old_file shouldn't be newer than itself");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_time_matcher() {
|
||||
// this file should already exist
|
||||
let file = get_dir_entry_for("test_data", "simple");
|
||||
|
||||
let files_mtime = file.metadata().unwrap().modified().unwrap();
|
||||
|
||||
let exactly_one_day_matcher = FileTimeMatcher::new(FileTimeType::Modified,
|
||||
ComparableValue::EqualTo(1));
|
||||
let more_than_one_day_matcher = FileTimeMatcher::new(FileTimeType::Modified,
|
||||
ComparableValue::MoreThan(1));
|
||||
let less_than_one_day_matcher = FileTimeMatcher::new(FileTimeType::Modified,
|
||||
ComparableValue::LessThan(1));
|
||||
let zero_day_matcher = FileTimeMatcher::new(FileTimeType::Modified,
|
||||
ComparableValue::EqualTo(0));
|
||||
|
||||
// set "now" to 2 days after the file was modified.
|
||||
let mut deps = FakeDependencies::new();
|
||||
deps.set_time(files_mtime + Duration::new(2 * super::SECONDS_PER_DAY as u64, 0));
|
||||
assert!(!exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"2 day old file shouldn't match exactly 1 day old");
|
||||
assert!(more_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"2 day old file should match more than 1 day old");
|
||||
assert!(!less_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"2 day old file shouldn't match less than 1 day old");
|
||||
assert!(!zero_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"2 day old file shouldn't match exactly 0 days old");
|
||||
|
||||
// set "now" to 1 day after the file was modified.
|
||||
deps.set_time(files_mtime + Duration::new((3 * super::SECONDS_PER_DAY / 2) as u64, 0));
|
||||
assert!(exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"1 day old file should match exactly 1 day old");
|
||||
assert!(!more_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"1 day old file shouldn't match more than 1 day old");
|
||||
assert!(!less_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"1 day old file shouldn't match less than 1 day old");
|
||||
assert!(!zero_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"1 day old file shouldn't match exactly 0 days old");
|
||||
|
||||
// set "now" to exactly the same time file was modified.
|
||||
deps.set_time(files_mtime);
|
||||
assert!(!exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"0 day old file shouldn't match exactly 1 day old");
|
||||
assert!(!more_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"0 day old file shouldn't match more than 1 day old");
|
||||
assert!(less_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"0 day old file should match less than 1 day old");
|
||||
assert!(zero_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"0 day old file should match exactly 0 days old");
|
||||
|
||||
|
||||
// set "now" to a second before the file was modified (e.g. the file was
|
||||
// modified after find started running
|
||||
deps.set_time(files_mtime - Duration::new(1 as u64, 0));
|
||||
assert!(!exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"future-modified file shouldn'1 match exactly 1 day old");
|
||||
assert!(!more_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"future-modified file shouldn't match more than 1 day old");
|
||||
assert!(less_than_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"future-modified file should match less than 1 day old");
|
||||
assert!(!zero_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"future-modified file shouldn't match exactly 0 days old");
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_time_matcher_modified_created_accessed() {
|
||||
|
||||
let temp_dir = TempDir::new("file_time_matcher_modified_created_accessed").unwrap();
|
||||
|
||||
// No easy way to independently set file times. So create it - setting creation time
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
{
|
||||
File::create(&foo_path).expect("create temp file");
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
// read the file - potentially changing accessed time
|
||||
let mut buffer = [0; 10];
|
||||
{
|
||||
let mut f = File::open(&foo_path).expect("open temp file");
|
||||
let _ = f.read(&mut buffer);
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
// write to the file - changing the modifiation and potentially the accessed time
|
||||
let mut buffer = [0; 10];
|
||||
{
|
||||
let mut f =
|
||||
OpenOptions::new().read(true).write(true).open(&foo_path).expect("open temp file");
|
||||
let _ = f.write(&mut buffer);
|
||||
}
|
||||
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
// read the file agaion - potentially changing accessed time
|
||||
{
|
||||
let mut f = File::open(&foo_path).expect("open temp file");
|
||||
let _ = f.read(&mut buffer);
|
||||
}
|
||||
|
||||
// OK our modification time and creation time should definitely be different
|
||||
// and depending on our platform and file system, our accessed time migh be
|
||||
// different too.
|
||||
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
|
||||
// metadata can return errors like StringError("creation time is not available on this platform currently")
|
||||
// so skip tests that won't pass due to shortcomings in std:;fs.
|
||||
if let Ok(accessed_time) = metadata.accessed() {
|
||||
test_matcher_for_file_time_type(&file_info, accessed_time, FileTimeType::Accessed);
|
||||
}
|
||||
|
||||
if let Ok(creation_time) = metadata.created() {
|
||||
test_matcher_for_file_time_type(&file_info, creation_time, FileTimeType::Created);
|
||||
}
|
||||
|
||||
if let Ok(modified_time) = metadata.modified() {
|
||||
test_matcher_for_file_time_type(&file_info, modified_time, FileTimeType::Modified);
|
||||
}
|
||||
}
|
||||
|
||||
/// helper function for file_time_matcher_modified_created_accessed
|
||||
fn test_matcher_for_file_time_type(file_info: &DirEntry,
|
||||
file_time: SystemTime,
|
||||
file_time_type: FileTimeType) {
|
||||
{
|
||||
let matcher = FileTimeMatcher::new(file_time_type, ComparableValue::EqualTo(0));
|
||||
|
||||
let mut deps = FakeDependencies::new();
|
||||
deps.set_time(file_time);
|
||||
assert!(matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"{:?} time matcher should match",
|
||||
file_time_type);
|
||||
|
||||
deps.set_time(file_time - Duration::from_secs(1));
|
||||
assert!(!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"{:?} time matcher shouldn't match a second before",
|
||||
file_time_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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::error::Error;
|
||||
use std::fs::FileType;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use find::matchers::{Matcher, MatcherIO};
|
||||
|
||||
/// This matcher checks the type of the file.
|
||||
pub struct TypeMatcher {
|
||||
file_type_fn: fn(&FileType) -> bool,
|
||||
}
|
||||
|
||||
impl TypeMatcher {
|
||||
pub fn new(type_string: &str) -> Result<TypeMatcher, Box<Error>> {
|
||||
let function = match type_string {
|
||||
"f" => FileType::is_file,
|
||||
"d" => FileType::is_dir,
|
||||
"b" | "c" | "p" | "l" | "s" | "D" => {
|
||||
return Err(From::from(format!("Type argument {} not supported yet", type_string)))
|
||||
}
|
||||
_ => return Err(From::from(format!("Unrecognised type argument {}", type_string))),
|
||||
};
|
||||
Ok(TypeMatcher { file_type_fn: function })
|
||||
}
|
||||
|
||||
pub fn new_box(type_string: &str) -> Result<Box<Matcher>, Box<Error>> {
|
||||
Ok(Box::new(TypeMatcher::new(type_string)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for TypeMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
(self.file_type_fn)(&file_info.file_type())
|
||||
}
|
||||
|
||||
fn has_side_effects(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
|
||||
mod tests {
|
||||
use find::matchers::Matcher;
|
||||
use find::matchers::tests::get_dir_entry_for;
|
||||
use find::tests::FakeDependencies;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn file_type_matcher() {
|
||||
let file = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let dir = get_dir_entry_for("test_data", "simple");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = TypeMatcher::new(&"f".to_string()).unwrap();
|
||||
assert!(!matcher.matches(&dir, &mut deps.new_matcher_io()));
|
||||
assert!(matcher.matches(&file, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dir_type_matcher() {
|
||||
let file = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let dir = get_dir_entry_for("test_data", "simple");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = TypeMatcher::new(&"d".to_string()).unwrap();
|
||||
assert!(matcher.matches(&dir, &mut deps.new_matcher_io()));
|
||||
assert!(!matcher.matches(&file, &mut deps.new_matcher_io()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cant_create_with_invalid_pattern() {
|
||||
let result = TypeMatcher::new(&"xxx".to_string());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
}
|
||||
+502
File diff suppressed because it is too large
Load Diff
+14
@@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
extern crate glob;
|
||||
extern crate regex;
|
||||
extern crate walkdir;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
pub mod find;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user