use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
#[allow(clippy::enum_variant_names)] pub enum Shell {
#[default]
Sh,
Bash,
Zsh,
Fish,
Cmd,
#[serde(alias = "pwsh")]
PowerShell,
}
impl Shell {
#[cfg(unix)]
pub fn default_for_platform() -> Self {
Shell::Sh
}
#[cfg(windows)]
pub fn default_for_platform() -> Self {
Shell::Cmd
}
pub fn program(&self) -> &'static str {
match self {
Shell::Sh => "sh",
Shell::Bash => "bash",
Shell::Zsh => "zsh",
Shell::Fish => "fish",
Shell::Cmd => "cmd",
Shell::PowerShell => {
#[cfg(windows)]
{
"powershell"
}
#[cfg(not(windows))]
{
"pwsh"
}
}
}
}
pub fn exec_args(&self, command: &str) -> Vec<String> {
match self {
Shell::Sh | Shell::Bash | Shell::Zsh => {
vec!["-c".to_string(), command.to_string()]
}
Shell::Fish => {
vec!["-c".to_string(), command.to_string()]
}
Shell::Cmd => {
vec!["/C".to_string(), command.to_string()]
}
Shell::PowerShell => {
vec!["-Command".to_string(), command.to_string()]
}
}
}
pub fn command(&self, cmd: &str) -> tokio::process::Command {
let mut command = tokio::process::Command::new(self.program());
command.args(self.exec_args(cmd));
command
}
#[allow(dead_code)] pub fn std_command(&self, cmd: &str) -> std::process::Command {
let mut command = std::process::Command::new(self.program());
command.args(self.exec_args(cmd));
command
}
}
impl std::fmt::Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Shell::Sh => write!(f, "sh"),
Shell::Bash => write!(f, "bash"),
Shell::Zsh => write!(f, "zsh"),
Shell::Fish => write!(f, "fish"),
Shell::Cmd => write!(f, "cmd"),
Shell::PowerShell => write!(f, "powershell"),
}
}
}
impl std::str::FromStr for Shell {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"sh" => Ok(Shell::Sh),
"bash" => Ok(Shell::Bash),
"zsh" => Ok(Shell::Zsh),
"fish" => Ok(Shell::Fish),
"cmd" => Ok(Shell::Cmd),
"powershell" | "pwsh" => Ok(Shell::PowerShell),
_ => Err(format!("unknown shell: {s}")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shell_program() {
assert_eq!(Shell::Sh.program(), "sh");
assert_eq!(Shell::Bash.program(), "bash");
assert_eq!(Shell::Zsh.program(), "zsh");
assert_eq!(Shell::Fish.program(), "fish");
assert_eq!(Shell::Cmd.program(), "cmd");
}
#[test]
fn test_shell_exec_args() {
assert_eq!(Shell::Sh.exec_args("echo hello"), vec!["-c", "echo hello"]);
assert_eq!(
Shell::Bash.exec_args("echo hello"),
vec!["-c", "echo hello"]
);
assert_eq!(Shell::Cmd.exec_args("echo hello"), vec!["/C", "echo hello"]);
assert_eq!(
Shell::PowerShell.exec_args("echo hello"),
vec!["-Command", "echo hello"]
);
}
#[test]
fn test_shell_from_str() {
assert_eq!("sh".parse::<Shell>().unwrap(), Shell::Sh);
assert_eq!("bash".parse::<Shell>().unwrap(), Shell::Bash);
assert_eq!("BASH".parse::<Shell>().unwrap(), Shell::Bash);
assert_eq!("powershell".parse::<Shell>().unwrap(), Shell::PowerShell);
assert_eq!("pwsh".parse::<Shell>().unwrap(), Shell::PowerShell);
assert!("unknown".parse::<Shell>().is_err());
}
#[test]
fn test_shell_display() {
assert_eq!(Shell::Sh.to_string(), "sh");
assert_eq!(Shell::Bash.to_string(), "bash");
assert_eq!(Shell::Cmd.to_string(), "cmd");
}
#[test]
fn test_default_shell() {
let default = Shell::default();
#[cfg(unix)]
assert_eq!(default, Shell::Sh);
#[cfg(windows)]
assert_eq!(default, Shell::Cmd);
}
}