1use std::{collections::HashMap, fs, path::Path};
2
3use super::{ascii::get_builtin_ascii_art, colors::get_builtin_distro_colors};
4
5pub const DEFAULT_ANSI_ALL_COLORS: [&str; 16] = [
6 "\x1b[1;30m", "\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m", "\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m", "\x1b[1;37m", "\x1b[1;90m", "\x1b[1;91m", "\x1b[1;92m", "\x1b[1;93m", "\x1b[1;94m", "\x1b[1;95m", "\x1b[1;96m", "\x1b[1;97m", ];
23
24pub fn get_bar(percent: u8) -> String {
36 let total_blocks = 14;
37 let filled_blocks = (percent as usize * total_blocks) / 100;
38 let empty_blocks = total_blocks - filled_blocks;
39
40 const BLOCKS: &[&str] = &[
42 "",
43 "█",
44 "██",
45 "███",
46 "████",
47 "█████",
48 "██████",
49 "███████",
50 "████████",
51 "█████████",
52 "██████████",
53 "███████████",
54 "████████████",
55 "█████████████",
56 "██████████████",
57 ];
58 const EMPTY_BLOCKS: &[&str] = &[
59 "",
60 "░",
61 "░░",
62 "░░░",
63 "░░░░",
64 "░░░░░",
65 "░░░░░░",
66 "░░░░░░░",
67 "░░░░░░░░",
68 "░░░░░░░░░",
69 "░░░░░░░░░░",
70 "░░░░░░░░░░░",
71 "░░░░░░░░░░░░",
72 "░░░░░░░░░░░░░",
73 "░░░░░░░░░░░░░░",
74 ];
75
76 let filled = BLOCKS[filled_blocks.min(14)];
77 let empty = EMPTY_BLOCKS[empty_blocks.min(14)];
78
79 format!("[{}{}]", filled, empty)
80}
81
82pub fn get_terminal_color(color_blocks: &str) -> String {
90 let color_codes: [u8; 8] = [30, 31, 32, 33, 34, 35, 36, 37]; let mut normal = Vec::with_capacity(8);
93 for &code in &color_codes {
96 normal.push(format!("\x1b[{}m{}\x1b[0m", code, color_blocks)); }
99
100 normal.join("")
102}
103
104pub fn get_custom_ascii(custom_path: &str) -> String {
119 if let Ok(content) = fs::read_to_string(Path::new(custom_path)) {
120 return content;
121 }
122
123 "".to_string()
124}
125
126pub fn get_ascii_and_colors(ascii_distro: &str) -> String {
130 if ascii_distro == "off" {
131 return "".to_string();
132 }
133
134 let ascii_art = resolve_ascii_art(ascii_distro);
135
136 ascii_art.to_string()
137}
138
139fn resolve_ascii_art(distro: &str) -> &'static str {
142 if let Some(art) = get_builtin_ascii_art(distro) {
144 return art;
145 }
146
147 #[cfg(target_os = "linux")]
148 if let Some(parent) = crate::modules::linux::system::distro::get_id_like() {
150 if let Some(art) = get_builtin_ascii_art(&parent) {
151 return art;
152 }
153 }
154
155 DEFAULT_ASCII
157}
158
159const DEFAULT_ASCII: &str = r#"${c2} #####
160${c2} #######
161${c2} ##${c1}O${c2}#${c1}O${c2}##
162${c2} #${c3}#####${c2}#
163${c2} ##${c1}##${c3}###${c1}##${c2}##
164${c2} #${c1}##########${c2}##
165${c2} #${c1}############${c2}##
166${c2} #${c1}############${c2}###
167${c3} ##${c2}#${c1}###########${c2}##${c3}#
168${c3}######${c2}#${c1}#######${c2}#${c3}######
169${c3}#######${c2}#${c1}#####${c2}#${c3}#######
170${c3} #####${c2}#######${c3}#####"#;
171
172pub fn colorize_text(input: String, colors: &HashMap<&str, &str>) -> String {
183 let mut result = String::new();
184
185 for line in input.lines() {
186 let mut colored = line.to_owned();
187 for (key, code) in colors {
188 let placeholder = format!("${{{}}}", key);
189 colored = colored.replace(&placeholder, code);
190 }
191 result.push_str(&colored);
192 result.push('\n');
193 }
194
195 result
196}
197
198pub fn color_palette(
219 entries: &[(&'static str, &'static str)],
220) -> HashMap<&'static str, &'static str> {
221 let mut map = HashMap::new();
222 for (k, v) in entries {
223 map.insert(*k, *v);
224
225 if let Some(code) = v.strip_prefix("\x1b[0;") {
227 let bold_code = format!("\x1b[1;{}", code);
228 let bold_key = format!("bold.{}", k);
229 map.insert(
230 Box::leak(bold_key.into_boxed_str()),
231 Box::leak(bold_code.into_boxed_str()),
232 );
233 }
234 }
235
236 map.insert("reset", "\x1b[0m");
237 map
238}
239
240pub fn get_colors_in_order(color_order: &[u8]) -> HashMap<&'static str, &'static str> {
258 let mut entries: Vec<(&'static str, &'static str)> = vec![("c0", "\x1b[1;30m")];
260
261 let mut used = vec![false; 16]; for (i, &idx) in color_order.iter().enumerate() {
265 if idx < 16 {
266 let key: &'static str = Box::leak(format!("c{}", i + 1).into_boxed_str());
267 entries.push((key, DEFAULT_ANSI_ALL_COLORS[idx as usize]));
268 used[idx as usize] = true;
269 }
270 }
271
272 let mut next_index = color_order.len() + 1;
274 for (i, &color) in DEFAULT_ANSI_ALL_COLORS.iter().enumerate() {
275 if !used[i] {
276 let key: &'static str = Box::leak(format!("c{}", next_index).into_boxed_str());
277 entries.push((key, color));
278 next_index += 1;
279 }
280 }
281
282 let mut map = color_palette(&entries);
284 map.insert("reset", "\x1b[0m");
285 map
286}
287
288pub fn get_custom_colors_order(colors_str_order: &str) -> HashMap<&'static str, &'static str> {
292 let custom_color_str_list: Vec<&str> = colors_str_order.split(',').map(str::trim).collect();
293
294 let all_parsed: Option<Vec<u8>> = custom_color_str_list
296 .iter()
297 .map(|s| s.parse::<u8>().ok())
298 .collect();
299
300 let color_list: Vec<u8> = if let Some(list) = all_parsed {
301 list
302 } else {
303 get_builtin_distro_colors(colors_str_order).to_vec()
305 };
306
307 get_colors_in_order(&color_list)
308}
309
310pub fn get_distro_colors(distro: &str) -> HashMap<&'static str, &'static str> {
314 let dist_color = get_builtin_distro_colors(distro);
315
316 get_colors_in_order(dist_color)
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn bar_respects_percentage_bounds() {
325 assert_eq!(get_bar(0), "[░░░░░░░░░░░░░░]");
326 assert_eq!(get_bar(100), "[██████████████]");
327 assert!(get_bar(50).contains('█'));
328 }
329
330 #[test]
331 fn terminal_color_emits_expected_blocks() {
332 let visual = get_terminal_color("■");
333 assert_eq!(visual.matches('■').count(), 8);
334 assert!(visual.contains("\x1b[31m"), "missing ANSI escape: {visual}");
335 }
336
337 #[test]
338 fn color_palette_includes_bold_and_reset() {
339 let map = color_palette(&[("c1", "\x1b[0;31m")]);
340 assert_eq!(map.get("c1"), Some(&"\x1b[0;31m"));
341 let bold_key = map
342 .keys()
343 .find(|k| k.starts_with("bold.c1"))
344 .expect("bold variant missing");
345 assert!(map.get(bold_key).unwrap().starts_with("\x1b[1;"));
346 assert_eq!(map.get("reset"), Some(&"\x1b[0m"));
347 }
348
349 #[test]
350 fn colors_in_order_fills_defaults() {
351 let map = get_colors_in_order(&[1, 2, 3]);
352 assert_eq!(map.get("c1"), Some(&DEFAULT_ANSI_ALL_COLORS[1]));
353 assert_eq!(map.get("c2"), Some(&DEFAULT_ANSI_ALL_COLORS[2]));
354 assert_eq!(map.get("c3"), Some(&DEFAULT_ANSI_ALL_COLORS[3]));
355 assert!(map.contains_key("c4"));
356 assert_eq!(map.get("reset"), Some(&"\x1b[0m"));
357 }
358}