1use serde::Serialize;
4use serde_json::Value;
5use std::collections::{BTreeMap, BTreeSet};
6
7pub fn rgb_to_ansi256((r, g, b): (u8, u8, u8)) -> u8 {
21 if r == g && g == b {
22 if r < 8 {
23 16
24 } else if r > 248 {
25 231
26 } else {
27 232 + ((r as u16 - 8) * 24 / 247) as u8
28 }
29 } else {
30 let red = (r as u16 * 5 / 255) as u8;
31 let green = (g as u16 * 5 / 255) as u8;
32 let blue = (b as u16 * 5 / 255) as u8;
33 16 + 36 * red + 6 * green + blue
34 }
35}
36
37pub fn rgb_to_truecolor(rgb: (u8, u8, u8)) -> (u8, u8, u8) {
42 rgb
43}
44
45pub fn truncate_to_width(s: &str, max_width: usize) -> String {
65 use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
66
67 if s.width() <= max_width {
69 return s.to_string();
70 }
71
72 let mut result = String::new();
73 let mut current_width = 0;
74 let limit = max_width.saturating_sub(1);
76
77 for c in s.chars() {
78 let char_width = c.width().unwrap_or(0);
79 if current_width + char_width > limit {
80 result.push('…');
81 return result;
82 }
83 result.push(c);
84 current_width += char_width;
85 }
86
87 result
88}
89
90pub fn serialize_to_xml<T: Serialize + ?Sized>(data: &T) -> Result<String, quick_xml::DeError> {
98 if let Ok(xml) = quick_xml::se::to_string(data) {
100 return Ok(xml);
101 }
102 let value = serde_json::to_value(data).unwrap_or(serde_json::Value::Null);
105 let sanitized = sanitize_xml_keys(&value);
106 match sanitized {
107 serde_json::Value::Object(_) => quick_xml::se::to_string_with_root("data", &sanitized),
108 serde_json::Value::Null => quick_xml::se::to_string_with_root(
109 "data",
110 &serde_json::Value::Object(serde_json::Map::new()),
111 ),
112 other => {
113 let mut map = serde_json::Map::new();
114 map.insert("value".to_string(), other);
115 quick_xml::se::to_string_with_root("data", &serde_json::Value::Object(map))
116 }
117 }
118}
119
120fn sanitize_xml_keys(value: &serde_json::Value) -> serde_json::Value {
122 match value {
123 serde_json::Value::Object(map) => {
124 let mut new_map = serde_json::Map::new();
125 for (key, val) in map {
126 let safe_key = sanitize_xml_name(key);
127 new_map.insert(safe_key, sanitize_xml_keys(val));
128 }
129 serde_json::Value::Object(new_map)
130 }
131 serde_json::Value::Array(arr) => {
132 serde_json::Value::Array(arr.iter().map(sanitize_xml_keys).collect())
133 }
134 other => other.clone(),
135 }
136}
137
138fn sanitize_xml_name(name: &str) -> String {
144 if name.is_empty() {
145 return "_".to_string();
146 }
147 let mut result = String::with_capacity(name.len() + 1);
148 for (i, c) in name.chars().enumerate() {
149 if i == 0 {
150 if c.is_ascii_alphabetic() || c == '_' {
151 result.push(c);
152 } else {
153 result.push('_');
154 if c.is_ascii_alphanumeric() {
155 result.push(c);
156 }
157 }
158 } else if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' {
159 result.push(c);
160 } else {
161 result.push('_');
162 }
163 }
164 result
165}
166
167pub fn flatten_json_for_csv(value: &Value) -> (Vec<String>, Vec<Vec<String>>) {
176 let mut rows: Vec<BTreeMap<String, String>> = Vec::new();
177
178 match value {
179 Value::Array(arr) => {
180 for item in arr {
181 rows.push(flatten_single_item(item));
182 }
183 }
184 _ => {
185 rows.push(flatten_single_item(value));
186 }
187 }
188
189 let mut headers_set = BTreeSet::new();
191 for row in &rows {
192 for key in row.keys() {
193 headers_set.insert(key.clone());
194 }
195 }
196 let headers: Vec<String> = headers_set.into_iter().collect();
197
198 let mut data = Vec::new();
200 for row in rows {
201 let mut row_data = Vec::new();
202 for header in &headers {
203 row_data.push(row.get(header).cloned().unwrap_or_default());
204 }
205 data.push(row_data);
206 }
207
208 (headers, data)
209}
210
211fn flatten_single_item(value: &Value) -> BTreeMap<String, String> {
212 let mut acc = BTreeMap::new();
213 flatten_recursive(value, "", &mut acc);
214 acc
215}
216
217fn flatten_recursive(value: &Value, prefix: &str, acc: &mut BTreeMap<String, String>) {
218 match value {
219 Value::Null => {}
220 Value::Bool(b) => {
221 let key = if prefix.is_empty() { "value" } else { prefix };
222 acc.insert(key.to_string(), b.to_string());
223 }
224 Value::Number(n) => {
225 let key = if prefix.is_empty() { "value" } else { prefix };
226 acc.insert(key.to_string(), n.to_string());
227 }
228 Value::String(s) => {
229 let key = if prefix.is_empty() { "value" } else { prefix };
230 acc.insert(key.to_string(), s.clone());
231 }
232 Value::Array(arr) => {
233 let key_prefix = if prefix.is_empty() { "value" } else { prefix };
234 for (i, item) in arr.iter().enumerate() {
235 let indexed_key = format!("{}.{}", key_prefix, i);
236 flatten_recursive(item, &indexed_key, acc);
237 }
238 }
239 Value::Object(map) => {
240 if map.is_empty() {
241 let key = if prefix.is_empty() { "value" } else { prefix };
242 acc.insert(key.to_string(), "{}".to_string());
243 } else {
244 for (k, v) in map {
245 let new_key = if prefix.is_empty() {
246 k.clone()
247 } else {
248 format!("{}.{}", prefix, k)
249 };
250 flatten_recursive(v, &new_key, acc);
251 }
252 }
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_rgb_to_ansi256_grayscale() {
263 assert_eq!(rgb_to_ansi256((0, 0, 0)), 16);
264 assert_eq!(rgb_to_ansi256((255, 255, 255)), 231);
265 let mid = rgb_to_ansi256((128, 128, 128));
266 assert!((232..=255).contains(&mid));
267 }
268
269 #[test]
270 fn test_rgb_to_ansi256_color_cube() {
271 assert_eq!(rgb_to_ansi256((255, 0, 0)), 196);
272 assert_eq!(rgb_to_ansi256((0, 255, 0)), 46);
273 assert_eq!(rgb_to_ansi256((0, 0, 255)), 21);
274 }
275
276 #[test]
277 fn test_truncate_to_width_no_truncation() {
278 assert_eq!(truncate_to_width("Hello", 10), "Hello");
279 assert_eq!(truncate_to_width("Hello", 5), "Hello");
280 }
281
282 #[test]
283 fn test_truncate_to_width_with_truncation() {
284 assert_eq!(truncate_to_width("Hello World", 6), "Hello…");
285 assert_eq!(truncate_to_width("Hello World", 7), "Hello …");
286 }
287
288 #[test]
289 fn test_truncate_to_width_empty() {
290 assert_eq!(truncate_to_width("", 5), "");
291 }
292
293 #[test]
294 fn test_truncate_to_width_exact_fit() {
295 assert_eq!(truncate_to_width("12345", 5), "12345");
296 }
297
298 #[test]
299 fn test_truncate_to_width_one_over() {
300 assert_eq!(truncate_to_width("123456", 5), "1234…");
301 }
302
303 #[test]
304 fn test_truncate_to_width_zero_width() {
305 assert_eq!(truncate_to_width("Hello", 0), "…");
306 }
307
308 #[test]
309 fn test_truncate_to_width_one_width() {
310 assert_eq!(truncate_to_width("Hello", 1), "…");
311 }
312
313 #[test]
314 fn test_serialize_to_xml_named_struct() {
315 #[derive(serde::Serialize)]
316 struct User {
317 name: String,
318 age: u32,
319 }
320
321 let data = User {
322 name: "Alice".into(),
323 age: 30,
324 };
325 let xml = serialize_to_xml(&data).unwrap();
326 assert!(xml.contains("<User>"));
327 assert!(xml.contains("<name>Alice</name>"));
328 assert!(xml.contains("<age>30</age>"));
329 }
330
331 #[test]
332 fn test_serialize_to_xml_json_object() {
333 let data = serde_json::json!({"name": "test", "count": 42});
334 let xml = serialize_to_xml(&data).unwrap();
335 assert!(xml.contains("<data>"));
336 assert!(xml.contains("<name>test</name>"));
337 assert!(xml.contains("<count>42</count>"));
338 }
339
340 #[test]
341 fn test_serialize_to_xml_nested_object() {
342 let data = serde_json::json!({"user": {"name": "Bob", "age": 25}});
343 let xml = serialize_to_xml(&data).unwrap();
344 assert!(xml.contains("<data>"));
345 assert!(xml.contains("<user>"));
346 assert!(xml.contains("<name>Bob</name>"));
347 }
348
349 #[test]
350 fn test_serialize_to_xml_with_array_field() {
351 let data = serde_json::json!({"tags": ["a", "b", "c"]});
352 let xml = serialize_to_xml(&data).unwrap();
353 assert!(xml.contains("<data>"));
354 assert!(xml.contains("<tags>a</tags>"));
355 }
356
357 #[test]
358 fn test_serialize_to_xml_empty_object() {
359 let data = serde_json::json!({});
360 let xml = serialize_to_xml(&data).unwrap();
361 assert!(xml.contains("<data"));
362 }
363
364 #[test]
365 fn test_serialize_to_xml_hashmap() {
366 let mut data = std::collections::HashMap::new();
367 data.insert("key", "value");
368 let xml = serialize_to_xml(&data).unwrap();
369 assert!(xml.contains("<data>"));
370 assert!(xml.contains("<key>value</key>"));
371 }
372
373 #[test]
374 fn test_serialize_to_xml_null() {
375 let data = serde_json::Value::Null;
376 let xml = serialize_to_xml(&data).unwrap();
377 assert!(xml.contains("<data"));
378 }
379
380 #[test]
381 fn test_serialize_to_xml_bare_string() {
382 let data = serde_json::json!("hello");
383 let xml = serialize_to_xml(&data).unwrap();
384 assert!(xml.contains("<data>"));
385 assert!(xml.contains("<value>hello</value>"));
386 }
387
388 #[test]
389 fn test_serialize_to_xml_bare_number() {
390 let data = serde_json::json!(42);
391 let xml = serialize_to_xml(&data).unwrap();
392 assert!(xml.contains("<data>"));
393 assert!(xml.contains("<value>42</value>"));
394 }
395
396 #[test]
397 fn test_serialize_to_xml_bare_bool() {
398 let data = serde_json::json!(true);
399 let xml = serialize_to_xml(&data).unwrap();
400 assert!(xml.contains("<data>"));
401 assert!(xml.contains("<value>true</value>"));
402 }
403
404 #[test]
405 fn test_serialize_to_xml_bare_array() {
406 let data = serde_json::json!(["a", "b", "c"]);
407 let xml = serialize_to_xml(&data).unwrap();
408 assert!(xml.contains("<data>"));
409 assert!(xml.contains("<value>"));
410 }
411
412 #[test]
413 fn test_serialize_to_xml_numeric_keys() {
414 let data = serde_json::json!({"0": "zero", "1": "one"});
415 let xml = serialize_to_xml(&data).unwrap();
416 assert!(xml.contains("<data>"));
417 assert!(xml.contains("<_0>zero</_0>"));
419 assert!(xml.contains("<_1>one</_1>"));
420 }
421
422 #[test]
423 fn test_sanitize_xml_name_valid() {
424 assert_eq!(sanitize_xml_name("name"), "name");
425 assert_eq!(sanitize_xml_name("_private"), "_private");
426 assert_eq!(sanitize_xml_name("item-1"), "item-1");
427 assert_eq!(sanitize_xml_name("a.b"), "a.b");
428 }
429
430 #[test]
431 fn test_sanitize_xml_name_digit_start() {
432 assert_eq!(sanitize_xml_name("0"), "_0");
433 assert_eq!(sanitize_xml_name("1abc"), "_1abc");
434 assert_eq!(sanitize_xml_name("42"), "_42");
435 }
436
437 #[test]
438 fn test_sanitize_xml_name_empty() {
439 assert_eq!(sanitize_xml_name(""), "_");
440 }
441
442 #[test]
443 fn test_sanitize_xml_name_special_chars() {
444 assert_eq!(sanitize_xml_name("a b"), "a_b");
445 assert_eq!(sanitize_xml_name("a@b"), "a_b");
446 }
447
448 #[test]
453 fn test_flatten_csv_simple_object() {
454 let data = serde_json::json!({"name": "Alice", "age": 30});
455 let (headers, rows) = flatten_json_for_csv(&data);
456 assert_eq!(headers, vec!["age", "name"]);
457 assert_eq!(rows, vec![vec!["30", "Alice"]]);
458 }
459
460 #[test]
461 fn test_flatten_csv_array_of_objects() {
462 let data = serde_json::json!([
463 {"name": "Alice", "age": 30},
464 {"name": "Bob", "age": 25}
465 ]);
466 let (headers, rows) = flatten_json_for_csv(&data);
467 assert_eq!(headers, vec!["age", "name"]);
468 assert_eq!(rows.len(), 2);
469 assert_eq!(rows[0], vec!["30", "Alice"]);
470 assert_eq!(rows[1], vec!["25", "Bob"]);
471 }
472
473 #[test]
474 fn test_flatten_csv_nested_objects() {
475 let data = serde_json::json!({"user": {"name": "Alice", "age": 30}});
476 let (headers, rows) = flatten_json_for_csv(&data);
477 assert_eq!(headers, vec!["user.age", "user.name"]);
478 assert_eq!(rows, vec![vec!["30", "Alice"]]);
479 }
480
481 #[test]
482 fn test_flatten_csv_array_field() {
483 let data = serde_json::json!({"name": "Alice", "tags": ["a", "b", "c"]});
484 let (headers, rows) = flatten_json_for_csv(&data);
485 assert!(headers.contains(&"name".to_string()));
486 assert!(headers.contains(&"tags.0".to_string()));
487 assert!(headers.contains(&"tags.1".to_string()));
488 assert!(headers.contains(&"tags.2".to_string()));
489 let name_idx = headers.iter().position(|h| h == "name").unwrap();
491 let t0_idx = headers.iter().position(|h| h == "tags.0").unwrap();
492 let t1_idx = headers.iter().position(|h| h == "tags.1").unwrap();
493 let t2_idx = headers.iter().position(|h| h == "tags.2").unwrap();
494 assert_eq!(rows[0][name_idx], "Alice");
495 assert_eq!(rows[0][t0_idx], "a");
496 assert_eq!(rows[0][t1_idx], "b");
497 assert_eq!(rows[0][t2_idx], "c");
498 }
499
500 #[test]
501 fn test_flatten_csv_nested_array_of_objects() {
502 let data = serde_json::json!({
503 "items": [
504 {"name": "x", "value": 1},
505 {"name": "y", "value": 2}
506 ]
507 });
508 let (headers, rows) = flatten_json_for_csv(&data);
509 assert!(headers.contains(&"items.0.name".to_string()));
510 assert!(headers.contains(&"items.0.value".to_string()));
511 assert!(headers.contains(&"items.1.name".to_string()));
512 assert!(headers.contains(&"items.1.value".to_string()));
513 }
514
515 #[test]
516 fn test_flatten_csv_empty_array_field() {
517 let data = serde_json::json!({"name": "Alice", "tags": []});
518 let (headers, rows) = flatten_json_for_csv(&data);
519 assert_eq!(headers, vec!["name"]);
520 assert_eq!(rows, vec![vec!["Alice"]]);
521 }
522
523 #[test]
524 fn test_flatten_csv_mixed_array_rows() {
525 let data = serde_json::json!([
527 {"name": "Alice", "tags": ["x"]},
528 {"name": "Bob"}
529 ]);
530 let (headers, rows) = flatten_json_for_csv(&data);
531 assert!(headers.contains(&"name".to_string()));
532 assert!(headers.contains(&"tags.0".to_string()));
533 assert_eq!(rows.len(), 2);
534 let t0_idx = headers.iter().position(|h| h == "tags.0").unwrap();
536 assert_eq!(rows[1][t0_idx], "");
537 }
538
539 #[test]
540 fn test_flatten_csv_bare_primitive() {
541 let data = serde_json::json!(42);
542 let (headers, rows) = flatten_json_for_csv(&data);
543 assert_eq!(headers, vec!["value"]);
544 assert_eq!(rows, vec![vec!["42"]]);
545 }
546}