rustfs_cli/output/
formatter.rs1use console::Style;
7use serde::Serialize;
8
9use super::OutputConfig;
10
11#[derive(Debug, Clone)]
13pub struct Theme {
14 pub dir: Style,
16 pub file: Style,
18 pub size: Style,
20 pub date: Style,
22 pub key: Style,
24 pub url: Style,
26 pub name: Style,
28 pub success: Style,
30 pub error: Style,
32 pub warning: Style,
34 pub tree_branch: Style,
36}
37
38impl Default for Theme {
39 fn default() -> Self {
40 Self {
41 dir: Style::new().blue().bold(),
42 file: Style::new(),
43 size: Style::new().green(),
44 date: Style::new().dim(),
45 key: Style::new().cyan(),
46 url: Style::new().cyan().underlined(),
47 name: Style::new().bold(),
48 success: Style::new().green(),
49 error: Style::new().red(),
50 warning: Style::new().yellow(),
51 tree_branch: Style::new().dim(),
52 }
53 }
54}
55
56impl Theme {
57 pub fn plain() -> Self {
59 Self {
60 dir: Style::new(),
61 file: Style::new(),
62 size: Style::new(),
63 date: Style::new(),
64 key: Style::new(),
65 url: Style::new(),
66 name: Style::new(),
67 success: Style::new(),
68 error: Style::new(),
69 warning: Style::new(),
70 tree_branch: Style::new(),
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
80#[allow(dead_code)]
81pub struct Formatter {
82 config: OutputConfig,
83 theme: Theme,
84}
85
86#[allow(dead_code)]
87impl Formatter {
88 pub fn new(config: OutputConfig) -> Self {
90 let theme = if config.no_color || config.json {
91 Theme::plain()
92 } else {
93 Theme::default()
94 };
95 Self { config, theme }
96 }
97
98 pub fn is_json(&self) -> bool {
100 self.config.json
101 }
102
103 pub fn is_quiet(&self) -> bool {
105 self.config.quiet
106 }
107
108 pub fn colors_enabled(&self) -> bool {
110 !self.config.no_color && !self.config.json
111 }
112
113 pub fn theme(&self) -> &Theme {
115 &self.theme
116 }
117
118 pub fn style_dir(&self, text: &str) -> String {
122 self.theme.dir.apply_to(text).to_string()
123 }
124
125 pub fn style_file(&self, text: &str) -> String {
127 self.theme.file.apply_to(text).to_string()
128 }
129
130 pub fn style_size(&self, text: &str) -> String {
132 self.theme.size.apply_to(text).to_string()
133 }
134
135 pub fn style_date(&self, text: &str) -> String {
137 self.theme.date.apply_to(text).to_string()
138 }
139
140 pub fn style_key(&self, text: &str) -> String {
142 self.theme.key.apply_to(text).to_string()
143 }
144
145 pub fn style_url(&self, text: &str) -> String {
147 self.theme.url.apply_to(text).to_string()
148 }
149
150 pub fn style_name(&self, text: &str) -> String {
152 self.theme.name.apply_to(text).to_string()
153 }
154
155 pub fn style_tree_branch(&self, text: &str) -> String {
157 self.theme.tree_branch.apply_to(text).to_string()
158 }
159
160 pub fn output<T: Serialize + std::fmt::Display>(&self, value: &T) {
167 if self.config.quiet {
168 return;
169 }
170
171 if self.config.json {
172 match serde_json::to_string_pretty(value) {
174 Ok(json) => println!("{json}"),
175 Err(e) => eprintln!("Error serializing output: {e}"),
176 }
177 } else {
178 println!("{value}");
179 }
180 }
181
182 pub fn success(&self, message: &str) {
184 if self.config.quiet {
185 return;
186 }
187
188 if self.config.json {
189 return;
191 }
192
193 let checkmark = self.theme.success.apply_to("✓");
194 println!("{checkmark} {message}");
195 }
196
197 pub fn error(&self, message: &str) {
201 if self.config.json {
202 let error = serde_json::json!({
203 "error": message
204 });
205 eprintln!(
206 "{}",
207 serde_json::to_string_pretty(&error).unwrap_or_else(|_| message.to_string())
208 );
209 } else {
210 let cross = self.theme.error.apply_to("✗");
211 eprintln!("{cross} {message}");
212 }
213 }
214
215 pub fn warning(&self, message: &str) {
217 if self.config.quiet || self.config.json {
218 return;
219 }
220
221 let warn_icon = self.theme.warning.apply_to("⚠");
222 eprintln!("{warn_icon} {message}");
223 }
224
225 pub fn json<T: Serialize>(&self, value: &T) {
229 match serde_json::to_string_pretty(value) {
230 Ok(json) => println!("{json}"),
231 Err(e) => eprintln!("Error serializing output: {e}"),
232 }
233 }
234
235 pub fn println(&self, message: &str) {
237 if self.config.quiet {
238 return;
239 }
240 println!("{message}");
241 }
242}
243
244impl Default for Formatter {
245 fn default() -> Self {
246 Self::new(OutputConfig::default())
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_formatter_default() {
256 let formatter = Formatter::default();
257 assert!(!formatter.is_json());
258 assert!(!formatter.is_quiet());
259 assert!(formatter.colors_enabled());
260 }
261
262 #[test]
263 fn test_formatter_json_mode() {
264 let config = OutputConfig {
265 json: true,
266 ..Default::default()
267 };
268 let formatter = Formatter::new(config);
269 assert!(formatter.is_json());
270 assert!(!formatter.colors_enabled()); }
272
273 #[test]
274 fn test_formatter_no_color() {
275 let config = OutputConfig {
276 no_color: true,
277 ..Default::default()
278 };
279 let formatter = Formatter::new(config);
280 assert!(!formatter.colors_enabled());
281 }
282}