Add command line argument for printing the executed command. No

highlighting yet.
This commit is contained in:
ilmari-h 2022-07-19 21:02:27 +03:00
parent 1b71425419
commit e1e4cc7fc1
6 changed files with 155 additions and 20 deletions

View File

@ -441,6 +441,15 @@ pub fn build_app() -> Command<'static> {
"
),
)
.arg(
Arg::new("print-exec")
.long("print-exec")
.short('P')
.help("Print each command ran with -x or -X.")
.long_help(
"Print each command to be ran. If -x or -X is not used, this argument has no effect."
),
)
.arg(
Arg::new("batch-size")
.long("batch-size")

View File

@ -5,11 +5,13 @@ use std::{
use once_cell::unsync::OnceCell;
#[derive(Clone)]
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
#[derive(Clone)]
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,

View File

@ -1,4 +1,6 @@
use std::sync::mpsc::Receiver;
use std::io::{Write, stdout};
use std::path::PathBuf;
use std::sync::mpsc::{Receiver, channel};
use std::sync::{Arc, Mutex};
use crate::dir_entry::DirEntry;
@ -6,7 +8,46 @@ use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::walk::WorkerResult;
use super::CommandSet;
use super::{CommandSet, CommandSetDisplay};
pub fn print_command<W: Write>(stdout: &mut W, path: &PathBuf, cmd: &CommandSet) {
if let Err(e) = write!(stdout,"{}\n", CommandSetDisplay::new(cmd, path.to_path_buf())) {
print_error(format!("Could not write to output: {}", e));
ExitCode::GeneralError.exit();
}
return
}
/// Print commands in the reciever without running them.
/// Returns a new channel with the same results to allow passing the
/// return value to the `job`-function to run the commands concurrently.
pub fn peek_job_commands(
rx: &Receiver<WorkerResult>,
cmd: &CommandSet,
show_filesystem_errors: bool,
) -> Receiver<WorkerResult> {
let (send, rx_new) = channel::<WorkerResult>();
let res: Vec<PathBuf> = rx
.into_iter()
.filter_map(|worker_result| {
send.send(worker_result.to_owned()).unwrap();
match worker_result {
WorkerResult::Entry(dir_entry) => {
Some(dir_entry.into_path())
},
WorkerResult::Error(err) => {
if show_filesystem_errors {
print_error(err.to_string());
}
None
}}
})
.collect();
for p in res.iter() {
print_command(&mut stdout(), p, cmd);
}
return rx_new
}
/// An event loop that listens for inputs from the `rx` receiver. Each received input will
/// generate a command with the supplied command template. The generated command will then
@ -16,7 +57,7 @@ pub fn job(
cmd: Arc<CommandSet>,
out_perm: Arc<Mutex<()>>,
show_filesystem_errors: bool,
buffer_output: bool,
buffer_output: bool
) -> ExitCode {
let mut results: Vec<ExitCode> = Vec::new();
loop {
@ -38,9 +79,11 @@ pub fn job(
// Drop the lock so that other threads can read from the receiver.
drop(lock);
// Generate a command, execute it and store its exit code.
results.push(cmd.execute(dir_entry.path(), Arc::clone(&out_perm), buffer_output))
}
// Returns error in case of any error.
merge_exitcodes(results)
}

View File

@ -5,6 +5,7 @@ mod token;
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::fmt::{Display, Debug};
use std::io;
use std::iter;
use std::path::{Component, Path, PathBuf, Prefix};
@ -20,7 +21,7 @@ use crate::exit_codes::ExitCode;
use self::command::{execute_commands, handle_cmd_error};
use self::input::{basename, dirname, remove_extension};
pub use self::job::{batch, job};
pub use self::job::{batch, job, peek_job_commands};
use self::token::Token;
/// Execution mode of the command
@ -36,11 +37,35 @@ pub enum ExecutionMode {
pub struct CommandSet {
mode: ExecutionMode,
path_separator: Option<String>,
print: bool,
commands: Vec<CommandTemplate>,
}
// Wrapper for displaying Commands.
pub struct CommandSetDisplay<'a> {
command_set: &'a CommandSet,
input: PathBuf
}
impl <'a> CommandSetDisplay<'a> {
pub fn new( command_set: &'a CommandSet, input: PathBuf ) -> CommandSetDisplay {
CommandSetDisplay { command_set, input }
}
}
impl Display for CommandSetDisplay<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self
.command_set.commands
.iter()
.for_each(|c| { write!(f, "{:?}", c.generate_string(&self.input, Some("/"))); return ()});
Ok(())
}
}
impl CommandSet {
pub fn new<I, S>(input: I, path_separator: Option<String>) -> Result<CommandSet>
pub fn new<I, S>(input: I, path_separator: Option<String>, print: bool) -> Result<CommandSet>
where
I: IntoIterator<Item = Vec<S>>,
S: AsRef<str>,
@ -48,6 +73,7 @@ impl CommandSet {
Ok(CommandSet {
mode: ExecutionMode::OneByOne,
path_separator,
print,
commands: input
.into_iter()
.map(CommandTemplate::new)
@ -55,7 +81,7 @@ impl CommandSet {
})
}
pub fn new_batch<I, S>(input: I, path_separator: Option<String>) -> Result<CommandSet>
pub fn new_batch<I, S>(input: I, path_separator: Option<String>, print: bool) -> Result<CommandSet>
where
I: IntoIterator<Item = Vec<S>>,
S: AsRef<str>,
@ -63,6 +89,7 @@ impl CommandSet {
Ok(CommandSet {
mode: ExecutionMode::Batch,
path_separator,
print,
commands: input
.into_iter()
.map(|args| {
@ -83,6 +110,10 @@ impl CommandSet {
self.mode == ExecutionMode::Batch
}
pub fn should_print(&self) -> bool {
self.print
}
pub fn execute(&self, input: &Path, out_perm: Arc<Mutex<()>>, buffer_output: bool) -> ExitCode {
let path_separator = self.path_separator.as_deref();
let commands = self
@ -215,6 +246,18 @@ struct CommandTemplate {
args: Vec<ArgumentTemplate>,
}
impl Display for CommandTemplate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for arg in self.args.iter() {
if let Err(e) = arg.fmt(f) {
return Err(e);
}
}
Ok(())
}
}
impl CommandTemplate {
fn new<I, S>(input: I) -> Result<CommandTemplate>
where
@ -299,6 +342,15 @@ impl CommandTemplate {
}
Ok(cmd)
}
fn generate_string(&self, input: &Path, path_separator: Option<&str>) -> OsString {
let mut res: OsString = self.args[0].generate(&input, path_separator);
for arg in &self.args[1..] {
res.push(" ");
res.push(arg.generate(&input, path_separator));
}
res
}
}
/// Represents a template for a single command argument.
@ -311,6 +363,22 @@ enum ArgumentTemplate {
Text(String),
}
//impl Display for ArgumentTemplate {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// match self {
// Self::Tokens(tokens) => {
// for token in tokens {
// if let Err(e) = token.fmt(f) {
// return Err(e)
// }
// }
// Ok(())
// },
// Self::Text(text) => write!(f,"{}",text)
// }
// }
//}
impl ArgumentTemplate {
pub fn has_tokens(&self) -> bool {
matches!(self, ArgumentTemplate::Tokens(_))
@ -413,7 +481,7 @@ mod tests {
#[test]
fn tokens_with_placeholder() {
assert_eq!(
CommandSet::new(vec![vec![&"echo", &"${SHELL}:"]], None).unwrap(),
CommandSet::new(vec![vec![&"echo", &"${SHELL}:"]], None, false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -422,6 +490,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::Placeholder]),
]
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -431,7 +500,7 @@ mod tests {
#[test]
fn tokens_with_no_extension() {
assert_eq!(
CommandSet::new(vec![vec!["echo", "{.}"]], None).unwrap(),
CommandSet::new(vec![vec!["echo", "{.}"]], None, false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -439,6 +508,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -448,7 +518,7 @@ mod tests {
#[test]
fn tokens_with_basename() {
assert_eq!(
CommandSet::new(vec![vec!["echo", "{/}"]], None).unwrap(),
CommandSet::new(vec![vec!["echo", "{/}"]], None,false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -456,6 +526,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::Basename]),
],
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -465,7 +536,7 @@ mod tests {
#[test]
fn tokens_with_parent() {
assert_eq!(
CommandSet::new(vec![vec!["echo", "{//}"]], None).unwrap(),
CommandSet::new(vec![vec!["echo", "{//}"]], None,false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -473,6 +544,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::Parent]),
],
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -482,7 +554,7 @@ mod tests {
#[test]
fn tokens_with_basename_no_extension() {
assert_eq!(
CommandSet::new(vec![vec!["echo", "{/.}"]], None).unwrap(),
CommandSet::new(vec![vec!["echo", "{/.}"]], None,false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -490,6 +562,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::BasenameNoExt]),
],
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -499,7 +572,7 @@ mod tests {
#[test]
fn tokens_multiple() {
assert_eq!(
CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None).unwrap(),
CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None,false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -511,6 +584,7 @@ mod tests {
]),
],
}],
print: false,
mode: ExecutionMode::OneByOne,
path_separator: None,
}
@ -520,7 +594,7 @@ mod tests {
#[test]
fn tokens_single_batch() {
assert_eq!(
CommandSet::new_batch(vec![vec!["echo", "{.}"]], None).unwrap(),
CommandSet::new_batch(vec![vec!["echo", "{.}"]], None,false).unwrap(),
CommandSet {
commands: vec![CommandTemplate {
args: vec![
@ -528,6 +602,7 @@ mod tests {
ArgumentTemplate::Tokens(vec![Token::NoExt]),
],
}],
print: false,
mode: ExecutionMode::Batch,
path_separator: None,
}
@ -536,7 +611,7 @@ mod tests {
#[test]
fn tokens_multiple_batch() {
assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None).is_err());
assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None,false).is_err());
}
#[test]
@ -546,7 +621,7 @@ mod tests {
#[test]
fn command_set_no_args() {
assert!(CommandSet::new(vec![vec!["echo"], vec![]], None).is_err());
assert!(CommandSet::new(vec![vec!["echo"], vec![]], None,false).is_err());
}
#[test]

View File

@ -399,12 +399,12 @@ fn extract_command(
None.or_else(|| {
matches
.grouped_values_of("exec")
.map(|args| CommandSet::new(args, path_separator.map(str::to_string)))
.map(|args| CommandSet::new(args, path_separator.map(str::to_string), matches.is_present("print-exec")))
})
.or_else(|| {
matches
.grouped_values_of("exec-batch")
.map(|args| CommandSet::new_batch(args, path_separator.map(str::to_string)))
.map(|args| CommandSet::new_batch(args, path_separator.map(str::to_string), matches.is_present("print-exec")))
})
.or_else(|| {
if !matches.is_present("list-details") {
@ -415,7 +415,7 @@ fn extract_command(
let color_arg = format!("--color={}", color);
let res = determine_ls_command(&color_arg, colored_output)
.map(|cmd| CommandSet::new_batch([cmd], path_separator.map(str::to_string)).unwrap());
.map(|cmd| CommandSet::new_batch([cmd], path_separator.map(str::to_string),false).unwrap());
Some(res)
})

View File

@ -34,6 +34,7 @@ enum ReceiverMode {
}
/// The Worker threads can result in a valid entry having PathBuf or an error.
#[derive(Clone)]
pub enum WorkerResult {
Entry(DirEntry),
Error(ignore::Error),
@ -351,7 +352,12 @@ fn spawn_receiver(
if cmd.in_batch_mode() {
exec::batch(rx, cmd, show_filesystem_errors, config.batch_size)
} else {
let shared_rx = Arc::new(Mutex::new(rx));
let shared_rx = if cmd.should_print() {
Arc::new(Mutex::new( exec::peek_job_commands(&rx, cmd, show_filesystem_errors) ))
} else {
Arc::new(Mutex::new(rx))
};
let out_perm = Arc::new(Mutex::new(()));
@ -369,7 +375,7 @@ fn spawn_receiver(
cmd,
out_perm,
show_filesystem_errors,
enable_output_buffering,
enable_output_buffering
)
});