1use crate::core::prelude::*;
2use log::Level;
3use std::collections::HashMap;
4use std::sync::LazyLock;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct AppColor(Color);
8
9static COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
10 let mut map = HashMap::new();
11
12 map.insert("black", Color::Black);
14 map.insert("red", Color::Red);
15 map.insert("green", Color::Green);
16 map.insert("yellow", Color::Yellow);
17 map.insert("blue", Color::Blue);
18 map.insert("magenta", Color::Magenta);
19 map.insert("cyan", Color::Cyan);
20 map.insert("gray", Color::Gray);
21 map.insert("darkgray", Color::DarkGray);
22 map.insert("lightred", Color::LightRed);
23 map.insert("lightgreen", Color::LightGreen);
24 map.insert("lightyellow", Color::LightYellow);
25 map.insert("lightblue", Color::LightBlue);
26 map.insert("lightmagenta", Color::LightMagenta);
27 map.insert("lightcyan", Color::LightCyan);
28 map.insert("white", Color::White);
29
30 map.insert("error", Color::Red);
32 map.insert("warning", Color::Yellow);
33 map.insert("warn", Color::Yellow);
34 map.insert("info", Color::Green);
35 map.insert("debug", Color::Blue);
36 map.insert("trace", Color::White);
37 map.insert("lang", Color::Cyan);
38 map.insert("version", Color::LightBlue);
39 map.insert("startup", Color::Magenta);
40 map.insert("theme", Color::LightMagenta);
41
42 map
43});
44
45static DISPLAY_COLOR_MAP: LazyLock<HashMap<&'static str, Color>> = LazyLock::new(|| {
47 let mut map = HashMap::new();
48
49 map.insert("CONFIRM", Color::Yellow);
51 map.insert("BESTÄTIGEN", Color::Yellow);
52
53 map.insert("ERROR", Color::Red);
55 map.insert("FEHLER", Color::Red);
56 map.insert("RENDER", Color::Red);
57
58 map.insert("WARN", Color::Yellow);
60 map.insert("WARNING", Color::Yellow);
61 map.insert("TERMINAL", Color::Yellow);
62
63 map.insert("INFO", Color::Green);
65 map.insert("CLIPBOARD", Color::Green);
66 map.insert("HISTORY", Color::Green);
67 map.insert("HISTORIE", Color::Green);
68 map.insert("LOG_LEVEL", Color::Green);
69 map.insert("SYSTEM", Color::Green);
70
71 map.insert("DEBUG", Color::Blue);
73
74 map.insert("TRACE", Color::White);
76
77 map.insert("THEME", Color::LightMagenta);
79 map.insert("LANG", Color::Cyan);
80 map.insert("SPRACHE", Color::Cyan);
81 map.insert("VERSION", Color::LightBlue);
82 map.insert("READY", Color::Magenta);
83 map.insert("BEREIT", Color::Magenta);
84
85 map.insert("Running", Color::Green);
87 map.insert("RUNNING", Color::Green);
88 map.insert("Stopped", Color::Red);
89 map.insert("STOPPED", Color::Red);
90 map.insert("Failed", Color::Red);
91 map.insert("FAILED", Color::Red);
92 map.insert("PERSISTENT", Color::Magenta);
93 map.insert("Started", Color::Green);
94 map.insert("STARTED", Color::Green);
95
96 map
97});
98
99impl AppColor {
100 pub fn new(color: Color) -> Self {
101 Self(color)
102 }
103
104 pub fn from_display_text(display_text: &str) -> Self {
106 let normalized = display_text.trim().to_uppercase();
107 let color = DISPLAY_COLOR_MAP
108 .get(normalized.as_str())
109 .copied()
110 .unwrap_or(Color::Green); Self(color)
113 }
114
115 pub fn from_category(category: &str) -> Self {
116 let normalized = category.trim().to_lowercase();
117 let color = COLOR_MAP
118 .get(normalized.as_str())
119 .copied()
120 .unwrap_or(Color::Green);
121 Self(color)
122 }
123
124 pub fn from_any<T: Into<String>>(source: T) -> Self {
126 let key = source.into().to_lowercase();
127 let color = COLOR_MAP.get(key.as_str()).copied().unwrap_or(Color::Green);
128 Self(color)
129 }
130
131 pub fn from_log_level(level: Level) -> Self {
132 Self::from_category(&level.to_string())
133 }
134
135 pub fn from_string(color_str: &str) -> crate::core::error::Result<Self> {
136 let normalized = color_str.trim().to_lowercase();
137 let color = COLOR_MAP
138 .get(normalized.as_str())
139 .copied()
140 .ok_or_else(|| AppError::Validation(format!("Invalid color: {}", color_str)))?;
141 Ok(Self(color))
142 }
143
144 pub fn from_display_text_with_timing(display_text: &str) -> (Self, std::time::Duration) {
146 let start = std::time::Instant::now();
147 let color = Self::from_display_text(display_text);
148 let duration = start.elapsed();
149 (color, duration)
150 }
151
152 pub fn available_display_texts() -> Vec<&'static str> {
153 DISPLAY_COLOR_MAP.keys().copied().collect()
154 }
155
156 pub fn available_categories() -> Vec<&'static str> {
157 COLOR_MAP.keys().copied().collect()
158 }
159
160 pub fn format_message(&self, level: &str, message: &str) -> String {
161 if level.is_empty() {
162 format!("\x1B[{}m{}\x1B[0m", self.to_ansi_code(), message)
163 } else {
164 format!(
165 "\x1B[{}m[{}] {}\x1B[0m",
166 self.to_ansi_code(),
167 level,
168 message
169 )
170 }
171 }
172
173 pub fn to_ansi_code(&self) -> u8 {
174 match self.0 {
175 Color::Black => 30,
176 Color::Red => 31,
177 Color::Green => 32,
178 Color::Yellow => 33,
179 Color::Blue => 34,
180 Color::Magenta => 35,
181 Color::Cyan => 36,
182 Color::Gray => 37,
183 Color::DarkGray => 90,
184 Color::LightRed => 91,
185 Color::LightGreen => 92,
186 Color::LightYellow => 93,
187 Color::LightBlue => 94,
188 Color::LightMagenta => 95,
189 Color::LightCyan => 96,
190 Color::White => 97,
191 _ => 37,
192 }
193 }
194
195 pub fn to_name(&self) -> &'static str {
196 COLOR_MAP
197 .iter()
198 .find(|(_, &v)| v == self.0)
199 .map(|(k, _)| *k)
200 .unwrap_or("gray")
201 }
202}
203
204impl From<AppColor> for Color {
205 fn from(app_color: AppColor) -> Self {
206 app_color.0
207 }
208}
209
210impl From<&AppColor> for Color {
211 fn from(app_color: &AppColor) -> Self {
212 app_color.0
213 }
214}
215
216impl Default for AppColor {
217 fn default() -> Self {
218 Self(Color::Gray)
219 }
220}