#![allow(unreachable_patterns)]
use clap::{ValueEnum, builder::PossibleValue};
use clap_complete::aot::Generator;
use clap_complete::aot::Shell as AotShell;
use clap_complete_nushell::Nushell as NushellShell;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
Nushell,
}
impl clap::ValueEnum for Shell {
fn value_variants<'a>() -> &'a [Self] {
&[
Self::Bash,
Self::Elvish,
Self::Fish,
Self::PowerShell,
Self::Zsh,
Self::Nushell,
]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(match self {
Self::Bash => PossibleValue::new("bash"),
Self::Elvish => PossibleValue::new("elvish"),
Self::Fish => PossibleValue::new("fish"),
Self::PowerShell => PossibleValue::new("powershell").alias("pwsh"),
Self::Zsh => PossibleValue::new("zsh"),
Self::Nushell => PossibleValue::new("nushell"),
})
}
}
impl std::fmt::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 std::str::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 TryFrom<AotShell> for Shell {
type Error = String;
fn try_from(shell: AotShell) -> Result<Self, Self::Error> {
match shell {
AotShell::Bash => Ok(Self::Bash),
AotShell::Elvish => Ok(Self::Elvish),
AotShell::Fish => Ok(Self::Fish),
AotShell::PowerShell => Ok(Self::PowerShell),
AotShell::Zsh => Ok(Self::Zsh),
_ => Err("Nushell is not supported in AOT mode".to_string()),
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
#[test]
fn test_from_path_bash() {
assert_eq!(Shell::from_path("/bin/bash"), Some(Shell::Bash));
assert_eq!(Shell::from_path("bash.exe"), Some(Shell::Bash));
}
#[test]
fn test_from_path_zsh() {
assert_eq!(Shell::from_path("/usr/bin/zsh"), Some(Shell::Zsh));
assert_eq!(Shell::from_path("zsh"), Some(Shell::Zsh));
}
#[test]
fn test_from_path_fish() {
assert_eq!(Shell::from_path("/usr/local/bin/fish"), Some(Shell::Fish));
assert_eq!(Shell::from_path("fish"), Some(Shell::Fish));
}
#[test]
fn test_from_path_powershell() {
assert_eq!(Shell::from_path("powershell.exe"), Some(Shell::PowerShell));
assert_eq!(Shell::from_path("/usr/bin/pwsh"), Some(Shell::PowerShell));
assert_eq!(
Shell::from_path("powershell_ise.exe"),
Some(Shell::PowerShell)
);
}
#[test]
fn test_from_path_elvish() {
assert_eq!(Shell::from_path("/usr/bin/elvish"), Some(Shell::Elvish));
assert_eq!(Shell::from_path("elvish"), Some(Shell::Elvish));
}
#[test]
fn test_from_path_nushell() {
assert_eq!(Shell::from_path("/usr/bin/nu"), Some(Shell::Nushell));
assert_eq!(Shell::from_path("nu.exe"), Some(Shell::Nushell));
assert_eq!(Shell::from_path("nushell"), Some(Shell::Nushell));
}
#[test]
fn test_from_path_unknown() {
assert_eq!(Shell::from_path("/bin/sh"), None);
assert_eq!(Shell::from_path("cmd.exe"), None);
assert_eq!(Shell::from_path("python"), None);
assert_eq!(Shell::from_path("unknown_shell"), None);
}
#[test]
fn test_from_path_empty_and_invalid() {
assert_eq!(Shell::from_path(""), None);
assert_eq!(Shell::from_path("/"), None);
assert_eq!(Shell::from_path("."), None);
assert_eq!(Shell::from_path(".."), None);
assert_eq!(Shell::from_path(".bash"), None);
}
#[test]
fn test_shell_from_str() {
assert_eq!(
<Shell as std::str::FromStr>::from_str("bash").unwrap(),
Shell::Bash
);
assert_eq!(
<Shell as std::str::FromStr>::from_str("zsh").unwrap(),
Shell::Zsh
);
assert_eq!(
<Shell as std::str::FromStr>::from_str("fish").unwrap(),
Shell::Fish
);
assert_eq!(
<Shell as std::str::FromStr>::from_str("powershell").unwrap(),
Shell::PowerShell
);
assert_eq!(
<Shell as std::str::FromStr>::from_str("elvish").unwrap(),
Shell::Elvish
);
assert_eq!(
<Shell as std::str::FromStr>::from_str("nushell").unwrap(),
Shell::Nushell
);
}
#[test]
fn test_shell_from_str_alias() {
assert_eq!(
<Shell as std::str::FromStr>::from_str("pwsh").unwrap(),
Shell::PowerShell
);
}
#[test]
fn test_shell_from_str_invalid() {
assert!(<Shell as std::str::FromStr>::from_str("cmd").is_err());
assert!(<Shell as std::str::FromStr>::from_str("").is_err());
assert!(<Shell as std::str::FromStr>::from_str("BASH").is_err()); }
#[test]
fn test_shell_display() {
assert_eq!(format!("{}", Shell::Bash), "bash");
assert_eq!(format!("{}", Shell::Zsh), "zsh");
assert_eq!(format!("{}", Shell::Fish), "fish");
assert_eq!(format!("{}", Shell::PowerShell), "powershell");
assert_eq!(format!("{}", Shell::Elvish), "elvish");
assert_eq!(format!("{}", Shell::Nushell), "nushell");
}
#[test]
fn test_shell_to_aotshell() {
let aot: AotShell = Shell::Bash.try_into().unwrap();
assert_eq!(aot, AotShell::Bash);
let aot: AotShell = Shell::Zsh.try_into().unwrap();
assert_eq!(aot, AotShell::Zsh);
let aot: AotShell = Shell::Fish.try_into().unwrap();
assert_eq!(aot, AotShell::Fish);
let aot: AotShell = Shell::PowerShell.try_into().unwrap();
assert_eq!(aot, AotShell::PowerShell);
let aot: AotShell = Shell::Elvish.try_into().unwrap();
assert_eq!(aot, AotShell::Elvish);
}
#[test]
fn test_nushell_to_aotshell_fails() {
let result: Result<AotShell, _> = Shell::Nushell.try_into();
assert!(result.is_err());
}
#[test]
fn test_aotshell_to_shell() {
let shell: Shell = AotShell::Bash.try_into().unwrap();
assert_eq!(shell, Shell::Bash);
let shell: Shell = AotShell::Zsh.try_into().unwrap();
assert_eq!(shell, Shell::Zsh);
let shell: Shell = AotShell::Fish.try_into().unwrap();
assert_eq!(shell, Shell::Fish);
let shell: Shell = AotShell::PowerShell.try_into().unwrap();
assert_eq!(shell, Shell::PowerShell);
let shell: Shell = AotShell::Elvish.try_into().unwrap();
assert_eq!(shell, Shell::Elvish);
}
#[test]
fn test_shell_to_nushell_shell() {
let _nu: NushellShell = Shell::Nushell.try_into().unwrap();
}
#[test]
fn test_non_nushell_to_nushell_shell_fails() {
let result: Result<NushellShell, _> = Shell::Bash.try_into();
assert!(result.is_err());
let result: Result<NushellShell, _> = Shell::Zsh.try_into();
assert!(result.is_err());
}
#[test]
fn test_nushell_shell_to_shell() {
let shell: Shell = NushellShell.try_into().unwrap();
assert_eq!(shell, Shell::Nushell);
}
#[test]
fn test_try_to_clap_complete_all_variants() {
assert!(Shell::Bash.try_to_clap_complete().is_ok());
assert!(Shell::Zsh.try_to_clap_complete().is_ok());
assert!(Shell::Fish.try_to_clap_complete().is_ok());
assert!(Shell::PowerShell.try_to_clap_complete().is_ok());
assert!(Shell::Elvish.try_to_clap_complete().is_ok());
assert!(Shell::Nushell.try_to_clap_complete().is_ok());
}
#[test]
fn test_value_variants_count() {
assert_eq!(Shell::value_variants().len(), 6);
}
#[test]
fn test_to_possible_value_all_some() {
for variant in Shell::value_variants() {
assert!(variant.to_possible_value().is_some());
}
}
#[test]
fn test_generator_file_name() {
let bash_name = <Shell as Generator>::file_name(&Shell::Bash, "submod");
assert!(!bash_name.is_empty());
let nu_name = <Shell as Generator>::file_name(&Shell::Nushell, "submod");
assert!(nu_name.contains("submod"));
}
#[test]
fn test_generator_try_generate_produces_output() {
use clap::Command;
for shell in Shell::value_variants() {
let mut cmd = Command::new("test-cmd").subcommand(Command::new("sub1"));
let mut buf = Vec::new();
clap_complete::generate(*shell, &mut cmd, "test-cmd", &mut buf);
assert!(!buf.is_empty(), "Empty output for {:?}", shell);
}
}
}
impl TryFrom<Shell> for AotShell {
type Error = String;
fn try_from(shell: Shell) -> Result<Self, Self::Error> {
match shell {
Shell::Bash => Ok(Self::Bash),
Shell::Elvish => Ok(Self::Elvish),
Shell::Fish => Ok(Self::Fish),
Shell::PowerShell => Ok(Self::PowerShell),
Shell::Zsh => Ok(Self::Zsh),
Shell::Nushell => Err("Nushell is not supported in AOT mode".to_string()),
}
}
}
impl TryFrom<Shell> for NushellShell {
type Error = String;
fn try_from(shell: Shell) -> Result<Self, Self::Error> {
if shell == Shell::Nushell {
Ok(Self)
} else {
Err("Only Nushell can be converted to NushellShell".to_string())
}
}
}
impl TryFrom<NushellShell> for Shell {
type Error = String;
fn try_from(shell: NushellShell) -> Result<Self, String> {
match shell {
NushellShell => Ok(Self::Nushell),
_ => Err("Only NushellShell can be converted to Shell::Nushell".to_string()),
}
}
}
impl Shell {
pub fn try_to_clap_complete(&self) -> Result<Box<dyn Generator>, String> {
match self {
Self::Bash => Ok(Box::new(AotShell::Bash)),
Self::Elvish => Ok(Box::new(AotShell::Elvish)),
Self::Fish => Ok(Box::new(AotShell::Fish)),
Self::PowerShell => Ok(Box::new(AotShell::PowerShell)),
Self::Zsh => Ok(Box::new(AotShell::Zsh)),
Self::Nushell => Ok(Box::new(NushellShell)),
}
}
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> Option<Self> {
Self::parse_shell_from_path(path.as_ref())
}
fn parse_shell_from_path(path: &std::path::Path) -> Option<Self> {
let name = path.file_stem()?.to_str()?;
match name {
"bash" => Some(Self::Bash),
"elvish" => Some(Self::Elvish),
"fish" => Some(Self::Fish),
"powershell" | "pwsh" | "powershell_ise" => Some(Self::PowerShell),
"zsh" => Some(Self::Zsh),
"nu" | "nushell" => Some(Self::Nushell),
_ => None,
}
}
#[must_use] pub fn from_env() -> Option<Self> {
if let Some(env_shell) = std::env::var_os("SHELL") {
Self::parse_shell_from_path(std::path::Path::new(&env_shell))
} else {
None
}
}
}
impl Generator for Shell {
fn file_name(&self, name: &str) -> String {
let shell_self = self.try_to_clap_complete();
shell_self.map_or_else(|_| format!("{name}.nu"), |s| s.file_name(name)) }
fn generate(&self, cmd: &clap::Command, buf: &mut dyn std::io::Write) {
let shell_self = self.try_to_clap_complete();
shell_self
.map(|s| {
s.try_generate(cmd, buf)
.unwrap_or_else(|e| panic!("failed to write completion file: {e}"));
})
.unwrap_or_else(|_| panic!("failed to write completion file"));
}
fn try_generate(
&self,
cmd: &clap::Command,
buf: &mut dyn std::io::Write,
) -> Result<(), std::io::Error> {
let shell_self = self.try_to_clap_complete();
match shell_self {
Ok(s) => s.try_generate(cmd, buf),
Err(e) => Err(std::io::Error::other(e)),
}
}
}