fd/src/main.rs

247 lines
8.3 KiB
Rust
Raw Normal View History

2017-10-21 10:16:03 +02:00
// Copyright (c) 2017 fd developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
2017-05-12 13:02:20 +02:00
extern crate ansi_term;
2017-06-10 17:30:48 +02:00
extern crate atty;
2017-10-14 18:04:11 +02:00
#[macro_use]
extern crate clap;
2017-05-15 22:38:34 +02:00
extern crate ignore;
2017-10-14 18:04:11 +02:00
#[macro_use]
extern crate lazy_static;
2017-10-15 03:25:56 +02:00
#[cfg(all(unix, not(target_os = "redox")))]
extern crate libc;
extern crate num_cpus;
2017-10-14 18:04:11 +02:00
extern crate regex;
2017-10-14 18:30:10 +02:00
extern crate regex_syntax;
2017-06-05 11:56:39 +02:00
2017-10-04 14:31:08 +02:00
mod app;
2017-10-14 18:04:11 +02:00
mod exec;
mod exit_codes;
2018-04-13 22:46:17 +02:00
pub mod fshelper;
2017-10-10 08:01:17 +02:00
mod internal;
2018-04-13 22:46:17 +02:00
pub mod lscolors;
2017-10-10 08:01:17 +02:00
mod output;
mod walk;
2017-05-12 11:50:03 +02:00
use std::env;
2017-05-12 19:34:31 +02:00
use std::error::Error;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time;
2017-05-12 11:50:03 +02:00
2017-06-10 17:30:48 +02:00
use atty::Stream;
2018-04-24 00:13:21 +02:00
use regex::{RegexBuilder, RegexSetBuilder};
2017-05-12 13:02:20 +02:00
use exec::CommandTemplate;
2018-05-14 18:39:47 +02:00
use internal::{
pattern_has_uppercase_char, print_error_and_exit, transform_args_with_exec, FdOptions,
FileTypes, SizeFilter,
2018-05-14 18:39:47 +02:00
};
2017-06-05 11:56:39 +02:00
use lscolors::LsColors;
2017-05-12 11:50:03 +02:00
fn main() {
let checked_args = transform_args_with_exec(env::args_os());
let matches = app::build_app().get_matches_from(checked_args);
2017-05-12 11:50:03 +02:00
// Get the search pattern
let pattern = matches.value_of("pattern").unwrap_or("");
2017-05-12 11:50:03 +02:00
// Get the current working directory
let current_dir = Path::new(".");
2017-10-22 11:47:05 +02:00
if !fshelper::is_dir(current_dir) {
print_error_and_exit("Error: could not get current directory.");
}
2017-05-12 11:50:03 +02:00
2018-03-14 22:49:53 +01:00
// Get one or more root directories to search.
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
2017-12-06 23:52:23 +01:00
let mut dir_vec: Vec<_> = match matches.values_of("path") {
2018-01-01 12:16:43 +01:00
Some(paths) => paths
.map(|path| {
let path_buffer = PathBuf::from(path);
if !fshelper::is_dir(&path_buffer) {
print_error_and_exit(&format!(
2018-01-01 12:16:43 +01:00
"Error: '{}' is not a directory.",
path_buffer.to_string_lossy()
));
}
path_buffer
2018-09-27 23:01:38 +02:00
})
.collect::<Vec<_>>(),
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
2017-12-06 23:52:23 +01:00
None => vec![current_dir.to_path_buf()],
};
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
2017-12-06 23:52:23 +01:00
if matches.is_present("absolute-path") {
dir_vec = dir_vec
.iter()
.map(|path_buffer| {
path_buffer
.canonicalize()
.and_then(|pb| fshelper::absolute_path(pb.as_path()))
.unwrap()
2018-09-27 23:01:38 +02:00
})
.collect();
}
// Detect if the user accidentally supplied a path instead of a search pattern
2018-05-14 18:39:47 +02:00
if !matches.is_present("full-path")
&& pattern.contains(std::path::MAIN_SEPARATOR)
&& fshelper::is_dir(Path::new(pattern))
{
print_error_and_exit(&format!(
"Error: The search pattern '{pattern}' contains a path-separation character ('{sep}') \
and will not lead to any search results.\n\n\
If you want to search for all files inside the '{pattern}' directory, use a match-all pattern:\n\n \
fd . '{pattern}'\n\n\
Instead, if you want to search for the pattern in the full path, use:\n\n \
fd --full-path '{pattern}'",
pattern = pattern,
sep = std::path::MAIN_SEPARATOR,
));
}
2018-02-10 15:19:53 +01:00
// Treat pattern as literal string if '--fixed-strings' is used
let pattern_regex = if matches.is_present("fixed-strings") {
regex::escape(pattern)
} else {
String::from(pattern)
};
2017-06-05 14:14:01 +02:00
// The search will be case-sensitive if the command line flag is set or
// if the pattern has an uppercase character (smart case).
2018-01-01 12:16:43 +01:00
let case_sensitive = !matches.is_present("ignore-case")
2018-02-10 15:19:53 +01:00
&& (matches.is_present("case-sensitive") || pattern_has_uppercase_char(&pattern_regex));
2017-06-05 14:14:01 +02:00
let colored_output = match matches.value_of("color") {
Some("always") => true,
Some("never") => false,
2017-10-12 08:01:51 +02:00
_ => atty::is(Stream::Stdout),
};
#[cfg(windows)]
2018-03-12 22:24:12 +01:00
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
2017-10-12 08:01:51 +02:00
let ls_colors = if colored_output {
Some(
env::var("LS_COLORS")
.ok()
.map(|val| LsColors::from_string(&val))
.unwrap_or_default(),
)
} else {
None
};
let command = matches.values_of("exec").map(CommandTemplate::new);
2017-10-14 18:04:11 +02:00
let size_limits: Vec<SizeFilter> = matches
.values_of("size")
2018-04-24 00:13:21 +02:00
.map(|v| {
v.map(|sf| {
if let Some(f) = SizeFilter::from_string(sf) {
return f;
}
print_error_and_exit(&format!("Error: {} is not a valid size constraint.", sf));
2018-09-27 23:01:38 +02:00
})
.collect()
})
.unwrap_or_else(|| vec![]);
let config = FdOptions {
2017-10-14 18:04:11 +02:00
case_sensitive,
2017-10-12 08:01:51 +02:00
search_full_path: matches.is_present("full-path"),
2018-01-01 12:16:43 +01:00
ignore_hidden: !(matches.is_present("hidden")
|| matches.occurrences_of("rg-alias-hidden-ignore") >= 2),
2018-02-21 21:41:52 +01:00
read_fdignore: !(matches.is_present("no-ignore")
2018-01-01 12:16:43 +01:00
|| matches.is_present("rg-alias-hidden-ignore")),
2018-02-21 21:41:52 +01:00
read_vcsignore: !(matches.is_present("no-ignore")
2018-01-01 12:16:43 +01:00
|| matches.is_present("rg-alias-hidden-ignore")
|| matches.is_present("no-ignore-vcs")),
2017-10-12 08:01:51 +02:00
follow_links: matches.is_present("follow"),
null_separator: matches.is_present("null_separator"),
2018-01-01 12:16:43 +01:00
max_depth: matches
.value_of("depth")
.or_else(|| matches.value_of("rg-depth"))
2018-01-01 12:16:43 +01:00
.and_then(|n| usize::from_str_radix(n, 10).ok()),
2017-10-12 08:01:51 +02:00
threads: std::cmp::max(
matches
.value_of("threads")
.and_then(|n| usize::from_str_radix(n, 10).ok())
.unwrap_or_else(num_cpus::get),
1,
),
max_buffer_time: matches
.value_of("max-buffer-time")
.and_then(|n| u64::from_str_radix(n, 10).ok())
.map(time::Duration::from_millis),
2017-10-14 18:04:11 +02:00
ls_colors,
file_types: matches.values_of("file-type").map(|values| {
let mut file_types = FileTypes::default();
for value in values {
match value {
"f" | "file" => file_types.files = true,
"d" | "directory" => file_types.directories = true,
"l" | "symlink" => file_types.symlinks = true,
2018-03-25 12:19:51 +02:00
"x" | "executable" => {
file_types.executables_only = true;
file_types.files = true;
2018-03-25 16:36:37 +02:00
}
"e" | "empty" => {
file_types.empty_only = true;
}
_ => unreachable!(),
}
}
// If only 'empty' was specified, search for both files and directories:
if file_types.empty_only && !(file_types.files || file_types.directories) {
file_types.files = true;
file_types.directories = true;
}
file_types
}),
extensions: matches.values_of("extension").map(|exts| {
2018-05-14 18:39:47 +02:00
let patterns = exts
.map(|e| e.trim_left_matches('.'))
.map(|e| format!(r".\.{}$", regex::escape(e)));
match RegexSetBuilder::new(patterns)
.case_insensitive(true)
.build()
{
Ok(re) => re,
Err(err) => print_error_and_exit(err.description()),
}
2017-10-12 08:01:51 +02:00
}),
2017-10-14 20:04:04 +02:00
command,
exclude_patterns: matches
.values_of("exclude")
.map(|v| v.map(|p| String::from("!") + p).collect())
2017-10-26 21:13:56 +02:00
.unwrap_or_else(|| vec![]),
2018-03-26 00:15:01 +02:00
ignore_files: matches
.values_of("ignore-file")
.map(|vs| vs.map(PathBuf::from).collect())
.unwrap_or_else(|| vec![]),
size_constraints: size_limits,
};
2017-05-12 13:32:30 +02:00
2018-02-10 15:19:53 +01:00
match RegexBuilder::new(&pattern_regex)
2017-10-12 08:01:51 +02:00
.case_insensitive(!config.case_sensitive)
.dot_matches_new_line(true)
2018-01-01 12:16:43 +01:00
.build()
{
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
2017-12-06 23:52:23 +01:00
Ok(re) => walk::scan(&dir_vec, Arc::new(re), Arc::new(config)),
Err(err) => print_error_and_exit(
format!(
"{}\nHint: You can use the '--fixed-strings' option to search for a \
literal string instead of a regular expression",
err.description()
2018-09-27 23:01:38 +02:00
)
.as_str(),
),
2017-05-12 11:50:03 +02:00
}
}