1use crate::models::AppConfig;
4use owo_colors::OwoColorize;
5
6pub fn color_speed(mbps: f64, config: &AppConfig) -> String {
8 let s = format!("{:.2} Mbps", mbps);
9 if !config.color {
10 return s;
11 }
12
13 if mbps >= 100.0 {
14 s.green().to_string()
15 } else if mbps >= 25.0 {
16 s.yellow().to_string()
17 } else {
18 s.red().to_string()
19 }
20}
21
22pub fn color_ping(ms: f64, config: &AppConfig) -> String {
24 let s = format!("{:.1} ms", ms);
25 if !config.color {
26 return s;
27 }
28
29 if ms <= 20.0 {
30 s.green().to_string()
31 } else if ms <= 80.0 {
32 s.yellow().to_string()
33 } else {
34 s.red().to_string()
35 }
36}
37
38pub fn color_jitter(ms: f64, config: &AppConfig) -> String {
40 let s = format!("{:.2} ms", ms);
41 if !config.color {
42 return s;
43 }
44
45 if ms <= 5.0 {
46 s.green().to_string()
47 } else if ms <= 20.0 {
48 s.yellow().to_string()
49 } else {
50 s.red().to_string()
51 }
52}
53
54pub fn color_loss(pct: f64, config: &AppConfig) -> String {
56 let s = format!("{:.1}%", pct);
57 if !config.color {
58 return s;
59 }
60
61 if pct == 0.0 {
62 s.green().to_string()
63 } else {
64 s.red().to_string()
65 }
66}
67
68pub fn speed_rating(mbps: f64, config: &AppConfig) -> String {
70 let label = if mbps >= 500.0 {
71 "Excellent"
72 } else if mbps >= 100.0 {
73 "Great"
74 } else if mbps >= 25.0 {
75 "Good"
76 } else if mbps >= 5.0 {
77 "Fair"
78 } else {
79 "Poor"
80 };
81
82 if !config.color {
83 return label.to_string();
84 }
85
86 if mbps >= 100.0 {
87 label.green().to_string()
88 } else if mbps >= 25.0 {
89 label.yellow().to_string()
90 } else {
91 label.red().to_string()
92 }
93}
94
95pub fn visible_len(s: &str) -> usize {
98 console::measure_text_width(s)
99}
100
101pub fn pad_to(s: &str, width: usize) -> String {
104 let vlen = visible_len(s);
105 if vlen >= width {
106 s.to_string()
107 } else {
108 format!("{}{}", s, " ".repeat(width - vlen))
109 }
110}
111
112pub fn truncate_to(s: &str, max_len: usize) -> String {
114 if s.len() <= max_len {
115 s.to_string()
116 } else {
117 format!("{}…", &s[..max_len - 1])
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn mock_config(color: bool) -> AppConfig {
126 AppConfig {
127 quiet: false,
128 color,
129 }
130 }
131
132 #[test]
133 fn color_speed_green() {
134 let res = color_speed(150.0, &mock_config(true));
135 assert!(res.contains("\x1b[32m")); assert!(res.contains("150.00 Mbps"));
137 }
138
139 #[test]
140 fn color_speed_plain_when_no_color() {
141 let res = color_speed(150.0, &mock_config(false));
142 assert!(!res.contains("\x1b"));
143 assert_eq!(res, "150.00 Mbps");
144 }
145
146 #[test]
147 fn speed_rating_boundaries() {
148 let c = mock_config(false);
149 assert_eq!(speed_rating(500.0, &c), "Excellent");
150 assert_eq!(speed_rating(100.0, &c), "Great");
151 assert_eq!(speed_rating(25.0, &c), "Good");
152 assert_eq!(speed_rating(5.0, &c), "Fair");
153 assert_eq!(speed_rating(4.9, &c), "Poor");
154 }
155
156 #[test]
157 fn truncate_to_short_string() {
158 assert_eq!(truncate_to("short", 10), "short");
159 assert_eq!(truncate_to("exact", 5), "exact");
160 }
161
162 #[test]
163 fn truncate_to_long_string() {
164 assert_eq!(truncate_to("long string", 5), "long…");
166 }
167
168 #[test]
169 fn visible_len_plain_string() {
170 assert_eq!(visible_len("hello"), 5);
171 }
172
173 #[test]
174 fn visible_len_colored_string() {
175 assert_eq!(visible_len("\x1b[32m401.74\x1b[0m"), 6);
176 }
177
178 #[test]
179 fn pad_to_short_string_pads_correctly() {
180 assert_eq!(pad_to("hi", 5), "hi ");
181 }
182
183 #[test]
184 fn pad_to_colored_string_pads_to_visible_width() {
185 let colored = "\x1b[32m401.74\x1b[0m";
186 let padded = pad_to(colored, 10);
187 assert_eq!(visible_len(&padded), 10);
188 assert!(padded.starts_with(colored));
189 }
190
191 #[test]
192 fn pad_to_already_at_width_unchanged() {
193 assert_eq!(pad_to("hello", 5), "hello");
194 }
195
196 #[test]
197 fn pad_to_over_width_unchanged() {
198 assert_eq!(pad_to("toolong", 4), "toolong");
199 }
200}