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    Text,
40}
41
42fn detect_input_format(content: &str) -> InputFormat {
43    let trimmed = content.trim();
44
45    // CSV判定を最初に行う(シンプルな形式から)
46    if is_likely_csv(trimmed) {
47        return InputFormat::Csv;
48    }
49
50    // JSON判定(厳密にチェック)- YAMLより先に判定
51    if (trimmed.starts_with('{') && trimmed.ends_with('}'))
52        || (trimmed.starts_with('[') && trimmed.ends_with(']'))
53    {
54        // さらに、全体がJSONとして有効かチェック
55        if serde_json::from_str::<serde_json::Value>(trimmed).is_ok() {
56            return InputFormat::Json;
57        }
58    }
59
60    // YAML判定 - より厳格な条件に変更
61    if is_structured_yaml(trimmed) {
62        return InputFormat::Yaml;
63    }
64
65    // 上記のいずれにも該当しない場合はText
66    InputFormat::Text
67}
68
69// 構造化されたYAMLかどうかを厳格に判定
70fn is_structured_yaml(content: &str) -> bool {
71    let lines: Vec<&str> = content.lines().collect();
72    
73    if lines.is_empty() {
74        return false;
75    }
76
77    // Kubernetes/Docker Compose等の明確なYAMLマーカー
78    if content.contains("apiVersion:") || content.contains("kind:") 
79       || content.contains("version:") || content.contains("services:") {
80        return true;
81    }
82
83    let mut yaml_indicators = 0;
84    let mut total_meaningful_lines = 0;
85
86    for line in lines {
87        let trimmed = line.trim();
88        
89        // 空行やコメントは除外
90        if trimmed.is_empty() || trimmed.starts_with('#') {
91            continue;
92        }
93        
94        total_meaningful_lines += 1;
95
96        // YAML構造の特徴を検出
97        if is_valid_yaml_line(trimmed) {
98            yaml_indicators += 1;
99        }
100    }
101
102    // 意味のある行が少ない場合はYAMLではない
103    if total_meaningful_lines < 3 {
104        return false;
105    }
106
107    // 80%以上の行がYAML構造ならYAMLと判定
108    (yaml_indicators as f64 / total_meaningful_lines as f64) > 0.8
109}
110
111// 有効なYAML行かどうかを判定
112fn is_valid_yaml_line(line: &str) -> bool {
113    // リスト形式 (- item)
114    if line.starts_with("- ") {
115        return true;
116    }
117
118    // key: value 形式
119    if let Some(colon_pos) = line.find(':') {
120        let key_part = line[..colon_pos].trim();
121        let value_part = line[colon_pos + 1..].trim();
122
123        // キー部分の検証
124        if key_part.is_empty() {
125            return false;
126        }
127
128        // キーに無効な文字が含まれていない
129        if key_part.contains(' ') && !key_part.starts_with('"') && !key_part.starts_with('\'') {
130            return false;
131        }
132
133        // インデントされたネスト構造
134        if line.starts_with("  ") || line.starts_with("\t") {
135            return true;
136        }
137
138        // 値が明らかにYAML的
139        if value_part.is_empty() 
140           || value_part.starts_with('[') 
141           || value_part.starts_with('{')
142           || value_part == "true" 
143           || value_part == "false" 
144           || value_part.parse::<f64>().is_ok() {
145            return true;
146        }
147
148        // パス、URL、タイムスタンプなどが含まれていたらYAMLではない可能性が高い
149        if value_part.contains('/') && value_part.len() > 10 {
150            return false;
151        }
152
153        return true;
154    }
155
156    false
157}
158
159fn parse_content(content: &str, format: InputFormat) -> Result<Value, Error> {
160    match format {
161        InputFormat::Json => serde_json::from_str(content).map_err(Error::Json),
162        InputFormat::Yaml => {
163            // 複数ドキュメントに対応
164            if content.contains("---") {
165                parse_multi_document_yaml(content)
166            } else {
167                serde_yaml::from_str(content).map_err(Error::Yaml)
168            }
169        }
170        InputFormat::Csv => parse_csv_to_json(content),
171        InputFormat::Text => parse_text_to_json(content),
172    }
173}
174
175fn parse_text_to_json(content: &str) -> Result<Value, Error> {
176    // テキストを行ごとに分割して配列として扱う
177    let lines: Vec<Value> = content
178        .lines()
179        .map(|line| Value::String(line.to_string()))
180        .collect();
181    
182    // 空のファイルの場合も配列として返す
183    Ok(Value::Array(lines))
184}
185
186fn parse_multi_document_yaml(content: &str) -> Result<Value, Error> {
187    let documents: Vec<&str> = content
188        .split("---")
189        .map(|doc| doc.trim())
190        .filter(|doc| !doc.is_empty())
191        .collect();
192
193    let mut parsed_docs = Vec::new();
194
195    for doc in documents {
196        let parsed: Value = serde_yaml::from_str(doc).map_err(Error::Yaml)?;
197        parsed_docs.push(parsed);
198    }
199
200    // 複数ドキュメントを配列として返す
201    Ok(Value::Array(parsed_docs))
202}
203
204fn is_likely_csv(content: &str) -> bool {
205    let lines: Vec<&str> = content.lines().take(5).collect();
206
207    if lines.is_empty() {
208        return false;
209    }
210
211    // 最初の行をヘッダーとして想定
212    let first_line = lines[0];
213    let comma_count = first_line.matches(',').count();
214
215    // カンマが1個以上あり、他の行も同じような構造
216    if comma_count > 0 {
217        // 他の行も同じようなカンマ数か確認
218        lines.iter().skip(1).all(|line| {
219            let line_comma_count = line.matches(',').count();
220            (line_comma_count as i32 - comma_count as i32).abs() <= 1
221        })
222    } else {
223        false
224    }
225}
226
227fn parse_csv_to_json(content: &str) -> Result<Value, Error> {
228    let mut reader = csv::Reader::from_reader(content.as_bytes());
229
230    // ヘッダーを取得
231    let headers: Vec<String> = reader
232        .headers()
233        .map_err(Error::Csv)?
234        .iter()
235        .map(|h| h.trim().to_string())
236        .collect();
237
238    let mut records = Vec::new();
239
240    for result in reader.records() {
241        let record = result.map_err(Error::Csv)?;
242        let mut object = serde_json::Map::new();
243
244        for (i, field) in record.iter().enumerate() {
245            if let Some(header) = headers.get(i) {
246                let value = infer_value_type(field.trim());
247                object.insert(header.clone(), value);
248            }
249        }
250
251        records.push(Value::Object(object));
252    }
253
254    // 直接配列を返す(二重配列にしない)
255    Ok(Value::Array(records))
256}
257
258fn infer_value_type(field: &str) -> Value {
259    // 空文字チェック
260    if field.is_empty() {
261        return Value::Null;
262    }
263
264    // 真偽値判定
265    match field.to_lowercase().as_str() {
266        "true" => return Value::Bool(true),
267        "false" => return Value::Bool(false),
268        _ => {}
269    }
270
271    // 整数判定
272    if let Ok(int_val) = field.parse::<i64>() {
273        return Value::Number(serde_json::Number::from(int_val));
274    }
275
276    // 浮動小数点数判定
277    if let Ok(float_val) = field.parse::<f64>() {
278        if let Some(num) = serde_json::Number::from_f64(float_val) {
279            return Value::Number(num);
280        }
281    }
282
283    // デフォルトは文字列
284    Value::String(field.to_string())
285}
286
287// テキスト処理用のヘルパー関数
288pub fn text_to_json_values(content: &str) -> Result<Vec<Value>, Error> {
289    let lines: Vec<Value> = content
290        .lines()
291        .map(|line| Value::String(line.to_string()))
292        .collect();
293    Ok(lines)
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_text_parsing() {
302        let content = "line1\nline2\nERROR: something happened";
303        let result = parse_text_to_json(content).unwrap();
304        
305        if let Value::Array(lines) = result {
306            assert_eq!(lines.len(), 3);
307            assert_eq!(lines[0], Value::String("line1".to_string()));
308            assert_eq!(lines[1], Value::String("line2".to_string()));
309            assert_eq!(lines[2], Value::String("ERROR: something happened".to_string()));
310        } else {
311            panic!("Expected array result");
312        }
313    }
314    
315    #[test]
316    fn test_yaml_detection() {
317        use super::{is_structured_yaml, is_valid_yaml_line};
318        
319        // 明確にYAMLとして認識されるべき
320        assert!(is_structured_yaml("apiVersion: v1\nkind: Pod"));
321        assert!(is_structured_yaml("key: value\nother: data\nnested:\n  sub: item"));
322        
323        // YAMLとして認識されないべき
324        assert!(!is_structured_yaml("2024-01-01 10:00:00 INFO Starting"));
325        assert!(!is_structured_yaml("plain text\nwith some: colons"));
326        assert!(!is_structured_yaml("ServerName: localhost\nServerPort: 8080")); // 設定ファイル風だがYAMLではない
327        
328        // 個別行のテスト
329        assert!(is_valid_yaml_line("key: value"));
330        assert!(is_valid_yaml_line("  nested: item"));
331        assert!(is_valid_yaml_line("- list_item"));
332        assert!(!is_valid_yaml_line("2024-01-01 10:00:00 INFO message"));
333        assert!(!is_valid_yaml_line("random text line"));
334    }
335}