Skip to main content

torrust_tracker_deployer_lib/presentation/cli/views/
theme.rs

1//! Theme configuration for user output
2//!
3//! This module provides theme support for user-facing messages, allowing customization
4//! of visual symbols used throughout the output.
5
6/// Output theme controlling symbols and formatting
7///
8/// A theme defines the visual appearance of user-facing messages through
9/// configurable symbols. Themes enable consistent styling across all output
10/// and support different environments (terminals, CI/CD, accessibility needs).
11///
12/// # Predefined Themes
13///
14/// - **Emoji** (default): Unicode emoji symbols for interactive terminals
15/// - **Plain**: Text labels like `[INFO]`, `[OK]` for CI/CD environments
16/// - **ASCII**: Basic ASCII characters for limited terminal support
17///
18/// # Examples
19///
20/// ```rust
21/// use torrust_tracker_deployer_lib::presentation::cli::views::Theme;
22///
23/// // Use emoji theme (default)
24/// let theme = Theme::emoji();
25/// assert_eq!(theme.progress_symbol(), "⏳");
26///
27/// // Use plain text theme for CI/CD
28/// let theme = Theme::plain();
29/// assert_eq!(theme.success_symbol(), "[OK]");
30///
31/// // Use ASCII theme for limited terminals
32/// let theme = Theme::ascii();
33/// assert_eq!(theme.error_symbol(), "[x]");
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[allow(clippy::struct_field_names)]
37pub struct Theme {
38    progress_symbol: String,
39    success_symbol: String,
40    warning_symbol: String,
41    error_symbol: String,
42    detail_symbol: String,
43    debug_symbol: String,
44}
45
46impl Theme {
47    /// Create emoji theme with Unicode symbols (default)
48    ///
49    /// Best for interactive terminals with good Unicode support.
50    /// Uses emoji characters that are visually distinctive and widely supported.
51    ///
52    /// # Examples
53    ///
54    /// ```rust
55    /// use torrust_tracker_deployer_lib::presentation::cli::views::Theme;
56    ///
57    /// let theme = Theme::emoji();
58    /// assert_eq!(theme.progress_symbol(), "⏳");
59    /// assert_eq!(theme.success_symbol(), "✅");
60    /// assert_eq!(theme.warning_symbol(), "⚠️");
61    /// assert_eq!(theme.error_symbol(), "❌");
62    /// ```
63    #[must_use]
64    pub fn emoji() -> Self {
65        Self {
66            progress_symbol: "⏳".to_string(),
67            success_symbol: "✅".to_string(),
68            warning_symbol: "⚠️".to_string(),
69            error_symbol: "❌".to_string(),
70            detail_symbol: "📋".to_string(),
71            debug_symbol: "🔍".to_string(),
72        }
73    }
74
75    /// Create plain text theme for CI/CD environments
76    ///
77    /// Uses text labels like `[INFO]`, `[OK]`, `[WARN]`, `[ERROR]` that work
78    /// in any environment without Unicode support. Ideal for CI/CD pipelines
79    /// and log aggregation systems.
80    ///
81    /// # Examples
82    ///
83    /// ```rust
84    /// use torrust_tracker_deployer_lib::presentation::cli::views::Theme;
85    ///
86    /// let theme = Theme::plain();
87    /// assert_eq!(theme.progress_symbol(), "[INFO]");
88    /// assert_eq!(theme.success_symbol(), "[OK]");
89    /// assert_eq!(theme.warning_symbol(), "[WARN]");
90    /// assert_eq!(theme.error_symbol(), "[ERROR]");
91    /// ```
92    #[must_use]
93    pub fn plain() -> Self {
94        Self {
95            progress_symbol: "[INFO]".to_string(),
96            success_symbol: "[OK]".to_string(),
97            warning_symbol: "[WARN]".to_string(),
98            error_symbol: "[ERROR]".to_string(),
99            detail_symbol: "[DETAIL]".to_string(),
100            debug_symbol: "[DEBUG]".to_string(),
101        }
102    }
103
104    /// Create ASCII-only theme using basic characters
105    ///
106    /// Uses simple ASCII characters that work on any terminal.
107    /// Good for environments with limited character set support or
108    /// when maximum compatibility is required.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use torrust_tracker_deployer_lib::presentation::cli::views::Theme;
114    ///
115    /// let theme = Theme::ascii();
116    /// assert_eq!(theme.progress_symbol(), "=>");
117    /// assert_eq!(theme.success_symbol(), "[+]");
118    /// assert_eq!(theme.warning_symbol(), "[!]");
119    /// assert_eq!(theme.error_symbol(), "[x]");
120    /// ```
121    #[must_use]
122    pub fn ascii() -> Self {
123        Self {
124            progress_symbol: "=>".to_string(),
125            success_symbol: "[+]".to_string(),
126            warning_symbol: "[!]".to_string(),
127            error_symbol: "[x]".to_string(),
128            detail_symbol: "[~]".to_string(),
129            debug_symbol: "[?]".to_string(),
130        }
131    }
132
133    /// Get the progress symbol for this theme
134    #[must_use]
135    pub fn progress_symbol(&self) -> &str {
136        &self.progress_symbol
137    }
138
139    /// Get the success symbol for this theme
140    #[must_use]
141    pub fn success_symbol(&self) -> &str {
142        &self.success_symbol
143    }
144
145    /// Get the warning symbol for this theme
146    #[must_use]
147    pub fn warning_symbol(&self) -> &str {
148        &self.warning_symbol
149    }
150
151    /// Get the error symbol for this theme
152    #[must_use]
153    pub fn error_symbol(&self) -> &str {
154        &self.error_symbol
155    }
156
157    /// Get the detail symbol for this theme (verbose progress)
158    #[must_use]
159    pub fn detail_symbol(&self) -> &str {
160        &self.detail_symbol
161    }
162
163    /// Get the debug symbol for this theme (debug-level details)
164    #[must_use]
165    pub fn debug_symbol(&self) -> &str {
166        &self.debug_symbol
167    }
168}
169
170impl Default for Theme {
171    /// Create the default theme (emoji)
172    ///
173    /// # Examples
174    ///
175    /// ```rust
176    /// use torrust_tracker_deployer_lib::presentation::cli::views::Theme;
177    ///
178    /// let theme = Theme::default();
179    /// assert_eq!(theme.progress_symbol(), "⏳");
180    /// ```
181    fn default() -> Self {
182        Self::emoji()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn it_should_return_emoji_symbols_when_using_emoji_theme() {
192        let theme = Theme::emoji();
193
194        assert_eq!(theme.progress_symbol(), "⏳");
195        assert_eq!(theme.success_symbol(), "✅");
196        assert_eq!(theme.warning_symbol(), "⚠️");
197        assert_eq!(theme.error_symbol(), "❌");
198    }
199
200    #[test]
201    fn it_should_return_text_labels_when_using_plain_theme() {
202        let theme = Theme::plain();
203
204        assert_eq!(theme.progress_symbol(), "[INFO]");
205        assert_eq!(theme.success_symbol(), "[OK]");
206        assert_eq!(theme.warning_symbol(), "[WARN]");
207        assert_eq!(theme.error_symbol(), "[ERROR]");
208    }
209
210    #[test]
211    fn it_should_return_ascii_symbols_when_using_ascii_theme() {
212        let theme = Theme::ascii();
213
214        assert_eq!(theme.progress_symbol(), "=>");
215        assert_eq!(theme.success_symbol(), "[+]");
216        assert_eq!(theme.warning_symbol(), "[!]");
217        assert_eq!(theme.error_symbol(), "[x]");
218    }
219
220    #[test]
221    fn it_should_default_to_emoji_theme_when_using_default() {
222        let theme = Theme::default();
223
224        assert_eq!(theme, Theme::emoji());
225    }
226
227    #[test]
228    fn themes_should_be_cloneable() {
229        let theme1 = Theme::emoji();
230        let theme2 = theme1.clone();
231        assert_eq!(theme1, theme2);
232    }
233}