From e1e4cc7fc1e62b89449738eb3b56e026985e9238 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Tue, 19 Jul 2022 21:02:27 +0300 Subject: [PATCH 01/11] Add command line argument for printing the executed command. No highlighting yet. --- src/app.rs | 9 +++++ src/dir_entry.rs | 2 + src/exec/job.rs | 49 ++++++++++++++++++++++-- src/exec/mod.rs | 99 ++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 6 +-- src/walk.rs | 10 ++++- 6 files changed, 155 insertions(+), 20 deletions(-) diff --git a/src/app.rs b/src/app.rs index a9eba45f9..d73e39ded 100644 --- a/src/app.rs +++ b/src/app.rs @@ -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") diff --git a/src/dir_entry.rs b/src/dir_entry.rs index 5def5de0e..6476af687 100644 --- a/src/dir_entry.rs +++ b/src/dir_entry.rs @@ -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>, diff --git a/src/exec/job.rs b/src/exec/job.rs index 9b95ac24b..2e2ba7daf 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -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(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, + cmd: &CommandSet, + show_filesystem_errors: bool, +) -> Receiver { + let (send, rx_new) = channel::(); + let res: Vec = 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, out_perm: Arc>, show_filesystem_errors: bool, - buffer_output: bool, + buffer_output: bool ) -> ExitCode { let mut results: Vec = 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) } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index fc26da2a6..37b540698 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -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, + print: bool, commands: Vec, } +// 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(input: I, path_separator: Option) -> Result + pub fn new(input: I, path_separator: Option, print: bool) -> Result where I: IntoIterator>, S: AsRef, @@ -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(input: I, path_separator: Option) -> Result + pub fn new_batch(input: I, path_separator: Option, print: bool) -> Result where I: IntoIterator>, S: AsRef, @@ -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>, buffer_output: bool) -> ExitCode { let path_separator = self.path_separator.as_deref(); let commands = self @@ -215,6 +246,18 @@ struct CommandTemplate { args: Vec, } +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(input: I) -> Result 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] diff --git a/src/main.rs b/src/main.rs index 9c16ff012..2dbf2f2e8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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) }) diff --git a/src/walk.rs b/src/walk.rs index 463417e65..55b60902d 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -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 ) }); From cb969bd4a0cea9506a9a13055d955ed2bc6a555a Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Wed, 20 Jul 2022 19:59:51 +0300 Subject: [PATCH 02/11] Rudimentary color highlighting for printed commands. --- src/exec/job.rs | 26 ++++++++++++------------- src/exec/mod.rs | 39 +++++++++++++++++++++++++------------- src/output.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ src/walk.rs | 4 +++- 4 files changed, 91 insertions(+), 28 deletions(-) diff --git a/src/exec/job.rs b/src/exec/job.rs index 2e2ba7daf..15e686eef 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -1,39 +1,37 @@ use std::io::{Write, stdout}; -use std::path::PathBuf; use std::sync::mpsc::{Receiver, channel}; use std::sync::{Arc, Mutex}; +use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; use crate::exit_codes::{merge_exitcodes, ExitCode}; +use crate::output; use crate::walk::WorkerResult; -use super::{CommandSet, CommandSetDisplay}; +use super::CommandSet; -pub fn print_command(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 +fn print_command(stdout: &mut W, entry: &DirEntry, cmd: &CommandSet, config: &Config) { + output::print_command_with_entry(stdout, entry, cmd, config); } -/// 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. +/// Print commands from the `rx` receiver without running them. +/// Returns a new channel with the same results to allow passing the return value to the +/// `job`-function to then execute the printed commands in multiple threads. pub fn peek_job_commands( rx: &Receiver, cmd: &CommandSet, + config: &Config, show_filesystem_errors: bool, ) -> Receiver { let (send, rx_new) = channel::(); - let res: Vec = rx + let res: Vec = 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()) + Some(dir_entry) }, WorkerResult::Error(err) => { if show_filesystem_errors { @@ -44,7 +42,7 @@ pub fn peek_job_commands( }) .collect(); for p in res.iter() { - print_command(&mut stdout(), p, cmd); + print_command(&mut stdout(), p, cmd,config); } return rx_new } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 37b540698..a5066fdbf 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -5,7 +5,7 @@ mod token; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; -use std::fmt::{Display, Debug}; +use std::fmt::{Display, Debug, Formatter}; use std::io; use std::iter; use std::path::{Component, Path, PathBuf, Prefix}; @@ -17,6 +17,7 @@ use argmax::Command; use once_cell::sync::Lazy; use regex::Regex; +use crate::dir_entry::DirEntry; use crate::exit_codes::ExitCode; use self::command::{execute_commands, handle_cmd_error}; @@ -44,26 +45,27 @@ pub struct CommandSet { // Wrapper for displaying Commands. pub struct CommandSetDisplay<'a> { command_set: &'a CommandSet, - input: PathBuf + input: DirEntry, + style: Option } impl <'a> CommandSetDisplay<'a> { - pub fn new( command_set: &'a CommandSet, input: PathBuf ) -> CommandSetDisplay { - CommandSetDisplay { command_set, input } + pub fn new( command_set: &'a CommandSet, input: DirEntry, style: Option ) -> CommandSetDisplay { + CommandSetDisplay { command_set, input, style } } } 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 ()}); + for c in self.command_set.commands.iter() { + if let Err(e) = c.display_highlighted(f,&self.input, self.style) { + return Err(e); + } + } Ok(()) } } - impl CommandSet { pub fn new(input: I, path_separator: Option, print: bool) -> Result where @@ -343,13 +345,24 @@ 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); + fn display_highlighted( + &self, + f: &mut Formatter, + entry: &DirEntry, + style: Option + ) -> std::fmt::Result { + let mut res: OsString = self.args[0].generate(&entry.path(), None); for arg in &self.args[1..] { res.push(" "); - res.push(arg.generate(&input, path_separator)); + res.push(arg.generate(&entry.path(), None)); + } + if let Some(s) = style { + let as_str = res.to_str().unwrap(); + write!(f,"{}\n",s.paint(as_str))?; + } else { + write!(f,"{:?}\n",res)?; } - res + Ok(()) } } diff --git a/src/output.rs b/src/output.rs index 261dbf329..e48ac342c 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,6 +7,7 @@ use lscolors::{Indicator, LsColors, Style}; use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; +use crate::exec::{CommandSet, CommandSetDisplay}; use crate::exit_codes::ExitCode; use crate::filesystem::strip_current_dir; @@ -42,6 +43,24 @@ pub fn print_entry(stdout: &mut W, entry: &DirEntry, config: &Config) } } +pub fn print_command_with_entry( + stdout: &mut W, + entry: &DirEntry, + cmd: &CommandSet, + config: &Config, +) -> io::Result<()> { + + let style: Option = if let Some(ref ls_colors) = config.ls_colors{ + Some(get_entry_style(entry, ls_colors, config)) + } else { + None + }; + + let cmd_display = CommandSetDisplay::new(cmd, entry.to_owned(), style); + write!(stdout, "{}",cmd_display)?; + Ok(()) +} + // Display a trailing slash if the path is a directory and the config option is enabled. // If the path_separator option is set, display that instead. // The trailing slash will not be colored. @@ -65,6 +84,37 @@ fn print_trailing_slash( Ok(()) } +fn get_entry_style(entry: &DirEntry, ls_colors: &LsColors, config: &Config) -> ansi_term::Style { + + // Split the path between the parent and the last component + let mut offset = 0; + let path = stripped_path(entry, config); + let path_str = path.to_string_lossy(); + + if let Some(parent) = path.parent() { + offset = parent.to_string_lossy().len(); + for c in path_str[offset..].chars() { + if std::path::is_separator(c) { + offset += c.len_utf8(); + } else { + break; + } + } + } + + if offset > 0 { + return ls_colors + .style_for_indicator(Indicator::Directory) + .map(Style::to_ansi_term_style) + .unwrap_or_default() + } + + return ls_colors + .style_for_path_with_metadata(path, entry.metadata()) + .map(Style::to_ansi_term_style) + .unwrap_or_default() +} + // TODO: this function is performance critical and can probably be optimized fn print_entry_colorized( stdout: &mut W, diff --git a/src/walk.rs b/src/walk.rs index 55b60902d..70abceec5 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -353,8 +353,10 @@ fn spawn_receiver( exec::batch(rx, cmd, show_filesystem_errors, config.batch_size) } else { + // If printing commands, wait for results from the receiver to print them before + // running commands. let shared_rx = if cmd.should_print() { - Arc::new(Mutex::new( exec::peek_job_commands(&rx, cmd, show_filesystem_errors) )) + Arc::new(Mutex::new(exec::peek_job_commands(&rx, cmd, &config, show_filesystem_errors))) } else { Arc::new(Mutex::new(rx)) }; From e7c6e4669d3a736d9e286119e08b9eabd030e905 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Fri, 22 Jul 2022 20:23:17 +0300 Subject: [PATCH 03/11] Highlighting for templates in commands. TODO: clean and optimize. --- src/exec/mod.rs | 88 ++++++++++++++++++++++++++++--------------------- src/output.rs | 86 ++++++++++++++++++++++++++--------------------- 2 files changed, 98 insertions(+), 76 deletions(-) diff --git a/src/exec/mod.rs b/src/exec/mod.rs index a5066fdbf..b86b71c20 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -17,8 +17,10 @@ use argmax::Command; use once_cell::sync::Lazy; use regex::Regex; +use crate::config::Config; use crate::dir_entry::DirEntry; use crate::exit_codes::ExitCode; +use crate::output; use self::command::{execute_commands, handle_cmd_error}; use self::input::{basename, dirname, remove_extension}; @@ -45,20 +47,24 @@ pub struct CommandSet { // Wrapper for displaying Commands. pub struct CommandSetDisplay<'a> { command_set: &'a CommandSet, - input: DirEntry, - style: Option + input: &'a DirEntry, + config: &'a Config } impl <'a> CommandSetDisplay<'a> { - pub fn new( command_set: &'a CommandSet, input: DirEntry, style: Option ) -> CommandSetDisplay { - CommandSetDisplay { command_set, input, style } + pub fn new( + command_set: &'a CommandSet, + input: &'a DirEntry, + config: &'a Config + ) -> CommandSetDisplay<'a> { + CommandSetDisplay { command_set, input, config } } } impl Display for CommandSetDisplay<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for c in self.command_set.commands.iter() { - if let Err(e) = c.display_highlighted(f,&self.input, self.style) { + if let Err(e) = c.display_highlighted(f,&self.input, self.config) { return Err(e); } } @@ -349,19 +355,14 @@ impl CommandTemplate { &self, f: &mut Formatter, entry: &DirEntry, - style: Option + config: &Config ) -> std::fmt::Result { - let mut res: OsString = self.args[0].generate(&entry.path(), None); + let mut res: OsString = self.args[0].generate_with_highlight(&entry.path(), None, None,None); for arg in &self.args[1..] { res.push(" "); - res.push(arg.generate(&entry.path(), None)); - } - if let Some(s) = style { - let as_str = res.to_str().unwrap(); - write!(f,"{}\n",s.paint(as_str))?; - } else { - write!(f,"{:?}\n",res)?; + res.push( arg.generate_with_highlight(&entry.path(), None, Some(config), Some(entry) )); } + write!(f,"{}\n",res.to_str().unwrap())?; Ok(()) } } @@ -376,22 +377,6 @@ 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(_)) @@ -401,33 +386,60 @@ impl ArgumentTemplate { /// 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_separator: Option<&str>) -> OsString { + self.generate_with_highlight(path, path_separator, None,None) + } + + fn generate_with_highlight( + &self, + path: impl AsRef, + path_separator: Option<&str>, + config: Option<&Config>, + entry: Option<&DirEntry> + ) -> OsString { use self::Token::*; let path = path.as_ref(); + let hl = |token_text: &OsStr| -> OsString { + if let Some(cfg) = config { + let path_from_token = Path::new(token_text); + output::paint_entry(entry.unwrap(), path_from_token, cfg) + } else { + OsString::from(token_text) + } + }; + match *self { ArgumentTemplate::Tokens(ref tokens) => { let mut s = OsString::new(); for token in tokens { match *token { - Basename => s.push(Self::replace_separator(basename(path), path_separator)), + Basename => s.push(hl(&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)), + NoExt => { + s.push(hl(&Self::replace_separator( + &remove_extension(path), + path_separator, + ))); + }, Placeholder => { - s.push(Self::replace_separator(path.as_ref(), path_separator)) + s.push(hl(&Self::replace_separator(path.as_ref(), path_separator))) } Text(ref string) => s.push(string), } } - s + return s + } + ArgumentTemplate::Text(ref text) => { + //if let Some(hl) = style { + // OsString::from(&hl.paint(text).to_string()) + //} else { + OsString::from(text) + //} } - ArgumentTemplate::Text(ref text) => OsString::from(text), } } diff --git a/src/output.rs b/src/output.rs index e48ac342c..a78a651e4 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::ffi::OsString; use std::io::{self, Write}; use std::path::Path; @@ -43,6 +44,52 @@ pub fn print_entry(stdout: &mut W, entry: &DirEntry, config: &Config) } } +pub fn paint_entry( + entry: &DirEntry, + path: &Path, + config: &Config, +) -> OsString { + + if let Some(ls_colors) = &config.ls_colors { + let mut offset = 0; + let path_str = path.to_string_lossy(); + let mut result = OsString::new(); + + if let Some(parent) = path.parent() { + offset = parent.to_string_lossy().len(); + for c in path_str[offset..].chars() { + if std::path::is_separator(c) { + offset += c.len_utf8(); + } else { + break; + } + } + } + + if offset > 0 { + let mut parent_str = Cow::from(&path_str[..offset]); + if let Some(ref separator) = config.path_separator { + *parent_str.to_mut() = replace_path_separator(&parent_str, separator); + } + + let style = ls_colors + .style_for_indicator(Indicator::Directory) + .map(Style::to_ansi_term_style) + .unwrap_or_default(); + result.push(style.paint(parent_str).to_string()); + } + + let style = ls_colors + .style_for_path_with_metadata(path, entry.metadata()) + .map(Style::to_ansi_term_style) + .unwrap_or_default(); + result.push(style.paint(&path_str[offset..]).to_string()); + result + } else { + OsString::from(path) + } +} + pub fn print_command_with_entry( stdout: &mut W, entry: &DirEntry, @@ -50,13 +97,7 @@ pub fn print_command_with_entry( config: &Config, ) -> io::Result<()> { - let style: Option = if let Some(ref ls_colors) = config.ls_colors{ - Some(get_entry_style(entry, ls_colors, config)) - } else { - None - }; - - let cmd_display = CommandSetDisplay::new(cmd, entry.to_owned(), style); + let cmd_display = CommandSetDisplay::new(cmd, entry, config); write!(stdout, "{}",cmd_display)?; Ok(()) } @@ -84,37 +125,6 @@ fn print_trailing_slash( Ok(()) } -fn get_entry_style(entry: &DirEntry, ls_colors: &LsColors, config: &Config) -> ansi_term::Style { - - // Split the path between the parent and the last component - let mut offset = 0; - let path = stripped_path(entry, config); - let path_str = path.to_string_lossy(); - - if let Some(parent) = path.parent() { - offset = parent.to_string_lossy().len(); - for c in path_str[offset..].chars() { - if std::path::is_separator(c) { - offset += c.len_utf8(); - } else { - break; - } - } - } - - if offset > 0 { - return ls_colors - .style_for_indicator(Indicator::Directory) - .map(Style::to_ansi_term_style) - .unwrap_or_default() - } - - return ls_colors - .style_for_path_with_metadata(path, entry.metadata()) - .map(Style::to_ansi_term_style) - .unwrap_or_default() -} - // TODO: this function is performance critical and can probably be optimized fn print_entry_colorized( stdout: &mut W, From e8d42be72a037e5dd8368cea797970788856d4bc Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Tue, 26 Jul 2022 16:55:30 +0300 Subject: [PATCH 04/11] Run command prints in correct sequence, clean and format code. --- src/exec/command.rs | 6 ++ src/exec/job.rs | 56 ++++--------- src/exec/mod.rs | 186 ++++++++++++++++++++++++++++++-------------- src/main.rs | 25 ++++-- src/output.rs | 12 +-- src/walk.rs | 14 +--- 6 files changed, 171 insertions(+), 128 deletions(-) diff --git a/src/exec/command.rs b/src/exec/command.rs index a642a405b..70c4f9c41 100644 --- a/src/exec/command.rs +++ b/src/exec/command.rs @@ -55,6 +55,7 @@ pub fn execute_commands>>( cmds: I, out_perm: &Mutex<()>, enable_output_buffering: bool, + cmd_display_result: Option, ) -> ExitCode { let mut output_buffer = OutputBuffer::new(out_perm); for result in cmds { @@ -72,6 +73,11 @@ pub fn execute_commands>>( cmd.spawn().and_then(|c| c.wait_with_output()) }; + if let Some(ref d) = cmd_display_result { + let cmd_bytes: Vec = d.clone().as_bytes().to_vec(); + output_buffer.push(cmd_bytes, vec![]); + } + // Then wait for the command to exit, if it was spawned. match output { Ok(output) => { diff --git a/src/exec/job.rs b/src/exec/job.rs index 15e686eef..9674c1740 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -1,51 +1,13 @@ -use std::io::{Write, stdout}; -use std::sync::mpsc::{Receiver, channel}; +use std::sync::mpsc::Receiver; use std::sync::{Arc, Mutex}; use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; use crate::exit_codes::{merge_exitcodes, ExitCode}; -use crate::output; use crate::walk::WorkerResult; -use super::CommandSet; - -fn print_command(stdout: &mut W, entry: &DirEntry, cmd: &CommandSet, config: &Config) { - output::print_command_with_entry(stdout, entry, cmd, config); -} - -/// Print commands from the `rx` receiver without running them. -/// Returns a new channel with the same results to allow passing the return value to the -/// `job`-function to then execute the printed commands in multiple threads. -pub fn peek_job_commands( - rx: &Receiver, - cmd: &CommandSet, - config: &Config, - show_filesystem_errors: bool, -) -> Receiver { - let (send, rx_new) = channel::(); - let res: Vec = rx - .into_iter() - .filter_map(|worker_result| { - send.send(worker_result.to_owned()).unwrap(); - match worker_result { - WorkerResult::Entry(dir_entry) => { - Some(dir_entry) - }, - 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,config); - } - return rx_new -} +use super::{CommandSet, CommandSetDisplay}; /// 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 @@ -55,7 +17,8 @@ pub fn job( cmd: Arc, out_perm: Arc>, show_filesystem_errors: bool, - buffer_output: bool + buffer_output: bool, + config: &Config, ) -> ExitCode { let mut results: Vec = Vec::new(); loop { @@ -79,7 +42,16 @@ pub fn job( 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)) + results.push(cmd.execute( + dir_entry.path(), + Arc::clone(&out_perm), + buffer_output, + if cmd.should_print() { + Some(CommandSetDisplay::new(&cmd, &dir_entry, config)) + } else { + None + }, + )) } // Returns error in case of any error. diff --git a/src/exec/mod.rs b/src/exec/mod.rs index b86b71c20..efdbf6e43 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -5,7 +5,7 @@ mod token; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; -use std::fmt::{Display, Debug, Formatter}; +use std::fmt::{Debug, Display, Formatter}; use std::io; use std::iter; use std::path::{Component, Path, PathBuf, Prefix}; @@ -24,7 +24,7 @@ use crate::output; use self::command::{execute_commands, handle_cmd_error}; use self::input::{basename, dirname, remove_extension}; -pub use self::job::{batch, job, peek_job_commands}; +pub use self::job::{batch, job}; use self::token::Token; /// Execution mode of the command @@ -48,23 +48,27 @@ pub struct CommandSet { pub struct CommandSetDisplay<'a> { command_set: &'a CommandSet, input: &'a DirEntry, - config: &'a Config + config: &'a Config, } -impl <'a> CommandSetDisplay<'a> { +impl<'a> CommandSetDisplay<'a> { pub fn new( command_set: &'a CommandSet, input: &'a DirEntry, - config: &'a Config + config: &'a Config, ) -> CommandSetDisplay<'a> { - CommandSetDisplay { command_set, input, config } + CommandSetDisplay { + command_set, + input, + config, + } } } impl Display for CommandSetDisplay<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for c in self.command_set.commands.iter() { - if let Err(e) = c.display_highlighted(f,&self.input, self.config) { + if let Err(e) = c.display_highlighted(f, &self.input, self.config) { return Err(e); } } @@ -89,7 +93,11 @@ impl CommandSet { }) } - pub fn new_batch(input: I, path_separator: Option, print: bool) -> Result + pub fn new_batch( + input: I, + path_separator: Option, + print: bool, + ) -> Result where I: IntoIterator>, S: AsRef, @@ -122,13 +130,26 @@ impl CommandSet { self.print } - pub fn execute(&self, input: &Path, out_perm: Arc>, buffer_output: bool) -> ExitCode { + pub fn execute( + &self, + input: &Path, + out_perm: Arc>, + buffer_output: bool, + cmd_display: Option, + ) -> ExitCode { let path_separator = self.path_separator.as_deref(); + + let printed_command = if let Some(existing_display) = cmd_display { + Some(format!("{}", existing_display)) + } else { + None + }; + let commands = self .commands .iter() .map(|c| c.generate(input, path_separator)); - execute_commands(commands, &out_perm, buffer_output) + execute_commands(commands, &out_perm, buffer_output, printed_command) } pub fn execute_batch(&self, paths: I, limit: usize) -> ExitCode @@ -265,7 +286,6 @@ impl Display for CommandTemplate { } } - impl CommandTemplate { fn new(input: I) -> Result where @@ -355,14 +375,14 @@ impl CommandTemplate { &self, f: &mut Formatter, entry: &DirEntry, - config: &Config + config: &Config, ) -> std::fmt::Result { - let mut res: OsString = self.args[0].generate_with_highlight(&entry.path(), None, None,None); + let mut res: OsString = self.args[0].generate(&entry.path(), None); for arg in &self.args[1..] { res.push(" "); - res.push( arg.generate_with_highlight(&entry.path(), None, Some(config), Some(entry) )); + res.push(arg.generate_with_highlight(&entry, None, config)); } - write!(f,"{}\n",res.to_str().unwrap())?; + write!(f, "{}\n", res.to_str().unwrap())?; Ok(()) } } @@ -385,64 +405,112 @@ impl ArgumentTemplate { /// 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_separator: Option<&str>) -> OsString { - self.generate_with_highlight(path, path_separator, None,None) - } - - fn generate_with_highlight( + /// Apply a callback `cb` to each token's result where templates are substituted to allow further + /// processing. Eg. function `generate_with_highlight` will apply text highlighting via the + /// callback to highlight found entries in the generated string. + fn generate_with_callback( &self, path: impl AsRef, path_separator: Option<&str>, - config: Option<&Config>, - entry: Option<&DirEntry> + cb: Option<&dyn Fn(&OsStr, &Token) -> OsString>, ) -> OsString { use self::Token::*; let path = path.as_ref(); - let hl = |token_text: &OsStr| -> OsString { - if let Some(cfg) = config { - let path_from_token = Path::new(token_text); - output::paint_entry(entry.unwrap(), path_from_token, cfg) - } else { - OsString::from(token_text) - } - }; + enum TokenGen<'a> { + Template(Cow<'a, OsStr>), + TextOnly(&'a String), + } match *self { ArgumentTemplate::Tokens(ref tokens) => { let mut s = OsString::new(); + let mut push_token_text = |token_text: TokenGen, tkn: &Token| match token_text { + TokenGen::Template(tmpl_text) => { + if let Some(callback) = cb { + s.push(callback(&tmpl_text, tkn)) + } else { + s.push(tmpl_text) + } + } + TokenGen::TextOnly(text) => s.push(text), + }; for token in tokens { match *token { - Basename => s.push(hl(&Self::replace_separator(basename(path), path_separator))), - BasenameNoExt => s.push(Self::replace_separator( - &remove_extension(basename(path).as_ref()), - path_separator, - )), - Parent => s.push(Self::replace_separator(&dirname(path), path_separator)), - NoExt => { - s.push(hl(&Self::replace_separator( + Basename => push_token_text( + TokenGen::Template(Self::replace_separator( + basename(path), + path_separator, + )), + token, + ), + BasenameNoExt => push_token_text( + TokenGen::Template(Self::replace_separator( + &remove_extension(basename(path).as_ref()), + path_separator, + )), + token, + ), + NoExt => push_token_text( + TokenGen::Template(Self::replace_separator( &remove_extension(path), path_separator, - ))); - }, - Placeholder => { - s.push(hl(&Self::replace_separator(path.as_ref(), path_separator))) - } - Text(ref string) => s.push(string), + )), + token, + ), + Parent => push_token_text( + TokenGen::Template(Self::replace_separator( + &dirname(path), + path_separator, + )), + token, + ), + Placeholder => push_token_text( + TokenGen::Template(Self::replace_separator( + path.as_ref(), + path_separator, + )), + token, + ), + Text(ref string) => push_token_text(TokenGen::TextOnly(string), token), } } - return s - } - ArgumentTemplate::Text(ref text) => { - //if let Some(hl) = style { - // OsString::from(&hl.paint(text).to_string()) - //} else { - OsString::from(text) - //} + s } + ArgumentTemplate::Text(ref text) => OsString::from(text), } } + /// 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_separator: Option<&str>) -> OsString { + self.generate_with_callback(path, path_separator, None) + } + + // Same functionality as `generate` but result will have highlighted entires in the generated + // string. + fn generate_with_highlight( + &self, + entry: &DirEntry, + path_separator: Option<&str>, + config: &Config, + ) -> OsString { + use self::Token::*; + let path = entry.path(); + + let apply_highlight = |token_text: &OsStr, token: &Token| -> OsString { + let path_from_token = Path::new(token_text); + match *token { + Basename => output::paint_entry(entry, path_from_token, config), + NoExt => output::paint_entry(entry, path_from_token, config), + Placeholder => output::paint_entry(entry, path_from_token, config), + _ => OsString::from(token_text), + } + }; + self.generate_with_callback(path, path_separator, Some(&apply_highlight)) + } + /// Replace the path separator in the input with the custom separator string. If path_separator /// is None, simply return a borrowed Cow of the input. Otherwise, the input is /// interpreted as a Path and its components are iterated through and re-joined into a new @@ -543,7 +611,7 @@ mod tests { #[test] fn tokens_with_basename() { assert_eq!( - CommandSet::new(vec![vec!["echo", "{/}"]], None,false).unwrap(), + CommandSet::new(vec![vec!["echo", "{/}"]], None, false).unwrap(), CommandSet { commands: vec![CommandTemplate { args: vec![ @@ -561,7 +629,7 @@ mod tests { #[test] fn tokens_with_parent() { assert_eq!( - CommandSet::new(vec![vec!["echo", "{//}"]], None,false).unwrap(), + CommandSet::new(vec![vec!["echo", "{//}"]], None, false).unwrap(), CommandSet { commands: vec![CommandTemplate { args: vec![ @@ -579,7 +647,7 @@ mod tests { #[test] fn tokens_with_basename_no_extension() { assert_eq!( - CommandSet::new(vec![vec!["echo", "{/.}"]], None,false).unwrap(), + CommandSet::new(vec![vec!["echo", "{/.}"]], None, false).unwrap(), CommandSet { commands: vec![CommandTemplate { args: vec![ @@ -597,7 +665,7 @@ mod tests { #[test] fn tokens_multiple() { assert_eq!( - CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None,false).unwrap(), + CommandSet::new(vec![vec!["cp", "{}", "{/.}.ext"]], None, false).unwrap(), CommandSet { commands: vec![CommandTemplate { args: vec![ @@ -619,7 +687,7 @@ mod tests { #[test] fn tokens_single_batch() { assert_eq!( - CommandSet::new_batch(vec![vec!["echo", "{.}"]], None,false).unwrap(), + CommandSet::new_batch(vec![vec!["echo", "{.}"]], None, false).unwrap(), CommandSet { commands: vec![CommandTemplate { args: vec![ @@ -636,7 +704,7 @@ mod tests { #[test] fn tokens_multiple_batch() { - assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None,false).is_err()); + assert!(CommandSet::new_batch(vec![vec!["echo", "{.}", "{}"]], None, false).is_err()); } #[test] @@ -646,7 +714,7 @@ mod tests { #[test] fn command_set_no_args() { - assert!(CommandSet::new(vec![vec!["echo"], vec![]], None,false).is_err()); + assert!(CommandSet::new(vec![vec!["echo"], vec![]], None, false).is_err()); } #[test] diff --git a/src/main.rs b/src/main.rs index 2dbf2f2e8..a8a23c29e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -397,14 +397,22 @@ fn extract_command( colored_output: bool, ) -> Result> { None.or_else(|| { - matches - .grouped_values_of("exec") - .map(|args| CommandSet::new(args, path_separator.map(str::to_string), matches.is_present("print-exec"))) + matches.grouped_values_of("exec").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), matches.is_present("print-exec"))) + matches.grouped_values_of("exec-batch").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") { @@ -414,8 +422,9 @@ fn extract_command( let color = matches.value_of("color").unwrap_or("auto"); 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),false).unwrap()); + let res = determine_ls_command(&color_arg, colored_output).map(|cmd| { + CommandSet::new_batch([cmd], path_separator.map(str::to_string), false).unwrap() + }); Some(res) }) diff --git a/src/output.rs b/src/output.rs index a78a651e4..c33f758e1 100644 --- a/src/output.rs +++ b/src/output.rs @@ -44,12 +44,7 @@ pub fn print_entry(stdout: &mut W, entry: &DirEntry, config: &Config) } } -pub fn paint_entry( - entry: &DirEntry, - path: &Path, - config: &Config, -) -> OsString { - +pub fn paint_entry(entry: &DirEntry, path: &Path, config: &Config) -> OsString { if let Some(ls_colors) = &config.ls_colors { let mut offset = 0; let path_str = path.to_string_lossy(); @@ -83,7 +78,7 @@ pub fn paint_entry( .style_for_path_with_metadata(path, entry.metadata()) .map(Style::to_ansi_term_style) .unwrap_or_default(); - result.push(style.paint(&path_str[offset..]).to_string()); + result.push(style.paint(&path_str[offset..]).to_string()); result } else { OsString::from(path) @@ -96,9 +91,8 @@ pub fn print_command_with_entry( cmd: &CommandSet, config: &Config, ) -> io::Result<()> { - let cmd_display = CommandSetDisplay::new(cmd, entry, config); - write!(stdout, "{}",cmd_display)?; + write!(stdout, "{}", cmd_display)?; Ok(()) } diff --git a/src/walk.rs b/src/walk.rs index 70abceec5..47d7d9b48 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -352,15 +352,7 @@ fn spawn_receiver( if cmd.in_batch_mode() { exec::batch(rx, cmd, show_filesystem_errors, config.batch_size) } else { - - // If printing commands, wait for results from the receiver to print them before - // running commands. - let shared_rx = if cmd.should_print() { - Arc::new(Mutex::new(exec::peek_job_commands(&rx, cmd, &config, show_filesystem_errors))) - } else { - Arc::new(Mutex::new(rx)) - }; - + let shared_rx = Arc::new(Mutex::new(rx)); let out_perm = Arc::new(Mutex::new(())); // Each spawned job will store it's thread handle in here. @@ -369,6 +361,7 @@ fn spawn_receiver( let rx = Arc::clone(&shared_rx); let cmd = Arc::clone(cmd); let out_perm = Arc::clone(&out_perm); + let config = Arc::clone(&config); // Spawn a job thread that will listen for and execute inputs. let handle = thread::spawn(move || { @@ -377,7 +370,8 @@ fn spawn_receiver( cmd, out_perm, show_filesystem_errors, - enable_output_buffering + enable_output_buffering, + &config, ) }); From caaa1e0c07e995b9a5c3cf5576b18c6a59808201 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Fri, 29 Jul 2022 19:12:40 +0300 Subject: [PATCH 05/11] Allow also printing batch command. --- src/app.rs | 1 - src/dir_entry.rs | 2 - src/exec/job.rs | 15 ++++- src/exec/mod.rs | 153 ++++++++++++++++++++++++++++++++++++++--------- src/output.rs | 12 ---- src/walk.rs | 3 +- 6 files changed, 138 insertions(+), 48 deletions(-) diff --git a/src/app.rs b/src/app.rs index d73e39ded..bed3a9cca 100644 --- a/src/app.rs +++ b/src/app.rs @@ -444,7 +444,6 @@ 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." diff --git a/src/dir_entry.rs b/src/dir_entry.rs index 6476af687..5def5de0e 100644 --- a/src/dir_entry.rs +++ b/src/dir_entry.rs @@ -5,13 +5,11 @@ 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>, diff --git a/src/exec/job.rs b/src/exec/job.rs index 9674c1740..b080299a9 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -63,11 +63,12 @@ pub fn batch( cmd: &CommandSet, show_filesystem_errors: bool, limit: usize, + config: &Config, ) -> ExitCode { - let paths = rx + let entries = rx .into_iter() .filter_map(|worker_result| match worker_result { - WorkerResult::Entry(dir_entry) => Some(dir_entry.into_path()), + WorkerResult::Entry(dir_entry) => Some(dir_entry), WorkerResult::Error(err) => { if show_filesystem_errors { print_error(err.to_string()); @@ -76,5 +77,13 @@ pub fn batch( } }); - cmd.execute_batch(paths, limit) + cmd.execute_batch( + entries, + limit, + if cmd.should_print() { + Some(config) + } else { + None + }, + ) } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index efdbf6e43..2f557db10 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -5,10 +5,11 @@ mod token; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; -use std::fmt::{Debug, Display, Formatter}; -use std::io; +use std::fmt::{Debug, Display}; +use std::io::{self, stdout, StdoutLock, Write}; use std::iter; -use std::path::{Component, Path, PathBuf, Prefix}; +use std::os::unix::prelude::OsStrExt; +use std::path::{Component, Path, Prefix}; use std::process::Stdio; use std::sync::{Arc, Mutex}; @@ -47,32 +48,36 @@ pub struct CommandSet { // Wrapper for displaying Commands. pub struct CommandSetDisplay<'a> { command_set: &'a CommandSet, - input: &'a DirEntry, + entry: &'a DirEntry, config: &'a Config, } impl<'a> CommandSetDisplay<'a> { pub fn new( command_set: &'a CommandSet, - input: &'a DirEntry, + entry: &'a DirEntry, config: &'a Config, ) -> CommandSetDisplay<'a> { CommandSetDisplay { command_set, - input, + entry, config, } } } -impl Display for CommandSetDisplay<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl CommandSetDisplay<'_> { + fn get_command_string(&self, path_separator: Option<&str>) -> Option { + let mut res = String::new(); for c in self.command_set.commands.iter() { - if let Err(e) = c.display_highlighted(f, &self.input, self.config) { - return Err(e); + let cmd_hl = c.generate_highlighted_string(self.entry, self.config, path_separator); + match cmd_hl { + None => return None, + Some(cmd_hl_str) => res.push_str(&cmd_hl_str), } } - Ok(()) + res.push_str("\n"); + Some(res) } } @@ -139,11 +144,7 @@ impl CommandSet { ) -> ExitCode { let path_separator = self.path_separator.as_deref(); - let printed_command = if let Some(existing_display) = cmd_display { - Some(format!("{}", existing_display)) - } else { - None - }; + let printed_command = cmd_display.and_then(|cd| cd.get_command_string(path_separator)); let commands = self .commands @@ -152,28 +153,56 @@ impl CommandSet { execute_commands(commands, &out_perm, buffer_output, printed_command) } - pub fn execute_batch(&self, paths: I, limit: usize) -> ExitCode + pub fn execute_batch( + &self, + entries: I, + limit: usize, + print_with_config: Option<&Config>, + ) -> ExitCode where - I: Iterator, + I: Iterator, { let path_separator = self.path_separator.as_deref(); + let mut cmd_display: Option = None; let builders: io::Result> = self .commands .iter() - .map(|c| CommandBuilder::new(c, limit)) + .map(|c| { + if let Some(config) = print_with_config { + let display = BatchCommandSetDisplay::new(c, config); + display.print_pre_args(); + cmd_display = Some(display); + } + CommandBuilder::new(c, limit) + }) .collect(); match builders { Ok(mut builders) => { - for path in paths { + for entry in entries { for builder in &mut builders { - if let Err(e) = builder.push(&path, path_separator) { + // If provided print config, print command arguments. + if let Some(config) = print_with_config { + if let Err(e) = BatchCommandSetDisplay::write_entry_string( + &entry, + &builder.path_arg, + config, + path_separator, + ) { + return handle_cmd_error(Some(&builder.cmd), e); + } + } + if let Err(e) = builder.push(entry.path(), path_separator) { return handle_cmd_error(Some(&builder.cmd), e); } } } + if let Some(display) = cmd_display { + display.print_post_args(); + } + for builder in &mut builders { if let Err(e) = builder.finish() { return handle_cmd_error(Some(&builder.cmd), e); @@ -187,6 +216,75 @@ impl CommandSet { } } +struct BatchCommandSetDisplay<'a> { + template: &'a CommandTemplate, + config: &'a Config, + post_args: Vec, + pre_args: Vec, +} + +impl<'a> BatchCommandSetDisplay<'a> { + pub fn new(template: &'a CommandTemplate, config: &'a Config) -> BatchCommandSetDisplay<'a> { + let mut pre_args = vec![]; + let mut post_args = vec![]; + + // Get arguments. + let mut path_arg = None; + for arg in &template.args { + if arg.has_tokens() { + path_arg = Some(arg.clone()); + } else if path_arg == None { + pre_args.push(arg.generate("", None)); + } else { + post_args.push(arg.generate("", None)); + } + } + + BatchCommandSetDisplay { + template, + config, + post_args, + pre_args, + } + } + + pub fn print_pre_args(&self) { + let mut stdout = stdout().lock(); + // Print pre-args, arguments before entry templates. + for arg in &self.pre_args { + stdout.write_all(arg.as_bytes()); + stdout.write_all(b" "); + } + stdout.flush(); + } + + pub fn print_post_args(&self) { + let mut stdout = stdout().lock(); + // Print pre-args, arguments before entry templates. + for arg in &self.post_args { + stdout.write_all(arg.as_bytes()); + stdout.write_all(b" "); + } + stdout.write_all(b"\n"); + stdout.flush(); + } + + fn write_entry_string( + entry: &DirEntry, + argt: &ArgumentTemplate, + config: &Config, + path_separator: Option<&str>, + ) -> Result<(), std::io::Error> { + let mut stdout = stdout().lock(); + stdout.write_all( + argt.generate_with_highlight(entry, path_separator, config) + .as_bytes(), + )?; + stdout.write_all(b" ")?; + stdout.flush() + } +} + /// Represents a multi-exec command as it is built. #[derive(Debug)] struct CommandBuilder { @@ -371,19 +469,18 @@ impl CommandTemplate { Ok(cmd) } - fn display_highlighted( + fn generate_highlighted_string( &self, - f: &mut Formatter, entry: &DirEntry, config: &Config, - ) -> std::fmt::Result { - let mut res: OsString = self.args[0].generate(&entry.path(), None); + path_separator: Option<&str>, + ) -> Option { + let mut res: OsString = self.args[0].generate(entry.path(), path_separator); for arg in &self.args[1..] { res.push(" "); - res.push(arg.generate_with_highlight(&entry, None, config)); + res.push(arg.generate_with_highlight(entry, path_separator, config)); } - write!(f, "{}\n", res.to_str().unwrap())?; - Ok(()) + return res.to_str().and_then(|s| Some(String::from(s))); } } diff --git a/src/output.rs b/src/output.rs index c33f758e1..1f095dfad 100644 --- a/src/output.rs +++ b/src/output.rs @@ -8,7 +8,6 @@ use lscolors::{Indicator, LsColors, Style}; use crate::config::Config; use crate::dir_entry::DirEntry; use crate::error::print_error; -use crate::exec::{CommandSet, CommandSetDisplay}; use crate::exit_codes::ExitCode; use crate::filesystem::strip_current_dir; @@ -85,17 +84,6 @@ pub fn paint_entry(entry: &DirEntry, path: &Path, config: &Config) -> OsString { } } -pub fn print_command_with_entry( - stdout: &mut W, - entry: &DirEntry, - cmd: &CommandSet, - config: &Config, -) -> io::Result<()> { - let cmd_display = CommandSetDisplay::new(cmd, entry, config); - write!(stdout, "{}", cmd_display)?; - Ok(()) -} - // Display a trailing slash if the path is a directory and the config option is enabled. // If the path_separator option is set, display that instead. // The trailing slash will not be colored. diff --git a/src/walk.rs b/src/walk.rs index 47d7d9b48..cea8fc5b3 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -34,7 +34,6 @@ 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), @@ -350,7 +349,7 @@ fn spawn_receiver( // This will be set to `Some` if the `--exec` argument was supplied. if let Some(ref cmd) = config.command { if cmd.in_batch_mode() { - exec::batch(rx, cmd, show_filesystem_errors, config.batch_size) + exec::batch(rx, cmd, show_filesystem_errors, config.batch_size, &config) } else { let shared_rx = Arc::new(Mutex::new(rx)); let out_perm = Arc::new(Mutex::new(())); From ea515c564b459decd0ab2408304f4923b8c4b95d Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:27:18 +0300 Subject: [PATCH 06/11] Fix --print-exec behavior in combination with --batch-size. Clean code, add tests and update CHANGELOG. --- CHANGELOG.md | 2 + src/exec/mod.rs | 150 ++++++++++++++++++++---------------------------- tests/tests.rs | 63 ++++++++++++++++++++ 3 files changed, 126 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89ff83964..c8a0db027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Features +- Added flag `--print-exec` for printing commands before they are ran. + ## Bugfixes diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 2f557db10..f9d9c0095 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -6,9 +6,8 @@ mod token; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::fmt::{Debug, Display}; -use std::io::{self, stdout, StdoutLock, Write}; +use std::io::{self, stdout, Write}; use std::iter; -use std::os::unix::prelude::OsStrExt; use std::path::{Component, Path, Prefix}; use std::process::Stdio; use std::sync::{Arc, Mutex}; @@ -79,6 +78,49 @@ impl CommandSetDisplay<'_> { res.push_str("\n"); Some(res) } + + fn write_entry_string( + entry: &DirEntry, + argt: &ArgumentTemplate, + config: &Config, + path_separator: Option<&str>, + ) -> Result<(), std::io::Error> { + let mut stdout = stdout().lock(); + if let Some(argt_str) = argt + .generate_with_highlight(entry, path_separator, config) + .to_str() + { + write!(stdout, "{} ", argt_str)?; + } + stdout.flush() + } + + fn print_pre_args(args: &Vec) -> std::io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + // Print pre-args; arguments before entry templates. + for arg in args { + if let Some(arg_str) = arg.to_str() { + write!(stdout, "{} ", arg_str)?; + } + } + stdout.flush() + } + + fn print_post_args(args: &Vec) -> std::io::Result<()> { + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + // Print post-args, arguments after entry templates. + for arg in args { + if let Some(arg_str) = arg.to_str() { + write!(stdout, "{} ", arg_str)?; + } + } + write!(stdout, "\n")?; + stdout.flush() + } } impl CommandSet { @@ -164,27 +206,23 @@ impl CommandSet { { let path_separator = self.path_separator.as_deref(); - let mut cmd_display: Option = None; let builders: io::Result> = self .commands .iter() - .map(|c| { - if let Some(config) = print_with_config { - let display = BatchCommandSetDisplay::new(c, config); - display.print_pre_args(); - cmd_display = Some(display); - } - CommandBuilder::new(c, limit) - }) + .map(|c| CommandBuilder::new(c, limit, print_with_config.is_some())) .collect(); match builders { Ok(mut builders) => { for entry in entries { for builder in &mut builders { + if let Err(e) = builder.push(entry.path(), path_separator) { + return handle_cmd_error(Some(&builder.cmd), e); + } + // If provided print config, print command arguments. if let Some(config) = print_with_config { - if let Err(e) = BatchCommandSetDisplay::write_entry_string( + if let Err(e) = CommandSetDisplay::write_entry_string( &entry, &builder.path_arg, config, @@ -193,16 +231,9 @@ impl CommandSet { return handle_cmd_error(Some(&builder.cmd), e); } } - if let Err(e) = builder.push(entry.path(), path_separator) { - return handle_cmd_error(Some(&builder.cmd), e); - } } } - if let Some(display) = cmd_display { - display.print_post_args(); - } - for builder in &mut builders { if let Err(e) = builder.finish() { return handle_cmd_error(Some(&builder.cmd), e); @@ -216,75 +247,6 @@ impl CommandSet { } } -struct BatchCommandSetDisplay<'a> { - template: &'a CommandTemplate, - config: &'a Config, - post_args: Vec, - pre_args: Vec, -} - -impl<'a> BatchCommandSetDisplay<'a> { - pub fn new(template: &'a CommandTemplate, config: &'a Config) -> BatchCommandSetDisplay<'a> { - let mut pre_args = vec![]; - let mut post_args = vec![]; - - // Get arguments. - let mut path_arg = None; - for arg in &template.args { - if arg.has_tokens() { - path_arg = Some(arg.clone()); - } else if path_arg == None { - pre_args.push(arg.generate("", None)); - } else { - post_args.push(arg.generate("", None)); - } - } - - BatchCommandSetDisplay { - template, - config, - post_args, - pre_args, - } - } - - pub fn print_pre_args(&self) { - let mut stdout = stdout().lock(); - // Print pre-args, arguments before entry templates. - for arg in &self.pre_args { - stdout.write_all(arg.as_bytes()); - stdout.write_all(b" "); - } - stdout.flush(); - } - - pub fn print_post_args(&self) { - let mut stdout = stdout().lock(); - // Print pre-args, arguments before entry templates. - for arg in &self.post_args { - stdout.write_all(arg.as_bytes()); - stdout.write_all(b" "); - } - stdout.write_all(b"\n"); - stdout.flush(); - } - - fn write_entry_string( - entry: &DirEntry, - argt: &ArgumentTemplate, - config: &Config, - path_separator: Option<&str>, - ) -> Result<(), std::io::Error> { - let mut stdout = stdout().lock(); - stdout.write_all( - argt.generate_with_highlight(entry, path_separator, config) - .as_bytes(), - )?; - stdout.write_all(b" ")?; - stdout.flush() - } -} - /// Represents a multi-exec command as it is built. #[derive(Debug)] struct CommandBuilder { @@ -294,10 +256,11 @@ struct CommandBuilder { cmd: Command, count: usize, limit: usize, + print: bool, } impl CommandBuilder { - fn new(template: &CommandTemplate, limit: usize) -> io::Result { + fn new(template: &CommandTemplate, limit: usize, print: bool) -> io::Result { let mut pre_args = vec![]; let mut path_arg = None; let mut post_args = vec![]; @@ -321,6 +284,7 @@ impl CommandBuilder { cmd, count: 0, limit, + print, }) } @@ -346,6 +310,11 @@ impl CommandBuilder { self.finish()?; } + // If started a new command, print the arguments before printing entry templates. + if self.count == 0 && self.print { + CommandSetDisplay::print_pre_args(&self.pre_args)?; + } + self.cmd.try_arg(arg)?; self.count += 1; Ok(()) @@ -353,6 +322,9 @@ impl CommandBuilder { fn finish(&mut self) -> io::Result<()> { if self.count > 0 { + if self.print { + CommandSetDisplay::print_post_args(&self.post_args)?; + } self.cmd.try_args(&self.post_args)?; self.cmd.status()?; diff --git a/tests/tests.rs b/tests/tests.rs index 72154dd9c..649f3c0bc 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1654,6 +1654,69 @@ fn test_exec_with_separator() { ); } +/// Shell script execution (--exec and --exec-batch) with printing +#[test] +fn test_print_exec() { + let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES); + let te = te.normalize_line(true); + + // TODO Windows tests: D:file.txt \file.txt \\server\share\file.txt ... + if !cfg!(windows) { + te.assert_output( + &["foo", "--print-exec", "--exec", "echo", "{}"], + "echo ./a.foo + echo ./one/b.foo + echo ./one/two/C.Foo2 + echo ./one/two/c.foo + echo ./one/two/three/d.foo + echo ./one/two/three/directory_foo + ./a.foo + ./one/b.foo + ./one/two/C.Foo2 + ./one/two/c.foo + ./one/two/three/d.foo + ./one/two/three/directory_foo", + ); + + te.assert_output( + &["foo", "--print-exec", "--exec", "echo", "{.}"], + "echo a + echo one/b + echo one/two/C + echo one/two/c + echo one/two/three/d + echo one/two/three/directory_foo + a + one/b + one/two/C + one/two/c + one/two/three/d + one/two/three/directory_foo", + ); + te.assert_output( + &["foo", "--print-exec", "--exec", "echo", "{/}"], + "echo a.foo + echo b.foo + echo C.Foo2 + echo c.foo + echo d.foo + echo directory_foo + a.foo + b.foo + C.Foo2 + c.foo + d.foo + directory_foo", + ); + + te.assert_output( + &["foo", "--print-exec", "--exec-batch", "echo", "beginarg", "{}", "endarg"], + "echo beginarg ./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo endarg + beginarg ./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo endarg", + ); + } +} + /// Non-zero exit code (--quiet) #[test] fn test_quiet() { From d6cc23949b6ea2483e6d3f053ae32e1e0f10cb80 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:40:59 +0300 Subject: [PATCH 07/11] Continue previous, fix minimum supported rust version issue. --- src/exec/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/exec/mod.rs b/src/exec/mod.rs index f9d9c0095..3989e9e01 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -6,7 +6,7 @@ mod token; use std::borrow::Cow; use std::ffi::{OsStr, OsString}; use std::fmt::{Debug, Display}; -use std::io::{self, stdout, Write}; +use std::io::{self, Write}; use std::iter; use std::path::{Component, Path, Prefix}; use std::process::Stdio; @@ -85,7 +85,8 @@ impl CommandSetDisplay<'_> { config: &Config, path_separator: Option<&str>, ) -> Result<(), std::io::Error> { - let mut stdout = stdout().lock(); + let stdout = io::stdout(); + let mut stdout = stdout.lock(); if let Some(argt_str) = argt .generate_with_highlight(entry, path_separator, config) .to_str() From ddf8f03d8ec18e1ce44bb2eefe6b04f4023b4576 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Mon, 1 Aug 2022 19:51:27 +0300 Subject: [PATCH 08/11] Better doc for --print-exec --- src/app.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index bed3a9cca..51529cd86 100644 --- a/src/app.rs +++ b/src/app.rs @@ -445,8 +445,10 @@ pub fn build_app() -> Command<'static> { Arg::new("print-exec") .long("print-exec") .help("Print each command ran with -x or -X.") + .requires("exec-batch") + .requires("exec") .long_help( - "Print each command to be ran. If -x or -X is not used, this argument has no effect." + "Print each command before it is executed. Must be ran with -x or -X." ), ) .arg( From 4b6fca75368b84c6ec26e1bf9e9d11d8dc32633b Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Tue, 2 Aug 2022 16:41:27 +0300 Subject: [PATCH 09/11] Fix code style warnings. --- src/exec/mod.rs | 14 +++++++------- tests/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/exec/mod.rs b/src/exec/mod.rs index 3989e9e01..c57c6e87d 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -75,11 +75,11 @@ impl CommandSetDisplay<'_> { Some(cmd_hl_str) => res.push_str(&cmd_hl_str), } } - res.push_str("\n"); + res.push('\n'); Some(res) } - fn write_entry_string( + fn print_entry_string( entry: &DirEntry, argt: &ArgumentTemplate, config: &Config, @@ -96,7 +96,7 @@ impl CommandSetDisplay<'_> { stdout.flush() } - fn print_pre_args(args: &Vec) -> std::io::Result<()> { + fn print_pre_args(args: &[OsString]) -> std::io::Result<()> { let stdout = io::stdout(); let mut stdout = stdout.lock(); @@ -109,7 +109,7 @@ impl CommandSetDisplay<'_> { stdout.flush() } - fn print_post_args(args: &Vec) -> std::io::Result<()> { + fn print_post_args(args: &[OsString]) -> std::io::Result<()> { let stdout = io::stdout(); let mut stdout = stdout.lock(); @@ -119,7 +119,7 @@ impl CommandSetDisplay<'_> { write!(stdout, "{} ", arg_str)?; } } - write!(stdout, "\n")?; + writeln!(stdout)?; stdout.flush() } } @@ -223,7 +223,7 @@ impl CommandSet { // If provided print config, print command arguments. if let Some(config) = print_with_config { - if let Err(e) = CommandSetDisplay::write_entry_string( + if let Err(e) = CommandSetDisplay::print_entry_string( &entry, &builder.path_arg, config, @@ -453,7 +453,7 @@ impl CommandTemplate { res.push(" "); res.push(arg.generate_with_highlight(entry, path_separator, config)); } - return res.to_str().and_then(|s| Some(String::from(s))); + return res.to_str().map(|s| String::from(s)); } } diff --git a/tests/tests.rs b/tests/tests.rs index 649f3c0bc..338d38eb8 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1709,6 +1709,32 @@ fn test_print_exec() { directory_foo", ); + te.assert_output( + &[ + "--path-separator=#", + "--print-exec", + "--absolute-path", + "foo", + "--exec", + "echo", + ], + &format!( + "echo {abs_path}#a.foo + echo {abs_path}#one#b.foo + echo {abs_path}#one#two#C.Foo2 + echo {abs_path}#one#two#c.foo + echo {abs_path}#one#two#three#d.foo + echo {abs_path}#one#two#three#directory_foo + {abs_path}#a.foo + {abs_path}#one#b.foo + {abs_path}#one#two#C.Foo2 + {abs_path}#one#two#c.foo + {abs_path}#one#two#three#d.foo + {abs_path}#one#two#three#directory_foo", + abs_path = abs_path.replace(std::path::MAIN_SEPARATOR, "#"), + ), + ); + te.assert_output( &["foo", "--print-exec", "--exec-batch", "echo", "beginarg", "{}", "endarg"], "echo beginarg ./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo endarg From bc564b8f2de6d13a75bd910d3087aa906138f4a5 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Tue, 2 Aug 2022 20:35:40 +0300 Subject: [PATCH 10/11] Fix printing when using multiple command instances. --- src/exec/command.rs | 12 +++-- src/exec/mod.rs | 109 +++++++++++++++++++------------------------- tests/tests.rs | 65 +++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 67 deletions(-) diff --git a/src/exec/command.rs b/src/exec/command.rs index 70c4f9c41..66d4dbb82 100644 --- a/src/exec/command.rs +++ b/src/exec/command.rs @@ -58,6 +58,13 @@ pub fn execute_commands>>( cmd_display_result: Option, ) -> ExitCode { let mut output_buffer = OutputBuffer::new(out_perm); + + if let Some(ref d) = cmd_display_result { + // Print command. + let cmd_bytes: Vec = d.clone().as_bytes().to_vec(); + output_buffer.push(cmd_bytes, vec![]); + } + for result in cmds { let mut cmd = match result { Ok(cmd) => cmd, @@ -73,11 +80,6 @@ pub fn execute_commands>>( cmd.spawn().and_then(|c| c.wait_with_output()) }; - if let Some(ref d) = cmd_display_result { - let cmd_bytes: Vec = d.clone().as_bytes().to_vec(); - output_buffer.push(cmd_bytes, vec![]); - } - // Then wait for the command to exit, if it was spawned. match output { Ok(output) => { diff --git a/src/exec/mod.rs b/src/exec/mod.rs index c57c6e87d..c53999194 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -68,7 +68,10 @@ impl<'a> CommandSetDisplay<'a> { impl CommandSetDisplay<'_> { fn get_command_string(&self, path_separator: Option<&str>) -> Option { let mut res = String::new(); - for c in self.command_set.commands.iter() { + for (i, c) in self.command_set.commands.iter().enumerate() { + if i > 0 { + res.push_str(&"; "); + } let cmd_hl = c.generate_highlighted_string(self.entry, self.config, path_separator); match cmd_hl { None => return None, @@ -78,50 +81,6 @@ impl CommandSetDisplay<'_> { res.push('\n'); Some(res) } - - fn print_entry_string( - entry: &DirEntry, - argt: &ArgumentTemplate, - config: &Config, - path_separator: Option<&str>, - ) -> Result<(), std::io::Error> { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - if let Some(argt_str) = argt - .generate_with_highlight(entry, path_separator, config) - .to_str() - { - write!(stdout, "{} ", argt_str)?; - } - stdout.flush() - } - - fn print_pre_args(args: &[OsString]) -> std::io::Result<()> { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - - // Print pre-args; arguments before entry templates. - for arg in args { - if let Some(arg_str) = arg.to_str() { - write!(stdout, "{} ", arg_str)?; - } - } - stdout.flush() - } - - fn print_post_args(args: &[OsString]) -> std::io::Result<()> { - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - - // Print post-args, arguments after entry templates. - for arg in args { - if let Some(arg_str) = arg.to_str() { - write!(stdout, "{} ", arg_str)?; - } - } - writeln!(stdout)?; - stdout.flush() - } } impl CommandSet { @@ -223,14 +182,7 @@ impl CommandSet { // If provided print config, print command arguments. if let Some(config) = print_with_config { - if let Err(e) = CommandSetDisplay::print_entry_string( - &entry, - &builder.path_arg, - config, - path_separator, - ) { - return handle_cmd_error(Some(&builder.cmd), e); - } + builder.push_display_entry(&entry, config, path_separator) } } } @@ -258,6 +210,7 @@ struct CommandBuilder { count: usize, limit: usize, print: bool, + entries_to_print: OsString, } impl CommandBuilder { @@ -286,6 +239,7 @@ impl CommandBuilder { count: 0, limit, print, + entries_to_print: OsString::new(), }) } @@ -298,6 +252,20 @@ impl CommandBuilder { Ok(cmd) } + fn push_display_entry( + &mut self, + entry: &DirEntry, + config: &Config, + path_separator: Option<&str>, + ) { + self.entries_to_print + .push( + self.path_arg + .generate_with_highlight(entry, path_separator, config), + ); + self.entries_to_print.push(" "); + } + fn push(&mut self, path: &Path, separator: Option<&str>) -> io::Result<()> { if self.limit > 0 && self.count >= self.limit { self.finish()?; @@ -311,21 +279,40 @@ impl CommandBuilder { self.finish()?; } - // If started a new command, print the arguments before printing entry templates. - if self.count == 0 && self.print { - CommandSetDisplay::print_pre_args(&self.pre_args)?; - } - self.cmd.try_arg(arg)?; self.count += 1; Ok(()) } fn finish(&mut self) -> io::Result<()> { - if self.count > 0 { - if self.print { - CommandSetDisplay::print_post_args(&self.post_args)?; + if self.print { + let mut to_print = OsString::new(); + + for arg in &self.pre_args { + to_print.push(arg); + to_print.push(" "); } + + to_print.push(&self.entries_to_print); + + for (i, arg) in self.post_args.iter().enumerate() { + if i > 0 { + to_print.push(" "); + } + to_print.push(arg); + } + + let stdout = io::stdout(); + let mut stdout = stdout.lock(); + + if let Some(to_print_str) = to_print.to_str() { + writeln!(stdout, "{}", to_print_str)?; + } + + stdout.flush()?; + self.entries_to_print = OsString::new(); + } + if self.count > 0 { self.cmd.try_args(&self.post_args)?; self.cmd.status()?; diff --git a/tests/tests.rs b/tests/tests.rs index 338d38eb8..cc6cc8509 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ mod testenv; use std::fs; -use std::io::Write; +use std::io::{stdout, Write}; use std::path::Path; use std::time::{Duration, SystemTime}; use test_case::test_case; @@ -1740,6 +1740,69 @@ fn test_print_exec() { "echo beginarg ./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo endarg beginarg ./a.foo ./one/b.foo ./one/two/C.Foo2 ./one/two/c.foo ./one/two/three/d.foo ./one/two/three/directory_foo endarg", ); + + te.assert_output( + &[ + "--absolute-path", + "foo", + "--print-exec", + "--exec", + "echo", + ";", + "--exec", + "echo", + "test", + "{/}", + ], + &format!( + "echo {abs_path}/a.foo; echo test a.foo + echo {abs_path}/one/b.foo; echo test b.foo + echo {abs_path}/one/two/C.Foo2; echo test C.Foo2 + echo {abs_path}/one/two/c.foo; echo test c.foo + echo {abs_path}/one/two/three/d.foo; echo test d.foo + echo {abs_path}/one/two/three/directory_foo; echo test directory_foo + {abs_path}/a.foo + {abs_path}/one/b.foo + {abs_path}/one/two/C.Foo2 + {abs_path}/one/two/c.foo + {abs_path}/one/two/three/d.foo + {abs_path}/one/two/three/directory_foo + test a.foo + test b.foo + test C.Foo2 + test c.foo + test d.foo + test directory_foo", + abs_path = &abs_path + ), + ); + + te.assert_output( + &[ + "foo", + "--print-exec", + "--exec", + "echo", + "-n", + "{/}: ", + ";", + "--exec", + "echo", + "{//}", + ], + "echo -n a.foo: ; echo . + echo -n b.foo: ; echo ./one + echo -n C.Foo2: ; echo ./one/two + echo -n c.foo: ; echo ./one/two + echo -n d.foo: ; echo ./one/two/three + echo -n directory_foo: ; echo ./one/two/three + a.foo: . + b.foo: ./one + C.Foo2: ./one/two + c.foo: ./one/two + d.foo: ./one/two/three + directory_foo: ./one/two/three", + ); } } From 357f932067d42e54dc417daae32c3fdaa34292d3 Mon Sep 17 00:00:00 2001 From: ilmari-h <52321471+ilmari-h@users.noreply.github.com> Date: Wed, 3 Aug 2022 14:42:07 +0300 Subject: [PATCH 11/11] Fix code quality warnings. --- src/exec/mod.rs | 4 ++-- tests/tests.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exec/mod.rs b/src/exec/mod.rs index c53999194..be9c9cb71 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -70,7 +70,7 @@ impl CommandSetDisplay<'_> { let mut res = String::new(); for (i, c) in self.command_set.commands.iter().enumerate() { if i > 0 { - res.push_str(&"; "); + res.push_str("; "); } let cmd_hl = c.generate_highlighted_string(self.entry, self.config, path_separator); match cmd_hl { @@ -440,7 +440,7 @@ impl CommandTemplate { res.push(" "); res.push(arg.generate_with_highlight(entry, path_separator, config)); } - return res.to_str().map(|s| String::from(s)); + return res.to_str().map(String::from); } } diff --git a/tests/tests.rs b/tests/tests.rs index cc6cc8509..37c71d726 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,7 +1,7 @@ mod testenv; use std::fs; -use std::io::{stdout, Write}; +use std::io::Write; use std::path::Path; use std::time::{Duration, SystemTime}; use test_case::test_case;