ckb-cli 0.100.0

ckb command line interface
use std::env;
use std::fmt;

use colored::Colorize;

use crate::utils::json_color::Colorizer;
use crate::utils::yaml_ser;

pub fn is_a_tty(stderr: bool) -> bool {
    let stream = if stderr {
        atty::Stream::Stderr
    } else {
        atty::Stream::Stdout
    };
    atty::is(stream)
}

pub fn is_term_dumb() -> bool {
    env::var("TERM").ok() == Some(String::from("dumb"))
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum OutputFormat {
    Yaml,
    Json,
}

impl fmt::Display for OutputFormat {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(
            f,
            "{}",
            match self {
                OutputFormat::Yaml => "yaml",
                OutputFormat::Json => "json",
            }
        )
    }
}

impl OutputFormat {
    pub fn from_str(format: &str) -> Result<OutputFormat, String> {
        match format {
            "yaml" => Ok(OutputFormat::Yaml),
            "json" => Ok(OutputFormat::Json),
            _ => Err(format!("Invalid output format: {}", format)),
        }
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub enum ColorWhen {
    Auto,
    Always,
    Never,
}

impl Default for ColorWhen {
    fn default() -> Self {
        let is_a_tty = is_a_tty(false);
        let is_term_dumb = is_term_dumb();
        if is_a_tty && !is_term_dumb {
            ColorWhen::Auto
        } else {
            ColorWhen::Never
        }
    }
}

impl ColorWhen {
    pub fn new(color: bool) -> ColorWhen {
        let is_a_tty = is_a_tty(false);
        let is_term_dumb = is_term_dumb();
        if is_a_tty && !is_term_dumb && color {
            ColorWhen::Always
        } else {
            ColorWhen::Never
        }
    }

    pub fn color(self) -> bool {
        self != ColorWhen::Never
    }
}

pub trait Printable {
    fn render(&self, format: OutputFormat, color: bool) -> String;
}

impl Printable for Box<dyn Printable> {
    fn render(&self, format: OutputFormat, color: bool) -> String {
        self.as_ref().render(format, color)
    }
}

impl<T: ?Sized> Printable for T
where
    T: serde::ser::Serialize,
{
    fn render(&self, format: OutputFormat, color: bool) -> String {
        match format {
            OutputFormat::Yaml => yaml_ser::to_string(self, color).unwrap(),
            OutputFormat::Json => {
                let value = serde_json::to_value(self).unwrap();
                if color {
                    Colorizer::arbitrary().colorize_json_value(&value).unwrap()
                } else {
                    serde_json::to_string_pretty(&value).unwrap()
                }
            }
        }
    }
}

#[derive(Clone, Debug)]
pub enum TypedStr<'a> {
    Null(Option<&'a str>),
    Bool(&'a str),
    Number(&'a str),
    String(&'a str),
    Key(&'a str),
    Escaped(&'a str),
}

impl<'a> TypedStr<'a> {
    pub fn colored(&self) -> String {
        let colored_content = match self {
            TypedStr::Null(content) => content
                .map(ToOwned::to_owned)
                .unwrap_or_else(|| "null".to_owned())
                .cyan(),
            TypedStr::Bool(content) => content.yellow(),
            TypedStr::Number(content) => content.magenta(),
            TypedStr::String(content) => content.green(),
            TypedStr::Key(content) => content.blue(),
            TypedStr::Escaped(content) => content.red(),
        };
        colored_content.to_string()
    }

    pub fn to_plain(&self) -> &'a str {
        match self {
            TypedStr::Null(content) => content.unwrap_or("null"),
            TypedStr::Bool(content) => content,
            TypedStr::Number(content) => content,
            TypedStr::String(content) => content,
            TypedStr::Key(content) => content,
            TypedStr::Escaped(content) => content,
        }
    }

    pub fn render(&self, color: bool) -> String {
        if color {
            self.colored()
        } else {
            self.to_plain().to_owned()
        }
    }
}