Merge branch 'master' into pr/opposing-options

This commit is contained in:
Vukašin Stepanović 2021-08-23 15:55:17 +02:00
commit c749c95136
15 changed files with 144 additions and 44 deletions

View File

@ -1,2 +1 @@
blank_issues_enabled: true blank_issues_enabled: true

View File

@ -6,5 +6,3 @@ labels: feature-request
assignees: '' assignees: ''
--- ---

View File

@ -27,16 +27,22 @@ jobs:
toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }}
default: true default: true
profile: minimal # minimal component installation (ie, no documentation) profile: minimal # minimal component installation (ie, no documentation)
components: clippy components: clippy, rustfmt
- name: Ensure `cargo fmt` has been run
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
- name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: clippy command: clippy
args: --all-targets --all-features args: --locked --all-targets --all-features
- name: Run tests - name: Run tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --locked
build: build:
name: ${{ matrix.job.os }} (${{ matrix.job.target }}) name: ${{ matrix.job.os }} (${{ matrix.job.target }})
@ -100,7 +106,7 @@ jobs:
with: with:
use-cross: ${{ matrix.job.use-cross }} use-cross: ${{ matrix.job.use-cross }}
command: build command: build
args: --release --target=${{ matrix.job.target }} args: --locked --release --target=${{ matrix.job.target }}
- name: Strip debug information from executable - name: Strip debug information from executable
id: strip id: strip
@ -142,6 +148,7 @@ jobs:
id: test-options id: test-options
shell: bash shell: bash
run: | run: |
# test only library unit tests and binary for arm-type targets
unset CARGO_TEST_OPTIONS unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac; unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--bin ${PROJECT_NAME}" ;; esac;
echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS} echo ::set-output name=CARGO_TEST_OPTIONS::${CARGO_TEST_OPTIONS}
@ -151,7 +158,7 @@ jobs:
with: with:
use-cross: ${{ matrix.job.use-cross }} use-cross: ${{ matrix.job.use-cross }}
command: test command: test
args: --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} args: --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}
- name: Create tarball - name: Create tarball
id: package id: package

View File

@ -1,6 +1,9 @@
# Upcoming release # Upcoming release
## Features ## Features
- Don't buffer command output from `--exec` when using a single thread. See #522
- Add new `-q, --quiet` flag, see #303 (@Asha20)
- Add opposing command-line options, see #595 (@Asha20) - Add opposing command-line options, see #595 (@Asha20)
@ -459,7 +462,7 @@ I'd also like to take this chance to say a special Thank You to a few people tha
* Add option to force colored output: `--color always`, see #49 (@Detegr) * Add option to force colored output: `--color always`, see #49 (@Detegr)
* Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel) * Generate Shell completions for Bash, ZSH, Fish and Powershell, see #64 (@ImbaKnugel)
* Better & extended `--help` text (@abaez and @Detegr) * Better & extended `--help` text (@abaez and @Detegr)
* Proper Windows support, see #70 * Proper Windows support, see #70
## Changes ## Changes
@ -487,9 +490,9 @@ I'd also like to take this chance to say a special Thank You to a few people tha
* Changed `--sensitive` to `--case-sensitive` * Changed `--sensitive` to `--case-sensitive`
* Changed `--absolute` to `--absolute-path` * Changed `--absolute` to `--absolute-path`
* Throw an error if root directory is not existent, see #39 * Throw an error if root directory is not existent, see #39
* Use absolute paths if the root dir is an absolute path, see #40 * Use absolute paths if the root dir is an absolute path, see #40
* Handle invalid UTF-8, see #34 #38 * Handle invalid UTF-8, see #34 #38
* Support `-V`, `--version` by switching from `getopts` to `clap`. * Support `-V`, `--version` by switching from `getopts` to `clap`.
Misc: Misc:
@ -497,8 +500,8 @@ Misc:
# v1.1.0 # v1.1.0
- Windows compatibility (@sebasv), see #29 #35 - Windows compatibility (@sebasv), see #29 #35
- Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24 - Safely exit on broken output pipes (e.g.: usage with `head`, `tail`, ..), see #24
- Backport for rust 1.16, see #23 - Backport for rust 1.16, see #23
# v1.0.0 # v1.0.0
@ -510,17 +513,17 @@ Misc:
# v0.3.0 # v0.3.0
- Parse dircolors files, closes #20 - Parse dircolors files, closes #20
- Colorize each path component, closes #19 - Colorize each path component, closes #19
- Add short command line option for --hidden, see #18 - Add short command line option for --hidden, see #18
# v0.2.0 # v0.2.0
- Option to follow symlinks, disable colors, closes #16, closes #17 - Option to follow symlinks, disable colors, closes #16, closes #17
- `--filename` instead of `--full-path` - `--filename` instead of `--full-path`
- Option to search hidden directories, closes #12 - Option to search hidden directories, closes #12
- Configurable search depth, closes #13 - Configurable search depth, closes #13
- Detect interactive terminal, closes #11 - Detect interactive terminal, closes #11
# v0.1.0 # v0.1.0

