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 output_config(&self) -> OutputConfig {
120 self.config.clone()
121 }
122
123 pub fn style_dir(&self, text: &str) -> String {
127 self.theme.dir.apply_to(text).to_string()
128 }
129
130 pub fn style_file(&self, text: &str) -> String {
132 self.theme.file.apply_to(text).to_string()
133 }
134
135 pub fn style_size(&self, text: &str) -> String {
137 self.theme.size.apply_to(text).to_string()
138 }
139
140 pub fn style_date(&self, text: &str) -> String {
142 self.theme.date.apply_to(text).to_string()
143 }
144
145 pub fn style_key(&self, text: &str) -> String {
147 self.theme.key.apply_to(text).to_string()
148 }
149
150 pub fn style_url(&self, text: &str) -> String {
152 self.theme.url.apply_to(text).to_string()
153 }
154
155 pub fn style_name(&self, text: &str) -> String {
157 self.theme.name.apply_to(text).to_string()
158 }
159
160 pub fn style_tree_branch(&self, text: &str) -> String {
162 self.theme.tree_branch.apply_to(text).to_string()
163 }
164
165 pub fn output<T: Serialize + std::fmt::Display>(&self, value: &T) {
172 if self.config.quiet {
173 return;
174 }
175
176 if self.config.json {
177 match serde_json::to_string_pretty(value) {
179 Ok(json) => println!("{json}"),
180 Err(e) => eprintln!("Error serializing output: {e}"),
181 }
182 } else {
183 println!("{value}");
184 }
185 }
186
187 pub fn success(&self, message: &str) {
189 if self.config.quiet {
190 return;
191 }
192
193 if self.config.json {
194 return;
196 }
197
198 let checkmark = self.theme.success.apply_to("✓");
199 println!("{checkmark} {message}");
200 }
201
202 pub fn error(&self, message: &str) {
206 if self.config.json {
207 let error = serde_json::json!({
208 "error": message
209 });
210 eprintln!(
211 "{}",
212 serde_json::to_string_pretty(&error).unwrap_or_else(|_| message.to_string())
213 );
214 } else {
215 let cross = self.theme.error.apply_to("✗");
216 eprintln!("{cross} {message}");
217 }
218 }
219
220 pub fn warning(&self, message: &str) {
222 if self.config.quiet || self.config.json {
223 return;
224 }
225
226 let warn_icon = self.theme.warning.apply_to("⚠");
227 eprintln!("{warn_icon} {message}");
228 }
229
230 pub fn json<T: Serialize>(&self, value: &T) {
234 match serde_json::to_string_pretty(value) {
235 Ok(json) => println!("{json}"),
236 Err(e) => eprintln!("Error serializing output: {e}"),
237 }
238 }
239
240 pub fn println(&self, message: &str) {
242 if self.config.quiet {
243 return;
244 }
245 println!("{message}");
246 }
247}
248
249impl Default for Formatter {
250 fn default() -> Self {
251 Self::new(OutputConfig::default())
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_formatter_default() {
261 let formatter = Formatter::default();
262 assert!(!formatter.is_json());
263 assert!(!formatter.is_quiet());
264 assert!(formatter.colors_enabled());
265 }
266
267 #[test]
268 fn test_formatter_json_mode() {
269 let config = OutputConfig {
270 json: true,
271 ..Default::default()
272 };
273 let formatter = Formatter::new(config);
274 assert!(formatter.is_json());
275 assert!(!formatter.colors_enabled()); }
277
278 #[test]
279 fn test_formatter_no_color() {
280 let config = OutputConfig {
281 no_color: true,
282 ..Default::default()
283 };
284 let formatter = Formatter::new(config);
285 assert!(!formatter.colors_enabled());
286 }
287}