complate 0.10.1

Standardizing messages the right way.
use std::result::Result;

pub struct CallArgs {
    pub privileges: Privilege,
    pub command: Command,
}

impl CallArgs {
    #[allow(clippy::single_match)]
    pub async fn validate(&self) -> Result<(), Box<dyn std::error::Error>> {
        match self.privileges {
            Privilege::Normal => match &self.command {
                Command::Render(args) => {
                    match args.backend {
                        #[cfg(feature = "backend+ui")]
                        Backend::UI => {
                            return Err(Box::new(crate::error::NeedExperimentalFlag::default()))
                        }
                        #[cfg(feature = "backend+cli")]
                        Backend::CLI => {}
                    };
                    if args.value_overrides.len() > 0 {
                        return Err(Box::new(crate::error::NeedExperimentalFlag::default()));
                    }
                    #[allow(unreachable_code)]
                    Ok(())
                }
                _ => Ok(()),
            },
            Privilege::Experimental => Ok(()),
        }
    }
}

pub enum Privilege {
    Normal,
    Experimental,
}

pub enum Command {
    Init,
    Render(RenderArguments),
}

pub struct RenderArguments {
    pub configuration: String,
    pub template: Option<String>,
    pub value_overrides: std::collections::HashMap<String, String>,
    pub shell_trust: ShellTrust,
    pub strict: bool,
    pub backend: Backend,
}

pub enum ShellTrust {
    None,
    Prompt,
    Ultimate,
}

pub enum Backend {
    #[cfg(feature = "backend+cli")]
    CLI,
    #[cfg(feature = "backend+ui")]
    UI,
}

pub struct ClapArgumentLoader {}

impl ClapArgumentLoader {
    pub async fn load_from_cli() -> std::result::Result<CallArgs, Box<dyn std::error::Error>> {
        let mut backend_values = Vec::new();
        if cfg!(feature = "backend+cli") {
            backend_values.push("cli");
        }
        if cfg!(feature = "backend+ui") {
            backend_values.push("ui");
        }

        let command = clap::App::new("complate")
            .version(env!("CARGO_PKG_VERSION"))
            .about("A rusty text templating application for CLIs.")
            .author("Weber, Alexander <aw@voidpointergroup.com>")
            .arg(clap::Arg::with_name("experimental")
                    .short("e")
                    .long("experimental")
                    .value_name("EXPERIMENTAL")
                    .help("Enables experimental features that do not count as stable.")
                    .required(false)
                    .takes_value(false))
            .subcommand(clap::App::new("init")
                .about("Initializes a dummy default configuration in \"./.complate/config.yaml\"."))
            .subcommand(clap::App::new("render")
                .about("Renders a template by replacing values as specified by the configuration.")
                .arg(clap::Arg::with_name("config")
                    .short("c")
                    .long("config")
                    .value_name("FILE")
                    .help("The configuration file to use.")
                    .default_value("./.complate/config.yaml")
                    .multiple(false)
                    .required(false)
                    .takes_value(true))
                .arg(clap::Arg::with_name("template")
                    .short("t")
                    .long("template")
                    .value_name("TEMPLATE")
                    .help("Specify the template to use from the config and skip it's selection.")
                    .multiple(false)
                    .required(false)
                    .takes_value(true))
                .arg(clap::Arg::with_name("shell-trust")
                    .long("shell-trust")
                    .value_name("SHELL_TRUST")
                    .help("Enables the shell mode. This is potentially insecure and should only be done for trustworthy sources.")
                    .possible_values(&["none", "prompt", "ultimate"])
                    .multiple(false)
                    .required(false)
                    .default_value("none")
                    .takes_value(true))
                .arg(clap::Arg::with_name("strict")
                    .short("s")
                    .long("strict")
                    .value_name("STRICT")
                    .help("Defines whether the templating is done in strict mode (fail on missing value for variable).")
                    .possible_values(&["true", "false"])
                    .default_value("true")
                    .multiple(false)
                    .required(false)
                    .takes_value(true))
                .arg(clap::Arg::with_name("backend")
                    .short("b")
                    .long("backend")
                    .value_name("BACKEND")
                    .help("The execution backend (cli=native-terminal, ui=ui emulator in terminal).")
                    .possible_values(backend_values.as_slice())
                    .default_value(backend_values.first().unwrap())
                    .multiple(false)
                    .required(false)
                    .takes_value(true))
                .arg(clap::Arg::with_name("value")
                    .short("v")
                    .long("value")
                    .value_name("VALUE")
                    .help("Overrides a certain value definition with a string.")
                    .multiple(true)
                    .required(false)
                    .takes_value(true)))
            .get_matches();

        let privileges = if command.is_present("experimental") {
            Privilege::Experimental
        } else {
            Privilege::Normal
        };

        if command.subcommand_matches("init").is_some() {
            return Ok(CallArgs {
                privileges,
                command: Command::Init,
            });
        }

        match command.subcommand_matches("render") {
            Some(x) => {
                let config = if x.is_present("config") {
                    let config_param = x.value_of("config").unwrap();
                    std::fs::read_to_string(config_param.to_owned())?
                } else {
                    return Err(Box::new(crate::error::Failed::new(
                        "configuration unspecified",
                    )));
                };

                let template = if x.is_present("template") {
                    Some(x.value_of("template").unwrap().to_owned())
                } else {
                    None
                };

                let shell_trust = match x.value_of("shell-trust") {
                    Some(x) => match x {
                        "none" => ShellTrust::None,
                        "prompt" => ShellTrust::Prompt,
                        "ultimate" => ShellTrust::Ultimate,
                        _ => ShellTrust::None,
                    },
                    None => ShellTrust::None,
                };

                let strict = match x.value_of("strict") {
                    Some(x) => x.parse::<bool>()?,
                    None => true,
                };

                let mut value_overrides: std::collections::HashMap<String, String> =
                    std::collections::HashMap::new();
                if let Some(values_overrides_arg) = x.values_of("value") {
                    for vo in values_overrides_arg {
                        let spl: Vec<&str> = vo.splitn(2, "=").collect();
                        value_overrides.insert(spl[0].to_owned(), spl[1].to_owned());
                    }
                }

                let backend = match x.value_of("backend") {
                    Some(x) => match x {
                        #[cfg(feature = "backend+cli")]
                        "cli" => Backend::CLI,
                        #[cfg(feature = "backend+ui")]
                        "ui" => Backend::UI,
                        _ => {
                            return Err(Box::new(crate::error::Failed::new("no backend specified")))
                        }
                    },
                    #[cfg(feature = "backend+cli")]
                    None => Backend::CLI,
                    #[cfg(feature = "backend+ui")]
                    #[allow(unreachable_patterns)]
                    None => Backend::UI,
                };

                Ok(CallArgs {
                    privileges,
                    command: Command::Render(RenderArguments {
                        configuration: config,
                        template,
                        value_overrides,
                        shell_trust,
                        strict,
                        backend,
                    }),
                })
            }
            None => Err(Box::new(crate::error::UnknownCommand::default())),
        }
    }
}