hawk_data/
setup.rs

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    // debug
30    // debug_json_order(&json);
31    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    // CSV判定を最初に行う(シンプルな形式から)
45    // Perform CSV detection first (from simple formats)
46    if is_likely_csv(trimmed) {
47        return InputFormat::Csv;
48    }
49
50    // YAML判定を先に行う(より具体的な条件)
51    // Perform YAML validation first (more specific conditions)
52    if trimmed.contains("apiVersion:")
53        || trimmed.contains("kind:")
54        || trimmed.contains("metadata:")
55        || trimmed.contains("spec:")
56        || (trimmed.contains(":\n") || trimmed.contains(": "))
57    {
58        // YAML特有のkey: value形式
59        // YAML-specific key: value format
60        return InputFormat::Yaml;
61    }
62
63    // JSON判定(厳密にチェック)
64    // JSON check (strict check)
65    if (trimmed.starts_with('{') && trimmed.ends_with('}'))
66        || (trimmed.starts_with('[') && trimmed.ends_with(']'))
67    {
68        // さらに、全体がJSONとして有効かチェック
69        // Furthermore, check whether the whole thing is valid as JSON.
70        if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
71            return InputFormat::Json;
72        }
73    }
74
75    // 最後の手段: コロンがあればYAML、なければJSON
76    // Last resort: YAML if there is a colon, JSON if there isn't
77    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            // 複数ドキュメントに対応
89            // Supports multiple documents
90            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    // 複数ドキュメントを配列として返す
115    // Return multiple documents as an array
116    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    // 最初の行をヘッダーとして想定
127    // Assume the first line as the header
128    let first_line = lines[0];
129    let comma_count = first_line.matches(',').count();
130
131    // カンマが1個以上あり、他の行も同じような構造
132    // There is at least one comma, and other lines have the same structure.
133    if comma_count > 0 {
134        // 他の行も同じようなカンマ数か確認
135        // Check if other lines have the same number of commas.
136        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    // ヘッダーを取得
149    // Get header
150    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    // 直接配列を返す(二重配列にしない)
174    // Return the array directly (do not make it a double array)
175    Ok(Value::Array(records))
176}
177
178fn infer_value_type(field: &str) -> Value {
179    // 空文字チェック
180    // Empty string check
181    if field.is_empty() {
182        return Value::Null;
183    }
184
185    // 真偽値判定
186    // True/False determination
187    match field.to_lowercase().as_str() {
188        "true" => return Value::Bool(true),
189        "false" => return Value::Bool(false),
190        _ => {}
191    }
192
193    // 整数判定
194    // Integer determination
195    if let Ok(int_val) = field.parse::<i64>() {
196        return Value::Number(serde_json::Number::from(int_val));
197    }
198
199    // 浮動小数点数判定
200    // Floating point number determination
201    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    // デフォルトは文字列
208    // Default is a string
209    Value::String(field.to_string())
210}