diff options
author | Canop <cano.petrole@gmail.com> | 2021-01-07 17:31:38 +0100 |
---|---|---|
committer | Canop <cano.petrole@gmail.com> | 2021-01-07 17:31:38 +0100 |
commit | fe901eecc52c672289949e7adfa03d8d0b7e804d (patch) | |
tree | 38954d92729e6b43e7756b317db5ef3c6bf305b2 /src/verb | |
parent | 284fea889eaeb0ec120cf36b55f075bd7d58f19f (diff) |
allow specifying execution pattern as array
Fix #319
Diffstat (limited to 'src/verb')
-rw-r--r-- | src/verb/builtin.rs | 7 | ||||
-rw-r--r-- | src/verb/exec_pattern.rs | 137 | ||||
-rw-r--r-- | src/verb/execution_builder.rs | 82 | ||||
-rw-r--r-- | src/verb/external_execution.rs | 4 | ||||
-rw-r--r-- | src/verb/mod.rs | 2 | ||||
-rw-r--r-- | src/verb/verb.rs | 17 |
6 files changed, 197 insertions, 52 deletions
diff --git a/src/verb/builtin.rs b/src/verb/builtin.rs index 38c4017..2f066b7 100644 --- a/src/verb/builtin.rs +++ b/src/verb/builtin.rs @@ -36,10 +36,9 @@ fn external( execution_str: &str, exec_mode: ExternalExecutionMode, ) -> Verb { - let execution = VerbExecution::External(ExternalExecution::new( - execution_str.to_string(), - exec_mode, - )); + let execution = VerbExecution::External( + ExternalExecution::new(ExecPattern::from_string(execution_str), exec_mode) + ); Verb::new( Some(invocation_str), execution, diff --git a/src/verb/exec_pattern.rs b/src/verb/exec_pattern.rs new file mode 100644 index 0000000..c1cdee8 --- /dev/null +++ b/src/verb/exec_pattern.rs @@ -0,0 +1,137 @@ +use { + crate::{ + verb::*, + }, + serde::Deserialize, + std::{ + path::Path, + fmt, + }, +}; + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum ExecPattern { + String(String), + Array(Vec<String>), +} + +impl ExecPattern { + pub fn is_empty(&self) -> bool { + match self { + Self::String(s) => s.is_empty(), + Self::Array(v) => v.is_empty(), + } + } + pub fn has_other_panel_group(&self) -> bool { + match self { + Self::String(s) => str_has_other_panel_group(s), + Self::Array(v) => v.iter().any(|s| str_has_other_panel_group(s)), + } + } + pub fn as_internal_pattern(&self) -> Option<&str> { + match self { + Self::String(s) => { + if s.starts_with(':') || s.starts_with(' ') { + Some(&s[1..]) + } else { + None + } + } + Self::Array(_) => None, + } + } + pub fn as_array(self) -> Vec<String> { + match self { + Self::String(s) => { + splitty::split_unquoted_whitespace(&s) + .unwrap_quotes(true) + .map(|s| s.to_string()) + .collect() + } + Self::Array(v) => v, + } + } + pub fn from_string<T: Into<String>>(t: T) -> Self { + Self::String(t.into()) + } + pub fn from_array(v: Vec<String>) -> Self { + Self::Array(v) + } + pub fn tokenize(self) -> Self { + Self::Array(self.as_array()) + } + pub fn apply(&self, f: &dyn Fn(&str) -> String) -> Self { + Self::Array( + match self { + Self::String(s) => { + splitty::split_unquoted_whitespace(&s) + .unwrap_quotes(true) + .map(|s| f(s)) + .collect() + } + Self::Array(v) => { + v.iter() + .map(|s| f(s)) + .collect() + } + } + ) + } + pub fn fix_paths(self) -> Self { + match self { + Self::String(s) => Self::Array( + splitty::split_unquoted_whitespace(&s) + .unwrap_quotes(true) + .map(fix_token_path) + .collect() + ), + Self::Array(v) => Self::Array( + v.iter() + .map(fix_token_path) + .collect() + ), + } + } +} + +fn str_has_other_panel_group(s: &str) -> bool { + for group in GROUP.find_iter(s) { + if group.as_str().starts_with("{other-panel-") { + return true; + } + } + false +} + +fn fix_token_path<T: Into<String> + AsRef<str>>(token: T) -> String { + let path = Path::new(token.as_ref()); + if path.exists() { + if let Some(path) = path.to_str() { + return path.to_string(); + } + } + token.into() +} + +// this implementation builds a string usable for exect +impl fmt::Display for ExecPattern { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::String(s) => s.fmt(f), + Self::Array(v) => { + for (idx, s) in v.iter().enumerate() { + if idx > 0 { + write!(f, " ")?; + } + if s.contains(' ') { + write!(f, "\"{}\"", s)?; + } else { + write!(f, "{}", s)?; + } + } + Ok(()) + } + } + } +} diff --git a/src/verb/execution_builder.rs b/src/verb/execution_builder.rs index e93e929..5a9bd9f 100644 --- a/src/verb/execution_builder.rs +++ b/src/verb/execution_builder.rs @@ -1,12 +1,11 @@ use { - super::{InvocationParser, GROUP}, + super::*, crate::{ app::Selection, path, }, fnv::FnvHashMap, regex::Captures, - splitty::split_unquoted_whitespace, std::path::{Path, PathBuf}, }; @@ -111,57 +110,58 @@ impl<'b> ExecutionStringBuilder<'b> { /// build a shell compatible command, with escapings pub fn shell_exec_string( &self, - exec_pattern: &str, + exec_pattern: &ExecPattern, ) -> String { - let replaced = GROUP - .replace_all( - exec_pattern, - |ec: &Captures<'_>| self.get_capture_replacement(ec, true), - ); - split_unquoted_whitespace(&replaced) - .unwrap_quotes(false) - .map(|token| { - let path = Path::new(token); - if path.exists() { - if let Some(path) = path.to_str() { - return path.to_string(); - } - } - token.to_string() + exec_pattern + .apply(&|s| { + GROUP.replace_all( + s, + |ec: &Captures<'_>| self.get_capture_replacement(ec, true), + ).to_string() }) - .collect::<Vec<String>>() - .join(" ") + .fix_paths() + .to_string() } /// build a vec of tokens which can be passed to Command to /// launch an executable pub fn exec_token( &self, - exec_pattern: &str, + exec_pattern: &ExecPattern, ) -> Vec<String> { - split_unquoted_whitespace(exec_pattern) - .unwrap_quotes(true) - .map(|token| { - GROUP - .replace_all( - token, - |ec: &Captures<'_>| self.get_capture_replacement(ec, false), - ) - .to_string() + exec_pattern + .apply(&|s| { + GROUP.replace_all( + s, + |ec: &Captures<'_>| self.get_capture_replacement(ec, false), + ).to_string() }) - .collect() + .fix_paths() + .as_array() } } #[cfg(test)] mod execution_builder_test { + // allows writing vo!["a", "b"] to build a vec of strings + macro_rules! vo { + ($($item:literal),* $(,)?) => {{ + let mut vec = Vec::new(); + $( + vec.push($item.to_owned()); + )* + vec + }} + } + + use { super::*, crate::app::SelectionType, }; fn check_build_execution_from_sel( - exec_pattern: &str, + exec_patterns: Vec<ExecPattern>, path: &str, replacements: Vec<(&str, &str)>, chk_exec_token: Vec<&str>, @@ -179,26 +179,34 @@ mod execution_builder_test { map.insert(k.to_owned(), v.to_owned()); } builder.invocation_values = Some(map); - let exec_token = builder.exec_token(exec_pattern); - assert_eq!(exec_token, chk_exec_token); + for exec_pattern in exec_patterns { + let exec_token = builder.exec_token(&exec_pattern); + assert_eq!(exec_token, chk_exec_token); + } } #[test] fn test_build_execution() { check_build_execution_from_sel( - "vi {file}", + vec![ExecPattern::from_string("vi {file}")], "/home/dys/dev", vec![], vec!["vi", "/home/dys/dev"], ); check_build_execution_from_sel( - "/bin/e.exe -a {arg} -e {file}", + vec![ + ExecPattern::from_string("/bin/e.exe -a {arg} -e {file}"), + ExecPattern::from_array(vo!["/bin/e.exe","-a", "{arg}", "-e", "{file}"]), + ], "expérimental & 试验性", vec![("arg", "deux mots")], vec!["/bin/e.exe", "-a", "deux mots", "-e", "expérimental & 试验性"], ); check_build_execution_from_sel( - "xterm -e \"kak {file}\"", // see https://github.com/Canop/broot/issues/316 + vec![ + ExecPattern::from_string("xterm -e \"kak {file}\""), + ExecPattern::from_array(vo!["xterm", "-e", "kak {file}"]), + ], "/path/to/file", vec![], vec!["xterm", "-e", "kak /path/to/file"], diff --git a/src/verb/external_execution.rs b/src/verb/external_execution.rs index 8328a8b..cd5a26a 100644 --- a/src/verb/external_execution.rs +++ b/src/verb/external_execution.rs @@ -29,7 +29,7 @@ pub struct ExternalExecution { /// * {other-panel-file} /// * {other-panel-directory} /// * {other-panel-parent} - pub exec_pattern: String, + pub exec_pattern: ExecPattern, /// how the external process must be launched pub exec_mode: ExternalExecutionMode, @@ -41,7 +41,7 @@ pub struct ExternalExecution { impl ExternalExecution { pub fn new( - exec_pattern: String, + exec_pattern: ExecPattern, exec_mode: ExternalExecutionMode, ) -> Self { Self { diff --git a/src/verb/mod.rs b/src/verb/mod.rs index ac7586c..1cbed7d 100644 --- a/src/verb/mod.rs +++ b/src/verb/mod.rs @@ -1,4 +1,5 @@ mod builtin; +mod exec_pattern; mod execution_builder; mod external_execution; mod external_execution_mode; @@ -14,6 +15,7 @@ mod verb_invocation; mod verb_store; pub use { + exec_pattern::*, execution_builder::ExecutionStringBuilder, external_execution::ExternalExecution, external_execution_mode::ExternalExecutionMode, diff --git a/src/verb/verb.rs b/src/verb/verb.rs index ae17c13..0d03baa 100644 --- a/src/verb/verb.rs +++ b/src/verb/verb.rs @@ -65,14 +65,11 @@ impl Verb { if let Some(ref invocation_parser) = invocation_parser { names.push(invocation_parser.name().to_string()); } - let mut need_another_panel = false; - if let VerbExecution::External(ref external) = execution { - for group in GROUP.find_iter(&external.exec_pattern) { - if group.as_str().starts_with("{other-panel-") { - need_another_panel = true; - } - } - } + let need_another_panel = if let VerbExecution::External(ref external) = execution { + external.exec_pattern.has_other_panel_group() + } else { + false + }; Ok(Self { names, keys: Vec::new(), @@ -179,7 +176,9 @@ impl Verb { ) }; if let VerbExecution::Sequence(seq_ex) = &self.execution { - let exec_desc = builder().shell_exec_string(&seq_ex.sequence.raw); + let exec_desc = builder().shell_exec_string( + &ExecPattern::from_string(&seq_ex.sequence.raw) + ); format!("Hit *enter* to **{}**: `{}`", name, &exec_desc) } else if let VerbExecution::External(external_exec) = &self.execution { let exec_desc = builder().shell_exec_string(&external_exec.exec_pattern); |