Quote arguments when executing in a shell (#118)
* Quote arguments when executing in a shell Fixes #107 * Parse quotes in `tmux_arguments` This makes it possible to encode spaces in arguments. Maybe the config value should be an array instead? * Print error causes Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -1606,6 +1606,12 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||
|
||||
[[package]]
|
||||
name = "shellexpand"
|
||||
version = "2.1.2"
|
||||
@@ -1943,6 +1949,7 @@ dependencies = [
|
||||
"self_update",
|
||||
"semver",
|
||||
"serde",
|
||||
"shell-words",
|
||||
"shellexpand",
|
||||
"strum 0.24.1",
|
||||
"sys-info",
|
||||
|
||||
@@ -44,6 +44,7 @@ futures = "0.3"
|
||||
regex = "1.5"
|
||||
sys-info = "0.9"
|
||||
semver = "1.0"
|
||||
shell-words = "1.1.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
notify-rust = "4.5"
|
||||
|
||||
@@ -4,7 +4,6 @@ use std::fs::write;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::{env, fs};
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{ArgEnum, Parser};
|
||||
use directories::BaseDirs;
|
||||
@@ -626,8 +625,16 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Extra Tmux arguments
|
||||
pub fn tmux_arguments(&self) -> &Option<String> {
|
||||
&self.config_file.tmux_arguments
|
||||
pub fn tmux_arguments(&self) -> anyhow::Result<Vec<String>> {
|
||||
let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default();
|
||||
shell_words::split(args)
|
||||
// The only time the parse failed is in case of a missing close quote.
|
||||
// The error message looks like this:
|
||||
// Error: Failed to parse `tmux_arguments`: `'foo`
|
||||
//
|
||||
// Caused by:
|
||||
// missing closing quote
|
||||
.with_context(|| format!("Failed to parse `tmux_arguments`: `{args}`"))
|
||||
}
|
||||
|
||||
/// Prompt for a key before exiting
|
||||
|
||||
@@ -194,11 +194,12 @@ impl DryCommand {
|
||||
print!(
|
||||
"Dry running: {} {}",
|
||||
self.program.to_string_lossy(),
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
shell_words::join(
|
||||
self.args
|
||||
.iter()
|
||||
.map(|a| String::from(a.to_string_lossy()))
|
||||
.collect::<Vec<String>>()
|
||||
)
|
||||
);
|
||||
match &self.directory {
|
||||
Some(dir) => println!(" in {}", dir.to_string_lossy()),
|
||||
|
||||
@@ -79,7 +79,7 @@ fn run() -> Result<()> {
|
||||
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
tmux::run_in_tmux(config.tmux_arguments());
|
||||
tmux::run_in_tmux(config.tmux_arguments()?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +524,10 @@ fn main() {
|
||||
.is_some());
|
||||
|
||||
if !skip_print {
|
||||
println!("Error: {}", error);
|
||||
// The `Debug` implementation of `anyhow::Result` prints a multi-line
|
||||
// error message that includes all the 'causes' added with
|
||||
// `.with_context(...)` calls.
|
||||
println!("Error: {:?}", error);
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ impl Powershell {
|
||||
println!("Updating modules...");
|
||||
ctx.run_type()
|
||||
.execute(powershell)
|
||||
// This probably doesn't need `shell_words::join`.
|
||||
.args(["-NoProfile", "-Command", &cmd.join(" ")])
|
||||
.check_run()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
prepare_async_ssh_command(&mut args);
|
||||
crate::tmux::run_command(ctx, &args.join(" "))?;
|
||||
crate::tmux::run_command(ctx, &shell_words::join(args))?;
|
||||
Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into())
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,10 @@ struct Tmux {
|
||||
}
|
||||
|
||||
impl Tmux {
|
||||
fn new(args: &Option<String>) -> Self {
|
||||
fn new(args: Vec<String>) -> Self {
|
||||
Self {
|
||||
tmux: which("tmux").expect("Could not find tmux"),
|
||||
args: args
|
||||
.as_ref()
|
||||
.map(|args| args.split_whitespace().map(String::from).collect()),
|
||||
args: if args.is_empty() { None } else { Some(args) },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +73,7 @@ impl Tmux {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_in_tmux(args: &Option<String>) -> ! {
|
||||
pub fn run_in_tmux(args: Vec<String>) -> ! {
|
||||
let command = {
|
||||
let mut command = vec![
|
||||
String::from("env"),
|
||||
@@ -83,10 +81,10 @@ pub fn run_in_tmux(args: &Option<String>) -> ! {
|
||||
String::from("TOPGRADE_INSIDE_TMUX=1"),
|
||||
];
|
||||
command.extend(env::args());
|
||||
command.join(" ")
|
||||
shell_words::join(command)
|
||||
};
|
||||
|
||||
let tmux = Tmux::new(args);
|
||||
let tmux = Tmux::new(args.clone());
|
||||
|
||||
if !tmux.has_session("topgrade").expect("Error detecting a tmux session") {
|
||||
tmux.new_session("topgrade").expect("Error creating a tmux session");
|
||||
@@ -108,7 +106,7 @@ pub fn run_in_tmux(args: &Option<String>) -> ! {
|
||||
}
|
||||
|
||||
pub fn run_command(ctx: &ExecutionContext, command: &str) -> Result<()> {
|
||||
Tmux::new(ctx.config().tmux_arguments())
|
||||
Tmux::new(ctx.config().tmux_arguments()?)
|
||||
.build()
|
||||
.args(["new-window", "-a", "-t", "topgrade:1", command])
|
||||
.env_remove("TMUX")
|
||||
|
||||
Reference in New Issue
Block a user