1use serde_json::Value;
2
3use crate::cli::args::OutputFormat;
4use crate::cli::common::tab_separated_values;
5use crate::cli::error::AppError;
6
7pub fn write_success(value: &Value, format: &OutputFormat) -> Result<(), AppError> {
9 if value["meta"]["output_shape"].as_str() == Some("jsonl") {
10 return write_jsonl_records(value);
11 }
12
13 match format {
14 OutputFormat::Json => {
15 let s = serde_json::to_string_pretty(value).map_err(|e| AppError::InternalError {
16 message: format!("JSON serialization failed: {}", e),
17 })?;
18 println!("{}", s);
19 }
20 OutputFormat::Text => {
21 write_text(value)?;
22 }
23 }
24 Ok(())
25}
26
27fn write_jsonl_records(value: &Value) -> Result<(), AppError> {
28 let Some(records) = value["data"]["records"].as_array() else {
29 return Err(AppError::InternalError {
30 message: "JSONL output requires record data".to_string(),
31 });
32 };
33
34 for record in records {
35 let s = serde_json::to_string(record).map_err(|e| AppError::InternalError {
36 message: format!("JSON serialization failed: {}", e),
37 })?;
38 println!("{}", s);
39 }
40
41 Ok(())
42}
43
44pub fn write_error(value: &Value) {
46 if let Ok(s) = serde_json::to_string_pretty(value) {
47 eprintln!("{}", s);
48 } else {
49 eprintln!("{{\"error\":\"Internal JSON serialization error\"}}");
50 }
51}
52
53fn write_text(value: &Value) -> Result<(), AppError> {
55 let command = value["command"].as_str().unwrap_or("unknown");
56
57 match command {
58 "inspect.workbook" => write_text_workbook(value)?,
59 "inspect.sheet" => write_text_sheet(value)?,
60 "inspect.sample" => write_text_sample(value)?,
61 "inspect.columns" => write_text_columns(value)?,
62 "inspect.tables" => write_text_tables(value)?,
63 "read.cell" => write_text_cell(value)?,
64 "read.range" => write_text_range(value)?,
65 "read.rows" => write_text_rows(value)?,
66 _ => {
67 let s = serde_json::to_string_pretty(value).map_err(|e| AppError::InternalError {
69 message: format!("JSON serialization failed: {}", e),
70 })?;
71 println!("{}", s);
72 }
73 }
74
75 Ok(())
76}
77
78fn write_text_workbook(value: &Value) -> Result<(), AppError> {
79 let data = &value["data"];
80 if let Some(sheets) = data["sheets"].as_array() {
81 for sheet in sheets {
82 let name = sheet["name"].as_str().unwrap_or("");
83 let index = sheet["index"].as_u64().unwrap_or(0);
84 println!("{} {}", index, name);
85 }
86 }
87 Ok(())
88}
89
90fn write_text_sheet(value: &Value) -> Result<(), AppError> {
91 let data = &value["data"];
92 if let Some(name) = data["name"].as_str() {
93 println!("name\t{}", name);
94 }
95 if let Some(index) = data["index"].as_u64() {
96 println!("index\t{}", index);
97 }
98 if let Some(max_rows) = data["max_rows"].as_u64() {
99 println!("max_rows\t{}", max_rows);
100 }
101 if let Some(max_cols) = data["max_cols"].as_u64() {
102 println!("max_cols\t{}", max_cols);
103 }
104 if let Some(used_range) = data["used_range"].as_str() {
105 println!("used_range\t{}", used_range);
106 }
107 Ok(())
108}
109
110fn write_text_sample(value: &Value) -> Result<(), AppError> {
111 let data = &value["data"];
112 if let Some(rows) = data["rows"].as_array() {
113 write_row_arrays(rows);
114 } else if let Some(records) = data["records"].as_array() {
115 write_record_objects(records);
116 }
117 Ok(())
118}
119
120fn write_text_columns(value: &Value) -> Result<(), AppError> {
121 println!("index\tname\tsafe_name\tis_duplicate\tinferred_type\tnon_null_ratio\tformula_ratio");
122
123 if let Some(columns) = value["data"]["columns"].as_array() {
124 for column in columns {
125 let index = column["index"].as_u64().unwrap_or(0);
126 let name = column["name"].as_str().unwrap_or("");
127 let safe_name = column["safe_name"].as_str().unwrap_or("");
128 let is_duplicate = column["is_duplicate"].as_bool().unwrap_or(false);
129 let inferred_type = column["inferred_type"].as_str().unwrap_or("");
130 let non_null_ratio = format_ratio(column["non_null_ratio"].as_f64().unwrap_or(0.0));
131 let formula_ratio = format_ratio(column["formula_ratio"].as_f64().unwrap_or(0.0));
132
133 println!(
134 "{}\t{}\t{}\t{}\t{}\t{}\t{}",
135 index, name, safe_name, is_duplicate, inferred_type, non_null_ratio, formula_ratio
136 );
137 }
138 }
139
140 Ok(())
141}
142
143fn format_ratio(value: f64) -> String {
144 let rounded = (value * 1000.0).round() / 1000.0;
145 if (rounded.fract()).abs() < f64::EPSILON {
146 format!("{rounded:.0}")
147 } else {
148 let formatted = format!("{rounded:.3}");
149 formatted
150 .trim_end_matches('0')
151 .trim_end_matches('.')
152 .to_string()
153 }
154}
155
156fn write_text_tables(value: &Value) -> Result<(), AppError> {
157 let data = &value["data"];
158 if let Some(candidates) = data["candidates"].as_array() {
159 for candidate in candidates {
160 let range = candidate["range"].as_str().unwrap_or("");
161 let header_row = candidate["header_row"].as_u64().unwrap_or(0);
162 let column_count = candidate["column_count"].as_u64().unwrap_or(0);
163 let row_count = candidate["row_count"].as_u64().unwrap_or(0);
164 let confidence = candidate["confidence"].as_f64().unwrap_or(0.0);
165 println!(
166 "{}\theader_row={}\tcolumns={}\trows={}\tconfidence={:.2}",
167 range, header_row, column_count, row_count, confidence
168 );
169 }
170 }
171 Ok(())
172}
173
174fn write_text_cell(value: &Value) -> Result<(), AppError> {
175 if let Some(v) = value["data"]["value"].as_str() {
176 println!("{}", v);
177 } else {
178 println!("{}", value["data"]["value"]);
179 }
180 Ok(())
181}
182
183fn write_text_range(value: &Value) -> Result<(), AppError> {
184 let data = &value["data"];
185 if let Some(rows) = data["rows"].as_array() {
186 write_row_arrays(rows);
187 }
188 Ok(())
189}
190
191fn write_text_rows(value: &Value) -> Result<(), AppError> {
192 let data = &value["data"];
193 if let Some(rows) = data["rows"].as_array() {
194 write_row_arrays(rows);
195 } else if let Some(records) = data["records"].as_array() {
196 write_record_objects(records);
197 }
198 Ok(())
199}
200
201fn write_row_arrays(rows: &[Value]) {
202 for row in rows {
203 if let Some(cells) = row.as_array() {
204 println!("{}", tab_separated_values(cells));
205 }
206 }
207}
208
209fn write_record_objects(records: &[Value]) {
210 for record in records {
211 if let Some(obj) = record.as_object() {
212 let parts: Vec<String> = obj.iter().map(|(k, v)| format!("{}={}", k, v)).collect();
213 println!("{}", parts.join("\t"));
214 }
215 }
216}