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}