Skip to main content

mk_lib/schema/
shell.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use std::process::Command as ProcessCommand;
4
5#[derive(Debug, Default, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
6/// Shell command with optional flags.
7pub struct ShellArgs {
8  /// The shell command to run
9  pub command: String,
10
11  /// The flags to pass to the shell command
12  pub args: Option<Vec<String>>,
13}
14
15#[derive(Debug, Deserialize, Clone, PartialEq, Eq, JsonSchema)]
16#[serde(untagged)]
17/// The shell to use. Either a string name (e.g. "bash") or an object with `command` and optional `args`.
18pub enum Shell {
19  String(String),
20  Shell(Box<ShellArgs>),
21}
22
23impl Default for Shell {
24  fn default() -> Self {
25    Shell::String("sh".to_string())
26  }
27}
28
29impl Shell {
30  pub fn new() -> anyhow::Result<Self> {
31    Ok(Shell::default())
32  }
33
34  pub fn new_with_flags(command: &str, args: Vec<String>) -> anyhow::Result<Self> {
35    let shell_def = ShellArgs {
36      command: command.to_string(),
37      args: Some(args),
38    };
39    Ok(Shell::Shell(Box::new(shell_def)))
40  }
41
42  pub fn from_shell(shell: &Shell) -> Self {
43    match shell {
44      Shell::String(command) => Shell::String(command.to_string()),
45      Shell::Shell(args) => Shell::Shell(args.clone()),
46    }
47  }
48
49  pub fn cmd(&self) -> String {
50    match self {
51      Shell::String(command) => ShellArgs {
52        command: command.to_string(),
53        args: None,
54      }
55      .cmd(),
56      Shell::Shell(args) => args.cmd(),
57    }
58  }
59
60  pub fn args(&self) -> Vec<String> {
61    match self {
62      Shell::String(command) => ShellArgs {
63        command: command.to_string(),
64        args: None,
65      }
66      .shell_args(),
67      Shell::Shell(args) => args.shell_args(),
68    }
69  }
70
71  pub fn proc(&self) -> ProcessCommand {
72    let shell = self.cmd();
73    let args = self.args();
74
75    let mut cmd = ProcessCommand::new(&shell);
76    for arg in args {
77      cmd.arg(arg);
78    }
79
80    cmd
81  }
82}
83
84impl From<Shell> for ProcessCommand {
85  fn from(shell: Shell) -> Self {
86    shell.proc()
87  }
88}
89
90impl ShellArgs {
91  pub fn cmd(&self) -> String {
92    self.command.clone()
93  }
94
95  pub fn shell_args(&self) -> Vec<String> {
96    let command = self.command.clone();
97    let args = self.args.clone().unwrap_or_default();
98    let posix_shell = ["sh", "bash", "zsh", "fish"];
99
100    // If the shell is not a POSIX shell, we don't need to add the `-c` flag
101    // to the command. We can just return the arguments as is.
102    if !posix_shell.contains(&command.as_str()) {
103      return args;
104    }
105
106    // If the shell is a POSIX shell, we need to add the `-c` flag
107    // to the command. If it's already present, we don't need to add it.
108    if args.iter().any(|arg| arg == "-c") {
109      return args;
110    }
111
112    // If the `-c` flag is not present, we need to add it
113    let mut args = args;
114    args.push("-c".to_string());
115    args
116  }
117}