Add multiple path support (#182)

* Adding support for multiple paths. (panic)

- Started adding multiple file support
- fd panics with multiple files right now

* Moved the ctrlc handler to main.

- Moved the ctrlc handler to main so we can search multiple files

* Tests now allow custom directory setup

- TestEnv::new() now takes two arguments, the directories to create and
the files to create inside those directories.

* rust-fmt changes

* rust-fmt changes

* Moving code around, no need to do everything in one big loop

- PathDisplay was never actually used for anything, removed it during refactor of main
- Removed redundant logic for absolute paths
- Moved code placed needlessly inside a loop in the last commit outside of that loop.

* Moving code around, no need to do everything in one big loop

- PathDisplay was never actually used for anything, removed it during refactor of main
- Removed redundant logic for absolute paths
- Moved code placed needlessly inside a loop in the last commit outside of that loop.

* Removed commented code in testenv

* Refactored walk::scan to accept the path buffer vector. Using the ParallelWalker allows for multithreaded searching of multiple directories

* Moved ctrlc handler back into walker, it is only called once from main now.

* Moved the colored output check back to it's original place

* Removing shell-escape, not sure how it got added...

* Added test for `fd 'a.foo' test1` to show that a.foo is only found in the test1 and not the test2 direcotry

* Removing side effect from walk::scan, `dir_vec` is no longer a mutable reference and an iterator is being used instead.

* Running rustfmt to format code correctly
This commit is contained in:
Dock 2017-12-06 17:52:23 -05:00 committed by David Peter
parent 8914b43845
commit 51aea57a6a
7 changed files with 115 additions and 82 deletions

View File

@ -41,7 +41,6 @@ num_cpus = "1.6.2"
regex = "0.2"
regex-syntax = "0.4"
ctrlc = "3.0"
shell-escape = "0.1.3"
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
libc = "0.2"

View File

@ -124,7 +124,7 @@ pub fn build_app() -> App<'static, 'static> {
.hidden(true),
)
.arg(arg("pattern"))
.arg(arg("path"))
.arg(arg("path").multiple(true))
}
#[cfg_attr(rustfmt, rustfmt_skip)]

View File

@ -15,16 +15,6 @@ use lscolors::LsColors;
use walk::FileType;
use regex_syntax::{Expr, ExprBuilder};
/// Defines how to display search result paths.
#[derive(PartialEq)]
pub enum PathDisplay {
/// As an absolute path
Absolute,
/// As a relative path
Relative,
}
/// Configuration options for *fd*.
pub struct FdOptions {
/// Whether the search is case-sensitive or case-insensitive.
@ -63,9 +53,6 @@ pub struct FdOptions {
/// `max_buffer_time`.
pub max_buffer_time: Option<time::Duration>,
/// Display results as relative or absolute path.
pub path_display: PathDisplay,
/// `None` if the output should not be colorized. Otherwise, a `LsColors` instance that defines
/// how to style different filetypes.
pub ls_colors: Option<LsColors>,

View File

@ -40,7 +40,7 @@ use atty::Stream;
use regex::RegexBuilder;
use exec::CommandTemplate;
use internal::{error, pattern_has_uppercase_char, FdOptions, PathDisplay};
use internal::{error, pattern_has_uppercase_char, FdOptions};
use lscolors::LsColors;
use walk::FileType;
@ -56,28 +56,31 @@ fn main() {
error("Error: could not get current directory.");
}
// Get the root directory for the search
let mut root_dir_buf = match matches.value_of("path") {
Some(path) => PathBuf::from(path),
None => current_dir.to_path_buf(),
};
if !fshelper::is_dir(&root_dir_buf) {
error(&format!(
"Error: '{}' is not a directory.",
root_dir_buf.to_string_lossy()
));
}
let path_display = if matches.is_present("absolute-path") || root_dir_buf.is_absolute() {
PathDisplay::Absolute
} else {
PathDisplay::Relative
//Get one or more root directories to search.
let mut dir_vec: Vec<_> = match matches.values_of("path") {
Some(paths) => {
paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
error(&format!(
"Error: '{}' is not a directory.",
path_buffer.to_string_lossy()
));
}
path_buffer
})
.collect::<Vec<_>>()
}
None => vec![current_dir.to_path_buf()],
};
if path_display == PathDisplay::Absolute && root_dir_buf.is_relative() {
root_dir_buf = fshelper::absolute_path(root_dir_buf.as_path()).unwrap();
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| fshelper::absolute_path(&path_buffer).unwrap())
.collect();
}
let root_dir = root_dir_buf.as_path();
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
@ -132,7 +135,6 @@ fn main() {
.value_of("max-buffer-time")
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
path_display,
ls_colors,
file_type: match matches.value_of("file-type") {
Some("f") | Some("file") => FileType::RegularFile,
@ -151,11 +153,12 @@ fn main() {
.unwrap_or_else(|| vec![]),
};
match RegexBuilder::new(pattern)
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)
.build() {
Ok(re) => walk::scan(root_dir, Arc::new(re), Arc::new(config)),
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
Err(err) => error(err.description()),
}
}

