easy_cmd/
traits.rs

1use std::process::Command;
2
3/// Trait to convert various types into a [`Command`].
4pub trait AsCommand {
5    /// Converts the implementing type into a [`Command`].
6    ///
7    /// # Returns
8    ///
9    /// A [`Command`] object that can be used to run a system command.
10    ///
11    /// # Example
12    ///
13    /// ```
14    /// use easy_cmd::AsCommand;
15    /// use std::process::Command;
16    ///
17    /// // Define the same command using four different types.
18    /// let command_str: &str = "echo \"Hello, World!\"";
19    /// let command_string: String = String::from("echo \"Hello, World!\"");
20    /// let command_vec: Vec<&str> = vec!["echo", "Hello, World!"];
21    /// let command_slice: &[&str] = &["echo", "Hello, World!"];
22    ///
23    /// // Convert them to Command objects. Note that the four Command objects will be identical.
24    /// let command_from_str = command_str.as_command();
25    /// let command_from_string = command_string.as_command();
26    /// let command_from_vec = command_vec.as_command();
27    /// let command_from_slice = command_slice.as_command();
28    /// ```
29    fn as_command(&self) -> Command;
30}
31
32impl AsCommand for [&str] {
33    fn as_command(&self) -> Command {
34        let mut command = Command::new(self[0]);
35        command.args(&self[1..]);
36        command
37    }
38}
39
40impl AsCommand for [String] {
41    fn as_command(&self) -> Command {
42        let parts: Vec<&str> = self.iter().map(|s| s.as_str()).collect();
43        parts.as_command()
44    }
45}
46
47impl AsCommand for &[&str] {
48    fn as_command(&self) -> Command {
49        self[..].as_command()
50    }
51}
52
53impl AsCommand for &[String] {
54    fn as_command(&self) -> Command {
55        self[..].as_command()
56    }
57}
58
59impl<const N: usize> AsCommand for [&str; N] {
60    fn as_command(&self) -> Command {
61        self.as_slice().as_command()
62    }
63}
64
65impl<const N: usize> AsCommand for [String; N] {
66    fn as_command(&self) -> Command {
67        self.as_slice().as_command()
68    }
69}
70
71impl AsCommand for Vec<&str> {
72    fn as_command(&self) -> Command {
73        self.as_slice().as_command()
74    }
75}
76
77impl AsCommand for Vec<String> {
78    fn as_command(&self) -> Command {
79        self.as_slice().as_command()
80    }
81}
82
83impl AsCommand for str {
84    fn as_command(&self) -> Command {
85        let parts = shell_words::split(self).expect("Failed to parse command string");
86        parts.as_command()
87    }
88}
89
90impl AsCommand for &str {
91    fn as_command(&self) -> Command {
92        let parts = shell_words::split(self).expect("Failed to parse command string");
93        parts.as_command()
94    }
95}
96
97impl AsCommand for String {
98    fn as_command(&self) -> Command {
99        self.as_str().as_command()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    /// Helper function to check if the command matches the expected program and arguments.
108    ///
109    /// # Arguments
110    ///
111    /// * `cmd` - The command to check.
112    /// * `expected_program` - The expected program name.
113    /// * `expected_args` - The expected arguments for the command.
114    fn check_command(cmd: &Command, expected_program: &str, expected_args: &[&str]) {
115        assert_eq!(cmd.get_program().to_str().unwrap(), expected_program);
116        let args: Vec<&str> = cmd.get_args().map(|a| a.to_str().unwrap()).collect();
117        assert_eq!(args, expected_args);
118    }
119
120    #[test]
121    fn test_as_command_from_str_slice_reference() {
122        let cmd: &[&str] = &["echo", "Hello, world!"];
123        let cmd = cmd.as_command();
124        check_command(&cmd, "echo", &["Hello, world!"]);
125    }
126
127    #[test]
128    fn test_as_command_from_string_slice_reference() {
129        let cmd: &[&str] = &["echo", "Hello, world!"];
130        let cmd = cmd.as_command();
131        check_command(&cmd, "echo", &["Hello, world!"]);
132    }
133
134    #[test]
135    fn test_as_command_from_str_array() {
136        let cmd: [&str; 2] = ["echo", "Hello, world!"];
137        let cmd = cmd.as_command();
138        check_command(&cmd, "echo", &["Hello, world!"]);
139    }
140
141    #[test]
142    fn test_as_command_from_string_array() {
143        let cmd: [String; 2] = [String::from("echo"), String::from("Hello, world!")];
144        let cmd = cmd.as_command();
145        check_command(&cmd, "echo", &["Hello, world!"]);
146    }
147
148    #[test]
149    fn test_as_command_from_str_vector() {
150        let cmd: Vec<&str> = vec!["echo", "Hello, world!"];
151        let cmd = cmd.as_command();
152        check_command(&cmd, "echo", &["Hello, world!"]);
153    }
154
155    #[test]
156    fn test_as_command_from_string_vector() {
157        let cmd: Vec<String> = vec![String::from("echo"), String::from("Hello, world!")];
158        let cmd = cmd.as_command();
159        check_command(&cmd, "echo", &["Hello, world!"]);
160    }
161
162    #[test]
163    fn test_as_command_from_str() {
164        let cmd: &str = "echo \"Hello, world!\"";
165        let cmd = cmd.as_command();
166        check_command(&cmd, "echo", &["Hello, world!"]);
167    }
168
169    #[test]
170    fn test_as_command_from_string() {
171        let cmd: String = "echo \"Hello, world!\"".into();
172        let cmd = cmd.as_command();
173        check_command(&cmd, "echo", &["Hello, world!"]);
174    }
175}