diff --git a/Cargo.lock b/Cargo.lock index e9c891ac..1146684b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -325,15 +325,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "cmdexpand" -version = "0.1.0" -source = "git+https://github.com/ndtoan96/cmdexpand.git?tag=v0.1.0#be905e223c7ed5f52655fb4096c842f2f587f0d7" -dependencies = [ - "nom", - "thiserror", -] - [[package]] name = "color_quant" version = "1.1.0" @@ -383,7 +374,6 @@ dependencies = [ "anyhow", "async-channel", "clipboard-win", - "cmdexpand", "config", "crossterm", "futures", @@ -1847,9 +1837,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "indexmap 2.0.2", "serde", @@ -1869,9 +1859,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap 2.0.2", "serde", diff --git a/core/src/external/shell.rs b/core/src/external/shell.rs index d6756570..ff030056 100644 --- a/core/src/external/shell.rs +++ b/core/src/external/shell.rs @@ -4,9 +4,9 @@ use anyhow::Result; use tokio::process::{Child, Command}; pub struct ShellOpt { - pub cmd: OsString, - pub args: Vec, - pub piped: bool, + pub cmd: OsString, + pub args: Vec, + pub piped: bool, pub orphan: bool, } @@ -43,129 +43,116 @@ pub fn shell(opt: ShellOpt) -> Result { .spawn()?, ); - #[cfg(target_os = "windows")] + #[cfg(windows)] { let args: Vec = opt.args.iter().map(|s| s.to_string_lossy().to_string()).collect(); - let expanded_args = cmdparse::parse_cmd_to_args(opt.cmd.to_string_lossy().as_ref(), &args); + let args_: Vec<&str> = args.iter().map(|s| s.as_ref()).collect(); + let expanded = parser::parse(opt.cmd.to_string_lossy().as_ref(), &args_); Ok( Command::new("cmd") .arg("/C") - .args(&expanded_args) - .stdin(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) - .stdout(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) - .stderr(if opt.piped { Stdio::piped() } else { Stdio::inherit() }) - .kill_on_drop(true) + .args(&expanded) + .stdin(opt.stdio()) + .stdout(opt.stdio()) + .stderr(opt.stdio()) + .kill_on_drop(!opt.orphan) .spawn()?, ) } } -#[cfg(target_os = "windows")] -mod cmdparse { - pub fn parse_cmd_to_args(cmd: &str, args: &[T]) -> Vec - where - T: AsRef, - { - let mut iter = cmd.chars().peekable(); - let mut expanded_args = Vec::new(); +#[cfg(windows)] +mod parser { + use std::{iter::Peekable, str::Chars}; - while let Some(c) = iter.peek() { + pub(super) fn parse(cmd: &str, args: &[&str]) -> Vec { + let mut it = cmd.chars().peekable(); + let mut expanded = Vec::new(); + + while let Some(c) = it.next() { if c.is_whitespace() { - while iter.peek().is_some_and(|_c| _c.is_whitespace()) { - iter.next(); - } - } else if *c == '\'' { - iter.next(); - let mut text = String::new(); - loop { - if iter.peek().is_none() { - break; - } - if iter.peek().is_some_and(|_c| *_c == '\'') { - iter.next(); - break; - } - get_next_char(&mut iter, &mut text, args); - } - expanded_args.push(text); - } else if *c == '"' { - iter.next(); - let mut text = String::new(); - loop { - if iter.peek().is_none() { - break; - } - if iter.peek().is_some_and(|_c| *_c == '"') { - iter.next(); - break; - } - get_next_char(&mut iter, &mut text, args); - } - expanded_args.push(text); - } else { - if *c == '%' { - let mut tmp_iter = iter.clone(); - tmp_iter.next(); - if tmp_iter.peek().is_some_and(|_c| *_c == '*') { - iter.next(); - iter.next(); - for arg in args { - expanded_args.push(arg.as_ref().to_string()) - } - continue; - } - } + continue; + } + let mut s = String::new(); - let mut text = String::new(); - loop { - if iter.peek().is_none() || iter.peek().is_some_and(|_c| _c.is_whitespace()) { + if c == '\'' { + s.clear(); + while let Some(c) = it.next() { + if c == '\'' { break; } - get_next_char(&mut iter, &mut text, args); + next_string(&mut it, args, &mut s, c); } - expanded_args.push(text); + expanded.push(s); + } else if c == '"' { + s.clear(); + while let Some(c) = it.next() { + if c == '"' { + break; + } + next_string(&mut it, args, &mut s, c); + } + expanded.push(s); + } else if c == '%' && it.peek().is_some_and(|&c| c == '*') { + it.next(); + for arg in args { + expanded.push(arg.to_string()); + } + } else { + s.clear(); + next_string(&mut it, args, &mut s, c); + + while let Some(c) = it.next() { + if c.is_whitespace() { + break; + } + next_string(&mut it, args, &mut s, c); + } + expanded.push(s); } } - expanded_args + expanded } - fn get_next_char( - iter: &mut std::iter::Peekable>, - text: &mut String, - args: &[T], - ) where - T: AsRef, - { - let ch = iter.next().unwrap(); - if ch == '\\' { - match iter.next() { - Some('n') => text.push('\n'), - Some('r') => text.push('\r'), - Some('t') => text.push('\t'), - Some(x) => text.push(x), - None => (), + fn next_string(it: &mut Peekable>, args: &[&str], s: &mut String, c: char) { + if c == '\\' { + match it.next() { + Some('\\') => s.push('\\'), // \\ ==> \ + Some('\'') => s.push('\''), // \' ==> ' + Some('"') => s.push('"'), // \" ==> " + Some('%') => s.push('%'), // \% ==> % + Some('n') => s.push('\n'), // \n ==> '\n' + Some('t') => s.push('\t'), // \t ==> '\t' + Some('r') => s.push('\r'), // \r ==> '\r' + Some(c) => { + s.push('\\'); + s.push(c); + } + None => s.push('\\'), } - } else if ch == '%' { - if iter.peek().is_some_and(|_c| *_c == '*') { - iter.next(); - text.push_str(&args.iter().map(|value| value.as_ref()).collect::>().join(" ")); - } else { - let mut num = String::new(); - while iter.peek().is_some_and(|_c| _c.is_numeric()) { - num.push(iter.next().unwrap()); + } else if c == '%' { + match it.peek() { + Some('*') => { + s.push_str(&args.join(" ")); + it.next(); } - if num.is_empty() { - text.push('%'); - } else { - let i: usize = num.parse().unwrap(); - if i > 0 { - text.push_str(args.get(i - 1).map(|value| value.as_ref()).unwrap_or_default()); + Some(n) if n.is_ascii_digit() => { + let mut pos = n.to_string(); + it.next(); + while let Some(&n) = it.peek() { + if n.is_ascii_digit() { + pos.push(it.next().unwrap()); + } else { + break; + } } + s.push_str(args.get(pos.parse::().unwrap() - 1).unwrap_or(&"")); } + _ => s.push('%'), } } else { - text.push(ch); + s.push(c); } } @@ -175,49 +162,49 @@ mod cmdparse { #[test] fn test_no_quote() { - let args = parse_cmd_to_args("echo abc xyz %1 %2", &["111", "222"]); + let args = parse("echo abc xyz %1 %2", &["111", "222"]); assert_eq!(args, vec!["echo", "abc", "xyz", "111", "222"]); - let args = parse_cmd_to_args(" echo abc xyz %1 %2 ", &["111", "222"]); + let args = parse(" echo abc xyz %1 %2 ", &["111", "222"]); assert_eq!(args, vec!["echo", "abc", "xyz", "111", "222"]); } #[test] fn test_single_quote() { - let args = parse_cmd_to_args("echo 'abc xyz' '%1' %2", &["111", "222"]); + let args = parse("echo 'abc xyz' '%1' %2", &["111", "222"]); assert_eq!(args, vec!["echo", "abc xyz", "111", "222"]); - let args = parse_cmd_to_args("echo 'abc \"\"xyz' '%1' %2", &["111", "222"]); + let args = parse("echo 'abc \"\"xyz' '%1' %2", &["111", "222"]); assert_eq!(args, vec!["echo", "abc \"\"xyz", "111", "222"]); } #[test] fn test_double_quote() { - let args = parse_cmd_to_args("echo \"abc ' 'xyz\" \"%1\" %2 %3", &["111", "222"]); + let args = parse("echo \"abc ' 'xyz\" \"%1\" %2 %3", &["111", "222"]); assert_eq!(args, vec!["echo", "abc ' 'xyz", "111", "222", ""]); } #[test] fn test_escaped() { - let args = parse_cmd_to_args("echo \"a\tbc ' 'x\nyz\" \"\\%1\" %2 %3", &["111", "22 2"]); + let args = parse("echo \"a\tbc ' 'x\nyz\" \"\\%1\" %2 %3", &["111", "22 2"]); assert_eq!(args, vec!["echo", "a\tbc ' 'x\nyz", "%1", "22 2", ""]); } #[test] fn test_percent_star() { - let args = parse_cmd_to_args("echo %* xyz", &["111", "222"]); + let args = parse("echo %* xyz", &["111", "222"]); assert_eq!(args, vec!["echo", "111", "222", "xyz"]); - let args = parse_cmd_to_args("echo '%*' xyz", &["111", "222"]); + let args = parse("echo '%*' xyz", &["111", "222"]); assert_eq!(args, vec!["echo", "111 222", "xyz"]); - let args = parse_cmd_to_args("echo -C%* xyz", &["111", "222"]); + let args = parse("echo -C%* xyz", &["111", "222"]); assert_eq!(args, vec!["echo", "-C111 222", "xyz"]); } #[test] fn test_env_var() { - let args = parse_cmd_to_args(" %EDITOR% %* xyz", &["111", "222"]); + let args = parse(" %EDITOR% %* xyz", &["111", "222"]); assert_eq!(args, vec!["%EDITOR%", "111", "222", "xyz"]); } }