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