1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::any::Any;
use std::io::Write;

use anyhow::Result;
use inflector::Inflector;
use prettytable::{format, Cell, Row, Table};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::output::OutputTrait;

pub struct ConsoleOutput;

impl ConsoleOutput {
    pub fn new() -> Self {
        Self {}
    }

    fn display_obj(
        &self,
        table: &mut Table,
        obj: &Value,
        include_keys: &Option<Vec<&str>>,
        exclude_keys: &Option<Vec<&str>>,
    ) -> Result<()> {
        let obj = obj.as_object().unwrap();

        let predict_include_keys = |it: &(&String, &Value)| {
            if let Some(keys) = include_keys {
                keys.contains(&it.0.to_lowercase().as_str())
            } else {
                true
            }
        };

        let predict_exclude_keys = |it: &(&String, &Value)| {
            if let Some(keys) = exclude_keys {
                !keys.contains(&it.0.to_lowercase().as_str())
            } else {
                true
            }
        };

        if table.is_empty() {
            let columns = obj
                .iter()
                .filter(predict_include_keys)
                .filter(predict_exclude_keys)
                .map(|it| Cell::new(&it.0.to_title_case()))
                .collect();

            table.add_row(Row::new(columns));
        }

        let column_values = obj
            .iter()
            .filter(predict_include_keys)
            .filter(predict_exclude_keys)
            .map(|it| Cell::new(&to_string_trim(it.1)))
            .collect();

        table.add_row(Row::new(column_values));

        Ok(())
    }
}

impl OutputTrait for ConsoleOutput {
    fn display<'a, T: Deserialize<'a> + Serialize>(
        &self,
        writer: impl Write,
        obj: &T,
        include_keys: Option<Vec<&str>>,
        exclude_keys: Option<Vec<&str>>,
    ) -> Result<()> {
        let mut writer = writer;
        let obj = serde_json::to_value(obj)?;

        let mut table = Table::new();
        table.set_format(*format::consts::FORMAT_CLEAN);

        match obj {
            _ if obj.is_array() => {
                for o in obj.as_array().unwrap() {
                    self.display_obj(&mut table, o, &include_keys, &exclude_keys)?;
                }
            }

            _ if obj.is_object() => {
                self.display_obj(&mut table, &obj, &include_keys, &exclude_keys)?;
            }

            _ => Err(anyhow!("Unsupported display type: {:?}", obj.type_id()))?,
        };

        table
            .print(&mut writer)
            .map(|_it| ())
            .map_err(|it| anyhow!(it))
    }
}

fn to_string_trim(v: &Value) -> String {
    match v {
        Value::Null => "".to_string(),
        Value::String(s) => truncate_str(s),
        Value::Bool(b) => b.to_string(),
        _ => serde_yaml::to_string(v)
            .unwrap()
            .trim_start_matches("---\n")
            .to_string(),
    }
}

fn truncate_str(str: &String) -> String {
    if str.len() > 100 {
        format!("{}...", &str[0..100])
    } else {
        str.clone()
    }
}