Skip to main content

cuenv_core/
shell.rs

1//! Shell type definitions and utilities for cuenv
2//!
3//! This module provides shell detection and formatting utilities
4//! used across cuenv for shell integration features.
5
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// Supported shell types for environment integration
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
11pub enum Shell {
12    /// Bash shell
13    #[default]
14    Bash,
15    /// Z shell
16    Zsh,
17    /// Fish shell
18    Fish,
19    /// PowerShell/pwsh
20    #[serde(rename = "powershell")]
21    PowerShell,
22}
23
24impl Shell {
25    /// Detect shell from environment or argument
26    pub fn detect(target: Option<&str>) -> Self {
27        if let Some(t) = target {
28            return Self::parse(t);
29        }
30
31        // Try to detect from environment
32        if let Ok(shell) = std::env::var("SHELL") {
33            if shell.contains("fish") {
34                return Shell::Fish;
35            } else if shell.contains("zsh") {
36                return Shell::Zsh;
37            } else if shell.contains("bash") {
38                return Shell::Bash;
39            }
40        }
41
42        // Default to bash
43        Shell::Bash
44    }
45
46    /// Parse shell from string
47    pub fn parse(s: &str) -> Self {
48        match s.to_lowercase().as_str() {
49            "zsh" => Shell::Zsh,
50            "fish" => Shell::Fish,
51            "powershell" | "pwsh" => Shell::PowerShell,
52            _ => Shell::Bash,
53        }
54    }
55
56    /// Get the name of the shell
57    pub fn name(&self) -> &'static str {
58        match self {
59            Shell::Bash => "bash",
60            Shell::Zsh => "zsh",
61            Shell::Fish => "fish",
62            Shell::PowerShell => "powershell",
63        }
64    }
65
66    /// Check if this shell is supported for integration
67    pub fn is_supported(&self) -> bool {
68        matches!(self, Shell::Bash | Shell::Zsh | Shell::Fish)
69    }
70}
71
72impl fmt::Display for Shell {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "{}", self.name())
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_shell_default() {
84        let shell = Shell::default();
85        assert_eq!(shell, Shell::Bash);
86    }
87
88    #[test]
89    fn test_shell_parse() {
90        assert_eq!(Shell::parse("bash"), Shell::Bash);
91        assert_eq!(Shell::parse("zsh"), Shell::Zsh);
92        assert_eq!(Shell::parse("fish"), Shell::Fish);
93        assert_eq!(Shell::parse("powershell"), Shell::PowerShell);
94        assert_eq!(Shell::parse("pwsh"), Shell::PowerShell);
95        assert_eq!(Shell::parse("unknown"), Shell::Bash);
96    }
97
98    #[test]
99    fn test_shell_parse_case_insensitive() {
100        assert_eq!(Shell::parse("BASH"), Shell::Bash);
101        assert_eq!(Shell::parse("ZSH"), Shell::Zsh);
102        assert_eq!(Shell::parse("Fish"), Shell::Fish);
103        assert_eq!(Shell::parse("PowerShell"), Shell::PowerShell);
104        assert_eq!(Shell::parse("PWSH"), Shell::PowerShell);
105    }
106
107    #[test]
108    fn test_shell_detect_with_target() {
109        assert_eq!(Shell::detect(Some("bash")), Shell::Bash);
110        assert_eq!(Shell::detect(Some("zsh")), Shell::Zsh);
111        assert_eq!(Shell::detect(Some("fish")), Shell::Fish);
112        assert_eq!(Shell::detect(Some("powershell")), Shell::PowerShell);
113    }
114
115    #[test]
116    fn test_shell_detect_from_env_fish() {
117        temp_env::with_var("SHELL", Some("/usr/bin/fish"), || {
118            let shell = Shell::detect(None);
119            assert_eq!(shell, Shell::Fish);
120        });
121    }
122
123    #[test]
124    fn test_shell_detect_from_env_zsh() {
125        temp_env::with_var("SHELL", Some("/bin/zsh"), || {
126            let shell = Shell::detect(None);
127            assert_eq!(shell, Shell::Zsh);
128        });
129    }
130
131    #[test]
132    fn test_shell_detect_from_env_bash() {
133        temp_env::with_var("SHELL", Some("/bin/bash"), || {
134            let shell = Shell::detect(None);
135            assert_eq!(shell, Shell::Bash);
136        });
137    }
138
139    #[test]
140    fn test_shell_detect_default_fallback() {
141        temp_env::with_var_unset("SHELL", || {
142            let shell = Shell::detect(None);
143            assert_eq!(shell, Shell::Bash);
144        });
145    }
146
147    #[test]
148    fn test_shell_name() {
149        assert_eq!(Shell::Bash.name(), "bash");
150        assert_eq!(Shell::Zsh.name(), "zsh");
151        assert_eq!(Shell::Fish.name(), "fish");
152        assert_eq!(Shell::PowerShell.name(), "powershell");
153    }
154
155    #[test]
156    fn test_shell_support() {
157        assert!(Shell::Bash.is_supported());
158        assert!(Shell::Zsh.is_supported());
159        assert!(Shell::Fish.is_supported());
160        assert!(!Shell::PowerShell.is_supported());
161    }
162
163    #[test]
164    fn test_shell_display() {
165        assert_eq!(format!("{}", Shell::Bash), "bash");
166        assert_eq!(format!("{}", Shell::Zsh), "zsh");
167        assert_eq!(format!("{}", Shell::Fish), "fish");
168        assert_eq!(format!("{}", Shell::PowerShell), "powershell");
169    }
170
171    #[test]
172    fn test_shell_serde_roundtrip() {
173        let shells = vec![Shell::Bash, Shell::Zsh, Shell::Fish, Shell::PowerShell];
174        for shell in shells {
175            let json = serde_json::to_string(&shell).unwrap();
176            let parsed: Shell = serde_json::from_str(&json).unwrap();
177            assert_eq!(shell, parsed);
178        }
179    }
180
181    #[test]
182    fn test_shell_serde_powershell_rename() {
183        let shell = Shell::PowerShell;
184        let json = serde_json::to_string(&shell).unwrap();
185        assert_eq!(json, "\"powershell\"");
186    }
187
188    #[test]
189    fn test_shell_clone() {
190        let shell = Shell::Fish;
191        let cloned = shell;
192        assert_eq!(shell, cloned);
193    }
194
195    #[test]
196    fn test_shell_copy() {
197        let shell = Shell::Zsh;
198        let copied = shell;
199        assert_eq!(shell, copied);
200    }
201
202    #[test]
203    fn test_shell_debug() {
204        let debug = format!("{:?}", Shell::Bash);
205        assert!(debug.contains("Bash"));
206    }
207}