1use colored::*;
7use std::path::Path;
8
9pub trait OutputFormatter {
11 fn success(&self, message: &str) -> String;
13
14 fn error(&self, message: &str) -> String;
16
17 fn warning(&self, message: &str) -> String;
19
20 fn info(&self, message: &str) -> String;
22
23 fn progress(&self, message: &str) -> String;
25
26 fn skip(&self, message: &str) -> String;
28
29 fn generation(&self, message: &str) -> String;
31
32 fn print_success(&self, message: &str) {
34 println!("{}", self.success(message));
35 }
36
37 fn print_error(&self, message: &str) {
39 eprintln!("{}", self.error(message));
40 }
41
42 fn print_warning(&self, message: &str) {
44 println!("{}", self.warning(message));
45 }
46
47 fn print_info(&self, message: &str) {
49 println!("{}", self.info(message));
50 }
51
52 fn print_progress(&self, message: &str) {
54 println!("{}", self.progress(message));
55 }
56
57 fn print_skip(&self, message: &str) {
59 println!("{}", self.skip(message));
60 }
61
62 fn print_generation(&self, message: &str) {
64 println!("{}", self.generation(message));
65 }
66}
67
68#[derive(Debug, Clone, Copy)]
70pub struct ConsoleFormatter;
71
72impl OutputFormatter for ConsoleFormatter {
73 fn success(&self, message: &str) -> String {
74 format!("{} {}", "✓".bright_green(), message.green())
75 }
76
77 fn error(&self, message: &str) -> String {
78 format!("{} {}", "✗".bright_red(), message.red())
79 }
80
81 fn warning(&self, message: &str) -> String {
82 format!("{} {}", "⚠".bright_yellow(), message.yellow())
83 }
84
85 fn info(&self, message: &str) -> String {
86 format!("{} {}", "ℹ".bright_blue(), message.dimmed())
87 }
88
89 fn progress(&self, message: &str) -> String {
90 format!("{} {}", "🔄".bright_blue(), message.bright_white())
91 }
92
93 fn skip(&self, message: &str) -> String {
94 format!("{} {}", "⏭".bright_yellow(), message.dimmed())
95 }
96
97 fn generation(&self, message: &str) -> String {
98 format!("{} {}", "🎨".bright_cyan(), message.bright_white())
99 }
100}
101
102pub trait FilePathFormatter {
104 fn format_file_operation(&self, operation: &str, path: &Path) -> String;
106
107 fn format_upload_success(&self, path: &Path) -> String;
109
110 fn format_upload_failure(&self, path: &Path) -> String;
112
113 fn format_skip_published(&self, path: &Path) -> String;
115
116 fn format_cover_generation(&self, path: &Path) -> String;
118
119 fn format_cover_success(&self, filename: &str) -> String;
121
122 fn format_cover_failure(&self) -> String;
124
125 fn format_image_prompt(&self, prompt: &str) -> String;
127
128 fn format_target_path(&self, path: &Path) -> String;
130
131 fn format_image_saved(&self, path: &Path) -> String;
133}
134
135impl<T: OutputFormatter> FilePathFormatter for T {
136 fn format_file_operation(&self, operation: &str, path: &Path) -> String {
137 format!("{}: {}", operation, path.display())
138 }
139
140 fn format_upload_success(&self, path: &Path) -> String {
141 self.success(&format!("uploaded: {}", path.display()))
142 }
143
144 fn format_upload_failure(&self, path: &Path) -> String {
145 self.error(&format!("failed: {}", path.display()))
146 }
147
148 fn format_skip_published(&self, path: &Path) -> String {
149 self.skip(&format!("skipped: {}", path.display()))
150 }
151
152 fn format_cover_generation(&self, path: &Path) -> String {
153 self.generation(&format!("generating cover: {}", path.display()))
154 }
155
156 fn format_cover_success(&self, filename: &str) -> String {
157 format!(
158 "{} {} {}",
159 "✨".bright_green(),
160 "cover generated:".green(),
161 filename
162 )
163 }
164
165 fn format_cover_failure(&self) -> String {
166 self.warning("cover generation failed, continuing...")
167 }
168
169 fn format_image_prompt(&self, prompt: &str) -> String {
170 format!(
171 " {} {}",
172 "→".bright_blue(),
173 format!("Image prompt: {}", prompt).bright_white()
174 )
175 }
176
177 fn format_target_path(&self, path: &Path) -> String {
178 format!(" {} Target path: {}", "📍".bright_cyan(), path.display())
179 }
180
181 fn format_image_saved(&self, path: &Path) -> String {
182 format!(
183 " {} Image saved to: {}",
184 "💾".bright_green(),
185 path.display()
186 )
187 }
188}
189
190pub trait ApiErrorFormatter {
192 fn format_openai_error(&self, status: u16, response: &str, endpoint: &str) -> String;
194
195 fn format_api_error(&self, service: &str, error: &str) -> String;
197
198 fn format_image_generation_failure(&self, error: &str) -> String;
200
201 fn format_image_download_failure(&self, error: &str) -> String;
203}
204
205impl<T: OutputFormatter> ApiErrorFormatter for T {
206 fn format_openai_error(&self, status: u16, response: &str, endpoint: &str) -> String {
207 format!(
208 " {} OpenAI API Error:\n Status: {}\n Response: {}\n Endpoint: {}",
209 "⚠".bright_yellow(),
210 status,
211 response,
212 endpoint
213 )
214 }
215
216 fn format_api_error(&self, service: &str, error: &str) -> String {
217 format!(" {} {} API Error: {}", "❌".bright_red(), service, error)
218 }
219
220 fn format_image_generation_failure(&self, error: &str) -> String {
221 format!(
222 " {} Failed to generate image: {}",
223 "❌".bright_red(),
224 error
225 )
226 }
227
228 fn format_image_download_failure(&self, error: &str) -> String {
229 format!(
230 " {} Failed to download/save image: {}",
231 "❌".bright_red(),
232 error
233 )
234 }
235}
236
237pub const FORMATTER: ConsoleFormatter = ConsoleFormatter;
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use std::path::PathBuf;
244
245 #[test]
246 fn test_console_formatter_success() {
247 let formatter = ConsoleFormatter;
248 let message = formatter.success("Operation completed");
249 assert!(message.contains("✓"));
250 assert!(message.contains("Operation completed"));
251 }
252
253 #[test]
254 fn test_console_formatter_error() {
255 let formatter = ConsoleFormatter;
256 let message = formatter.error("Operation failed");
257 assert!(message.contains("✗"));
258 assert!(message.contains("Operation failed"));
259 }
260
261 #[test]
262 fn test_console_formatter_warning() {
263 let formatter = ConsoleFormatter;
264 let message = formatter.warning("Warning message");
265 assert!(message.contains("⚠"));
266 assert!(message.contains("Warning message"));
267 }
268
269 #[test]
270 fn test_file_path_formatter() {
271 let formatter = ConsoleFormatter;
272 let path = PathBuf::from("/path/to/file.md");
273
274 let upload_success = formatter.format_upload_success(&path);
275 assert!(upload_success.contains("uploaded"));
276 assert!(upload_success.contains("file.md"));
277
278 let upload_failure = formatter.format_upload_failure(&path);
279 assert!(upload_failure.contains("failed"));
280 assert!(upload_failure.contains("file.md"));
281 }
282
283 #[test]
284 fn test_api_error_formatter() {
285 let formatter = ConsoleFormatter;
286
287 let openai_error = formatter.format_openai_error(429, "Rate limit", "/endpoint");
288 assert!(openai_error.contains("OpenAI API Error"));
289 assert!(openai_error.contains("429"));
290 assert!(openai_error.contains("Rate limit"));
291 assert!(openai_error.contains("/endpoint"));
292
293 let api_error = formatter.format_api_error("WeChat", "Authentication failed");
294 assert!(api_error.contains("WeChat API Error"));
295 assert!(api_error.contains("Authentication failed"));
296 }
297
298 #[test]
299 fn test_cover_generation_messages() {
300 let formatter = ConsoleFormatter;
301 let path = PathBuf::from("/path/to/article.md");
302
303 let generation_msg = formatter.format_cover_generation(&path);
304 assert!(generation_msg.contains("generating cover"));
305 assert!(generation_msg.contains("article.md"));
306
307 let success_msg = formatter.format_cover_success("cover_image.png");
308 assert!(success_msg.contains("cover generated"));
309 assert!(success_msg.contains("cover_image.png"));
310
311 let failure_msg = formatter.format_cover_failure();
312 assert!(failure_msg.contains("cover generation failed"));
313 }
314}