#![allow(clippy::tabs_in_doc_comments)]
use std::collections::HashMap;
use clap::{Arg, ArgMatches, ErrorKind};
use serde::Serialize;
use serde_json::Value;
use crate::{
utils::config::{CliArg, CliConfig},
PackageInfo
};
#[macro_use]
mod macros;
mod clapfix {
#![allow(deprecated)]
pub type ClapCommand<'help> = clap::App<'help>;
pub trait ErrorExt {
fn kind(&self) -> clap::ErrorKind;
}
impl ErrorExt for clap::Error {
fn kind(&self) -> clap::ErrorKind {
self.kind
}
}
}
use clapfix::{ClapCommand as App, ErrorExt};
#[derive(Default, Debug, Serialize)]
#[non_exhaustive]
pub struct ArgData {
pub value: Value,
pub occurrences: u64
}
#[derive(Default, Debug, Serialize)]
#[non_exhaustive]
pub struct SubcommandMatches {
pub name: String,
pub matches: Matches
}
#[derive(Default, Debug, Serialize)]
#[non_exhaustive]
pub struct Matches {
pub args: HashMap<String, ArgData>,
pub subcommand: Option<Box<SubcommandMatches>>
}
impl Matches {
pub(crate) fn set_arg(&mut self, name: String, value: ArgData) {
self.args.insert(name, value);
}
pub(crate) fn set_subcommand(&mut self, name: String, matches: Matches) {
self.subcommand = Some(Box::new(SubcommandMatches { name, matches }));
}
}
pub fn get_matches(cli: &CliConfig, package_info: &PackageInfo) -> crate::api::Result<Matches> {
let about = cli.description().unwrap_or(&package_info.description.to_string()).to_string();
let version = &*package_info.version.to_string();
let app = get_app(package_info, version, &package_info.name, Some(&about), cli);
match app.try_get_matches() {
Ok(matches) => Ok(get_matches_internal(cli, &matches)),
Err(e) => match ErrorExt::kind(&e) {
ErrorKind::DisplayHelp => {
let mut matches = Matches::default();
let help_text = e.to_string();
matches.args.insert(
"help".to_string(),
ArgData {
value: Value::String(help_text),
occurrences: 0
}
);
Ok(matches)
}
ErrorKind::DisplayVersion => {
let mut matches = Matches::default();
matches.args.insert("version".to_string(), Default::default());
Ok(matches)
}
_ => Err(e.into())
}
}
}
fn get_matches_internal(config: &CliConfig, matches: &ArgMatches) -> Matches {
let mut cli_matches = Matches::default();
map_matches(config, matches, &mut cli_matches);
if let Some((subcommand_name, subcommand_matches)) = matches.subcommand() {
let mut subcommand_cli_matches = Matches::default();
map_matches(config.subcommands().unwrap().get(subcommand_name).unwrap(), subcommand_matches, &mut subcommand_cli_matches);
cli_matches.set_subcommand(subcommand_name.to_string(), subcommand_cli_matches);
}
cli_matches
}
fn map_matches(config: &CliConfig, matches: &ArgMatches, cli_matches: &mut Matches) {
if let Some(args) = config.args() {
for arg in args {
let occurrences = matches.occurrences_of(arg.name.clone());
let value = if occurrences == 0 || !arg.takes_value {
Value::Bool(occurrences > 0)
} else if arg.multiple {
matches
.values_of(arg.name.clone())
.map(|v| {
let mut values = Vec::new();
for value in v {
values.push(Value::String(value.to_string()));
}
Value::Array(values)
})
.unwrap_or(Value::Null)
} else {
matches
.value_of(arg.name.clone())
.map(|v| Value::String(v.to_string()))
.unwrap_or(Value::Null)
};
cli_matches.set_arg(arg.name.clone(), ArgData { value, occurrences });
}
}
}
fn get_app<'a>(package_info: &'a PackageInfo, version: &'a str, command_name: &'a str, about: Option<&'a String>, config: &'a CliConfig) -> App<'a> {
let mut app = App::new(command_name).author(package_info.authors).version(version);
if let Some(about) = about {
app = app.about(&**about);
}
if let Some(long_description) = config.long_description() {
app = app.long_about(&**long_description);
}
if let Some(before_help) = config.before_help() {
app = app.before_help(&**before_help);
}
if let Some(after_help) = config.after_help() {
app = app.after_help(&**after_help);
}
if let Some(args) = config.args() {
for arg in args {
let arg_name = arg.name.as_ref();
app = app.arg(get_arg(arg_name, arg));
}
}
if let Some(subcommands) = config.subcommands() {
for (subcommand_name, subcommand) in subcommands {
let clap_subcommand = get_app(package_info, version, subcommand_name, subcommand.description(), subcommand);
app = app.subcommand(clap_subcommand);
}
}
app
}
fn get_arg<'a>(arg_name: &'a str, arg: &'a CliArg) -> Arg<'a> {
let mut clap_arg = Arg::new(arg_name);
if arg.index.is_none() {
clap_arg = clap_arg.long(arg_name);
if let Some(short) = arg.short {
clap_arg = clap_arg.short(short);
}
}
clap_arg = bind_string_arg!(arg, clap_arg, description, help);
clap_arg = bind_string_arg!(arg, clap_arg, long_description, long_help);
clap_arg = clap_arg.takes_value(arg.takes_value);
clap_arg = clap_arg.multiple_values(arg.multiple);
clap_arg = clap_arg.multiple_occurrences(arg.multiple_occurrences);
clap_arg = bind_value_arg!(arg, clap_arg, number_of_values);
clap_arg = bind_string_slice_arg!(arg, clap_arg, possible_values);
clap_arg = bind_value_arg!(arg, clap_arg, min_values);
clap_arg = bind_value_arg!(arg, clap_arg, max_values);
clap_arg = clap_arg.required(arg.required);
clap_arg = bind_string_arg!(arg, clap_arg, required_unless_present, required_unless_present);
clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_all);
clap_arg = bind_string_slice_arg!(arg, clap_arg, required_unless_present_any);
clap_arg = bind_string_arg!(arg, clap_arg, conflicts_with, conflicts_with);
if let Some(value) = &arg.conflicts_with_all {
let v: Vec<&str> = value.iter().map(|x| &**x).collect();
clap_arg = clap_arg.conflicts_with_all(&v);
}
clap_arg = bind_string_arg!(arg, clap_arg, requires, requires);
if let Some(value) = &arg.requires_all {
let v: Vec<&str> = value.iter().map(|x| &**x).collect();
clap_arg = clap_arg.requires_all(&v);
}
clap_arg = bind_if_arg!(arg, clap_arg, requires_if);
clap_arg = bind_if_arg!(arg, clap_arg, required_if_eq);
clap_arg = bind_value_arg!(arg, clap_arg, require_equals);
clap_arg = bind_value_arg!(arg, clap_arg, index);
clap_arg
}