1use std::io::{self, Read};
2
3use clap::Parser;
4use serde_json::Value;
5
6use crate::{Args, Error, OutputFormat};
7
8pub fn setup() -> Result<(Value, String, OutputFormat), Error> {
9 let args = Args::parse();
10
11 let content = if let Some(path) = args.path {
12 std::fs::read_to_string(path)?
13 } else {
14 let mut buffer = String::new();
15 io::stdin().read_to_string(&mut buffer)?;
16 buffer
17 };
18
19 let input_format = detect_input_format(&content);
20
21 let data = parse_content(&content, input_format)?;
22 let query = args.query;
23
24 let format = args
25 .format
26 .parse::<OutputFormat>()
27 .map_err(|e| Error::InvalidFormat(e.to_string()))?;
28
29 Ok((data, query, format))
32}
33
34#[derive(Debug)]
35enum InputFormat {
36 Json,
37 Yaml,
38 Csv,
39}
40
41fn detect_input_format(content: &str) -> InputFormat {
42 let trimmed = content.trim();
43
44 if is_likely_csv(trimmed) {
47 return InputFormat::Csv;
48 }
49
50 if trimmed.contains("apiVersion:")
53 || trimmed.contains("kind:")
54 || trimmed.contains("metadata:")
55 || trimmed.contains("spec:")
56 || (trimmed.contains(":\n") || trimmed.contains(": "))
57 {
58 return InputFormat::Yaml;
61 }
62
63 if (trimmed.starts_with('{') && trimmed.ends_with('}'))
66 || (trimmed.starts_with('[') && trimmed.ends_with(']'))
67 {
68 if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
71 return InputFormat::Json;
72 }
73 }
74
75 if trimmed.contains(':') {
78 InputFormat::Yaml
79 } else {
80 InputFormat::Json
81 }
82}
83
84fn parse_content(content: &str, format: InputFormat) -> Result<Value, Error> {
85 match format {
86 InputFormat::Json => serde_json::from_str(content).map_err(Error::Json),
87 InputFormat::Yaml => {
88 if content.contains("---") {
91 parse_multi_document_yaml(content)
92 } else {
93 serde_yaml::from_str(content).map_err(Error::Yaml)
94 }
95 }
96 InputFormat::Csv => parse_csv_to_json(content),
97 }
98}
99
100fn parse_multi_document_yaml(content: &str) -> Result<Value, Error> {
101 let documents: Vec<&str> = content
102 .split("---")
103 .map(|doc| doc.trim())
104 .filter(|doc| !doc.is_empty())
105 .collect();
106
107 let mut parsed_docs = Vec::new();
108
109 for doc in documents {
110 let parsed: Value = serde_yaml::from_str(doc).map_err(Error::Yaml)?;
111 parsed_docs.push(parsed);
112 }
113
114 Ok(Value::Array(parsed_docs))
117}
118
119fn is_likely_csv(content: &str) -> bool {
120 let lines: Vec<&str> = content.lines().take(5).collect();
121
122 if lines.is_empty() {
123 return false;
124 }
125
126 let first_line = lines[0];
129 let comma_count = first_line.matches(',').count();
130
131 if comma_count > 0 {
134 lines.iter().skip(1).all(|line| {
137 let line_comma_count = line.matches(',').count();
138 (line_comma_count as i32 - comma_count as i32).abs() <= 1
139 })
140 } else {
141 false
142 }
143}
144
145fn parse_csv_to_json(content: &str) -> Result<Value, Error> {
146 let mut reader = csv::Reader::from_reader(content.as_bytes());
147
148 let headers: Vec<String> = reader
151 .headers()
152 .map_err(|e| Error::Csv(e))?
153 .iter()
154 .map(|h| h.trim().to_string())
155 .collect();
156
157 let mut records = Vec::new();
158
159 for result in reader.records() {
160 let record = result.map_err(|e| Error::Csv(e))?;
161 let mut object = serde_json::Map::new();
162
163 for (i, field) in record.iter().enumerate() {
164 if let Some(header) = headers.get(i) {
165 let value = infer_value_type(field.trim());
166 object.insert(header.clone(), value);
167 }
168 }
169
170 records.push(Value::Object(object));
171 }
172
173 Ok(Value::Array(records))
176}
177
178fn infer_value_type(field: &str) -> Value {
179 if field.is_empty() {
182 return Value::Null;
183 }
184
185 match field.to_lowercase().as_str() {
188 "true" => return Value::Bool(true),
189 "false" => return Value::Bool(false),
190 _ => {}
191 }
192
193 if let Ok(int_val) = field.parse::<i64>() {
196 return Value::Number(serde_json::Number::from(int_val));
197 }
198
199 if let Ok(float_val) = field.parse::<f64>() {
202 if let Some(num) = serde_json::Number::from_f64(float_val) {
203 return Value::Number(num);
204 }
205 }
206
207 Value::String(field.to_string())
210}