mirror of https://github.com/sharkdp/fd.git
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:
parent
8914b43845
commit
51aea57a6a
|
@ -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"
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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>,
|
||||
|
|
47
src/main.rs
47
src/main.rs
|
@ -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()),
|
||||
}
|
||||
}
|
||||
|
|
30
src/walk.rs
30
src/walk.rs
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue