-
-
Notifications
You must be signed in to change notification settings - Fork 828
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add --filter argument that filters results based on command exit-code #1545
base: master
Are you sure you want to change the base?
Changes from 5 commits
a93fa75
30cd3ea
ad9cd5d
ee1de0f
6f61c18
500b851
dc0cc1a
6fe670d
d1b3bf4
aefd6ac
8360c9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -776,6 +776,11 @@ impl clap::FromArgMatches for Exec { | |
.get_occurrences::<String>("exec_batch") | ||
.map(CommandSet::new_batch) | ||
}) | ||
.or_else(|| { | ||
matches | ||
.get_occurrences::<String>("filter") | ||
.map(CommandSet::new_filter) | ||
}) | ||
.transpose() | ||
.map_err(|e| clap::Error::raw(ErrorKind::InvalidValue, e))?; | ||
Ok(Exec { command }) | ||
|
@@ -797,7 +802,7 @@ impl clap::Args for Exec { | |
.allow_hyphen_values(true) | ||
.value_terminator(";") | ||
.value_name("cmd") | ||
.conflicts_with("list_details") | ||
.conflicts_with_all(["list_details", "exec_batch"]) | ||
.help("Execute a command for each search result") | ||
.long_help( | ||
"Execute a command for each search result in parallel (use --threads=1 for sequential command execution). \ | ||
|
@@ -851,7 +856,35 @@ impl clap::Args for Exec { | |
fd -g 'test_*.py' -X vim\n\n \ | ||
- Find all *.rs files and count the lines with \"wc -l ...\":\n\n \ | ||
fd -e rs -X wc -l\ | ||
" | ||
" | ||
), | ||
) | ||
.arg( | ||
Arg::new("filter") | ||
.action(ArgAction::Append) | ||
.long("filter") | ||
.short('f') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we should use a short option for this. After all we don't have a short option for |
||
.num_args(1..) | ||
.allow_hyphen_values(true) | ||
.value_terminator(";") | ||
.value_name("cmd") | ||
.conflicts_with_all(["exec", "exec_batch", "list_details"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this need to conflict? Logically, it makes sense to allow filtering items first, and then passing the results that succeed through to exec or exec-batch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this idea, I'll consider how we can make these commands compatible. |
||
.help("Execute a command to determine whether each result should be filtered") | ||
.long_help( | ||
"Execute a command in parallel for each search result, filtering out results where the exit code is non-zero. \ | ||
There is no guarantee of the order commands are executed in, and the order should not be depended upon. \ | ||
All positional arguments following --filter are considered to be arguments to the command - not to fd. \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe mention that you can end with a |
||
It is therefore recommended to place the '-f'/'--filter' option last.\n\ | ||
The following placeholders are substituted before the command is executed:\n \ | ||
'{}': path (of the current search result)\n \ | ||
'{/}': basename\n \ | ||
'{//}': parent directory\n \ | ||
'{.}': path without file extension\n \ | ||
'{/.}': basename without file extension\n \ | ||
'{{': literal '{' (for escaping)\n \ | ||
'}}': literal '}' (for escaping)\n\n\ | ||
If no placeholder is present, an implicit \"{}\" at the end is assumed.\n\n\ | ||
" | ||
), | ||
) | ||
} | ||
|
@@ -874,3 +907,4 @@ fn ensure_current_directory_exists(current_directory: &Path) -> anyhow::Result<( | |
)) | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -93,6 +93,41 @@ pub fn execute_commands<I: Iterator<Item = io::Result<Command>>>( | |
ExitCode::Success | ||
} | ||
|
||
pub fn execute_commands_filtering<I: Iterator<Item = io::Result<Command>>>( | ||
path: &std::path::Path, | ||
cmds: I, | ||
out_perm: &Mutex<()>, | ||
) -> ExitCode { | ||
let mut output_buffer = OutputBuffer::new(out_perm); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Writing to this output buffer skips all the logic in output.rs. The filter code shouldn't be handling output at all. Rather, this should be used as one of the filters that we use to exclude files to be processed in walk.rs in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I based this on how the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, with exec, the output of the command itself is the desired output, but with filter, the desired output is the path (but possibly with some transformations done on it). The question here is what should we do with the output from the filter command, if any (I imagine in most cases there won't be any). Some options are:
I do think if we do print the output it should be to stderr. |
||
let path = format!("{}\n", path.to_str().unwrap()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we add a newline to the path? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The newline is necessary for each result to be shown on a different line if it passed the filter |
||
let path: Vec<u8> = path.into(); | ||
|
||
for result in cmds { | ||
let mut cmd = match result { | ||
Ok(cmd) => cmd, | ||
Err(e) => return handle_cmd_error(None, e), | ||
}; | ||
|
||
let output = cmd.output(); | ||
|
||
match output { | ||
Ok(output) => { | ||
if output.status.code() != Some(0) { | ||
return ExitCode::GeneralError; | ||
} else { | ||
output_buffer.push(path.clone(), vec![]); | ||
} | ||
}, | ||
Err(why) => { | ||
return handle_cmd_error(Some(&cmd), why); | ||
}, | ||
} | ||
} | ||
|
||
output_buffer.write(); | ||
ExitCode::Success | ||
} | ||
|
||
pub fn handle_cmd_error(cmd: Option<&Command>, err: io::Error) -> ExitCode { | ||
match (cmd, err) { | ||
(Some(cmd), err) if err.kind() == io::ErrorKind::NotFound => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be implemented in a way that would allow using it along with exec or exec-batch?