use std::env;
use std::error::Error;
use std::fmt;
use std::io::{self, Write};
use std::path::Path;
use std::str::FromStr;
use clap::Command;
use clap::builder::PossibleValue;
use clap_complete::Shell as ClapShell;
use clap_complete::generate;
use clap_complete_nushell::Nushell;
const ALL_SHELLS: &[CompletionShell] = &[
CompletionShell::Bash,
CompletionShell::Zsh,
CompletionShell::Fish,
CompletionShell::Elvish,
CompletionShell::Nushell,
CompletionShell::PowerShell,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CompletionShell {
#[default]
Bash,
Zsh,
Fish,
Elvish,
Nushell,
PowerShell,
}
impl clap::ValueEnum for CompletionShell {
fn value_variants<'a>() -> &'a [Self] {
ALL_SHELLS
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(match self {
Self::Bash => PossibleValue::new("bash"),
Self::Zsh => PossibleValue::new("zsh"),
Self::Fish => PossibleValue::new("fish"),
Self::Elvish => PossibleValue::new("elvish"),
Self::Nushell => PossibleValue::new("nushell").alias("nu"),
Self::PowerShell => PossibleValue::new("powershell").alias("pwsh"),
})
}
}
impl CompletionShell {
#[must_use]
pub fn detect() -> Self {
if env::var("NU_VERSION").is_ok() {
return Self::Nushell;
}
env::var("SHELL")
.ok()
.and_then(|s| {
let shell_name = Path::new(&s)
.file_name()
.and_then(|n| n.to_str())
.unwrap_or(&s);
match shell_name {
"bash" => Some(Self::Bash),
"zsh" => Some(Self::Zsh),
"fish" => Some(Self::Fish),
"elvish" => Some(Self::Elvish),
"nu" | "nushell" => Some(Self::Nushell),
"pwsh" | "powershell" => Some(Self::PowerShell),
_ => None,
}
})
.unwrap_or(Self::Bash)
}
#[must_use]
pub fn all() -> &'static [Self] {
ALL_SHELLS
}
}
impl fmt::Display for CompletionShell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
Self::Bash => "bash",
Self::Zsh => "zsh",
Self::Fish => "fish",
Self::Elvish => "elvish",
Self::Nushell => "nushell",
Self::PowerShell => "powershell",
};
f.write_str(name)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseShellError {
input: String,
}
impl fmt::Display for ParseShellError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown shell: {}", self.input)
}
}
impl Error for ParseShellError {}
impl FromStr for CompletionShell {
type Err = ParseShellError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"bash" => Ok(Self::Bash),
"zsh" => Ok(Self::Zsh),
"fish" => Ok(Self::Fish),
"elvish" => Ok(Self::Elvish),
"nu" | "nushell" => Ok(Self::Nushell),
"pwsh" | "powershell" => Ok(Self::PowerShell),
_ => Err(ParseShellError { input: s.into() }),
}
}
}
pub fn generate_completions<W: Write>(
shell: CompletionShell,
cmd: &mut Command,
bin_name: &str,
out: &mut W,
) {
match shell {
CompletionShell::Bash => generate(ClapShell::Bash, cmd, bin_name, out),
CompletionShell::Zsh => generate(ClapShell::Zsh, cmd, bin_name, out),
CompletionShell::Fish => generate(ClapShell::Fish, cmd, bin_name, out),
CompletionShell::Elvish => generate(ClapShell::Elvish, cmd, bin_name, out),
CompletionShell::Nushell => generate(Nushell, cmd, bin_name, out),
CompletionShell::PowerShell => generate(ClapShell::PowerShell, cmd, bin_name, out),
}
}
pub fn generate_completions_to_stdout(shell: CompletionShell, cmd: &mut Command, bin_name: &str) {
generate_completions(shell, cmd, bin_name, &mut io::stdout());
}