From 15e47aa13fe2993fe9199597e2dd6cfb7fe415db Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 4 Oct 2024 01:52:59 +0200 Subject: [PATCH 1/4] fix(commands): make CommandInput work with file redirects Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository/command_input.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 6a258d4d..e00f8c0d 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -224,3 +224,24 @@ impl OnFailure { fn split(s: &str) -> RusticResult> { Ok(shell_words::split(s).map_err(|err| RusticErrorKind::Command(err.into()))?) } + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case("echo hello", "echo", &["hello"])] + #[case("echo hello >> /tmp/hello", "echo", &["hello", ">>", "/tmp/hello"])] + fn test_command_input_passes( + #[case] input: &str, + #[case] command: &str, + #[case] args: &[&str], + ) { + let cmd = CommandInput::from_str(input).unwrap(); + assert_eq!(cmd.command(), command); + assert_eq!(cmd.args(), args); + assert_eq!(cmd.on_failure(), OnFailure::default()); + assert_eq!(cmd.to_string(), input); + } +} From adcb1eead09f10e6d2db80dbd479c171d8c0a4cb Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:16:45 +0200 Subject: [PATCH 2/4] join on iter, not with shell-split Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository/command_input.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index e00f8c0d..4066d1b9 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -4,6 +4,7 @@ use std::{ str::FromStr, }; +use itertools::Itertools; use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize, Serializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; @@ -145,7 +146,7 @@ impl FromStr for _CommandInput { impl Display for _CommandInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = shell_words::join(self.iter()); + let s = self.iter().join(" "); f.write_str(&s) } } From 8e3b41268ccc51ccb963db58a172b11d2a8922fc Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 4 Oct 2024 02:51:17 +0200 Subject: [PATCH 3/4] use winsplit, more cases on win, failing Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/Cargo.toml | 1 + crates/core/src/repository/command_input.rs | 33 ++++++++++++++------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 07e6b8f7..9d5b3859 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -108,6 +108,7 @@ itertools = "0.13.0" quick_cache = "0.6.9" shell-words = "1.1.0" strum = { version = "0.26.3", features = ["derive"] } +winsplit = "0.1" zstd = "0.13.2" [target.'cfg(not(windows))'.dependencies] diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 4066d1b9..5e98f142 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -9,10 +9,10 @@ use log::{debug, error, trace, warn}; use serde::{Deserialize, Serialize, Serializer}; use serde_with::{serde_as, DisplayFromStr, PickFirst}; -use crate::{ - error::{RepositoryErrorKind, RusticErrorKind}, - RusticError, RusticResult, -}; +use crate::{error::RepositoryErrorKind, RusticError, RusticResult}; + +#[cfg(not(windows))] +use crate::error::RusticErrorKind; /// A command to be called which can be given as CLI option as well as in config files /// `CommandInput` implements Serialize/Deserialize as well as FromStr. @@ -221,11 +221,21 @@ impl OnFailure { } /// helper to split arguments -// TODO: Maybe use special parser (winsplit?) for windows? +#[cfg(not(windows))] fn split(s: &str) -> RusticResult> { Ok(shell_words::split(s).map_err(|err| RusticErrorKind::Command(err.into()))?) } +/// helper to split arguments +/// +// We keep the return type as `RusticResult>` to keep the internal api +// consistent with the other platforms. +#[allow(clippy::unnecessary_wraps)] +#[cfg(windows)] +fn split(s: &str) -> RusticResult> { + Ok(winsplit::split(s)) +} + #[cfg(test)] mod tests { use super::*; @@ -234,15 +244,18 @@ mod tests { #[rstest] #[case("echo hello", "echo", &["hello"])] #[case("echo hello >> /tmp/hello", "echo", &["hello", ">>", "/tmp/hello"])] - fn test_command_input_passes( + #[case("test -f /tmp/hello --key 'some value' arg1 arg2", "test", &["-f", "/tmp/hello", "--key", "some value", "arg1", "arg2"])] + #[cfg(windows)] + #[case("C:\\ProgramFiles\\Example\\example.exe --key 'some value' arg1 arg2", "C:\\ProgramFiles\\Example\\example.exe", &["--key", "some value", "arg1", "arg2"])] + fn test_command_input_parsing_passes( #[case] input: &str, #[case] command: &str, #[case] args: &[&str], ) { let cmd = CommandInput::from_str(input).unwrap(); - assert_eq!(cmd.command(), command); - assert_eq!(cmd.args(), args); - assert_eq!(cmd.on_failure(), OnFailure::default()); - assert_eq!(cmd.to_string(), input); + assert_eq!(command, cmd.command()); + assert_eq!(args, cmd.args()); + assert_eq!(OnFailure::default(), cmd.on_failure()); + assert_eq!(input, cmd.to_string()); } } From cff8aa73ca2ac7faf27484fb42800339e7170f90 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:07:49 +0200 Subject: [PATCH 4/4] more test cases Signed-off-by: simonsan <14062932+simonsan@users.noreply.github.com> --- crates/core/src/repository/command_input.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/core/src/repository/command_input.rs b/crates/core/src/repository/command_input.rs index 5e98f142..053a35a7 100644 --- a/crates/core/src/repository/command_input.rs +++ b/crates/core/src/repository/command_input.rs @@ -146,7 +146,7 @@ impl FromStr for _CommandInput { impl Display for _CommandInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = self.iter().join(" "); + let s = shell_words::join(self.iter()); f.write_str(&s) } } @@ -243,6 +243,7 @@ mod tests { #[rstest] #[case("echo hello", "echo", &["hello"])] + #[case("sh -c 'echo bla >> out'", "sh", &["-c", "echo", "bla", ">>", "out"])] #[case("echo hello >> /tmp/hello", "echo", &["hello", ">>", "/tmp/hello"])] #[case("test -f /tmp/hello --key 'some value' arg1 arg2", "test", &["-f", "/tmp/hello", "--key", "some value", "arg1", "arg2"])] #[cfg(windows)]