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-10-14 18:04:11 +02:00
|
|
|
// TODO: Possible optimization could avoid pushing characters on a buffer.
|
2017-11-15 01:56:32 +01:00
|
|
|
mod command;
|
2017-10-20 21:54:43 +02:00
|
|
|
mod input;
|
2018-04-13 22:46:17 +02:00
|
|
|
mod job;
|
|
|
|
mod token;
|
2017-10-14 18:04:11 +02:00
|
|
|
|
2017-11-03 01:39:03 +01:00
|
|
|
use std::borrow::Cow;
|
2018-11-11 18:00:01 +01:00
|
|
|
use std::path::{Path, PathBuf};
|
2018-11-12 18:43:40 +01:00
|
|
|
use std::process::{Command, Stdio};
|
2017-10-14 23:59:36 +02:00
|
|
|
use std::sync::{Arc, Mutex};
|
2017-10-14 18:04:11 +02:00
|
|
|
|
2019-01-05 18:24:33 +01:00
|
|
|
use lazy_static::lazy_static;
|
2017-11-03 01:39:03 +01:00
|
|
|
use regex::Regex;
|
|
|
|
|
2019-09-13 22:26:27 +02:00
|
|
|
use crate::exit_codes::ExitCode;
|
|
|
|
|
2017-11-15 01:56:32 +01:00
|
|
|
use self::command::execute_command;
|
2018-04-13 22:46:17 +02:00
|
|
|
use self::input::{basename, dirname, remove_extension};
|
2018-11-11 18:00:01 +01:00
|
|
|
pub use self::job::{batch, job};
|
2018-04-13 22:46:17 +02:00
|
|
|
use self::token::Token;
|
2017-10-14 18:04:11 +02:00
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
/// Execution mode of the command
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
|
|
pub enum ExecutionMode {
|
2018-11-12 18:43:40 +01:00
|
|
|
/// Command is executed for each search result
|
2018-11-11 18:00:01 +01:00
|
|
|
OneByOne,
|
|
|
|
/// Command is run for a batch of results at once
|
|
|
|
Batch,
|
|
|
|
}
|
|
|
|
|
2017-11-15 02:14:04 +01:00
|
|
|
/// Represents a template that is utilized to generate command strings.
|
2017-10-14 18:04:11 +02:00
|
|
|
///
|
2017-11-15 02:14:04 +01:00
|
|
|
/// 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.
|
2017-10-14 18:04:11 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2017-11-15 02:14:04 +01:00
|
|
|
pub struct CommandTemplate {
|
|
|
|
args: Vec<ArgumentTemplate>,
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode,
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 02:14:04 +01:00
|
|
|
impl CommandTemplate {
|
|
|
|
pub fn new<I, S>(input: I) -> CommandTemplate
|
2018-11-11 18:00:01 +01:00
|
|
|
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);
|
2018-11-12 18:43:40 +01:00
|
|
|
if cmd.number_of_tokens() > 1 {
|
2018-11-11 18:00:01 +01:00
|
|
|
return Err("Only one placeholder allowed for batch commands");
|
|
|
|
}
|
2018-11-12 18:43:40 +01:00
|
|
|
if cmd.args[0].has_tokens() {
|
|
|
|
return Err("First argument of exec-batch is expected to be a fixed executable");
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
Ok(cmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn build<I, S>(input: I, mode: ExecutionMode) -> CommandTemplate
|
2017-11-03 01:39:03 +01:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = S>,
|
|
|
|
S: AsRef<str>,
|
|
|
|
{
|
|
|
|
lazy_static! {
|
2017-11-15 01:56:32 +01:00
|
|
|
static ref PLACEHOLDER_PATTERN: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
2017-11-03 01:39:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut args = Vec::new();
|
|
|
|
let mut has_placeholder = false;
|
|
|
|
|
|
|
|
for arg in input {
|
|
|
|
let arg = arg.as_ref();
|
|
|
|
|
|
|
|
let mut tokens = Vec::new();
|
|
|
|
let mut start = 0;
|
|
|
|
|
2017-11-15 01:56:32 +01:00
|
|
|
for placeholder in PLACEHOLDER_PATTERN.find_iter(arg) {
|
2017-11-03 01:39:03 +01:00
|
|
|
// Leading text before the placeholder.
|
|
|
|
if placeholder.start() > start {
|
|
|
|
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|
2017-11-03 01:39:03 +01:00
|
|
|
|
|
|
|
start = placeholder.end();
|
|
|
|
|
|
|
|
match placeholder.as_str() {
|
|
|
|
"{}" => tokens.push(Token::Placeholder),
|
|
|
|
"{.}" => tokens.push(Token::NoExt),
|
|
|
|
"{/}" => tokens.push(Token::Basename),
|
|
|
|
"{//}" => tokens.push(Token::Parent),
|
|
|
|
"{/.}" => tokens.push(Token::BasenameNoExt),
|
|
|
|
_ => panic!("Unhandled placeholder"),
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|
2017-11-03 01:39:03 +01:00
|
|
|
|
|
|
|
has_placeholder = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Without a placeholder, the argument is just fixed text.
|
|
|
|
if tokens.is_empty() {
|
2017-11-15 02:14:04 +01:00
|
|
|
args.push(ArgumentTemplate::Text(arg.to_owned()));
|
2017-11-03 01:39:03 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if start < arg.len() {
|
|
|
|
// Trailing text after last placeholder.
|
|
|
|
tokens.push(Token::Text(arg[start..].to_owned()));
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 02:14:04 +01:00
|
|
|
args.push(ArgumentTemplate::Tokens(tokens));
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|
2017-10-14 18:04:11 +02:00
|
|
|
|
|
|
|
// If a placeholder token was not supplied, append one at the end of the command.
|
2017-11-03 01:39:03 +01:00
|
|
|
if !has_placeholder {
|
2017-11-15 02:14:04 +01:00
|
|
|
args.push(ArgumentTemplate::Tokens(vec![Token::Placeholder]));
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
CommandTemplate { args, mode }
|
|
|
|
}
|
|
|
|
|
2018-11-12 18:43:40 +01:00
|
|
|
fn number_of_tokens(&self) -> usize {
|
2018-11-11 18:00:01 +01:00
|
|
|
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()
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2017-11-15 01:56:32 +01:00
|
|
|
/// Generates and executes a command.
|
2017-10-14 18:04:11 +02:00
|
|
|
///
|
2017-11-15 01:56:32 +01:00
|
|
|
/// 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.
|
2020-01-25 15:54:56 +01:00
|
|
|
pub fn generate_and_execute(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> ExitCode {
|
2018-11-11 18:00:01 +01:00
|
|
|
let input = Self::prepare_path(input);
|
2017-11-03 01:39:03 +01:00
|
|
|
|
2017-11-15 01:56:32 +01:00
|
|
|
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());
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2020-01-25 15:54:56 +01:00
|
|
|
execute_command(cmd, &out_perm)
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
2018-11-12 18:43:40 +01:00
|
|
|
pub fn in_batch_mode(&self) -> bool {
|
2018-11-11 18:00:01 +01:00
|
|
|
self.mode == ExecutionMode::Batch
|
|
|
|
}
|
|
|
|
|
2019-09-13 22:26:27 +02:00
|
|
|
pub fn generate_and_execute_batch<I>(&self, paths: I) -> ExitCode
|
2018-11-11 18:00:01 +01:00
|
|
|
where
|
|
|
|
I: Iterator<Item = PathBuf>,
|
|
|
|
{
|
|
|
|
let mut cmd = Command::new(self.args[0].generate("").as_ref());
|
2018-11-12 18:43:40 +01:00
|
|
|
cmd.stdin(Stdio::inherit());
|
|
|
|
cmd.stdout(Stdio::inherit());
|
|
|
|
cmd.stderr(Stdio::inherit());
|
|
|
|
|
2020-01-02 16:45:49 +01:00
|
|
|
let mut paths: Vec<String> = paths.map(|p| Self::prepare_path(&p)).collect();
|
2018-11-11 18:00:01 +01:00
|
|
|
let mut has_path = false;
|
|
|
|
|
|
|
|
for arg in &self.args[1..] {
|
|
|
|
if arg.has_tokens() {
|
2020-01-02 16:45:49 +01:00
|
|
|
paths.sort();
|
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
// A single `Tokens` is expected
|
2020-01-02 16:45:49 +01:00
|
|
|
// So we can directly consume the iterator once and for all
|
2018-11-11 18:00:01 +01:00
|
|
|
for path in &mut paths {
|
|
|
|
cmd.arg(arg.generate(&path).as_ref());
|
|
|
|
has_path = true;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
cmd.arg(arg.generate("").as_ref());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if has_path {
|
2019-09-13 22:26:27 +02:00
|
|
|
execute_command(cmd, &Mutex::new(()))
|
|
|
|
} else {
|
|
|
|
ExitCode::Success
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 02:14:04 +01:00
|
|
|
/// Represents a template for a single command argument.
|
|
|
|
///
|
|
|
|
/// The argument is either a collection of `Token`s including at least one placeholder variant, or
|
|
|
|
/// a fixed text.
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
|
|
enum ArgumentTemplate {
|
|
|
|
Tokens(Vec<Token>),
|
|
|
|
Text(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ArgumentTemplate {
|
2018-11-11 18:00:01 +01:00
|
|
|
pub fn has_tokens(&self) -> bool {
|
2018-11-12 18:43:40 +01:00
|
|
|
match self {
|
|
|
|
ArgumentTemplate::Tokens(_) => true,
|
|
|
|
_ => false,
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-15 02:14:04 +01:00
|
|
|
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
|
|
|
use self::Token::*;
|
|
|
|
|
|
|
|
match *self {
|
|
|
|
ArgumentTemplate::Tokens(ref tokens) => {
|
|
|
|
let mut s = String::new();
|
|
|
|
for token in tokens {
|
|
|
|
match *token {
|
|
|
|
Basename => s += basename(path),
|
|
|
|
BasenameNoExt => s += remove_extension(basename(path)),
|
|
|
|
NoExt => s += remove_extension(path),
|
|
|
|
Parent => s += dirname(path),
|
|
|
|
Placeholder => s += path,
|
|
|
|
Text(ref string) => s += string,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Cow::Owned(s)
|
|
|
|
}
|
|
|
|
ArgumentTemplate::Text(ref text) => Cow::Borrowed(text),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-14 18:04:11 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-10-03 10:36:15 +02:00
|
|
|
use super::*;
|
2017-10-14 18:04:11 +02:00
|
|
|
|
|
|
|
#[test]
|
2018-10-03 10:36:15 +02:00
|
|
|
fn tokens_with_placeholder() {
|
|
|
|
assert_eq!(
|
|
|
|
CommandTemplate::new(&[&"echo", &"${SHELL}:"]),
|
|
|
|
CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Text("${SHELL}:".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2017-11-03 01:39:03 +01:00
|
|
|
|
2018-10-03 10:36:15 +02:00
|
|
|
#[test]
|
|
|
|
fn tokens_with_no_extension() {
|
2017-10-14 20:04:04 +02:00
|
|
|
assert_eq!(
|
2017-11-15 02:14:04 +01:00
|
|
|
CommandTemplate::new(&["echo", "{.}"]),
|
|
|
|
CommandTemplate {
|
2017-11-03 01:39:03 +01:00
|
|
|
args: vec![
|
2017-11-15 02:14:04 +01:00
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
2017-11-03 01:39:03 +01:00
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2017-11-03 01:39:03 +01:00
|
|
|
}
|
2017-10-14 20:04:04 +02:00
|
|
|
);
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
2018-10-03 10:36:15 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_with_basename() {
|
|
|
|
assert_eq!(
|
|
|
|
CommandTemplate::new(&["echo", "{/}"]),
|
|
|
|
CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_with_parent() {
|
|
|
|
assert_eq!(
|
|
|
|
CommandTemplate::new(&["echo", "{//}"]),
|
|
|
|
CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_with_basename_no_extension() {
|
|
|
|
assert_eq!(
|
|
|
|
CommandTemplate::new(&["echo", "{/.}"]),
|
|
|
|
CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2018-11-09 21:35:01 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_multiple() {
|
|
|
|
assert_eq!(
|
|
|
|
CommandTemplate::new(&["cp", "{}", "{/.}.ext"]),
|
|
|
|
CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("cp".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
|
|
|
|
ArgumentTemplate::Tokens(vec![
|
|
|
|
Token::BasenameNoExt,
|
|
|
|
Token::Text(".ext".into())
|
|
|
|
]),
|
|
|
|
],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2018-11-09 21:35:01 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
|
|
|
|
#[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());
|
|
|
|
}
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|