summaryrefslogtreecommitdiffstats
path: root/src/verb
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2021-01-07 17:31:38 +0100
committerCanop <cano.petrole@gmail.com>2021-01-07 17:31:38 +0100
commitfe901eecc52c672289949e7adfa03d8d0b7e804d (patch)
tree38954d92729e6b43e7756b317db5ef3c6bf305b2 /src/verb
parent284fea889eaeb0ec120cf36b55f075bd7d58f19f (diff)
allow specifying execution pattern as array
Fix #319
Diffstat (limited to 'src/verb')
-rw-r--r--src/verb/builtin.rs7
-rw-r--r--src/verb/exec_pattern.rs137
-rw-r--r--src/verb/execution_builder.rs82
-rw-r--r--src/verb/external_execution.rs4
-rw-r--r--src/verb/mod.rs2
-rw-r--r--src/verb/verb.rs17
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);