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
|
|
|
|
2021-01-03 20:17:52 +01:00
|
|
|
use std::borrow::Cow;
|
|
|
|
use std::ffi::{OsStr, OsString};
|
|
|
|
use std::path::{Component, Path, PathBuf, Prefix};
|
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
|
|
|
|
2020-04-03 20:55:14 +02:00
|
|
|
use anyhow::{anyhow, Result};
|
2021-11-25 14:34:54 +01:00
|
|
|
use once_cell::sync::Lazy;
|
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;
|
2021-10-17 05:52:04 +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-10-14 18:04:11 +02:00
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
2022-02-18 09:21:00 +01:00
|
|
|
pub struct CommandSet {
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: Option<String>,
|
2022-02-18 09:21:00 +01:00
|
|
|
commands: Vec<CommandTemplate>,
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2022-02-18 09:21:00 +01:00
|
|
|
impl CommandSet {
|
|
|
|
pub fn new<I, S>(input: I, path_separator: Option<String>) -> CommandSet
|
2018-11-11 18:00:01 +01:00
|
|
|
where
|
2022-02-18 09:21:00 +01:00
|
|
|
I: IntoIterator<Item = Vec<S>>,
|
2018-11-11 18:00:01 +01:00
|
|
|
S: AsRef<str>,
|
|
|
|
{
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet {
|
|
|
|
mode: ExecutionMode::OneByOne,
|
|
|
|
path_separator,
|
|
|
|
commands: input.into_iter().map(CommandTemplate::new).collect(),
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
2022-02-18 09:21:00 +01:00
|
|
|
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandSet>
|
2018-11-11 18:00:01 +01:00
|
|
|
where
|
2022-02-18 09:21:00 +01:00
|
|
|
I: IntoIterator<Item = Vec<S>>,
|
2018-11-11 18:00:01 +01:00
|
|
|
S: AsRef<str>,
|
|
|
|
{
|
2022-02-18 09:21:00 +01:00
|
|
|
Ok(CommandSet {
|
|
|
|
mode: ExecutionMode::Batch,
|
|
|
|
path_separator,
|
|
|
|
commands: input
|
|
|
|
.into_iter()
|
|
|
|
.map(|args| {
|
|
|
|
let cmd = CommandTemplate::new(args);
|
|
|
|
if cmd.number_of_tokens() > 1 {
|
|
|
|
return Err(anyhow!("Only one placeholder allowed for batch commands"));
|
|
|
|
}
|
|
|
|
if cmd.args[0].has_tokens() {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"First argument of exec-batch is expected to be a fixed executable"
|
|
|
|
));
|
|
|
|
}
|
|
|
|
Ok(cmd)
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>>>()?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn in_batch_mode(&self) -> bool {
|
|
|
|
self.mode == ExecutionMode::Batch
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn execute(
|
|
|
|
&self,
|
|
|
|
input: &Path,
|
|
|
|
mut out_perm: Arc<Mutex<()>>,
|
|
|
|
buffer_output: bool,
|
|
|
|
) -> ExitCode {
|
|
|
|
let path_separator = self.path_separator.as_deref();
|
|
|
|
for cmd in &self.commands {
|
|
|
|
let exit =
|
|
|
|
cmd.generate_and_execute(input, path_separator, &mut out_perm, buffer_output);
|
|
|
|
if exit != ExitCode::Success {
|
|
|
|
return exit;
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
2022-02-18 09:21:00 +01:00
|
|
|
ExitCode::Success
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn execute_batch<I>(&self, paths: I, buffer_output: bool) -> ExitCode
|
|
|
|
where
|
|
|
|
I: Iterator<Item = PathBuf>,
|
|
|
|
{
|
|
|
|
let path_separator = self.path_separator.as_deref();
|
|
|
|
let mut paths = paths.collect::<Vec<_>>();
|
|
|
|
paths.sort();
|
|
|
|
for cmd in &self.commands {
|
|
|
|
let exit = cmd.generate_and_execute_batch(&paths, path_separator, buffer_output);
|
|
|
|
if exit != ExitCode::Success {
|
|
|
|
return exit;
|
|
|
|
}
|
2018-11-12 18:43:40 +01:00
|
|
|
}
|
2022-02-18 09:21:00 +01:00
|
|
|
ExitCode::Success
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
2022-02-18 09:21:00 +01:00
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
|
2022-02-18 09:21:00 +01:00
|
|
|
/// 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)]
|
|
|
|
struct CommandTemplate {
|
|
|
|
args: Vec<ArgumentTemplate>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl CommandTemplate {
|
|
|
|
fn new<I, S>(input: I) -> CommandTemplate
|
2017-11-03 01:39:03 +01:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = S>,
|
|
|
|
S: AsRef<str>,
|
|
|
|
{
|
2021-11-25 14:34:54 +01:00
|
|
|
static PLACEHOLDER_PATTERN: Lazy<Regex> =
|
|
|
|
Lazy::new(|| 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),
|
2020-04-04 10:13:13 +02:00
|
|
|
_ => unreachable!("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
|
|
|
}
|
|
|
|
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandTemplate { args }
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
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.
|
2022-02-18 09:21:00 +01:00
|
|
|
fn generate_and_execute(
|
2021-08-09 09:02:30 +02:00
|
|
|
&self,
|
|
|
|
input: &Path,
|
2022-02-18 09:21:00 +01:00
|
|
|
path_separator: Option<&str>,
|
|
|
|
out_perm: &mut Arc<Mutex<()>>,
|
2021-08-09 09:02:30 +02:00
|
|
|
buffer_output: bool,
|
|
|
|
) -> ExitCode {
|
2022-02-18 09:21:00 +01:00
|
|
|
let mut cmd = Command::new(self.args[0].generate(&input, path_separator));
|
2017-11-15 01:56:32 +01:00
|
|
|
for arg in &self.args[1..] {
|
2022-02-18 09:21:00 +01:00
|
|
|
cmd.arg(arg.generate(&input, path_separator));
|
2017-10-14 18:04:11 +02:00
|
|
|
}
|
|
|
|
|
2022-02-18 09:27:40 +01:00
|
|
|
execute_command(cmd, out_perm, buffer_output)
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
2022-02-18 09:21:00 +01:00
|
|
|
fn generate_and_execute_batch(
|
|
|
|
&self,
|
2022-02-18 09:27:40 +01:00
|
|
|
paths: &[PathBuf],
|
2022-02-18 09:21:00 +01:00
|
|
|
path_separator: Option<&str>,
|
|
|
|
buffer_output: bool,
|
|
|
|
) -> ExitCode {
|
2021-01-03 20:17:52 +01:00
|
|
|
let mut cmd = Command::new(self.args[0].generate("", None));
|
2018-11-12 18:43:40 +01:00
|
|
|
cmd.stdin(Stdio::inherit());
|
|
|
|
cmd.stdout(Stdio::inherit());
|
|
|
|
cmd.stderr(Stdio::inherit());
|
|
|
|
|
2018-11-11 18:00:01 +01:00
|
|
|
let mut has_path = false;
|
|
|
|
|
|
|
|
for arg in &self.args[1..] {
|
|
|
|
if arg.has_tokens() {
|
2022-02-18 09:21:00 +01:00
|
|
|
for path in paths {
|
|
|
|
cmd.arg(arg.generate(path, path_separator));
|
2018-11-11 18:00:01 +01:00
|
|
|
has_path = true;
|
|
|
|
}
|
|
|
|
} else {
|
2021-01-03 20:17:52 +01:00
|
|
|
cmd.arg(arg.generate("", None));
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if has_path {
|
2021-08-09 09:02:30 +02:00
|
|
|
execute_command(cmd, &Mutex::new(()), buffer_output)
|
2019-09-13 22:26:27 +02:00
|
|
|
} 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 {
|
2021-07-27 08:38:09 +02:00
|
|
|
matches!(self, ArgumentTemplate::Tokens(_))
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
|
2021-01-03 20:17:52 +01:00
|
|
|
/// Generate an argument from this template. If path_separator is Some, then it will replace
|
|
|
|
/// the path separator in all placeholder tokens. Text arguments and tokens are not affected by
|
|
|
|
/// path separator substitution.
|
|
|
|
pub fn generate(&self, path: impl AsRef<Path>, path_separator: Option<&str>) -> OsString {
|
2017-11-15 02:14:04 +01:00
|
|
|
use self::Token::*;
|
2021-01-03 20:17:52 +01:00
|
|
|
let path = path.as_ref();
|
2017-11-15 02:14:04 +01:00
|
|
|
|
|
|
|
match *self {
|
|
|
|
ArgumentTemplate::Tokens(ref tokens) => {
|
2020-04-04 11:40:27 +02:00
|
|
|
let mut s = OsString::new();
|
2017-11-15 02:14:04 +01:00
|
|
|
for token in tokens {
|
|
|
|
match *token {
|
2021-01-03 20:17:52 +01:00
|
|
|
Basename => s.push(Self::replace_separator(basename(path), path_separator)),
|
|
|
|
BasenameNoExt => s.push(Self::replace_separator(
|
|
|
|
&remove_extension(basename(path).as_ref()),
|
|
|
|
path_separator,
|
|
|
|
)),
|
|
|
|
NoExt => s.push(Self::replace_separator(
|
|
|
|
&remove_extension(path),
|
|
|
|
path_separator,
|
|
|
|
)),
|
|
|
|
Parent => s.push(Self::replace_separator(&dirname(path), path_separator)),
|
|
|
|
Placeholder => {
|
|
|
|
s.push(Self::replace_separator(path.as_ref(), path_separator))
|
2020-04-04 11:40:27 +02:00
|
|
|
}
|
|
|
|
Text(ref string) => s.push(string),
|
2017-11-15 02:14:04 +01:00
|
|
|
}
|
|
|
|
}
|
2020-04-04 11:40:27 +02:00
|
|
|
s
|
2017-11-15 02:14:04 +01:00
|
|
|
}
|
2020-04-04 11:40:27 +02:00
|
|
|
ArgumentTemplate::Text(ref text) => OsString::from(text),
|
2017-11-15 02:14:04 +01:00
|
|
|
}
|
|
|
|
}
|
2021-01-03 20:17:52 +01:00
|
|
|
|
|
|
|
/// Replace the path separator in the input with the custom separator string. If path_separator
|
|
|
|
/// is None, simply return a borrowed Cow<OsStr> of the input. Otherwise, the input is
|
|
|
|
/// interpreted as a Path and its components are iterated through and re-joined into a new
|
|
|
|
/// OsString.
|
|
|
|
fn replace_separator<'a>(path: &'a OsStr, path_separator: Option<&str>) -> Cow<'a, OsStr> {
|
|
|
|
// fast-path - no replacement necessary
|
|
|
|
if path_separator.is_none() {
|
|
|
|
return Cow::Borrowed(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
let path_separator = path_separator.unwrap();
|
|
|
|
let mut out = OsString::with_capacity(path.len());
|
|
|
|
let mut components = Path::new(path).components().peekable();
|
|
|
|
|
|
|
|
while let Some(comp) = components.next() {
|
|
|
|
match comp {
|
|
|
|
// Absolute paths on Windows are tricky. A Prefix component is usually a drive
|
|
|
|
// letter or UNC path, and is usually followed by RootDir. There are also
|
|
|
|
// "verbatim" prefixes beginning with "\\?\" that skip normalization. We choose to
|
|
|
|
// ignore verbatim path prefixes here because they're very rare, might be
|
|
|
|
// impossible to reach here, and there's no good way to deal with them. If users
|
|
|
|
// are doing something advanced involving verbatim windows paths, they can do their
|
|
|
|
// own output filtering with a tool like sed.
|
|
|
|
Component::Prefix(prefix) => {
|
|
|
|
if let Prefix::UNC(server, share) = prefix.kind() {
|
|
|
|
// Prefix::UNC is a parsed version of '\\server\share'
|
|
|
|
out.push(path_separator);
|
|
|
|
out.push(path_separator);
|
|
|
|
out.push(server);
|
|
|
|
out.push(path_separator);
|
|
|
|
out.push(share);
|
|
|
|
} else {
|
|
|
|
// All other Windows prefix types are rendered as-is. This results in e.g. "C:" for
|
|
|
|
// drive letters. DeviceNS and Verbatim* prefixes won't have backslashes converted,
|
|
|
|
// but they're not returned by directories fd can search anyway so we don't worry
|
|
|
|
// about them.
|
|
|
|
out.push(comp.as_os_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Root directory is always replaced with the custom separator.
|
|
|
|
Component::RootDir => out.push(path_separator),
|
|
|
|
|
|
|
|
// Everything else is joined normally, with a trailing separator if we're not last
|
|
|
|
_ => {
|
|
|
|
out.push(comp.as_os_str());
|
|
|
|
if components.peek().is_some() {
|
|
|
|
out.push(path_separator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Cow::Owned(out)
|
|
|
|
}
|
2017-11-15 02:14:04 +01:00
|
|
|
}
|
|
|
|
|
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!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec![&"echo", &"${SHELL}:"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![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,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
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!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec!["echo", "{.}"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
|
|
|
],
|
|
|
|
}],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
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!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec!["echo", "{/}"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Basename]),
|
|
|
|
],
|
|
|
|
}],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_with_parent() {
|
|
|
|
assert_eq!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec!["echo", "{//}"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::Parent]),
|
|
|
|
],
|
|
|
|
}],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_with_basename_no_extension() {
|
|
|
|
assert_eq!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec!["echo", "{/.}"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![CommandTemplate {
|
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
|
|
|
|
],
|
|
|
|
}],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::OneByOne,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
2018-10-03 10:36:15 +02:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2018-11-09 21:35:01 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_multiple() {
|
|
|
|
assert_eq!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![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,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
2018-11-09 21:35:01 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2018-11-11 18:00:01 +01:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_single_batch() {
|
|
|
|
assert_eq!(
|
2022-02-18 09:21:00 +01:00
|
|
|
CommandSet::new_batch(vec![vec!["echo", "{.}"]], None).unwrap(),
|
|
|
|
CommandSet {
|
|
|
|
commands: vec![CommandTemplate {
|
2022-02-18 09:27:40 +01:00
|
|
|
args: vec![
|
|
|
|
ArgumentTemplate::Text("echo".into()),
|
|
|
|
ArgumentTemplate::Tokens(vec![Token::NoExt]),
|
|
|
|
],
|
2022-02-18 09:21:00 +01:00
|
|
|
}],
|
2018-11-11 18:00:01 +01:00
|
|
|
mode: ExecutionMode::Batch,
|
2021-01-03 20:17:52 +01:00
|
|
|
path_separator: None,
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn tokens_multiple_batch() {
|
2022-02-18 09:21:00 +01:00
|
|
|
assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None).is_err());
|
2021-01-03 20:17:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn generate_custom_path_separator() {
|
|
|
|
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
|
|
|
|
macro_rules! check {
|
|
|
|
($input:expr, $expected:expr) => {
|
|
|
|
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
check!("foo", "foo");
|
|
|
|
check!("foo/bar", "foo#bar");
|
|
|
|
check!("/foo/bar/baz", "#foo#bar#baz");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(windows)]
|
|
|
|
#[test]
|
|
|
|
fn generate_custom_path_separator_windows() {
|
|
|
|
let arg = ArgumentTemplate::Tokens(vec![Token::Placeholder]);
|
|
|
|
macro_rules! check {
|
|
|
|
($input:expr, $expected:expr) => {
|
|
|
|
assert_eq!(arg.generate($input, Some("#")), OsString::from($expected));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// path starting with a drive letter
|
|
|
|
check!(r"C:\foo\bar", "C:#foo#bar");
|
|
|
|
// UNC path
|
|
|
|
check!(r"\\server\share\path", "##server#share#path");
|
|
|
|
// Drive Relative path - no separator after the colon omits the RootDir path component.
|
|
|
|
// This is uncommon, but valid
|
|
|
|
check!(r"C:foo\bar", "C:foo#bar");
|
|
|
|
|
2021-08-14 11:46:53 +02:00
|
|
|
// forward slashes should get normalized and interpreted as separators
|
2021-01-03 20:17:52 +01:00
|
|
|
check!("C:/foo/bar", "C:#foo#bar");
|
|
|
|
check!("C:foo/bar", "C:foo#bar");
|
|
|
|
|
2021-08-14 11:46:53 +02:00
|
|
|
// Rust does not interpret "//server/share" as a UNC path, but rather as a normal
|
2021-01-03 20:17:52 +01:00
|
|
|
// absolute path that begins with RootDir, and the two slashes get combined together as
|
|
|
|
// a single path separator during normalization.
|
|
|
|
//check!("//server/share/path", "##server#share#path");
|
2018-11-11 18:00:01 +01:00
|
|
|
}
|
2017-10-14 20:04:04 +02:00
|
|
|
}
|