hawk_data/
output.rs

1use indexmap::IndexSet;
2use serde_json::Value;
3use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
4
5use crate::{Error, OutputFormat, value_to_string};
6
7#[derive(Debug)]
8enum DataType {
9    SimpleList,  // [string, number, bool]
10    ObjectArray, // [{"name": "Alice"}, {"name": "Bob"}]
11    NestedArray, // [[{"name": "Project1"}], [{"name": "Project2"}]]
12    Mixed,       // Other complex structures
13}
14
15/// カラー出力設定
16struct ColorScheme {
17    header: ColorSpec,
18    number: ColorSpec,
19    string: ColorSpec,
20    boolean: ColorSpec,
21    null: ColorSpec,
22    array_info: ColorSpec,
23}
24
25impl ColorScheme {
26    fn new() -> Self {
27        let mut header = ColorSpec::new();
28        header.set_fg(Some(Color::Blue)).set_bold(true);
29        
30        let mut number = ColorSpec::new();
31        number.set_fg(Some(Color::Green));
32        
33        let mut boolean = ColorSpec::new();
34        boolean.set_fg(Some(Color::Yellow));
35        
36        let mut null = ColorSpec::new();
37        null.set_fg(Some(Color::Black)).set_intense(true); // グレー
38        
39        let mut array_info = ColorSpec::new();
40        array_info.set_fg(Some(Color::Cyan));
41        
42        Self {
43            header,
44            number,
45            string: ColorSpec::new(), // デフォルト色
46            boolean,
47            null,
48            array_info,
49        }
50    }
51}
52
53/// TTY判定とカラー出力の可否
54fn should_use_colors() -> bool {
55    std::io::IsTerminal::is_terminal(&std::io::stdout()) 
56        && std::env::var("NO_COLOR").is_err()
57}
58
59/// 値の型に応じたカラースペックを取得
60fn get_color_for_value<'a>(value: &Value, colors: &'a ColorScheme) -> &'a ColorSpec {
61    match value {
62        Value::Number(_) => &colors.number,
63        Value::Bool(_) => &colors.boolean,
64        Value::Null => &colors.null,
65        _ => &colors.string,
66    }
67}
68
69pub fn format_output(data: &[Value], format: OutputFormat) -> Result<(), Error> {
70    if data.is_empty() {
71        return Ok(());
72    }
73
74    let use_colors = should_use_colors();
75
76    match format {
77        OutputFormat::Json => {
78            // 明示的にJSON出力
79            print_as_json(data, use_colors)?;
80        }
81        OutputFormat::Table => {
82            // 明示的にテーブル出力(可能な場合)
83            if is_object_array(data) {
84                print_as_table(data, use_colors)?;
85            } else {
86                let flattened = flatten_nested_arrays(data);
87                if is_object_array(&flattened) {
88                    print_as_table(&flattened, use_colors)?;
89                } else {
90                    return Err(Error::InvalidQuery(
91                        "Cannot display as table: data is not object array".into(),
92                    ));
93                }
94            }
95        }
96        OutputFormat::List => {
97            // 明示的にリスト出力
98            print_as_list(data, use_colors)?;
99        }
100        OutputFormat::Auto => {
101            // 既存のスマート判定ロジック
102            match analyze_data_structure(data) {
103                DataType::SimpleList => print_as_list(data, use_colors)?,
104                DataType::ObjectArray => print_as_table(data, use_colors)?,
105                DataType::NestedArray => {
106                    let flattened = flatten_nested_arrays(data);
107                    if is_object_array(&flattened) {
108                        print_as_table(&flattened, use_colors)?;
109                    } else if is_simple_values(&flattened) {
110                        print_as_list(&flattened, use_colors)?;
111                    } else {
112                        print_as_json(data, use_colors)?;
113                    }
114                }
115                DataType::Mixed => print_as_json(data, use_colors)?,
116            }
117        }
118    }
119
120    Ok(())
121}
122
123fn analyze_data_structure(data: &[Value]) -> DataType {
124    if is_simple_values(data) {
125        return DataType::SimpleList;
126    }
127
128    if is_object_array(data) {
129        return DataType::ObjectArray;
130    }
131
132    // ネストした配列かチェック
133    if data.len() == 1 && data[0].is_array() {
134        return DataType::NestedArray;
135    }
136
137    DataType::Mixed
138}
139
140fn flatten_nested_arrays(data: &[Value]) -> Vec<Value> {
141    let mut flattened = Vec::new();
142
143    for item in data {
144        match item {
145            Value::Array(arr) => {
146                // 配列の中身を展開
147                flattened.extend(arr.iter().cloned());
148            }
149            _ => {
150                flattened.push(item.clone());
151            }
152        }
153    }
154
155    flattened
156}
157
158fn collect_flattened_fields_ordered(value: &Value, prefix: &str, fields: &mut IndexSet<String>) {
159    match value {
160        Value::Object(obj) => {
161            for (key, val) in obj {
162                // serde_json::Mapは順序を保持
163                let field_name = if prefix.is_empty() {
164                    key.clone()
165                } else {
166                    format!("{}.{}", prefix, key)
167                };
168
169                match val {
170                    Value::Object(_) => {
171                        collect_flattened_fields_ordered(val, &field_name, fields);
172                    }
173                    _ => {
174                        fields.insert(field_name);
175                    }
176                }
177            }
178        }
179        _ => {
180            if !prefix.is_empty() {
181                fields.insert(prefix.to_string());
182            }
183        }
184    }
185}
186
187fn get_flattened_value(item: &Value, field_path: &str) -> String {
188    let parts: Vec<&str> = field_path.split('.').collect();
189    let mut current = item;
190
191    for part in parts {
192        match current.get(part) {
193            Some(val) => current = val,
194            None => return "".to_string(),
195        }
196    }
197
198    match current {
199        Value::Array(arr) => {
200            // 配列は簡略表示
201            format!("[{} items]", arr.len())
202        }
203        _ => value_to_string(current),
204    }
205}
206
207fn get_field_value_for_coloring(item: &Value, field_path: &str) -> Value {
208    let parts: Vec<&str> = field_path.split('.').collect();
209    let mut current = item;
210
211    for part in parts {
212        match current.get(part) {
213            Some(val) => current = val,
214            None => return Value::Null,
215        }
216    }
217
218    current.clone()
219}
220
221fn get_value_type_info(value: &Value) -> &'static str {
222    match value {
223        Value::String(_) => "String",
224        Value::Number(_) => "Number",
225        Value::Bool(_) => "Boolean",
226        Value::Array(_) => "Array",
227        Value::Object(_) => "Object",
228        Value::Null => "Null",
229    }
230}
231
232fn get_sample_value(value: &Value) -> String {
233    match value {
234        Value::String(s) => format!("\"{}\"", s.chars().take(20).collect::<String>()),
235        Value::Number(n) => n.to_string(),
236        Value::Bool(b) => b.to_string(),
237        Value::Array(arr) => format!("[{} items]", arr.len()),
238        Value::Object(obj) => format!("{{{}...}}", obj.keys().next().unwrap_or(&"".to_string())),
239        Value::Null => "null".to_string(),
240    }
241}
242
243fn is_simple_values(data: &[Value]) -> bool {
244    data.iter()
245        .all(|v| matches!(v, Value::String(_) | Value::Number(_) | Value::Bool(_)))
246}
247
248fn is_object_array(data: &[Value]) -> bool {
249    data.iter().all(|v| v.is_object())
250}
251
252fn print_as_list(data: &[Value], use_colors: bool) -> Result<(), Error> {
253    if use_colors {
254        let mut stdout = StandardStream::stdout(ColorChoice::Always);
255        let colors = ColorScheme::new();
256        
257        for item in data {
258            let color = get_color_for_value(item, &colors);
259            stdout.set_color(color)?;
260            print!("{}", value_to_string(item));
261            stdout.reset()?;
262            println!();
263        }
264    } else {
265        data.iter().for_each(|item| {
266            println!("{}", value_to_string(item));
267        });
268    }
269    
270    Ok(())
271}
272
273fn print_as_json(data: &[Value], use_colors: bool) -> Result<(), Error> {
274    if use_colors {
275        print_colored_json_simple(data)?;
276    } else {
277        let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
278        println!("{}", json);
279    }
280    
281    Ok(())
282}
283
284fn print_colored_json_simple(data: &[Value]) -> Result<(), Error> {
285    let mut stdout = StandardStream::stdout(ColorChoice::Always);
286    let colors = ColorScheme::new();
287    
288    // シンプルなアプローチ:行ごとに処理
289    let json = serde_json::to_string_pretty(data).map_err(Error::Json)?;
290    
291    for line in json.lines() {
292        let trimmed = line.trim();
293        
294        // 行の内容に応じて色付け
295        if trimmed.starts_with('"') && trimmed.contains(':') {
296            // キー行: "key": value
297            if let Some(colon_pos) = trimmed.find(':') {
298                let key_part = &trimmed[..colon_pos + 1];
299                let value_part = &trimmed[colon_pos + 1..].trim();
300                
301                // インデントを保持
302                let indent = &line[..line.len() - line.trim_start().len()];
303                print!("{}", indent);
304                
305                // キー部分(青色)
306                stdout.set_color(&colors.header)?;
307                print!("{}", key_part);
308                stdout.reset()?;
309                
310                print!(" ");
311                
312                // 値部分(型に応じた色)
313                print_colored_json_value(value_part, &colors, &mut stdout)?;
314                println!();
315            } else {
316                println!("{}", line);
317            }
318        } else if trimmed.starts_with('{') || trimmed.starts_with('}') || 
319                  trimmed.starts_with('[') || trimmed.starts_with(']') {
320            // 構造文字(青色)
321            let indent = &line[..line.len() - line.trim_start().len()];
322            print!("{}", indent);
323            stdout.set_color(&colors.header)?;
324            print!("{}", trimmed);
325            stdout.reset()?;
326            println!();
327        } else {
328            // その他の行(配列要素など)
329            let indent = &line[..line.len() - line.trim_start().len()];
330            print!("{}", indent);
331            print_colored_json_value(trimmed, &colors, &mut stdout)?;
332            println!();
333        }
334    }
335    
336    Ok(())
337}
338
339fn print_colored_json_value(value_str: &str, colors: &ColorScheme, stdout: &mut StandardStream) -> Result<(), Error> {
340    let clean_value = value_str.trim_end_matches(',');
341    
342    if clean_value == "null" {
343        stdout.set_color(&colors.null)?;
344        print!("{}", value_str);
345        stdout.reset()?;
346    } else if clean_value == "true" || clean_value == "false" {
347        stdout.set_color(&colors.boolean)?;
348        print!("{}", value_str);
349        stdout.reset()?;
350    } else if clean_value.starts_with('"') && clean_value.ends_with('"') {
351        // 文字列値
352        stdout.set_color(&colors.string)?;
353        print!("{}", value_str);
354        stdout.reset()?;
355    } else if clean_value.parse::<f64>().is_ok() {
356        // 数値
357        stdout.set_color(&colors.number)?;
358        print!("{}", value_str);
359        stdout.reset()?;
360    } else {
361        // その他
362        print!("{}", value_str);
363    }
364    
365    Ok(())
366}
367
368fn print_as_table(data: &[Value], use_colors: bool) -> Result<(), Error> {
369    if data.is_empty() {
370        return Ok(());
371    }
372
373    // 1. 全オブジェクトからフラット化されたフィールド名を収集
374    let mut all_fields = IndexSet::new();
375    for item in data {
376        collect_flattened_fields_ordered(item, "", &mut all_fields);
377    }
378
379    let fields: Vec<String> = all_fields.into_iter().collect();
380
381    // 2. 各列の最大幅を計算
382    let mut max_widths = vec![0; fields.len()];
383
384    // ヘッダーの幅
385    for (i, field) in fields.iter().enumerate() {
386        max_widths[i] = field.len();
387    }
388
389    // データの幅
390    for item in data {
391        for (i, field) in fields.iter().enumerate() {
392            let value_str = get_flattened_value(item, field);
393            max_widths[i] = max_widths[i].max(value_str.len());
394        }
395    }
396
397    if use_colors {
398        print_colored_table(data, &fields, &max_widths)?;
399    } else {
400        print_plain_table(data, &fields, &max_widths);
401    }
402
403    Ok(())
404}
405
406fn print_colored_table(data: &[Value], fields: &[String], max_widths: &[usize]) -> Result<(), Error> {
407    let mut stdout = StandardStream::stdout(ColorChoice::Always);
408    let colors = ColorScheme::new();
409
410    // 3. ヘッダー出力(色付き)
411    stdout.set_color(&colors.header)?;
412    for (i, field) in fields.iter().enumerate() {
413        print!("{:<width$}", field, width = max_widths[i]);
414        if i < fields.len() - 1 {
415            print!("  ");
416        }
417    }
418    stdout.reset()?;
419    println!();
420
421    // 4. データ行出力(色付き)
422    for item in data {
423        for (i, field) in fields.iter().enumerate() {
424            let value_str = get_flattened_value(item, field);
425            
426            // 値の型に応じて色を設定
427            let value = get_field_value_for_coloring(item, field);
428            let color = get_color_for_value(&value, &colors);
429            
430            stdout.set_color(color)?;
431            print!("{:<width$}", value_str, width = max_widths[i]);
432            stdout.reset()?;
433            
434            if i < fields.len() - 1 {
435                print!("  ");
436            }
437        }
438        println!();
439    }
440
441    Ok(())
442}
443
444fn print_plain_table(data: &[Value], fields: &[String], max_widths: &[usize]) {
445    // 3. ヘッダー出力
446    for (i, field) in fields.iter().enumerate() {
447        print!("{:<width$}", field, width = max_widths[i]);
448        if i < fields.len() - 1 {
449            print!("  ");
450        }
451    }
452    println!();
453
454    // 4. データ行出力
455    for item in data {
456        for (i, field) in fields.iter().enumerate() {
457            let value_str = get_flattened_value(item, field);
458            print!("{:<width$}", value_str, width = max_widths[i]);
459            if i < fields.len() - 1 {
460                print!("  ");
461            }
462        }
463        println!();
464    }
465}
466
467pub fn print_data_info(data: &[Value]) {
468    let use_colors = should_use_colors();
469    
470    if use_colors {
471        print_colored_data_info(data).unwrap_or_else(|_| print_plain_data_info(data));
472    } else {
473        print_plain_data_info(data);
474    }
475}
476
477fn print_colored_data_info(data: &[Value]) -> Result<(), Error> {
478    let mut stdout = StandardStream::stdout(ColorChoice::Always);
479    let colors = ColorScheme::new();
480
481    // タイトル
482    stdout.set_color(&colors.header)?;
483    println!("=== Data Information ===");
484    stdout.reset()?;
485    
486    println!("Total records: {}", data.len());
487
488    if data.is_empty() {
489        return Ok(());
490    }
491
492    // データ型の分析
493    let first_item = &data[0];
494    match first_item {
495        Value::Object(obj) => {
496            println!("Type: Object Array");
497            println!("Fields: {}", obj.len());
498            println!();
499
500            // フィールド一覧と型情報
501            stdout.set_color(&colors.header)?;
502            println!("Field Details:");
503            stdout.reset()?;
504            
505            for (key, value) in obj {
506                let field_type = get_value_type_info(value);
507                let sample_value = get_sample_value(value);
508                
509                // フィールド名を色付き
510                stdout.set_color(&colors.string)?;
511                print!("  {:<15}", key);
512                stdout.reset()?;
513                
514                // 型情報を色付き
515                let type_color = get_color_for_value(value, &colors);
516                stdout.set_color(type_color)?;
517                print!(" {:<10}", field_type);
518                stdout.reset()?;
519                
520                println!(" (e.g., {})", sample_value);
521            }
522
523            // 配列フィールドの詳細
524            println!();
525            stdout.set_color(&colors.header)?;
526            println!("Array Fields:");
527            stdout.reset()?;
528            
529            for (key, value) in obj {
530                if let Value::Array(arr) = value {
531                    stdout.set_color(&colors.array_info)?;
532                    print!("  {:<15}", key);
533                    stdout.reset()?;
534                    println!(" [{} items]", arr.len());
535                    
536                    if let Some(first_elem) = arr.first() {
537                        if let Value::Object(elem_obj) = first_elem {
538                            print!("    └─ ");
539                            let sub_fields: Vec<&String> = elem_obj.keys().collect();
540                            let sub_fields: Vec<&str> =
541                                sub_fields.into_iter().map(|f| f.as_str()).collect();
542                            println!("{}", sub_fields.join(", "));
543                        }
544                    }
545                }
546            }
547        }
548        Value::Array(_) => {
549            println!("Type: Nested Array");
550            // ネストした配列の詳細
551        }
552        _ => {
553            println!("Type: Simple Values");
554            // プリミティブ値の統計
555        }
556    }
557
558    Ok(())
559}
560
561fn print_plain_data_info(data: &[Value]) {
562    println!("=== Data Information ===");
563    println!("Total records: {}", data.len());
564
565    if data.is_empty() {
566        return;
567    }
568
569    // データ型の分析
570    let first_item = &data[0];
571    match first_item {
572        Value::Object(obj) => {
573            println!("Type: Object Array");
574            println!("Fields: {}", obj.len());
575            println!();
576
577            // フィールド一覧と型情報
578            println!("Field Details:");
579            for (key, value) in obj {
580                let field_type = get_value_type_info(value);
581                let sample_value = get_sample_value(value);
582                println!("  {:<15} {:<10} (e.g., {})", key, field_type, sample_value);
583            }
584
585            // 配列フィールドの詳細
586            println!();
587            println!("Array Fields:");
588            for (key, value) in obj {
589                if let Value::Array(arr) = value {
590                    println!("  {:<15} [{} items]", key, arr.len());
591                    if let Some(first_elem) = arr.first() {
592                        if let Value::Object(elem_obj) = first_elem {
593                            print!("    └─ ");
594                            let sub_fields: Vec<&String> = elem_obj.keys().collect();
595                            let sub_fields: Vec<&str> =
596                                sub_fields.into_iter().map(|f| f.as_str()).collect();
597                            println!("{}", sub_fields.join(", "));
598                        }
599                    }
600                }
601            }
602        }
603        Value::Array(_) => {
604            println!("Type: Nested Array");
605            // ネストした配列の詳細
606        }
607        _ => {
608            println!("Type: Simple Values");
609            // プリミティブ値の統計
610        }
611    }
612}