roder_api/
command_shell.rs1use std::path::Path;
2
3pub fn default_command_shell() -> String {
4 default_command_shell_for(
5 std::env::var("SHELL").ok().as_deref(),
6 cfg!(target_os = "macos"),
7 cfg!(target_os = "windows"),
8 )
9}
10
11pub fn normalize_command_shell(shell: &str) -> Option<String> {
12 let shell = shell.trim();
13 (!shell.is_empty()).then(|| shell.to_string())
14}
15
16pub fn command_shell_options(active: &str) -> Vec<String> {
17 let mut shells = Vec::new();
18 push_unique_shell(&mut shells, active);
19 push_unique_shell(&mut shells, "zsh");
20 push_unique_shell(&mut shells, "bash");
21 shells
22}
23
24fn push_unique_shell(shells: &mut Vec<String>, shell: &str) {
25 if let Some(shell) = normalize_command_shell(shell)
26 && !shells.iter().any(|known| known == &shell)
27 {
28 shells.push(shell);
29 }
30}
31
32fn default_command_shell_for(
33 login_shell: Option<&str>,
34 is_macos: bool,
35 is_windows: bool,
36) -> String {
37 if is_windows {
38 return "powershell".to_string();
39 }
40
41 if let Some(login_shell) = login_shell.and_then(normalize_command_shell)
42 && shell_name(&login_shell) == Some("zsh")
43 {
44 return login_shell;
45 }
46
47 if is_macos {
48 "/bin/zsh".to_string()
49 } else {
50 "bash".to_string()
51 }
52}
53
54fn shell_name(shell: &str) -> Option<&str> {
55 Path::new(shell)
56 .file_name()
57 .and_then(|name| name.to_str())
58 .or_else(|| (!shell.trim().is_empty()).then_some(shell))
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn default_prefers_zsh_login_shell() {
67 assert_eq!(
68 default_command_shell_for(Some("/usr/local/bin/zsh"), false, false),
69 "/usr/local/bin/zsh"
70 );
71 }
72
73 #[test]
74 fn default_uses_macos_zsh_when_login_shell_is_not_zsh() {
75 assert_eq!(
76 default_command_shell_for(Some("/usr/local/bin/fish"), true, false),
77 "/bin/zsh"
78 );
79 }
80
81 #[test]
82 fn default_uses_bash_off_macos_when_login_shell_is_not_zsh() {
83 assert_eq!(
84 default_command_shell_for(Some("/usr/local/bin/fish"), false, false),
85 "bash"
86 );
87 }
88
89 #[test]
90 fn default_uses_powershell_on_windows() {
91 assert_eq!(
92 default_command_shell_for(Some("/usr/local/bin/zsh"), false, true),
93 "powershell"
94 );
95 }
96
97 #[test]
98 fn command_shell_options_include_active_and_common_shells_once() {
99 assert_eq!(
100 command_shell_options("/opt/homebrew/bin/fish"),
101 vec![
102 "/opt/homebrew/bin/fish".to_string(),
103 "zsh".to_string(),
104 "bash".to_string(),
105 ]
106 );
107 assert_eq!(
108 command_shell_options("zsh"),
109 vec!["zsh".to_string(), "bash".to_string()]
110 );
111 }
112}