libcli-rs 0.1.4

libcli-rs, a common library for CLI development
Documentation
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()
    }
}