entry 0.2.1

An interactive CLI for generating JSON-formatted data from a local schema
mod args;
mod config;
mod errors;
mod schema;

use args::{Command, ConfigCommand, Entry, SchemaCommand};
use config::Config;
use schema::{Schema, SchemaCount, SchemaDataType, SchemaType};

use exitfailure::ExitFailure;
use failure::{err_msg, ResultExt};
use serde_json::{Map, Value};
use std::{
    fs::{self, OpenOptions},
    io::{self, Read, Write},
    str::FromStr,
};
use structopt::StructOpt;

fn main() -> Result<(), ExitFailure> {
    let mut config: Config = confy::load("entry")?;
    initialize(&config)?;

    let args = Entry::from_args();
    match args.cmd {
        Command::Schema { cmd: subcommand } => match subcommand {
            SchemaCommand::New => {
                let mut schema = Schema::default();
                println!("Enter a name for the schema");
                let schema_name = read_line();
                loop {
                    println!("Enter a name for the new field (or nothing to finish up):");
                    let field_name = read_line();
                    if field_name.is_empty() {
                        break;
                    }
                    println!("found {}", field_name);
                    println!("Enter a type for the new field (string, number, date, datetime):");
                    loop {
                        if let Ok(data_type) = SchemaDataType::from_str(&read_line()) {
                            println!("Is this an array? (y/n)");
                            let ans = read_line().to_lowercase();
                            schema.insert(
                                field_name,
                                SchemaType {
                                    count: if ans == "y" {
                                        SchemaCount::Many
                                    } else {
                                        SchemaCount::One
                                    },
                                    data_type,
                                },
                            );
                            break;
                        } else {
                            println!("Invalid data type received, try again.");
                        }
                    }
                }
                if !schema.is_empty() {
                    println!("Saving schema...");
                    schema
                        .save(
                            &format!("{}/schema", &config.data_directory()),
                            &schema_name,
                        )
                        .unwrap();
                } else {
                    println!("Schema is empty, not saving.");
                }
            }
            SchemaCommand::List => {
                Schema::list(&config).iter().for_each(|schema_name| {
                    println!("{}", schema_name);
                });
            }
            SchemaCommand::Show { schema_name } => {
                if let Ok(schema) = Schema::load(
                    &format!("{}/schema", &config.data_directory()),
                    &schema_name,
                ) {
                    schema.print();
                }
            }
            SchemaCommand::Remove { schema_name } => {
                match Schema::remove(
                    &format!("{}/schema", &config.data_directory()),
                    &schema_name,
                ) {
                    Ok(_) => println!("Successfully removed schema `{}`", schema_name),
                    Err(_) => eprintln!("Failed to remove schema `{}`", schema_name),
                }
            }
        },
        Command::Config { cmd } => match cmd {
            ConfigCommand::Get { key } => match key.as_str() {
                "data_directory" => println!("{}", config.data_directory()),
                _ => return Err(err_msg("invalid key, failed to retrieve config"))?,
            },
            ConfigCommand::Set { key, value } => {
                match key.as_str() {
                    "data_directory" => {
                        let res: Result<String, _> = value.parse();
                        match res {
                            Ok(v) => config.set_data_directory(v),
                            _ => return Err(err_msg("invalid value, failed to set config"))?,
                        }
                    }
                    _ => return Err(err_msg("invalid key, failed to retrieve config"))?,
                }
                confy::store("entry", config).with_context(|_| "failed to save config")?;
            }
            ConfigCommand::List {} => {
                println!("data_directory={}", config.data_directory());
            }
        },
        Command::For { schema_name } => {
            if let Ok(schema) = Schema::load(
                &format!("{}/schema", &config.data_directory()),
                &schema_name,
            ) {
                let mut map: Map<String, Value> = Map::new();
                for (field_name, field_type) in schema {
                    println!("Please provide the {} ({}):", field_name, field_type);
                    loop {
                        let response = read_line();
                        if let Some(value) = field_type.parse(&response) {
                            map.insert(field_name, value);
                            break;
                        }
                        println!(
                            "Invalid value received, make sure it is a valid {}.",
                            field_type
                        );
                    }
                }
                if let Ok(json) = serde_json::to_string(&Value::Object(map)) {
                    println!("{}", json);
                    save(&format!("{}/cached.json", &config.data_directory()), &json);
                }
            }
        }
        Command::Last => {
            println!(
                "{}",
                load(&format!("{}/cached.json", &config.data_directory()))
            )
        }
    }
    Ok(())
}

fn save(path: &str, data: &str) -> () {
    let result = OpenOptions::new()
        .create(true)
        .write(true)
        .truncate(true)
        .open(path);
    if let Ok(mut file) = result {
        file.write_all(data.as_bytes());
    }
}

fn load(path: &str) -> String {
    let result = OpenOptions::new().read(true).open(path);
    let mut buf = String::new();
    if let Ok(mut file) = result {
        file.read_to_string(&mut buf);
    }
    return buf;
}

fn read_line() -> String {
    let mut s = String::new();
    io::stdin().read_line(&mut s);
    return s.trim().to_string();
}

fn initialize(config: &Config) -> Result<(), io::Error> {
    let path = format!("{}/schema", config.data_directory());
    return fs::create_dir_all(path);
}

#[cfg(test)]
mod tests {
    use assert_cmd::Command;

    #[test]
    fn displays_help_without_failure() -> Result<(), Box<dyn std::error::Error>> {
        let mut cmd = Command::cargo_bin("entry").unwrap();
        cmd.arg("help");
        cmd.assert().success();
        Ok(())
    }
}