1use crate::color::Color;
2use indexmap::IndexMap;
3use std::fmt::Display;
4use std::fs::{self, OpenOptions};
5use std::io::Write;
6use std::path::PathBuf;
7
8pub struct Logger {
9 frame_color: Option<Color>,
10 content_color: Option<Color>,
11 log_file: Option<PathBuf>,
12}
13
14impl Logger {
15 pub fn new() -> Self {
16 Logger {
17 frame_color: None,
18 content_color: None,
19 log_file: None,
20 }
21 }
22
23 pub fn with_frame_color(mut self, color: Color) -> Self {
24 self.frame_color = Some(color);
25 self
26 }
27
28 pub fn with_content_color(mut self, color: Color) -> Self {
29 self.content_color = Some(color);
30 self
31 }
32
33 pub fn with_save_log(mut self, path: impl Into<PathBuf>) -> Self {
34 self.log_file = Some(path.into());
35 if let Some(log_file) = &self.log_file {
37 if let Some(parent) = log_file.parent() {
38 fs::create_dir_all(parent).ok();
39 }
40 }
41 self
42 }
43
44 fn write_to_log(&self, content: &str) {
45 if let Some(log_file) = &self.log_file {
46 if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(log_file) {
47 writeln!(file, "{}", content).ok();
48 }
49 }
50 }
51
52 fn print_and_log(&self, content: &str) {
53 println!("{}", content);
54 self.write_to_log(content);
55 }
56
57 fn colorize_frame(&self, s: &str) -> String {
58 if let Some(color) = self.frame_color {
59 color.to_colored_string(s)
60 } else {
61 s.to_string()
62 }
63 }
64
65 fn colorize_content(&self, s: &str) -> String {
66 if let Some(color) = self.content_color {
67 color.to_colored_string(s)
68 } else {
69 s.to_string()
70 }
71 }
72
73 pub fn table<'a, T: Display>(&self, title: &str, data: impl Into<IndexMap<&'a str, T>>) {
74 let data_map: IndexMap<&str, T> = data.into();
75 let data: IndexMap<&str, String> = data_map
76 .into_iter()
77 .map(|(k, v)| (k, format!("{}", v)))
78 .collect();
79
80 let max_key_len = data.keys().map(|k| k.len()).max().unwrap_or(0);
81 let max_val_len = data.values().map(|v| v.len()).max().unwrap_or(0);
82
83 let key_col_width = max_key_len + 2;
84 let val_col_width = max_val_len + 2;
85 let total_width = key_col_width + val_col_width + 3;
86
87 let top_border = self.colorize_frame(&format!("┌{}┐", "─".repeat(total_width - 2)));
89 let title_border_left = self.colorize_frame("│");
90 let title_border_right = self.colorize_frame("│");
91 let title_content =
92 self.colorize_content(&format!("{:^width$}", title, width = total_width - 2));
93
94 self.print_and_log(&top_border);
95 self.print_and_log(&format!(
96 "{}{}{}",
97 title_border_left, title_content, title_border_right
98 ));
99 self.print_and_log(&self.colorize_frame(&format!(
100 "├{}┬{}┤",
101 "─".repeat(key_col_width),
102 "─".repeat(val_col_width)
103 )));
104
105 let header_left = self.colorize_frame("│");
107 let header_middle = self.colorize_frame("│");
108 let header_right = self.colorize_frame("│");
109 let key_header =
110 self.colorize_content(&format!(" {:<width$} ", "Key", width = max_key_len));
111 let value_header =
112 self.colorize_content(&format!(" {:<width$} ", "Value", width = max_val_len));
113
114 self.print_and_log(&format!(
115 "{}{}{}{}{}",
116 header_left, key_header, header_middle, value_header, header_right
117 ));
118 self.print_and_log(&self.colorize_frame(&format!(
119 "├{}┼{}┤",
120 "─".repeat(key_col_width),
121 "─".repeat(val_col_width)
122 )));
123
124 let entries: Vec<_> = data.iter().collect();
125 for (i, (key, value)) in entries.iter().enumerate() {
126 let row_left = self.colorize_frame("│");
127 let row_middle = self.colorize_frame("│");
128 let row_right = self.colorize_frame("│");
129 let key_content =
130 self.colorize_content(&format!(" {:<width$} ", key, width = max_key_len));
131 let value_content =
132 self.colorize_content(&format!(" {:<width$} ", value, width = max_val_len));
133
134 self.print_and_log(&format!(
135 "{}{}{}{}{}",
136 row_left, key_content, row_middle, value_content, row_right
137 ));
138
139 if i < entries.len() - 1 {
140 self.print_and_log(&self.colorize_frame(&format!(
141 "├{}┼{}┤",
142 "─".repeat(key_col_width),
143 "─".repeat(val_col_width)
144 )));
145 }
146 }
147
148 self.print_and_log(&self.colorize_frame(&format!(
149 "└{}┴{}┘",
150 "─".repeat(key_col_width),
151 "─".repeat(val_col_width)
152 )));
153 self.print_and_log("");
154 }
155}