1use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "lowercase")]
12#[allow(clippy::enum_variant_names)] pub enum Shell {
14 #[default]
16 Sh,
17 Bash,
19 Zsh,
21 Fish,
23 Cmd,
25 #[serde(alias = "pwsh")]
27 PowerShell,
28}
29
30impl Shell {
31 #[cfg(unix)]
33 pub fn default_for_platform() -> Self {
34 Shell::Sh
35 }
36
37 #[cfg(windows)]
39 pub fn default_for_platform() -> Self {
40 Shell::Cmd
41 }
42
43 pub fn program(&self) -> &'static str {
45 match self {
46 Shell::Sh => "sh",
47 Shell::Bash => "bash",
48 Shell::Zsh => "zsh",
49 Shell::Fish => "fish",
50 Shell::Cmd => "cmd",
51 Shell::PowerShell => {
52 #[cfg(windows)]
54 {
55 "powershell"
56 }
57 #[cfg(not(windows))]
58 {
59 "pwsh"
60 }
61 }
62 }
63 }
64
65 pub fn exec_args(&self, command: &str) -> Vec<String> {
67 match self {
68 Shell::Sh | Shell::Bash | Shell::Zsh => {
69 vec!["-c".to_string(), command.to_string()]
70 }
71 Shell::Fish => {
72 vec!["-c".to_string(), command.to_string()]
73 }
74 Shell::Cmd => {
75 vec!["/C".to_string(), command.to_string()]
76 }
77 Shell::PowerShell => {
78 vec!["-Command".to_string(), command.to_string()]
79 }
80 }
81 }
82
83 pub fn command(&self, cmd: &str) -> tokio::process::Command {
85 let mut command = tokio::process::Command::new(self.program());
86 command.args(self.exec_args(cmd));
87 command
88 }
89
90 #[allow(dead_code)] pub fn std_command(&self, cmd: &str) -> std::process::Command {
93 let mut command = std::process::Command::new(self.program());
94 command.args(self.exec_args(cmd));
95 command
96 }
97}
98
99impl std::fmt::Display for Shell {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 match self {
102 Shell::Sh => write!(f, "sh"),
103 Shell::Bash => write!(f, "bash"),
104 Shell::Zsh => write!(f, "zsh"),
105 Shell::Fish => write!(f, "fish"),
106 Shell::Cmd => write!(f, "cmd"),
107 Shell::PowerShell => write!(f, "powershell"),
108 }
109 }
110}
111
112impl std::str::FromStr for Shell {
113 type Err = String;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 match s.to_lowercase().as_str() {
117 "sh" => Ok(Shell::Sh),
118 "bash" => Ok(Shell::Bash),
119 "zsh" => Ok(Shell::Zsh),
120 "fish" => Ok(Shell::Fish),
121 "cmd" => Ok(Shell::Cmd),
122 "powershell" | "pwsh" => Ok(Shell::PowerShell),
123 _ => Err(format!("unknown shell: {s}")),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn test_shell_program() {
134 assert_eq!(Shell::Sh.program(), "sh");
135 assert_eq!(Shell::Bash.program(), "bash");
136 assert_eq!(Shell::Zsh.program(), "zsh");
137 assert_eq!(Shell::Fish.program(), "fish");
138 assert_eq!(Shell::Cmd.program(), "cmd");
139 }
140
141 #[test]
142 fn test_shell_exec_args() {
143 assert_eq!(Shell::Sh.exec_args("echo hello"), vec!["-c", "echo hello"]);
144 assert_eq!(
145 Shell::Bash.exec_args("echo hello"),
146 vec!["-c", "echo hello"]
147 );
148 assert_eq!(Shell::Cmd.exec_args("echo hello"), vec!["/C", "echo hello"]);
149 assert_eq!(
150 Shell::PowerShell.exec_args("echo hello"),
151 vec!["-Command", "echo hello"]
152 );
153 }
154
155 #[test]
156 fn test_shell_from_str() {
157 assert_eq!("sh".parse::<Shell>().unwrap(), Shell::Sh);
158 assert_eq!("bash".parse::<Shell>().unwrap(), Shell::Bash);
159 assert_eq!("BASH".parse::<Shell>().unwrap(), Shell::Bash);
160 assert_eq!("powershell".parse::<Shell>().unwrap(), Shell::PowerShell);
161 assert_eq!("pwsh".parse::<Shell>().unwrap(), Shell::PowerShell);
162 assert!("unknown".parse::<Shell>().is_err());
163 }
164
165 #[test]
166 fn test_shell_display() {
167 assert_eq!(Shell::Sh.to_string(), "sh");
168 assert_eq!(Shell::Bash.to_string(), "bash");
169 assert_eq!(Shell::Cmd.to_string(), "cmd");
170 }
171
172 #[test]
173 fn test_default_shell() {
174 let default = Shell::default();
176 #[cfg(unix)]
177 assert_eq!(default, Shell::Sh);
178 #[cfg(windows)]
179 assert_eq!(default, Shell::Cmd);
180 }
181}