-
-
Notifications
You must be signed in to change notification settings - Fork 814
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 support for batch execution of command #360
Changes from all commits
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 |
---|---|---|
|
@@ -13,28 +13,61 @@ mod job; | |
mod token; | ||
|
||
use std::borrow::Cow; | ||
use std::path::Path; | ||
use std::process::Command; | ||
use std::path::{Path, PathBuf}; | ||
use std::process::{Command, Stdio}; | ||
use std::sync::{Arc, Mutex}; | ||
|
||
use regex::Regex; | ||
|
||
use self::command::execute_command; | ||
use self::input::{basename, dirname, remove_extension}; | ||
pub use self::job::job; | ||
pub use self::job::{batch, job}; | ||
use self::token::Token; | ||
|
||
/// Execution mode of the command | ||
#[derive(Debug, Clone, Copy, PartialEq)] | ||
pub enum ExecutionMode { | ||
/// Command is executed for each search result | ||
OneByOne, | ||
/// Command is run for a batch of results at once | ||
Batch, | ||
} | ||
|
||
/// Represents a template that is utilized to generate command strings. | ||
/// | ||
/// The template is meant to be coupled with an input in order to generate a command. The | ||
/// `generate_and_execute()` method will be used to generate a command and execute it. | ||
#[derive(Debug, Clone, PartialEq)] | ||
pub struct CommandTemplate { | ||
args: Vec<ArgumentTemplate>, | ||
mode: ExecutionMode, | ||
} | ||
|
||
impl CommandTemplate { | ||
pub fn new<I, S>(input: I) -> CommandTemplate | ||
where | ||
I: IntoIterator<Item = S>, | ||
S: AsRef<str>, | ||
{ | ||
Self::build(input, ExecutionMode::OneByOne) | ||
} | ||
|
||
pub fn new_batch<I, S>(input: I) -> Result<CommandTemplate, &'static str> | ||
where | ||
I: IntoIterator<Item = S>, | ||
S: AsRef<str>, | ||
{ | ||
let cmd = Self::build(input, ExecutionMode::Batch); | ||
if cmd.number_of_tokens() > 1 { | ||
return Err("Only one placeholder allowed for batch commands"); | ||
} | ||
if cmd.args[0].has_tokens() { | ||
return Err("First argument of exec-batch is expected to be a fixed executable"); | ||
} | ||
Ok(cmd) | ||
} | ||
|
||
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate | ||
where | ||
I: IntoIterator<Item = S>, | ||
S: AsRef<str>, | ||
|
@@ -91,26 +124,68 @@ impl CommandTemplate { | |
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder])); | ||
} | ||
|
||
CommandTemplate { args } | ||
CommandTemplate { args, mode } | ||
} | ||
|
||
fn number_of_tokens(&self) -> usize { | ||
self.args.iter().filter(|arg| arg.has_tokens()).count() | ||
} | ||
|
||
fn prepare_path(input: &Path) -> String { | ||
input | ||
.strip_prefix(".") | ||
.unwrap_or(input) | ||
.to_string_lossy() | ||
.into_owned() | ||
} | ||
|
||
/// Generates and executes a command. | ||
/// | ||
/// 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. | ||
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) { | ||
let input = input | ||
.strip_prefix(".") | ||
.unwrap_or(input) | ||
.to_string_lossy() | ||
.into_owned(); | ||
let input = Self::prepare_path(input); | ||
|
||
let mut cmd = Command::new(self.args[0].generate(&input).as_ref()); | ||
for arg in &self.args[1..] { | ||
cmd.arg(arg.generate(&input).as_ref()); | ||
} | ||
|
||
execute_command(cmd, out_perm) | ||
execute_command(cmd, &out_perm) | ||
} | ||
|
||
pub fn in_batch_mode(&self) -> bool { | ||
self.mode == ExecutionMode::Batch | ||
} | ||
|
||
pub fn generate_and_execute_batch<I>(&self, paths: I) | ||
where | ||
I: Iterator<Item = PathBuf>, | ||
{ | ||
let mut cmd = Command::new(self.args[0].generate("").as_ref()); | ||
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. That 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 this is what causes the panic you mention for `fd -X "echo {}". Will have a look. 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. With the latest update, the first argument is not expected to have a token (checked at |
||
cmd.stdin(Stdio::inherit()); | ||
cmd.stdout(Stdio::inherit()); | ||
cmd.stderr(Stdio::inherit()); | ||
|
||
let mut paths = paths.map(|p| Self::prepare_path(&p)); | ||
let mut has_path = false; | ||
|
||
for arg in &self.args[1..] { | ||
if arg.has_tokens() { | ||
// A single `Tokens` is expected | ||
// So we can directy consume the iterator once and for all | ||
for path in &mut paths { | ||
cmd.arg(arg.generate(&path).as_ref()); | ||
has_path = true; | ||
} | ||
} else { | ||
cmd.arg(arg.generate("").as_ref()); | ||
} | ||
} | ||
|
||
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. If we add the following lines here: cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit()); we can use 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. It's indeed much better with these lines added. |
||
if has_path { | ||
execute_command(cmd, &Mutex::new(())); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -125,6 +200,13 @@ enum ArgumentTemplate { | |
} | ||
|
||
impl ArgumentTemplate { | ||
pub fn has_tokens(&self) -> bool { | ||
match self { | ||
ArgumentTemplate::Tokens(_) => true, | ||
_ => false, | ||
} | ||
} | ||
|
||
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> { | ||
use self::Token::*; | ||
|
||
|
@@ -162,6 +244,7 @@ mod tests { | |
ArgumentTemplate::Text("${SHELL}:".into()), | ||
ArgumentTemplate::Tokens(vec![Token::Placeholder]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
@@ -175,6 +258,7 @@ mod tests { | |
ArgumentTemplate::Text("echo".into()), | ||
ArgumentTemplate::Tokens(vec![Token::NoExt]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
@@ -188,6 +272,7 @@ mod tests { | |
ArgumentTemplate::Text("echo".into()), | ||
ArgumentTemplate::Tokens(vec![Token::Basename]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
@@ -201,6 +286,7 @@ mod tests { | |
ArgumentTemplate::Text("echo".into()), | ||
ArgumentTemplate::Tokens(vec![Token::Parent]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
@@ -214,6 +300,7 @@ mod tests { | |
ArgumentTemplate::Text("echo".into()), | ||
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
@@ -231,7 +318,27 @@ mod tests { | |
Token::Text(".ext".into()) | ||
]), | ||
], | ||
mode: ExecutionMode::OneByOne, | ||
} | ||
); | ||
} | ||
|
||
#[test] | ||
fn tokens_single_batch() { | ||
assert_eq!( | ||
CommandTemplate::new_batch(&["echo", "{.}"]).unwrap(), | ||
CommandTemplate { | ||
args: vec![ | ||
ArgumentTemplate::Text("echo".into()), | ||
ArgumentTemplate::Tokens(vec![Token::NoExt]), | ||
], | ||
mode: ExecutionMode::Batch, | ||
} | ||
); | ||
} | ||
|
||
#[test] | ||
fn tokens_multiple_batch() { | ||
assert!(CommandTemplate::new_batch(&["echo", "{.}", "{}"]).is_err()); | ||
} | ||
} |
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.
Nice!