vizo 1.0.0

Visualize structured data formats in a more pretty way.
Documentation
use crate::args::Cli;
use crate::processors::*;
use crate::values::VizValue;
use anyhow::{Result, anyhow, bail};
use clap::Parser;
use colored::Colorize;
use std::env::var;
use std::fs;
use std::io::{Read, stdin};
use std::path::Path;
use std::process::exit;

pub fn run() -> Result<()> {
    let cli = Cli::parse();

    configure_colors(&cli);

    let (contents, extension) = get_content_and_extension(&cli)?;
    let indent = get_indent(&cli)?;
    let data = get_parsed_data(&contents, &extension)?;

    print_parsed_data(data, indent);

    Ok(())
}

fn configure_colors(cli: &Cli) {
    let no_color = var("NO_COLOR");

    if no_color.is_ok() {
        colored::control::set_override(false);
    } else {
        colored::control::set_override(!cli.no_color);
    }
}

fn get_content_and_extension(cli: &Cli) -> Result<(String, String)> {
    let file_path = cli.path.clone().unwrap_or_default();

    if file_path.is_empty() {
        get_from_stdin(cli)
    } else {
        get_file_content(&file_path)
    }
}

fn get_from_stdin(cli: &Cli) -> Result<(String, String)> {
    let mut contents = String::new();
    stdin()
        .read_to_string(&mut contents)
        .map_err(|e| anyhow!("failed to read from stdin: {}", e.to_string()))?;

    if let Some(lang) = &cli.language {
        Ok((contents, lang.clone()))
    } else {
        bail!("language is not specified for stdin")
    }
}

fn get_file_content(file_path: &str) -> Result<(String, String)> {
    let path = Path::new(file_path);

    if !path.exists() {
        return Err(anyhow!("file not found"));
    }

    let contents = fs::read_to_string(file_path)
        .map_err(|e| anyhow!("failed to read file: {}", e.to_string()))?;

    let ext = path
        .extension()
        .unwrap()
        .to_str()
        .unwrap()
        .to_string()
        .to_lowercase();

    Ok((contents, ext))
}

fn get_indent(cli: &Cli) -> Result<usize> {
    let indent = &cli.indent;

    if *indent > 10 {
        return Err(anyhow!("indentation level must be less than or equal 10."));
    }

    Ok(*indent)
}

fn get_parsed_data(contents: &str, extension: &str) -> Result<VizValue> {
    let parsed_data = match extension {
        "json" => json::JSONProcessor::process_data(contents),
        "toml" => toml::TOMLProcessor::process_data(contents),
        "yaml" | "yml" => yaml::YAMLProcessor::process_data(contents),
        _ => {
            return Err(anyhow!("unsupported file format."));
        }
    }?;

    Ok(parsed_data)
}

fn print_parsed_data(data: VizValue, indent: usize) {
    if let VizValue::Object(map) = data {
        println!("{{");
        let entries: Vec<_> = map.into_iter().collect();
        let total = entries.len();
        for (i, (key, val)) in entries.into_iter().enumerate() {
            let last = i + 1 == total;
            crate::prints::print_object_data(&key, val, indent, indent, last, true);
        }
        println!("}}");
    } else {
        println!(
            "{}: parsed data is not a valid object.",
            "error".red().bold()
        );
        exit(1);
    }
}