40
doc/fd.1 vendored
View File

@ -32,19 +32,38 @@ Include hidden files and directories in the search results
.TP .TP
.B \-I, \-\-no\-ignore .B \-I, \-\-no\-ignore
Show search results from files and directories that would otherwise be ignored by Show search results from files and directories that would otherwise be ignored by
.IR .gitignore , .RS
.IR .ignore , .IP \[bu] 2
.IR .fdignore , .I .gitignore
or the global ignore file. .IP \[bu]
.I .git/info/exclude
.IP \[bu]
The global gitignore configuration (by default
.IR $HOME/.config/git/ignore )
.IP \[bu]
.I .ignore
.IP \[bu]
.I .fdignore
.IP \[bu]
The global fd ignore file (usually
.I $HOME/.config/fd/ignore
)
.RE
.IP
The flag can be overridden with '--ignore'. The flag can be overridden with '--ignore'.
.TP .TP
.B \-u, \-\-unrestricted .B \-u, \-\-unrestricted
Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'. Alias for '--no-ignore'. Can be repeated; '-uu' is an alias for '--no-ignore --hidden'.
.TP .TP
.B \-\-no\-ignore\-vcs .B \-\-no\-ignore\-vcs
Show search results from files and directories that would otherwise be ignored by Show search results from files and directories that would otherwise be ignored by gitignore files
.I .gitignore including
files. .IR .gitignore ,
.IR .git/info/exclude ,
and the global gitignore configuration
.RI ( core.excludesFile
git setting, which defaults to
.IR $HOME/.config/git/ignore ).
The flag can be overridden with '--ignore-vcs'. The flag can be overridden with '--ignore-vcs'.
.TP .TP
.B \-s, \-\-case\-sensitive .B \-s, \-\-case\-sensitive
@ -96,6 +115,13 @@ Limit the number of search results to 'count' and quit immediately.
.B \-1 .B \-1
Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'. Limit the search to a single result and quit immediately. This is an alias for '--max-results=1'.
.TP .TP
.B \-q, \-\-quiet
When the flag is present, the program does not print anything and will instead exit with a code of 0 if there is at least one search result.
Otherwise, the exit code will be 1.
This is mainly for usage in scripts and can be faster than checking for output because the search can be stopped early after the first match.
.B \-\-has\-results
can be used as an alias.
.TP
.B \-\-show-errors .B \-\-show-errors
Enable the display of filesystem errors for situations such as insufficient Enable the display of filesystem errors for situations such as insufficient
permissions or dead symlinks. permissions or dead symlinks.

2
doc/screencast.sh vendored
View File

