1use crate::ShellCommandError;
2
3#[derive(Clone, Debug)]
4pub struct ShellCommand {
5 pub command: String,
6 pub args: Vec<String>,
7}
8
9impl ShellCommand {
10 #[tracing::instrument(skip(self), level = "trace")]
11 pub fn run(&self) -> Result<(), ShellCommandError> {
12 tracing::trace!(command = ?self.command_and_args(), "running shell command");
13 std::process::Command::new(&self.command)
14 .args(&self.args)
15 .spawn()?
16 .wait()?;
17
18 Ok(())
19 }
20
21 pub fn command_and_args(&self) -> Vec<String> {
22 let mut command = vec![self.command.clone()];
23 command.extend(self.args.clone());
24 command
25 }
26}
27
28impl From<ShellCommand> for Vec<ShellCommand> {
29 fn from(process: ShellCommand) -> Self {
30 vec![process]
31 }
32}
33
34impl TryFrom<String> for ShellCommand {
35 type Error = ShellCommandError;
36
37 fn try_from(command: String) -> Result<Self, Self::Error> {
38 let command: &str = command.as_ref();
39 command.try_into()
40 }
41}
42
43impl TryFrom<&str> for ShellCommand {
44 type Error = ShellCommandError;
45
46 fn try_from(command: &str) -> Result<Self, Self::Error> {
47 let command_chunks: Vec<String> = command.split_whitespace().map(String::from).collect();
48
49 if let Some((command, args)) = command_chunks.split_first() {
50 Ok(Self {
51 command: command.clone(),
52 args: args.to_vec(),
53 })
54 } else {
55 Err(ShellCommandError::MalformedError(command.to_string()))
56 }
57 }
58}
59
60#[tracing::instrument(skip(shell_command), level = "trace")]
61pub fn shell<T: Into<String>>(shell_command: T) -> crate::Result<ShellCommand> {
62 let shell_command = shell_command.into();
63
64 tracing::trace!(shell_command = ?shell_command, "converting to shell command");
65
66 Ok(shell_command.try_into()?)
67}