systemprompt_cli/commands/analytics/shared/
export.rs1use anyhow::{Context, Result};
2use serde::Serialize;
3use std::fs::File;
4use std::io::Write;
5use std::path::Path;
6
7pub fn export_to_csv<T: Serialize>(data: &[T], path: &Path) -> Result<()> {
8 let file = File::create(path).context("Failed to create export file")?;
9 let mut writer = std::io::BufWriter::new(file);
10
11 if data.is_empty() {
12 return Ok(());
13 }
14
15 let json_value = serde_json::to_value(&data[0])?;
16 if let serde_json::Value::Object(obj) = json_value {
17 let headers: Vec<&str> = obj.keys().map(String::as_str).collect();
18 writeln!(writer, "{}", headers.join(","))?;
19 }
20
21 for record in data {
22 let json = serde_json::to_value(record)?;
23 if let serde_json::Value::Object(obj) = json {
24 let values: Vec<String> = obj
25 .values()
26 .map(|v| match v {
27 serde_json::Value::String(s) => escape_csv_field(s),
28 serde_json::Value::Null => String::new(),
29 _ => v.to_string(),
30 })
31 .collect();
32 writeln!(writer, "{}", values.join(","))?;
33 }
34 }
35
36 writer.flush()?;
37 Ok(())
38}
39
40pub fn export_single_to_csv<T: Serialize>(data: &T, path: &Path) -> Result<()> {
41 let file = File::create(path).context("Failed to create export file")?;
42 let mut writer = std::io::BufWriter::new(file);
43
44 let json = serde_json::to_value(data)?;
45 if let serde_json::Value::Object(obj) = json {
46 let headers: Vec<&str> = obj.keys().map(String::as_str).collect();
47 writeln!(writer, "{}", headers.join(","))?;
48
49 let values: Vec<String> = obj
50 .values()
51 .map(|v| match v {
52 serde_json::Value::String(s) => escape_csv_field(s),
53 serde_json::Value::Null => String::new(),
54 _ => v.to_string(),
55 })
56 .collect();
57 writeln!(writer, "{}", values.join(","))?;
58 }
59
60 writer.flush()?;
61 Ok(())
62}
63
64fn escape_csv_field(s: &str) -> String {
65 if s.contains(',') || s.contains('"') || s.contains('\n') {
66 format!("\"{}\"", s.replace('"', "\"\""))
67 } else {
68 s.to_string()
69 }
70}
71
72#[derive(Debug)]
73pub struct CsvBuilder {
74 headers: Vec<String>,
75 rows: Vec<Vec<String>>,
76}
77
78impl CsvBuilder {
79 pub const fn new() -> Self {
80 Self {
81 headers: Vec::new(),
82 rows: Vec::new(),
83 }
84 }
85
86 pub fn headers(mut self, headers: Vec<&str>) -> Self {
87 self.headers = headers.into_iter().map(String::from).collect();
88 self
89 }
90
91 pub fn add_row(&mut self, row: Vec<String>) {
92 self.rows.push(row);
93 }
94
95 pub fn write_to_file(&self, path: &Path) -> Result<()> {
96 let mut file = File::create(path).context("Failed to create export file")?;
97
98 writeln!(file, "{}", self.headers.join(","))?;
99
100 for row in &self.rows {
101 let escaped: Vec<String> = row.iter().map(|cell| escape_csv_field(cell)).collect();
102 writeln!(file, "{}", escaped.join(","))?;
103 }
104
105 Ok(())
106 }
107}
108
109impl Default for CsvBuilder {
110 fn default() -> Self {
111 Self::new()
112 }
113}