hawk_data/
output.rs

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