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:
mcharsley
2017-03-08 14:39:37 +00:00
committed by GitHub
parent d26924b896
commit 3198ff3b82
23 changed files with 2762 additions and 2 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
target
Cargo.lock
.gitignore
.project
.cargo
.settings
+4
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
+15
View File
@@ -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));
}
+481
View File
@@ -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
+139
View File
@@ -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());
}
}
+55
View File
@@ -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());
}
}
+54
View File
@@ -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());
}
}
+173
View File
@@ -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");
}
}
+332
View File
@@ -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);
}
}
}
+81
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -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;
View File
View File
View File
View File

Some files were not shown because too many files have changed in this diff Show More