tv-cli 0.7.0

Format json into table view
Documentation
use crate::input;
use anyhow::Result;
use clap::{App, Arg};
use tokio::io::BufReader;
use tv_cli::data::Data;
use tv_cli::table::{cell::Align, style::Style, Table};

pub struct Application<'a, 'b> {
    app: App<'a, 'b>,
    path: Option<String>,
    sort_key: Option<String>,
    pick: Option<String>,
    align: Align,
    style: Style,
    no_headers: bool,
    recursive: bool,
}

impl<'a, 'b> Application<'a, 'b> {
    pub fn new() -> Self {
        let app = App::new("tv")
            .version(env!("CARGO_PKG_VERSION"))
            .author(env!("CARGO_PKG_AUTHORS"))
            .about(env!("CARGO_PKG_DESCRIPTION"))
            .global_setting(clap::AppSettings::ColoredHelp)
            .arg(Arg::with_name("PATH").help("json file path").index(1))
            .arg(
                Arg::with_name("sort")
                    .short("s")
                    .long("sort")
                    .value_name("SORT_KEY")
                    .help("Options for sorting by key")
                    .takes_value(true),
            )
            .arg(
                Arg::with_name("align")
                    .short("a")
                    .long("align")
                    .value_name("left | center | right | none")
                    .help("Table alignment")
                    .takes_value(true)
                    .default_value("none"),
            )
            .arg(
                Arg::with_name("style")
                    .long("style")
                    .value_name("ascii | sharp | rounded | markdown | plane | plain")
                    .help("Table style")
                    .takes_value(true)
                    .default_value("ascii"),
            )
            .arg(
                Arg::with_name("no headers")
                    .long("no-headers")
                    .help("Specify that the input has no header row"),
            )
            .arg(
                Arg::with_name("recursive")
                    .short("r")
                    .long("recursive")
                    .help("Recursive display"),
            )
            .arg(
                Arg::with_name("pick")
                    .short("p")
                    .long("pick")
                    .value_name("PICK FIELD")
                    .help("Pick up field")
                    .takes_value(true),
            );

        let matcher = app.clone().get_matches();
        let path = matcher.value_of("PATH").map(String::from);
        let sort_key = matcher.value_of("sort").map(String::from);
        let align = matcher
            .value_of("align")
            .map(String::from)
            .map(Align::new)
            .unwrap_or(Align::None);
        let style = matcher
            .value_of("style")
            .map(String::from)
            .map(Style::new)
            .unwrap_or(Style::Ascii);
        let no_headers = matcher.is_present("no headers");
        let recursive = matcher.is_present("recursive");
        let pick = matcher.value_of("pick").map(String::from);

        Self {
            app,
            path,
            sort_key,
            align,
            style,
            no_headers,
            recursive,
            pick,
        }
    }

    pub async fn run(&mut self) -> Result<()> {
        if self.path.is_none() && !input::is_pipe() {
            self.app.print_long_help()?;
            return Ok(());
        }

        let raw = match self.path.clone() {
            Some(p) => {
                let file = tokio::fs::File::open(String::from(p)).await?;
                let mut reader = BufReader::new(file);
                input::read(&mut reader).await
            }
            None => input::read_stdin().await,
        }?;

        let mut data = Data::from(&raw)?;

        if let Some(key) = self.pick.clone() {
            data = data.pick(key)?;
        }
        data.set_sort_key(self.sort_key.clone());

        self.show(data);

        Ok(())
    }

    fn show(&self, data: Data) {
        let mut table: Table<String> = data.clone().into();
        table
            .set_style(self.style)
            .set_align(self.align)
            .set_no_headers(self.no_headers);

        println!("{}", table);

        if self.recursive {
            let nested_fields = data.clone().nested_fields();
            nested_fields.into_iter().for_each(|(k, v)| {
                println!("\n# {}", k);
                let mut data = v;
                data.set_sort_key(self.sort_key.clone());
                self.show(data);
            });
        }
    }
}