1use serde_json::Value;
4use std::collections::{BTreeMap, BTreeSet};
5
6pub fn rgb_to_ansi256((r, g, b): (u8, u8, u8)) -> u8 {
20 if r == g && g == b {
21 if r < 8 {
22 16
23 } else if r > 248 {
24 231
25 } else {
26 232 + ((r as u16 - 8) * 24 / 247) as u8
27 }
28 } else {
29 let red = (r as u16 * 5 / 255) as u8;
30 let green = (g as u16 * 5 / 255) as u8;
31 let blue = (b as u16 * 5 / 255) as u8;
32 16 + 36 * red + 6 * green + blue
33 }
34}
35
36pub fn rgb_to_truecolor(rgb: (u8, u8, u8)) -> (u8, u8, u8) {
41 rgb
42}
43
44pub fn truncate_to_width(s: &str, max_width: usize) -> String {
64 use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
65
66 if s.width() <= max_width {
68 return s.to_string();
69 }
70
71 let mut result = String::new();
72 let mut current_width = 0;
73 let limit = max_width.saturating_sub(1);
75
76 for c in s.chars() {
77 let char_width = c.width().unwrap_or(0);
78 if current_width + char_width > limit {
79 result.push('…');
80 return result;
81 }
82 result.push(c);
83 current_width += char_width;
84 }
85
86 result
87}
88
89pub fn flatten_json_for_csv(value: &Value) -> (Vec<String>, Vec<Vec<String>>) {
98 let mut rows: Vec<BTreeMap<String, String>> = Vec::new();
99
100 match value {
101 Value::Array(arr) => {
102 for item in arr {
103 rows.push(flatten_single_item(item));
104 }
105 }
106 _ => {
107 rows.push(flatten_single_item(value));
108 }
109 }
110
111 let mut headers_set = BTreeSet::new();
113 for row in &rows {
114 for key in row.keys() {
115 headers_set.insert(key.clone());
116 }
117 }
118 let headers: Vec<String> = headers_set.into_iter().collect();
119
120 let mut data = Vec::new();
122 for row in rows {
123 let mut row_data = Vec::new();
124 for header in &headers {
125 row_data.push(row.get(header).cloned().unwrap_or_default());
126 }
127 data.push(row_data);
128 }
129
130 (headers, data)
131}
132
133fn flatten_single_item(value: &Value) -> BTreeMap<String, String> {
134 let mut acc = BTreeMap::new();
135 flatten_recursive(value, "", &mut acc);
136 acc
137}
138
139fn flatten_recursive(value: &Value, prefix: &str, acc: &mut BTreeMap<String, String>) {
140 match value {
141 Value::Null => {}
142 Value::Bool(b) => {
143 let key = if prefix.is_empty() { "value" } else { prefix };
144 acc.insert(key.to_string(), b.to_string());
145 }
146 Value::Number(n) => {
147 let key = if prefix.is_empty() { "value" } else { prefix };
148 acc.insert(key.to_string(), n.to_string());
149 }
150 Value::String(s) => {
151 let key = if prefix.is_empty() { "value" } else { prefix };
152 acc.insert(key.to_string(), s.clone());
153 }
154 Value::Array(_) => {
155 let key = if prefix.is_empty() { "value" } else { prefix };
157 acc.insert(key.to_string(), value.to_string());
158 }
159 Value::Object(map) => {
160 if map.is_empty() {
161 let key = if prefix.is_empty() { "value" } else { prefix };
162 acc.insert(key.to_string(), "{}".to_string());
163 } else {
164 for (k, v) in map {
165 let new_key = if prefix.is_empty() {
166 k.clone()
167 } else {
168 format!("{}.{}", prefix, k)
169 };
170 flatten_recursive(v, &new_key, acc);
171 }
172 }
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_rgb_to_ansi256_grayscale() {
183 assert_eq!(rgb_to_ansi256((0, 0, 0)), 16);
184 assert_eq!(rgb_to_ansi256((255, 255, 255)), 231);
185 let mid = rgb_to_ansi256((128, 128, 128));
186 assert!((232..=255).contains(&mid));
187 }
188
189 #[test]
190 fn test_rgb_to_ansi256_color_cube() {
191 assert_eq!(rgb_to_ansi256((255, 0, 0)), 196);
192 assert_eq!(rgb_to_ansi256((0, 255, 0)), 46);
193 assert_eq!(rgb_to_ansi256((0, 0, 255)), 21);
194 }
195
196 #[test]
197 fn test_truncate_to_width_no_truncation() {
198 assert_eq!(truncate_to_width("Hello", 10), "Hello");
199 assert_eq!(truncate_to_width("Hello", 5), "Hello");
200 }
201
202 #[test]
203 fn test_truncate_to_width_with_truncation() {
204 assert_eq!(truncate_to_width("Hello World", 6), "Hello…");
205 assert_eq!(truncate_to_width("Hello World", 7), "Hello …");
206 }
207
208 #[test]
209 fn test_truncate_to_width_empty() {
210 assert_eq!(truncate_to_width("", 5), "");
211 }
212
213 #[test]
214 fn test_truncate_to_width_exact_fit() {
215 assert_eq!(truncate_to_width("12345", 5), "12345");
216 }
217
218 #[test]
219 fn test_truncate_to_width_one_over() {
220 assert_eq!(truncate_to_width("123456", 5), "1234…");
221 }
222
223 #[test]
224 fn test_truncate_to_width_zero_width() {
225 assert_eq!(truncate_to_width("Hello", 0), "…");
226 }
227
228 #[test]
229 fn test_truncate_to_width_one_width() {
230 assert_eq!(truncate_to_width("Hello", 1), "…");
231 }
232}