1use std::env;
10use std::fmt;
11use std::str::FromStr;
12
13use clap::ValueEnum;
14
15use crate::common::env_vars::FRM_SHELL;
16use crate::errors::Error;
17use crate::paths::Paths;
18use crate::version::Version;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
21pub enum Shell {
22 Bash,
23 Zsh,
24 Nu,
25}
26
27const ENV_BASH_TEMPLATE: &str = include_str!("../shells/env/bash.template");
28const ENV_ZSH_TEMPLATE: &str = include_str!("../shells/env/zsh.template");
29const ENV_NU_TEMPLATE: &str = include_str!("../shells/env/nu.template");
30
31const INIT_BASH_TEMPLATE: &str = include_str!("../shells/init/bash.template");
32const INIT_ZSH_TEMPLATE: &str = include_str!("../shells/init/zsh.template");
33const INIT_NU_TEMPLATE: &str = include_str!("../shells/init/nu.template");
34
35impl Shell {
36 pub fn detect() -> Option<Self> {
37 if let Ok(shell) = env::var(FRM_SHELL) {
38 return shell.parse().ok();
39 }
40
41 if env::var("NU_VERSION").is_ok() {
42 return Some(Shell::Nu);
43 }
44
45 env::var("SHELL").ok().and_then(|s| {
46 if s.contains("bash") {
47 Some(Shell::Bash)
48 } else if s.contains("zsh") {
49 Some(Shell::Zsh)
50 } else if s.contains("nu") {
51 Some(Shell::Nu)
52 } else {
53 None
54 }
55 })
56 }
57
58 pub fn env_script(&self, paths: &Paths, version: &Version) -> String {
59 let sbin_path = paths.version_sbin_dir(version).display().to_string();
60 let base_dir = paths.base_dir().display().to_string();
61 let version_dir = paths.version_dir(version).display().to_string();
62
63 let template = match self {
64 Shell::Bash => ENV_BASH_TEMPLATE,
65 Shell::Zsh => ENV_ZSH_TEMPLATE,
66 Shell::Nu => ENV_NU_TEMPLATE,
67 };
68
69 template
70 .replace("{{sbin_path}}", &sbin_path)
71 .replace("{{base_dir}}", &base_dir)
72 .replace("{{version_dir}}", &version_dir)
73 }
74
75 pub fn init_script(&self, paths: &Paths) -> String {
76 let base_dir = paths.base_dir().display().to_string();
77
78 let template = match self {
79 Shell::Bash => INIT_BASH_TEMPLATE,
80 Shell::Zsh => INIT_ZSH_TEMPLATE,
81 Shell::Nu => INIT_NU_TEMPLATE,
82 };
83
84 template.replace("{{base_dir}}", &base_dir)
85 }
86}
87
88impl fmt::Display for Shell {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 match self {
91 Shell::Bash => write!(f, "bash"),
92 Shell::Zsh => write!(f, "zsh"),
93 Shell::Nu => write!(f, "nu"),
94 }
95 }
96}
97
98impl FromStr for Shell {
99 type Err = Error;
100
101 fn from_str(s: &str) -> Result<Self, Self::Err> {
102 match s.to_lowercase().as_str() {
103 "bash" => Ok(Shell::Bash),
104 "zsh" => Ok(Shell::Zsh),
105 "nu" | "nushell" => Ok(Shell::Nu),
106 _ => Err(Error::Config(format!("unsupported shell: {}", s))),
107 }
108 }
109}