mirror of
https://github.com/sharkdp/fd
synced 2024-10-06 07:49:18 +00:00
Remove shell with --exec
This commit is contained in:
parent
1a6c92c475
commit
18709b1ede
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -13,7 +13,6 @@ dependencies = [
|
|||
"num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shell-escape 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -228,11 +227,6 @@ dependencies = [
|
|||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-escape"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.6.0"
|
||||
|
@ -365,7 +359,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b"
|
||||
"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db"
|
||||
"checksum same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d931a44fdaa43b8637009e7632a02adc4f2b2e0733c08caa4cf00e8da4a117a7"
|
||||
"checksum shell-escape 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dd5cc96481d54583947bfe88bf30c23d53f883c6cd0145368b69989d97b84ef8"
|
||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
|
||||
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
|
||||
|
|
|
@ -40,7 +40,6 @@ lazy_static = "0.2.9"
|
|||
num_cpus = "1.6.2"
|
||||
regex = "0.2"
|
||||
regex-syntax = "0.4"
|
||||
shell-escape = "0.1.3"
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
|
||||
libc = "0.2"
|
||||
|
|
|
@ -85,7 +85,9 @@ pub fn build_app() -> App<'static, 'static> {
|
|||
arg("exec")
|
||||
.long("exec")
|
||||
.short("x")
|
||||
.takes_value(true)
|
||||
.multiple(true)
|
||||
.allow_hyphen_values(true)
|
||||
.value_terminator(";")
|
||||
.value_name("cmd"),
|
||||
)
|
||||
.arg(
|
||||
|
|
|
@ -7,228 +7,159 @@
|
|||
// according to those terms.
|
||||
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use shell_escape::escape;
|
||||
/// Removes the parent component of the path
|
||||
pub fn basename(path: &str) -> &str {
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for efficiently generating input strings.
|
||||
///
|
||||
/// After choosing your required specs, the `get()` method will escape special characters found
|
||||
/// in the input. Allocations will only occur if special characters are found that need to be
|
||||
/// escaped.
|
||||
pub struct Input<'a> {
|
||||
data: &'a str,
|
||||
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if index != 0 {
|
||||
return &path[index + 1..];
|
||||
}
|
||||
|
||||
path
|
||||
}
|
||||
|
||||
impl<'a> Input<'a> {
|
||||
/// Creates a new `Input` structure, which provides access to command-building
|
||||
/// primitives, such as `basename()` and `dirname()`.
|
||||
pub fn new(data: &'a str) -> Input<'a> {
|
||||
Input { data }
|
||||
/// Removes the extension from the path
|
||||
pub fn remove_extension(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut dir_index = 0;
|
||||
let mut ext_index = 0;
|
||||
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
dir_index = id;
|
||||
}
|
||||
if character == '.' {
|
||||
ext_index = id;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the parent component of the path
|
||||
pub fn basename(&'a mut self) -> &'a mut Self {
|
||||
let mut index = 0;
|
||||
for (id, character) in self.data.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, should return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if index != 0 {
|
||||
self.data = &self.data[index + 1..]
|
||||
}
|
||||
|
||||
self
|
||||
// Account for hidden files and directories
|
||||
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
|
||||
return &path[0..ext_index];
|
||||
}
|
||||
|
||||
/// Removes the extension from the path
|
||||
pub fn remove_extension(&'a mut self) -> &'a mut Self {
|
||||
let mut has_dir = false;
|
||||
let mut dir_index = 0;
|
||||
let mut ext_index = 0;
|
||||
path
|
||||
}
|
||||
|
||||
for (id, character) in self.data.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
dir_index = id;
|
||||
}
|
||||
if character == '.' {
|
||||
ext_index = id;
|
||||
}
|
||||
/// Removes the basename from the path.
|
||||
pub fn dirname(path: &str) -> &str {
|
||||
let mut has_dir = false;
|
||||
let mut index = 0;
|
||||
for (id, character) in path.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
index = id;
|
||||
}
|
||||
|
||||
// Account for hidden files and directories
|
||||
if ext_index != 0 && (!has_dir || dir_index + 2 <= ext_index) {
|
||||
self.data = &self.data[0..ext_index];
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes the basename from the path.
|
||||
pub fn dirname(&'a mut self) -> &'a mut Self {
|
||||
let mut has_dir = false;
|
||||
let mut index = 0;
|
||||
for (id, character) in self.data.char_indices() {
|
||||
if character == MAIN_SEPARATOR {
|
||||
has_dir = true;
|
||||
index = id;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
|
||||
self.data = if !has_dir {
|
||||
"."
|
||||
} else if index == 0 {
|
||||
&self.data[..1]
|
||||
} else {
|
||||
&self.data[0..index]
|
||||
};
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get(&'a self) -> Cow<'a, str> {
|
||||
escape(Cow::Borrowed(self.data))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get_private(&'a self) -> Cow<'a, str> {
|
||||
Cow::Borrowed(self.data)
|
||||
// FIXME: On Windows, return what for C:file.txt D:file.txt and \\server\share ?
|
||||
if !has_dir {
|
||||
"."
|
||||
} else if index == 0 {
|
||||
&path[..1]
|
||||
} else {
|
||||
&path[0..index]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{MAIN_SEPARATOR, Input};
|
||||
use super::{MAIN_SEPARATOR, basename, dirname, remove_extension};
|
||||
|
||||
fn correct(input: &str) -> String {
|
||||
let mut sep = String::new();
|
||||
sep.push(MAIN_SEPARATOR);
|
||||
input.replace('/', &sep)
|
||||
input.replace('/', &MAIN_SEPARATOR.to_string())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_simple() {
|
||||
assert_eq!(
|
||||
&Input::new("foo.txt").remove_extension().get_private(),
|
||||
"foo"
|
||||
);
|
||||
assert_eq!(remove_extension("foo.txt"), "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_dir() {
|
||||
assert_eq!(
|
||||
&Input::new(&correct("dir/foo.txt"))
|
||||
.remove_extension()
|
||||
.get_private(),
|
||||
&correct("dir/foo")
|
||||
remove_extension(&correct("dir/foo.txt")),
|
||||
correct("dir/foo")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_hidden() {
|
||||
assert_eq!(&Input::new(".foo").remove_extension().get_private(), ".foo")
|
||||
assert_eq!(remove_extension(".foo"), ".foo")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_utf8() {
|
||||
assert_eq!(
|
||||
&Input::new("💖.txt").remove_extension().get_private(),
|
||||
"💖"
|
||||
);
|
||||
assert_eq!(remove_extension("💖.txt"), "💖");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_remove_ext_empty() {
|
||||
assert_eq!(&Input::new("").remove_extension().get_private(), "");
|
||||
assert_eq!(remove_extension(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_simple() {
|
||||
assert_eq!(&Input::new("foo.txt").basename().get_private(), "foo.txt");
|
||||
assert_eq!(basename("foo.txt"), "foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_no_ext() {
|
||||
assert_eq!(
|
||||
&Input::new("foo.txt")
|
||||
.basename()
|
||||
.remove_extension()
|
||||
.get_private(),
|
||||
"foo"
|
||||
);
|
||||
assert_eq!(remove_extension(basename("foo.txt")), "foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_dir() {
|
||||
assert_eq!(
|
||||
&Input::new(&correct("dir/foo.txt")).basename().get_private(),
|
||||
"foo.txt"
|
||||
);
|
||||
assert_eq!(basename(&correct("dir/foo.txt")), "foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_empty() {
|
||||
assert_eq!(&Input::new("").basename().get_private(), "");
|
||||
assert_eq!(basename(""), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_basename_utf8() {
|
||||
assert_eq!(
|
||||
&Input::new(&correct("💖/foo.txt"))
|
||||
.basename()
|
||||
.get_private(),
|
||||
"foo.txt"
|
||||
);
|
||||
assert_eq!(
|
||||
&Input::new(&correct("dir/💖.txt"))
|
||||
.basename()
|
||||
.get_private(),
|
||||
"💖.txt"
|
||||
);
|
||||
assert_eq!(basename(&correct("💖/foo.txt")), "foo.txt");
|
||||
assert_eq!(basename(&correct("dir/💖.txt")), "💖.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_simple() {
|
||||
assert_eq!(&Input::new("foo.txt").dirname().get_private(), ".");
|
||||
assert_eq!(dirname("foo.txt"), ".");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_dir() {
|
||||
assert_eq!(
|
||||
&Input::new(&correct("dir/foo.txt")).dirname().get_private(),
|
||||
"dir"
|
||||
);
|
||||
assert_eq!(dirname(&correct("dir/foo.txt")), "dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_utf8() {
|
||||
assert_eq!(
|
||||
&Input::new(&correct("💖/foo.txt")).dirname().get_private(),
|
||||
"💖"
|
||||
);
|
||||
assert_eq!(
|
||||
&Input::new(&correct("dir/💖.txt")).dirname().get_private(),
|
||||
"dir"
|
||||
);
|
||||
assert_eq!(dirname(&correct("💖/foo.txt")), "💖");
|
||||
assert_eq!(dirname(&correct("dir/💖.txt")), "dir");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_empty() {
|
||||
assert_eq!(&Input::new("").dirname().get_private(), ".");
|
||||
assert_eq!(dirname(""), ".");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_dirname_root() {
|
||||
#[cfg(windows)]
|
||||
assert_eq!(&Input::new("C:\\").dirname().get_private(), "C:");
|
||||
assert_eq!(dirname("C:\\"), "C:");
|
||||
#[cfg(windows)]
|
||||
assert_eq!(&Input::new("\\").dirname().get_private(), "\\");
|
||||
assert_eq!(dirname("\\"), "\\");
|
||||
#[cfg(not(windows))]
|
||||
assert_eq!(&Input::new("/").dirname().get_private(), "/");
|
||||
assert_eq!(dirname("/"), "/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,6 @@ pub fn job(
|
|||
cmd: Arc<TokenizedCommand>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
) {
|
||||
// A string buffer that will be re-used in each iteration.
|
||||
let buffer = &mut String::with_capacity(256);
|
||||
|
||||
loop {
|
||||
// Create a lock on the shared receiver for this thread.
|
||||
let lock = rx.lock().unwrap();
|
||||
|
@ -36,9 +33,7 @@ pub fn job(
|
|||
|
||||
// Drop the lock so that other threads can read from the the receiver.
|
||||
drop(lock);
|
||||
// Generate a command to store within the buffer, and execute the command.
|
||||
// Note that the `then_execute()` method will clear the buffer for us.
|
||||
cmd.generate(buffer, &value, Arc::clone(&out_perm))
|
||||
.then_execute();
|
||||
// Generate a command and execute it.
|
||||
cmd.generate(&value, Arc::clone(&out_perm)).then_execute();
|
||||
}
|
||||
}
|
||||
|
|
242
src/exec/mod.rs
242
src/exec/mod.rs
|
@ -12,163 +12,167 @@ mod token;
|
|||
mod job;
|
||||
mod input;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// use self::paths::{basename, dirname, remove_extension};
|
||||
use self::input::Input;
|
||||
use regex::Regex;
|
||||
|
||||
use self::input::{basename, dirname, remove_extension};
|
||||
use self::ticket::CommandTicket;
|
||||
use self::token::Token;
|
||||
pub use self::job::job;
|
||||
|
||||
/// Signifies that a placeholder token was found
|
||||
const PLACE: u8 = 1;
|
||||
|
||||
/// Signifies that the '{' character was found.
|
||||
const OPEN: u8 = 2;
|
||||
|
||||
/// Contains a collection of `Token`'s that are utilized to generate command strings.
|
||||
/// Contains a collection of `TokenizedArgument`s that are utilized to generate command strings.
|
||||
///
|
||||
/// The tokens are a represntation of the supplied command template, and are meant to be coupled
|
||||
/// with an input in order to generate a command. The `generate()` method will be used to
|
||||
/// generate a command and obtain a ticket for executing that command.
|
||||
/// The arguments are a representation of the supplied command template, and are meant to be coupled
|
||||
/// with an input in order to generate a command. The `generate()` method will be used to generate
|
||||
/// a command and obtain a ticket for executing that command.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct TokenizedCommand {
|
||||
pub tokens: Vec<Token>,
|
||||
args: Vec<TokenizedArgument>,
|
||||
}
|
||||
|
||||
/// Represents 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 TokenizedArgument {
|
||||
Tokens(Vec<Token>),
|
||||
Text(String),
|
||||
}
|
||||
|
||||
impl TokenizedArgument {
|
||||
pub fn generate<'a>(&'a self, path: &str) -> Cow<'a, str> {
|
||||
use self::Token::*;
|
||||
|
||||
match *self {
|
||||
TokenizedArgument::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)
|
||||
}
|
||||
TokenizedArgument::Text(ref text) => Cow::Borrowed(text),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenizedCommand {
|
||||
pub fn new(input: &str) -> TokenizedCommand {
|
||||
let mut tokens = Vec::new();
|
||||
let mut start = 0;
|
||||
let mut flags = 0;
|
||||
let mut chars = input.char_indices();
|
||||
let mut text = String::new();
|
||||
|
||||
while let Some((id, character)) = chars.next() {
|
||||
match character {
|
||||
// Backslashes are useful in cases where we want to use the '{' character
|
||||
// without having all occurrences of it to collect placeholder tokens.
|
||||
'\\' => {
|
||||
if let Some((_, nchar)) = chars.next() {
|
||||
if nchar != '{' {
|
||||
text.push(character);
|
||||
}
|
||||
text.push(nchar);
|
||||
}
|
||||
}
|
||||
// When a raw '{' is discovered, we will note it's position, and use that for a
|
||||
// later comparison against valid placeholder tokens.
|
||||
'{' if flags & OPEN == 0 => {
|
||||
flags |= OPEN;
|
||||
start = id;
|
||||
if !text.is_empty() {
|
||||
append(&mut tokens, &text);
|
||||
text.clear();
|
||||
}
|
||||
}
|
||||
// If the `OPEN` bit is set, we will compare the contents between the discovered
|
||||
// '{' and '}' characters against a list of valid tokens, then pushing the
|
||||
// corresponding token onto the `tokens` vector.
|
||||
'}' if flags & OPEN != 0 => {
|
||||
flags ^= OPEN;
|
||||
match &input[start + 1..id] {
|
||||
"" => tokens.push(Token::Placeholder),
|
||||
"." => tokens.push(Token::NoExt),
|
||||
"/" => tokens.push(Token::Basename),
|
||||
"//" => tokens.push(Token::Parent),
|
||||
"/." => tokens.push(Token::BasenameNoExt),
|
||||
_ => {
|
||||
append(&mut tokens, &input[start..id + 1]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
flags |= PLACE;
|
||||
}
|
||||
// We aren't collecting characters for a text string if the `OPEN` bit is set.
|
||||
_ if flags & OPEN != 0 => (),
|
||||
// Push the character onto the text buffer
|
||||
_ => text.push(character),
|
||||
}
|
||||
pub fn new<I, S>(input: I) -> TokenizedCommand
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
lazy_static! {
|
||||
static ref PLACEHOLDER: Regex = Regex::new(r"\{(/?\.?|//)\}").unwrap();
|
||||
}
|
||||
|
||||
// Take care of any stragglers left behind.
|
||||
if !text.is_empty() {
|
||||
append(&mut tokens, &text);
|
||||
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;
|
||||
|
||||
for placeholder in PLACEHOLDER.find_iter(arg) {
|
||||
// Leading text before the placeholder.
|
||||
if placeholder.start() > start {
|
||||
tokens.push(Token::Text(arg[start..placeholder.start()].to_owned()));
|
||||
}
|
||||
|
||||
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"),
|
||||
}
|
||||
|
||||
has_placeholder = true;
|
||||
}
|
||||
|
||||
// Without a placeholder, the argument is just fixed text.
|
||||
if tokens.is_empty() {
|
||||
args.push(TokenizedArgument::Text(arg.to_owned()));
|
||||
continue;
|
||||
}
|
||||
|
||||
if start < arg.len() {
|
||||
// Trailing text after last placeholder.
|
||||
tokens.push(Token::Text(arg[start..].to_owned()));
|
||||
}
|
||||
|
||||
args.push(TokenizedArgument::Tokens(tokens));
|
||||
}
|
||||
|
||||
// If a placeholder token was not supplied, append one at the end of the command.
|
||||
if flags & PLACE == 0 {
|
||||
append(&mut tokens, " ");
|
||||
tokens.push(Token::Placeholder)
|
||||
if !has_placeholder {
|
||||
args.push(TokenizedArgument::Tokens(vec![Token::Placeholder]));
|
||||
}
|
||||
|
||||
TokenizedCommand { tokens: tokens }
|
||||
TokenizedCommand { args: args }
|
||||
}
|
||||
|
||||
/// Generates a ticket that is required to execute the generated command.
|
||||
///
|
||||
/// Using the internal `tokens` field, and a supplied `input` variable, commands will be
|
||||
/// written into the `command` buffer. Once all tokens have been processed, the mutable
|
||||
/// reference of the `command` will be wrapped within a `CommandTicket`, which will be
|
||||
/// responsible for executing the command and clearing the buffer.
|
||||
pub fn generate<'a>(
|
||||
&self,
|
||||
command: &'a mut String,
|
||||
input: &Path,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
) -> CommandTicket<'a> {
|
||||
use self::Token::*;
|
||||
let input = input.strip_prefix(".").unwrap_or(input).to_string_lossy();
|
||||
for token in &self.tokens {
|
||||
match *token {
|
||||
Basename => *command += &Input::new(&input).basename().get(),
|
||||
BasenameNoExt => {
|
||||
*command += &Input::new(&input).basename().remove_extension().get()
|
||||
}
|
||||
NoExt => *command += &Input::new(&input).remove_extension().get(),
|
||||
Parent => *command += &Input::new(&input).dirname().get(),
|
||||
Placeholder => *command += &Input::new(&input).get(),
|
||||
Text(ref string) => *command += string,
|
||||
}
|
||||
/// Using the internal `args` field, and a supplied `input` variable, arguments will be
|
||||
/// collected in a Vec. Once all arguments have been processed, the Vec will be wrapped
|
||||
/// within a `CommandTicket`, which will be responsible for executing the command.
|
||||
pub fn generate(&self, input: &Path, out_perm: Arc<Mutex<()>>) -> CommandTicket {
|
||||
let input = input
|
||||
.strip_prefix(".")
|
||||
.unwrap_or(input)
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
|
||||
let mut args = Vec::with_capacity(self.args.len());
|
||||
for arg in &self.args {
|
||||
args.push(arg.generate(&input));
|
||||
}
|
||||
|
||||
CommandTicket::new(command, out_perm)
|
||||
}
|
||||
}
|
||||
|
||||
/// If the last token is a text token, append to that token. Otherwise, create a new token.
|
||||
fn append(tokens: &mut Vec<Token>, elem: &str) {
|
||||
// Useful to avoid a borrowing issue with the tokens vector.
|
||||
let mut append_text = false;
|
||||
|
||||
// If the last token is a `Text` token, simply the `elem` at the end.
|
||||
match tokens.last_mut() {
|
||||
Some(&mut Token::Text(ref mut string)) => *string += elem,
|
||||
_ => append_text = true,
|
||||
};
|
||||
|
||||
// Otherwise, we will need to add a new `Text` token that contains the `elem`
|
||||
if append_text {
|
||||
tokens.push(Token::Text(String::from(elem)));
|
||||
CommandTicket::new(args, out_perm)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{TokenizedCommand, Token};
|
||||
use super::{TokenizedCommand, TokenizedArgument, Token};
|
||||
|
||||
#[test]
|
||||
fn tokens() {
|
||||
let expected = TokenizedCommand {
|
||||
tokens: vec![Token::Text("echo ${SHELL}: ".into()), Token::Placeholder],
|
||||
args: vec![
|
||||
TokenizedArgument::Text("echo".into()),
|
||||
TokenizedArgument::Text("${SHELL}:".into()),
|
||||
TokenizedArgument::Tokens(vec![Token::Placeholder]),
|
||||
],
|
||||
};
|
||||
|
||||
assert_eq!(TokenizedCommand::new("echo $\\{SHELL}: {}"), expected);
|
||||
assert_eq!(TokenizedCommand::new("echo ${SHELL}:"), expected);
|
||||
assert_eq!(TokenizedCommand::new(&[&"echo", &"${SHELL}:"]), expected);
|
||||
|
||||
assert_eq!(
|
||||
TokenizedCommand::new("echo {.}"),
|
||||
TokenizedCommand { tokens: vec![Token::Text("echo ".into()), Token::NoExt] }
|
||||
TokenizedCommand::new(&["echo", "{.}"]),
|
||||
TokenizedCommand {
|
||||
args: vec![
|
||||
TokenizedArgument::Text("echo".into()),
|
||||
TokenizedArgument::Tokens(vec![Token::NoExt]),
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,47 +6,39 @@
|
|||
// notice may not be copied, modified, or distributed except
|
||||
// according to those terms.
|
||||
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::io;
|
||||
|
||||
lazy_static! {
|
||||
/// On non-Windows systems, the `SHELL` environment variable will be used to determine the
|
||||
/// preferred shell of choice for execution. Windows will simply use `cmd`.
|
||||
static ref COMMAND: (String, &'static str) = if cfg!(target_os = "windows") {
|
||||
("cmd".into(), "/C")
|
||||
} else {
|
||||
(env::var("SHELL").unwrap_or_else(|_| "/bin/sh".into()), "-c")
|
||||
};
|
||||
}
|
||||
|
||||
/// A state that offers access to executing a generated command.
|
||||
///
|
||||
/// The ticket holds a mutable reference to a string that contains the command to be executed.
|
||||
/// After execution of the the command via the `then_execute()` method, the string will be
|
||||
/// cleared so that a new command can be written to the string in the future.
|
||||
pub struct CommandTicket<'a> {
|
||||
command: &'a mut String,
|
||||
/// The ticket holds the collection of arguments of a command to be executed.
|
||||
pub struct CommandTicket {
|
||||
args: Vec<String>,
|
||||
out_perm: Arc<Mutex<()>>,
|
||||
}
|
||||
|
||||
impl<'a> CommandTicket<'a> {
|
||||
pub fn new(command: &'a mut String, out_perm: Arc<Mutex<()>>) -> CommandTicket<'a> {
|
||||
CommandTicket { command, out_perm }
|
||||
impl CommandTicket {
|
||||
pub fn new<I, S>(args: I, out_perm: Arc<Mutex<()>>) -> CommandTicket
|
||||
where
|
||||
I: IntoIterator<Item = S>,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
CommandTicket {
|
||||
args: args.into_iter().map(|x| x.as_ref().to_owned()).collect(),
|
||||
out_perm: out_perm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the command stored within the ticket, and
|
||||
/// clearing the command's buffer when finished.'
|
||||
/// Executes the command stored within the ticket.
|
||||
#[cfg(not(unix))]
|
||||
pub fn then_execute(self) {
|
||||
use std::process::Stdio;
|
||||
use std::io::Write;
|
||||
|
||||
// Spawn a shell with the supplied command.
|
||||
let cmd = Command::new(COMMAND.0.as_str())
|
||||
.arg(COMMAND.1)
|
||||
.arg(&self.command)
|
||||
// Spawn the supplied command.
|
||||
let cmd = Command::new(&self.args[0])
|
||||
.args(&self.args[1..])
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.output();
|
||||
|
@ -66,9 +58,6 @@ impl<'a> CommandTicket<'a> {
|
|||
}
|
||||
Err(why) => eprintln!("fd: exec error: {}", why),
|
||||
}
|
||||
|
||||
// Clear the buffer for later re-use.
|
||||
self.command.clear();
|
||||
}
|
||||
|
||||
#[cfg(all(unix))]
|
||||
|
@ -88,10 +77,9 @@ impl<'a> CommandTicket<'a> {
|
|||
pipe(stderr_fds.as_mut_ptr());
|
||||
}
|
||||
|
||||
// Spawn a shell with the supplied command.
|
||||
let cmd = Command::new(COMMAND.0.as_str())
|
||||
.arg(COMMAND.1)
|
||||
.arg(&self.command)
|
||||
// Spawn the supplied command.
|
||||
let cmd = Command::new(&self.args[0])
|
||||
.args(&self.args[1..])
|
||||
// Configure the pipes accordingly in the child.
|
||||
.before_exec(move || unsafe {
|
||||
// Redirect the child's std{out,err} to the write ends of our pipe.
|
||||
|
@ -134,8 +122,5 @@ impl<'a> CommandTicket<'a> {
|
|||
}
|
||||
Err(why) => eprintln!("fd: exec error: {}", why),
|
||||
}
|
||||
|
||||
// Clear the command string's buffer for later re-use.
|
||||
self.command.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ extern crate libc;
|
|||
extern crate num_cpus;
|
||||
extern crate regex;
|
||||
extern crate regex_syntax;
|
||||
extern crate shell_escape;
|
||||
|
||||
pub mod fshelper;
|
||||
pub mod lscolors;
|
||||
|
@ -106,7 +105,7 @@ fn main() {
|
|||
None
|
||||
};
|
||||
|
||||
let command = matches.value_of("exec").map(|x| TokenizedCommand::new(x));
|
||||
let command = matches.values_of("exec").map(TokenizedCommand::new);
|
||||
|
||||
let config = FdOptions {
|
||||
case_sensitive,
|
||||
|
|
Loading…
Reference in a new issue