View File

@ -13,7 +13,7 @@ use fshelper;
use internal::{error, FdOptions};
use output;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::channel;
@ -48,11 +48,15 @@ pub enum FileType {
/// If the `--exec` argument was supplied, this will create a thread pool for executing
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
/// path will simply be written to standard output.
pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
pub fn scan(path_vec: &Vec<PathBuf>, pattern: Arc<Regex>, config: Arc<FdOptions>) {
let mut path_iter = path_vec.iter();
let first_path_buf = path_iter.next().expect(
"Error: Path vector can not be empty",
);
let (tx, rx) = channel();
let threads = config.threads;
let mut override_builder = OverrideBuilder::new(root);
let mut override_builder = OverrideBuilder::new(first_path_buf.as_path());
for pattern in &config.exclude_patterns {
let res = override_builder.add(pattern);
@ -64,7 +68,8 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
error("Mismatch in exclude patterns");
});
let walker = WalkBuilder::new(root)
let mut walker = WalkBuilder::new(first_path_buf.as_path());
walker
.hidden(config.ignore_hidden)
.ignore(config.read_ignore)
.git_ignore(config.read_gitignore)
@ -73,16 +78,20 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
.git_exclude(config.read_gitignore)
.overrides(overrides)
.follow_links(config.follow_links)
.max_depth(config.max_depth)
.threads(threads)
.build_parallel();
.max_depth(config.max_depth);
for path_entry in path_iter {
walker.add(path_entry.as_path());
}
let parallel_walker = walker.threads(threads).build_parallel();
let wants_to_quit = Arc::new(AtomicBool::new(false));
if let Some(_) = config.ls_colors {
let wq = Arc::clone(&wants_to_quit);
ctrlc::set_handler(move || { wq.store(true, Ordering::Relaxed); }).unwrap();
}
// Spawn the thread that receives all results through the channel.
let rx_config = Arc::clone(&config);
let receiver_thread = thread::spawn(move || {
@ -164,11 +173,10 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
});
// Spawn the sender threads.
walker.run(|| {
parallel_walker.run(|| {
let config = Arc::clone(&config);
let pattern = Arc::clone(&pattern);
let tx_thread = tx.clone();
let root = root.to_owned();
Box::new(move |entry_o| {
let entry = match entry_o {
@ -178,7 +186,7 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
let entry_path = entry.path();
if entry_path == root {
if entry.depth() == 0 {
return ignore::WalkState::Continue;
}

View File

@ -35,24 +35,22 @@ pub struct TestEnv {
}
/// Create the working directory and the test files.
fn create_working_directory() -> Result<TempDir, io::Error> {
fn create_working_directory(
directories: &[&'static str],
files: &[&'static str],
) -> Result<TempDir, io::Error> {
let temp_dir = TempDir::new("fd-tests")?;
{
let root = temp_dir.path();
fs::create_dir_all(root.join("one/two/three"))?;
for directory in directories {
fs::create_dir_all(root.join(directory))?;
}
fs::File::create(root.join("a.foo"))?;
fs::File::create(root.join("one/b.foo"))?;
fs::File::create(root.join("one/two/c.foo"))?;
fs::File::create(root.join("one/two/C.Foo2"))?;
fs::File::create(root.join("one/two/three/d.foo"))?;
fs::create_dir(root.join("one/two/three/directory_foo"))?;
fs::File::create(root.join("ignored.foo"))?;
fs::File::create(root.join("gitignored.foo"))?;
fs::File::create(root.join(".hidden.foo"))?;
fs::File::create(root.join("e1 e2"))?;
for file in files {
fs::File::create(root.join(file))?;
}
#[cfg(unix)] unix::fs::symlink(root.join("one/two"), root.join("symlink"))?;
@ -138,8 +136,8 @@ fn normalize_output(s: &str, trim_left: bool) -> String {
}
impl TestEnv {
pub fn new() -> TestEnv {
let temp_dir = create_working_directory().expect("working directory");
pub fn new(directories: &[&'static str], files: &[&'static str]) -> TestEnv {
let temp_dir = create_working_directory(directories, files).expect("working directory");
let fd_exe = find_fd_exe();
TestEnv {

View File

@ -15,6 +15,20 @@ mod testenv;
use testenv::TestEnv;
use regex::escape;
static DEFAULT_DIRS: &'static [&'static str] = &["one/two/three", "one/two/three/directory_foo"];
static DEFAULT_FILES: &'static [&'static str] = &[
"a.foo",
"one/b.foo",
"one/two/c.foo",
"one/two/C.Foo2",
"one/two/three/d.foo",
"ignored.foo",
"gitignored.foo",
".hidden.foo",
"e1 e2",
];
fn get_absolute_root_path(env: &TestEnv) -> String {
let path = env.test_root()
.canonicalize()
@ -32,7 +46,7 @@ fn get_absolute_root_path(env: &TestEnv) -> String {
/// Simple tests
#[test]
fn test_simple() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["a.foo"], "a.foo");
te.assert_output(&["b.foo"], "one/b.foo");
@ -64,10 +78,34 @@ fn test_simple() {
);
}
/// Test multiple directory searches
#[test]
fn test_multi_file() {
let dirs = &["test1", "test2"];
let files = &["test1/a.foo", "test1/b.foo", "test2/a.foo"];
let te = TestEnv::new(dirs, files);
te.assert_output(
&["a.foo", "test1", "test2"],
"test1/a.foo
test2/a.foo",
);
te.assert_output(
&["", "test1", "test2"],
"test1/a.foo
test2/a.foo
test1/b.foo",
);
te.assert_output(&["a.foo", "test1"], "test1/a.foo");
te.assert_output(&["b.foo", "test1", "test2"], "test1/b.foo");
}
/// Explicit root path
#[test]
fn test_explicit_root_path() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["foo", "one"],
@ -109,7 +147,7 @@ fn test_explicit_root_path() {
/// Regex searches
#[test]
fn test_regex_searches() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["[a-c].foo"],
@ -130,7 +168,7 @@ fn test_regex_searches() {
/// Smart case
#[test]
fn test_smart_case() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["c.foo"],
@ -154,7 +192,7 @@ fn test_smart_case() {
/// Case sensitivity (--case-sensitive)
#[test]
fn test_case_sensitive() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(&["--case-sensitive", "c.foo"], "one/two/c.foo");
@ -169,7 +207,7 @@ fn test_case_sensitive() {
/// Case insensitivity (--ignore-case)
#[test]
fn test_case_insensitive() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--ignore-case", "C.Foo"],
@ -187,7 +225,7 @@ fn test_case_insensitive() {
/// Full path search (--full-path)
#[test]
fn test_full_path() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let root = te.system_root();
let prefix = escape(&root.to_string_lossy());
@ -205,7 +243,7 @@ fn test_full_path() {
/// Hidden files (--hidden)
#[test]
fn test_hidden() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--hidden", "foo"],
@ -222,7 +260,7 @@ fn test_hidden() {
/// Ignored files (--no-ignore)
#[test]
fn test_no_ignore() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore", "foo"],
@ -253,7 +291,7 @@ fn test_no_ignore() {
/// VCS ignored files (--no-ignore-vcs)
#[test]
fn test_no_ignore_vcs() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--no-ignore-vcs", "foo"],
@ -270,7 +308,7 @@ fn test_no_ignore_vcs() {
/// Ignored files with ripgrep aliases (-u / -uu)
#[test]
fn test_no_ignore_aliases() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["-u", "foo"],
@ -301,7 +339,7 @@ fn test_no_ignore_aliases() {
/// Symlinks (--follow)
#[test]
fn test_follow() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--follow", "c.foo"],
@ -315,7 +353,7 @@ fn test_follow() {
/// Null separator (--print0)
#[test]
fn test_print0() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--print0", "foo"],
@ -331,7 +369,7 @@ fn test_print0() {
/// Maximum depth (--max-depth)
#[test]
fn test_max_depth() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--max-depth", "3"],
@ -368,7 +406,7 @@ fn test_max_depth() {
/// Absolute paths (--absolute-path)
#[test]
fn test_absolute_path() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
@ -420,7 +458,7 @@ fn test_absolute_path() {
/// File type filter (--type)
#[test]
fn test_type() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--type", "f"],
@ -446,7 +484,7 @@ fn test_type() {
/// File extension (--extension)
#[test]
fn test_extension() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--extension", "foo"],
@ -470,7 +508,7 @@ fn test_extension() {
/// Symlinks misc
#[test]
fn test_symlink() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);
@ -570,7 +608,7 @@ fn test_symlink() {
/// Exclude patterns (--exclude)
#[test]
fn test_excludes() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
te.assert_output(
&["--exclude", "*.foo"],
@ -620,7 +658,7 @@ fn test_excludes() {
/// Shell script execution (--exec)
#[test]
fn test_exec() {
let te = TestEnv::new();
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let abs_path = get_absolute_root_path(&te);