Skip to main content

quasar_cli/
style.rs

1use std::sync::atomic::{AtomicBool, Ordering};
2
3static COLOR_ENABLED: AtomicBool = AtomicBool::new(true);
4
5/// Initialize style settings from global config. Call once at startup.
6pub fn init(color: bool) {
7    COLOR_ENABLED.store(color, Ordering::Relaxed);
8}
9
10fn use_color() -> bool {
11    COLOR_ENABLED.load(Ordering::Relaxed)
12}
13
14/// Colored checkmark (green).
15pub fn success(msg: &str) -> String {
16    if use_color() {
17        format!("\x1b[38;5;83m\u{2714}\x1b[0m {msg}")
18    } else {
19        format!("[ok] {msg}")
20    }
21}
22
23/// Colored X (red).
24pub fn fail(msg: &str) -> String {
25    if use_color() {
26        format!("\x1b[38;5;196m\u{2718}\x1b[0m {msg}")
27    } else {
28        format!("[error] {msg}")
29    }
30}
31
32/// Colored arrow (cyan) for in-progress steps.
33pub fn step(msg: &str) -> String {
34    if use_color() {
35        format!("\x1b[38;5;45m\u{279c}\x1b[0m {msg}")
36    } else {
37        format!("[..] {msg}")
38    }
39}
40
41/// Warning triangle (yellow).
42pub fn warn(msg: &str) -> String {
43    if use_color() {
44        format!("\x1b[38;5;208m\u{26a0}\x1b[0m {msg}")
45    } else {
46        format!("[warn] {msg}")
47    }
48}
49
50/// Bold text.
51pub fn bold(s: &str) -> String {
52    if use_color() {
53        format!("\x1b[1m{s}\x1b[0m")
54    } else {
55        s.to_string()
56    }
57}
58
59/// Dim text.
60pub fn dim(s: &str) -> String {
61    if use_color() {
62        format!("\x1b[2m{s}\x1b[0m")
63    } else {
64        s.to_string()
65    }
66}
67
68/// 256-color text.
69pub fn color(code: u8, s: &str) -> String {
70    if use_color() {
71        format!("\x1b[38;5;{code}m{s}\x1b[0m")
72    } else {
73        s.to_string()
74    }
75}
76
77/// Format a byte size in a human-readable way.
78pub fn human_size(bytes: u64) -> String {
79    if bytes < 1024 {
80        format!("{bytes} B")
81    } else if bytes < 1024 * 1024 {
82        format!("{:.1} KB", bytes as f64 / 1024.0)
83    } else {
84        format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
85    }
86}
87
88/// Create a space-themed cyan spinner with a message.
89pub fn spinner(msg: &str) -> indicatif::ProgressBar {
90    use {
91        indicatif::{ProgressBar, ProgressStyle},
92        std::time::Duration,
93    };
94
95    let sp = ProgressBar::new_spinner();
96    sp.set_style(
97        ProgressStyle::default_spinner()
98            .tick_strings(&[
99                "✦ ·  · ",
100                " ✦ ·  ·",
101                "·  ✦ · ",
102                " ·  ✦ ·",
103                "·  · ✦ ",
104                " ·  · ✦",
105                "·  · ✦ ",
106                " ·  ✦ ·",
107                "·  ✦ · ",
108                " ✦ ·  ·",
109                "✦ ·  · ",
110            ])
111            .template("  {spinner:.cyan} {msg}")
112            .unwrap(),
113    );
114    sp.enable_steady_tick(Duration::from_millis(80));
115    sp.set_message(msg.to_string());
116    sp
117}
118
119/// Format a duration in a human-readable way.
120pub fn human_duration(d: std::time::Duration) -> String {
121    let secs = d.as_secs_f64();
122    if secs < 0.1 {
123        format!("{:.0}ms", secs * 1000.0)
124    } else if secs < 60.0 {
125        format!("{secs:.1}s")
126    } else {
127        let mins = secs as u64 / 60;
128        let rem = secs as u64 % 60;
129        format!("{mins}m {rem}s")
130    }
131}