use std::fmt::Display;
use std::io::Error;
use std::path::Path;
use std::str::FromStr;
use clap::ValueEnum;
use clap::builder::PossibleValue;
use crate::Generator;
use crate::shells;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
impl Display for Shell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value()
.expect("no values are skipped")
.get_name()
.fmt(f)
}
}
impl FromStr for Shell {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().unwrap().matches(s, false) {
return Ok(*variant);
}
}
Err(format!("invalid variant: {s}"))
}
}
impl ValueEnum for Shell {
fn value_variants<'a>() -> &'a [Self] {
&[
Shell::Bash,
Shell::Elvish,
Shell::Fish,
Shell::PowerShell,
Shell::Zsh,
]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(match self {
Shell::Bash => PossibleValue::new("bash"),
Shell::Elvish => PossibleValue::new("elvish"),
Shell::Fish => PossibleValue::new("fish"),
Shell::PowerShell => PossibleValue::new("powershell"),
Shell::Zsh => PossibleValue::new("zsh"),
})
}
}
impl Generator for Shell {
fn file_name(&self, name: &str) -> String {
match self {
Shell::Bash => shells::Bash.file_name(name),
Shell::Elvish => shells::Elvish.file_name(name),
Shell::Fish => shells::Fish.file_name(name),
Shell::PowerShell => shells::PowerShell.file_name(name),
Shell::Zsh => shells::Zsh.file_name(name),
}
}
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
self.try_generate(cmd, buf)
.expect("failed to write completion file");
}
fn try_generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) -> Result<(), Error> {
match self {
Shell::Bash => shells::Bash.try_generate(cmd, buf),
Shell::Elvish => shells::Elvish.try_generate(cmd, buf),
Shell::Fish => shells::Fish.try_generate(cmd, buf),
Shell::PowerShell => shells::PowerShell.try_generate(cmd, buf),
Shell::Zsh => shells::Zsh.try_generate(cmd, buf),
}
}
}
impl Shell {
pub fn from_shell_path<P: AsRef<Path>>(path: P) -> Option<Shell> {
parse_shell_from_path(path.as_ref())
}
pub fn from_env() -> Option<Shell> {
if let Some(env_shell) = std::env::var_os("SHELL") {
Shell::from_shell_path(env_shell)
} else if cfg!(windows) {
Some(Shell::PowerShell)
} else {
None
}
}
}
fn parse_shell_from_path(path: &Path) -> Option<Shell> {
let name = path.file_stem()?.to_str()?;
match name {
"bash" => Some(Shell::Bash),
"zsh" => Some(Shell::Zsh),
"fish" => Some(Shell::Fish),
"elvish" => Some(Shell::Elvish),
"powershell" | "powershell_ise" => Some(Shell::PowerShell),
_ => None,
}
}