use std::{
ffi::OsStr,
io,
path::Path,
process::{Child, ChildStdin, Command},
};
use super::ShellOptions;
use crate::{
traits::{ConfigureCommand, Echoing, SpawnShell, SpawnedShell},
Captured, ExitStatus,
};
#[derive(Debug, Clone, Copy)]
enum StdShellType {
Sh,
Bash,
PowerShell,
}
#[derive(Debug)]
pub struct StdShell {
shell_type: StdShellType,
command: Command,
}
impl ConfigureCommand for StdShell {
fn current_dir(&mut self, dir: &Path) {
self.command.current_dir(dir);
}
fn env(&mut self, name: &str, value: &OsStr) {
self.command.env(name, value);
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", ret))]
fn check_sh_exit_code(response: &Captured) -> Option<ExitStatus> {
let response = response.to_plaintext().ok()?;
response.trim().parse().ok().map(ExitStatus)
}
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", ret))]
fn check_ps_exit_code(response: &Captured) -> Option<ExitStatus> {
let response = response.to_plaintext().ok()?;
match response.trim() {
"True" => Some(ExitStatus(0)),
"False" => Some(ExitStatus(1)),
_ => None,
}
}
impl ShellOptions<StdShell> {
pub fn sh() -> Self {
let this = Self::new(StdShell {
shell_type: StdShellType::Sh,
command: Command::new("sh"),
});
this.with_status_check("echo $?", check_sh_exit_code)
}
pub fn bash() -> Self {
let this = Self::new(StdShell {
shell_type: StdShellType::Bash,
command: Command::new("bash"),
});
this.with_status_check("echo $?", check_sh_exit_code)
}
pub fn pwsh() -> Self {
let mut command = Command::new("pwsh");
command.arg("-NoLogo").arg("-NoExit");
let command = StdShell {
shell_type: StdShellType::PowerShell,
command,
};
Self::new(command)
.with_init_command("function prompt { }")
.with_status_check("echo $?", check_ps_exit_code)
}
#[must_use]
pub fn with_alias(self, name: &str, path_to_bin: &str) -> Self {
let alias_command = match self.command.shell_type {
StdShellType::Sh => {
format!("alias {name}=\"'{path_to_bin}'\"")
}
StdShellType::Bash => format!("{name}() {{ '{path_to_bin}' \"$@\"; }}"),
StdShellType::PowerShell => format!("function {name} {{ & '{path_to_bin}' @Args }}"),
};
self.with_init_command(alias_command)
}
}
impl SpawnShell for StdShell {
type ShellProcess = Echoing<Child>;
type Reader = os_pipe::PipeReader;
type Writer = ChildStdin;
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", err))]
fn spawn_shell(&mut self) -> io::Result<SpawnedShell<Self>> {
let SpawnedShell {
shell,
reader,
writer,
} = self.command.spawn_shell()?;
let is_echoing = matches!(self.shell_type, StdShellType::PowerShell);
Ok(SpawnedShell {
shell: Echoing::new(shell, is_echoing),
reader,
writer,
})
}
}