@ -20,7 +20,7 @@ enter() {
} }
prompt() { prompt() {
printf '%b ' $PROMPT | pv -q printf '%b ' "$PROMPT" | pv -q
} }
type() { type() {

View File

@ -554,6 +554,20 @@ pub fn build_app() -> App<'static, 'static> {
.long_help("Limit the search to a single result and quit immediately. \ .long_help("Limit the search to a single result and quit immediately. \
This is an alias for '--max-results=1'.") This is an alias for '--max-results=1'.")
) )
.arg(
Arg::with_name("quiet")
.long("quiet")
.short("q")
.alias("has-results")
.hidden_short_help(true)
.conflicts_with_all(&["exec", "exec-batch", "list-details", "max-results"])
.long_help(
"When the flag is present, the program does not print anything and will \
return with an exit code of 0 if there is at least one match. Otherwise, the \
exit code will be 1. \
'--has-results' can be used as an alias."
)
)
.arg( .arg(
Arg::with_name("show-errors") Arg::with_name("show-errors")
.long("show-errors") .long("show-errors")

View File

@ -7,9 +7,19 @@ use crate::error::print_error;
use crate::exit_codes::ExitCode; use crate::exit_codes::ExitCode;
/// Executes a command. /// Executes a command.
pub fn execute_command(mut cmd: Command, out_perm: &Mutex<()>) -> ExitCode { pub fn execute_command(
mut cmd: Command,
out_perm: &Mutex<()>,
enable_output_buffering: bool,
) -> ExitCode {
// Spawn the supplied command. // Spawn the supplied command.
let output = cmd.output(); let output = if enable_output_buffering {
cmd.output()
} else {
// If running on only one thread, don't buffer output
// Allows for viewing and interacting with intermediate command output
cmd.spawn().and_then(|c| c.wait_with_output())
};
// Then wait for the command to exit, if it was spawned. // Then wait for the command to exit, if it was spawned.
match output { match output {

View File

@ -16,6 +16,7 @@ pub fn job(
cmd: Arc<CommandTemplate>, cmd: Arc<CommandTemplate>,
out_perm: Arc<Mutex<()>>, out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool, show_filesystem_errors: bool,
buffer_output: bool,
) -> ExitCode { ) -> ExitCode {
let mut results: Vec<ExitCode> = Vec::new(); let mut results: Vec<ExitCode> = Vec::new();
loop { loop {
@ -38,7 +39,7 @@ pub fn job(
// Drop the lock so that other threads can read from the receiver. // Drop the lock so that other threads can read from the receiver.
drop(lock); drop(lock);
// Generate a command, execute it and store its exit code. // Generate a command, execute it and store its exit code.
results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm))) results.push(cmd.generate_and_execute(&value, Arc::clone(&out_perm), buffer_output))
} }
// Returns error in case of any error. // Returns error in case of any error.
merge_exitcodes(&results) merge_exitcodes(&results)
@ -48,6 +49,7 @@ pub fn batch(
rx: Receiver<WorkerResult>, rx: Receiver<WorkerResult>,
cmd: &CommandTemplate, cmd: &CommandTemplate,
show_filesystem_errors: bool, show_filesystem_errors: bool,
buffer_output: bool,
) -> ExitCode { ) -> ExitCode {
let paths = rx.iter().filter_map(|value| match value { let paths = rx.iter().filter_map(|value| match value {
WorkerResult::Entry(val) => Some(val), WorkerResult::Entry(val) => Some(val),
@ -58,5 +60,5 @@ pub fn batch(
None None
} }
}); });
cmd.generate_and_execute_batch(paths) cmd.generate_and_execute_batch(paths, buffer_output)
} }

View File

@ -139,7 +139,12 @@ impl CommandTemplate {
/// ///
/// Using the internal `args` field, and a supplied `input` variable, a `Command` will be /// Using the internal `args` field, and a supplied `input` variable, a `Command` will be
/// build. Once all arguments have been processed, the command is executed. /// build. Once all arguments have been processed, the command is executed.
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode { pub fn generate_and_execute(
&self,
input: &Path,
out_perm: Arc<Mutex<()>>,
buffer_output: bool,
) -> ExitCode {
let input = strip_current_dir(input); let input = strip_current_dir(input);
let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref())); let mut cmd = Command::new(self.args[0].generate(&input, self.path_separator.as_deref()));
@ -147,14 +152,14 @@ impl CommandTemplate {
cmd.arg(arg.generate(&input, self.path_separator.as_deref())); cmd.arg(arg.generate(&input, self.path_separator.as_deref()));
} }
execute_command(cmd, &out_perm) execute_command(cmd, &out_perm, buffer_output)
} }
pub fn in_batch_mode(&self) -> bool { pub fn in_batch_mode(&self) -> bool {
self.mode == ExecutionMode::Batch self.mode == ExecutionMode::Batch
} }
pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode pub fn generate_and_execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
where where
I: Iterator<Item = PathBuf>, I: Iterator<Item = PathBuf>,
{ {
@ -182,7 +187,7 @@ impl CommandTemplate {
} }
if has_path { if has_path {
execute_command(cmd, &Mutex::new(())) execute_command(cmd, &Mutex::new(()), buffer_output)
} else { } else {
ExitCode::Success ExitCode::Success
} }
@ -445,11 +450,11 @@ mod tests {
// This is uncommon, but valid // This is uncommon, but valid
check!(r"C:foo\bar", "C:foo#bar"); check!(r"C:foo\bar", "C:foo#bar");
// forward slashses should get normalized and interpreted as separators // forward slashes should get normalized and interpreted as separators
check!("C:/foo/bar", "C:#foo#bar"); check!("C:/foo/bar", "C:#foo#bar");
check!("C:foo/bar", "C:foo#bar"); check!("C:foo/bar", "C:foo#bar");
// Rust does not intrepret "//server/share" as a UNC path, but rather as a normal // Rust does not interpret "//server/share" as a UNC path, but rather as a normal
// absolute path that begins with RootDir, and the two slashes get combined together as // absolute path that begins with RootDir, and the two slashes get combined together as
// a single path separator during normalization. // a single path separator during normalization.
//check!("//server/share/path", "##server#share#path"); //check!("//server/share/path", "##server#share#path");

View File

@ -1,6 +1,7 @@
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum ExitCode { pub enum ExitCode {
Success, Success,
HasResults(bool),
GeneralError, GeneralError,
KilledBySigint, KilledBySigint,
} }
@ -9,6 +10,7 @@ impl From<ExitCode> for i32 {
fn from(code: ExitCode) -> Self { fn from(code: ExitCode) -> Self {
match code { match code {
ExitCode::Success => 0, ExitCode::Success => 0,
ExitCode::HasResults(has_results) => !has_results as i32,
ExitCode::GeneralError => 1, ExitCode::GeneralError => 1,
ExitCode::KilledBySigint => 130, ExitCode::KilledBySigint => 130,
} }
@ -17,7 +19,7 @@ impl From<ExitCode> for i32 {
impl ExitCode { impl ExitCode {
fn is_error(self) -> bool { fn is_error(self) -> bool {
self != ExitCode::Success i32::from(self) != 0
} }
} }

View File

@ -346,6 +346,7 @@ fn run() -> Result<ExitCode> {
follow_links: matches.is_present("follow"), follow_links: matches.is_present("follow"),
one_file_system: matches.is_present("one-file-system"), one_file_system: matches.is_present("one-file-system"),
null_separator: matches.is_present("null_separator"), null_separator: matches.is_present("null_separator"),
quiet: matches.is_present("quiet"),
max_depth: matches max_depth: matches
.value_of("max-depth") .value_of("max-depth")
.or_else(|| matches.value_of("rg-depth")) .or_else(|| matches.value_of("rg-depth"))

View File

@ -54,6 +54,10 @@ pub struct Options {
/// The number of threads to use. /// The number of threads to use.
pub threads: usize, pub threads: usize,
/// If true, the program doesn't print anything and will instead return an exit code of 0
/// if there's at least one match. Otherwise, the exit code will be 1.
pub quiet: bool,
/// Time to buffer results internally before streaming to the console. This is useful to /// Time to buffer results internally before streaming to the console. This is useful to
/// provide a sorted output, in case the total execution time is shorter than /// provide a sorted output, in case the total execution time is shorter than
/// `max_buffer_time`. /// `max_buffer_time`.

View File

@ -170,12 +170,13 @@ fn spawn_receiver(
let show_filesystem_errors = config.show_filesystem_errors; let show_filesystem_errors = config.show_filesystem_errors;
let threads = config.threads; let threads = config.threads;
// This will be used to check if output should be buffered when only running a single thread
let enable_output_buffering: bool = threads > 1;
thread::spawn(move || { thread::spawn(move || {
// This will be set to `Some` if the `--exec` argument was supplied. // This will be set to `Some` if the `--exec` argument was supplied.
if let Some(ref cmd) = config.command { if let Some(ref cmd) = config.command {
if cmd.in_batch_mode() { if cmd.in_batch_mode() {
exec::batch(rx, cmd, show_filesystem_errors) exec::batch(rx, cmd, show_filesystem_errors, enable_output_buffering)
} else { } else {
let shared_rx = Arc::new(Mutex::new(rx)); let shared_rx = Arc::new(Mutex::new(rx));
@ -189,8 +190,15 @@ fn spawn_receiver(
let out_perm = Arc::clone(&out_perm); let out_perm = Arc::clone(&out_perm);
// Spawn a job thread that will listen for and execute inputs. // Spawn a job thread that will listen for and execute inputs.
let handle = let handle = thread::spawn(move || {
thread::spawn(move || exec::job(rx, cmd, out_perm, show_filesystem_errors)); exec::job(
rx,
cmd,
out_perm,
show_filesystem_errors,
enable_output_buffering,
)
});
// Push the handle of the spawned thread into the vector for later joining. // Push the handle of the spawned thread into the vector for later joining.
handles.push(handle); handles.push(handle);
@ -225,6 +233,10 @@ fn spawn_receiver(
for worker_result in rx { for worker_result in rx {
match worker_result { match worker_result {
WorkerResult::Entry(value) => { WorkerResult::Entry(value) => {
if config.quiet {
return ExitCode::HasResults(true);
}
match mode { match mode {
ReceiverMode::Buffering => { ReceiverMode::Buffering => {
buffer.push(value); buffer.push(value);
@ -278,7 +290,11 @@ fn spawn_receiver(
} }
} }
ExitCode::Success if config.quiet {
ExitCode::HasResults(false)
} else {
ExitCode::Success
}
} }
}) })
} }

View File

@ -1426,6 +1426,19 @@ fn test_exec_with_separator() {
); );
} }
/// Non-zero exit code (--quiet)
#[test]
fn test_quiet() {
let dirs = &[];
let files = &["a.foo", "b.foo"];
let te = TestEnv::new(dirs, files);
te.assert_output(&["-q"], "");
te.assert_output(&["--quiet"], "");
te.assert_output(&["--has-results"], "");
te.assert_failure_with_error(&["--quiet", "c.foo"], "")
}
/// Literal search (--fixed-strings) /// Literal search (--fixed-strings)
#[test] #[test]
fn test_fixed_strings() { fn test_fixed_strings() {