diff --git a/Cargo.lock b/Cargo.lock index be583d37e..36fa84273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,8 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/globset/Cargo.toml b/globset/Cargo.toml index 96d47e3fd..ab3feb07e 100644 --- a/globset/Cargo.toml +++ b/globset/Cargo.toml @@ -24,9 +24,11 @@ fnv = "1.0" log = "0.3" memchr = "2" regex = "0.2.1" +walkdir = "2.0" [dev-dependencies] glob = "0.2" +tempdir = "0.3" [features] simd-accel = ["regex/simd-accel"] diff --git a/globset/src/lib.rs b/globset/src/lib.rs index dd6922e6d..b4d40a67f 100644 --- a/globset/src/lib.rs +++ b/globset/src/lib.rs @@ -104,6 +104,10 @@ extern crate fnv; extern crate log; extern crate memchr; extern crate regex; +extern crate walkdir; + +#[cfg(test)] +extern crate tempdir; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; @@ -111,7 +115,7 @@ use std::error::Error as StdError; use std::ffi::{OsStr, OsString}; use std::fmt; use std::hash; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::str; use aho_corasick::{Automaton, AcAutomaton, FullAcAutomaton}; @@ -417,6 +421,44 @@ impl GlobSet { ], }) } + + /// Iterate files and directories matching this globset at the given directory. + pub fn iter_at>(&self, dir: P) -> GlobWalker { + GlobWalker { + glob: self, + base: dir.as_ref().into(), + walker: walkdir::WalkDir::new(dir).into_iter() + } + } +} + +/// An iterator for recursively yielding glob matches. +/// +/// The order of elements yielded by this iterator is unspecified. +pub struct GlobWalker<'a> { + glob: &'a GlobSet, + base: PathBuf, + walker: walkdir::IntoIter, +} + +impl<'a> Iterator for GlobWalker<'a> { + type Item = walkdir::DirEntry; + + fn next(&mut self) -> Option { + for entry in &mut self.walker { + if let Ok(entry) = entry { + // Strip the common base directory so that the matcher will be + // able to recognize the file name. + // `unwrap` here is safe, since walkdir returns the files with relation + // to the given base-dir. + if self.glob.is_match(entry.path().strip_prefix(&*self.base).unwrap()) { + return Some(entry) + } + } + } + + None + } } /// GlobSetBuilder builds a group of patterns that can be used to @@ -804,6 +846,8 @@ impl RequiredExtensionStrategyBuilder { mod tests { use super::GlobSetBuilder; use glob::Glob; + use ::tempdir::TempDir; + use ::std::fs::{File, create_dir_all}; #[test] fn set_works() { @@ -832,4 +876,63 @@ mod tests { assert!(!set.is_match("")); assert!(!set.is_match("a")); } + + fn touch(dir: &TempDir, names: &[&str]) { + for name in names { + File::create(dir.path().join(name)).expect("Failed to create a test file"); + } + } + + #[test] + fn do_the_globwalk() { + let dir = TempDir::new("globset_walkdir").expect("Failed to create temporary folder"); + let dir_path = dir.path(); + create_dir_all(dir_path.join("src/some_mod")).expect(""); + create_dir_all(dir_path.join("tests")).expect(""); + create_dir_all(dir_path.join("contrib")).expect(""); + + touch(&dir, &[ + "a.rs", + "b.rs", + "avocado.rs", + "lib.c", + "src/hello.rs", + "src/world.rs", + "src/some_mod/unexpected.rs", + "src/cruel.txt", + "contrib/README.md", + "contrib/README.rst", + "contrib/lib.rs", + ][..]); + + + let mut builder = GlobSetBuilder::new(); + builder.add(Glob::new("src/**/*.rs").unwrap()); + builder.add(Glob::new("*.c").unwrap()); + builder.add(Glob::new("**/lib.rs").unwrap()); + builder.add(Glob::new("**/*.{md,rst}").unwrap()); + let set = builder.build().unwrap(); + + let mut expected = vec!["src/some_mod/unexpected.rs", + "src/world.rs", + "src/hello.rs", + "lib.c", + "contrib/lib.rs", + "contrib/README.md", + "contrib/README.rst"]; + + for matched_file in set.iter_at(dir_path) { + let path = matched_file.path().strip_prefix(dir_path).unwrap().to_str().unwrap(); + + let del_idx = if let Some(idx) = expected.iter().position(|n| &path == n) { + idx + } else { + panic!("Iterated file is unexpected: {}", path); + }; + expected.remove(del_idx); + } + + let empty: &[&str] = &[][..]; + assert_eq!(expected, empty); + } }