claptrap 0.3.0

Bring the power of Clap to shell scripts.
Documentation
use crate::types::arg::Arg;
use crate::types::arg_group::ArgGroup;
use crate::types::style::Styles;
use crate::types::values::ValueParser;
use indexmap::IndexMap;
use serde::{Deserialize, Deserializer};

/// A deserializable representation of a Clap command.
///
/// # Example
///
/// ```rust
/// use claptrap::Command;
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let spec = r#"
///     name = "myapp"
///     version = "1.0.0"
///     about = "An example application"
///     [args]
///     cfg = { short = 'c', long = "config", required = true }
///  "#;
/// let cmd: Command = toml::from_str(spec)?;
/// let clap_cmd = clap::Command::from(cmd);
/// let matches = clap_cmd.try_get_matches_from(vec!["myapp", "--config", "config.toml"])?;
/// assert_eq!(matches.get_one::<String>("cfg"), Some(&"config.toml".to_string()));
/// # Ok(())
/// # }
/// ```
///
/// See the Clap [`Command`](https://docs.rs/clap/latest/clap/struct.Command.html) documentation.
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Command {
    name: String,
    #[serde(default, deserialize_with = "deserialize_args")]
    args: Option<IndexMap<String, Arg>>,
    #[serde(default)]
    subcommands: Vec<Command>,
    #[serde(default, deserialize_with = "deserialize_groups")]
    groups: Option<IndexMap<String, ArgGroup>>,
    no_binary_name: Option<bool>,
    ignore_errors: Option<bool>,
    args_override_self: Option<bool>,
    dont_delimit_trailing_values: Option<bool>,
    color: Option<ColorChoice>,
    styles: Option<Styles>,
    term_width: Option<usize>,
    max_term_width: Option<usize>,
    disable_version_flag: Option<bool>,
    propagate_version: Option<bool>,
    next_line_help: Option<bool>,
    disable_help_flag: Option<bool>,
    disable_help_subcommand: Option<bool>,
    disable_colored_help: Option<bool>,
    help_expected: Option<bool>,
    hide_possible_values: Option<bool>,
    infer_long_args: Option<bool>,
    infer_subcommands: Option<bool>,
    bin_name: Option<String>,
    display_name: Option<String>,
    author: Option<String>,
    about: Option<String>,
    long_about: Option<String>,
    after_help: Option<String>,
    after_long_help: Option<String>,
    before_help: Option<String>,
    before_long_help: Option<String>,
    version: Option<String>,
    long_version: Option<String>,
    override_usage: Option<String>,
    override_help: Option<String>,
    help_template: Option<String>,
    flatten_help: Option<bool>,
    next_help_heading: Option<String>,
    next_display_order: Option<usize>,
    arg_required_else_help: Option<bool>,
    allow_missing_positional: Option<bool>,
    short_flag: Option<char>,
    long_flag: Option<String>,
    alias: Option<String>,
    short_flag_alias: Option<char>,
    long_flag_alias: Option<String>,
    aliases: Option<Vec<String>>,
    short_flag_aliases: Option<Vec<char>>,
    long_flag_aliases: Option<Vec<String>>,
    visible_alias: Option<String>,
    visible_short_flag_alias: Option<char>,
    visible_long_flag_alias: Option<String>,
    visible_aliases: Option<Vec<String>>,
    visible_short_flag_aliases: Option<Vec<char>>,
    visible_long_flag_aliases: Option<Vec<String>>,
    display_order: Option<usize>,
    hide: Option<bool>,
    subcommand_required: Option<bool>,
    allow_external_subcommands: Option<bool>,
    external_subcommand_value_parser: Option<ValueParser>,
    args_conflicts_with_subcommands: Option<bool>,
    subcommand_precedence_over_arg: Option<bool>,
    subcommand_negates_reqs: Option<bool>,
    multicall: Option<bool>,
    subcommand_value_name: Option<String>,
    subcommand_help_heading: Option<String>,
}

fn deserialize_args<'de, D>(de: D) -> Result<Option<IndexMap<String, Arg>>, D::Error>
where
    D: Deserializer<'de>,
{
    let mut args = Option::<IndexMap<String, Arg>>::deserialize(de)?;
    if let Some(map) = &mut args {
        map.iter_mut().for_each(|(id, arg)| arg.id.clone_from(id));
    }
    Ok(args)
}

fn deserialize_groups<'de, D>(de: D) -> Result<Option<IndexMap<String, ArgGroup>>, D::Error>
where
    D: Deserializer<'de>,
{
    let mut groups = Option::<IndexMap<String, ArgGroup>>::deserialize(de)?;
    if let Some(map) = &mut groups {
        map.iter_mut()
            .for_each(|(name, group)| group.id.clone_from(name));
    }
    Ok(groups)
}

impl Command {
    #[must_use]
    pub fn get_name(&self) -> &str {
        &self.name
    }

    pub fn get_subcommands(&self) -> impl Iterator<Item = &Self> {
        self.subcommands.iter()
    }

    pub fn get_arguments(&self) -> impl Iterator<Item = &Arg> {
        self.args
            .as_ref()
            .into_iter()
            .flat_map(|args| args.values())
    }
}

#[expect(clippy::cognitive_complexity, clippy::too_many_lines)]
impl From<Command> for clap::Command {
    fn from(cmd: Command) -> Self {
        let mut command = Self::new(cmd.name);
        if let Some(no_binary_name) = cmd.no_binary_name {
            command = command.no_binary_name(no_binary_name);
        }
        if let Some(ignore_errors) = cmd.ignore_errors {
            command = command.ignore_errors(ignore_errors);
        }
        if let Some(args_override_self) = cmd.args_override_self {
            command = command.args_override_self(args_override_self);
        }
        if let Some(dont_delimit_trailing_values) = cmd.dont_delimit_trailing_values {
            command = command.dont_delimit_trailing_values(dont_delimit_trailing_values);
        }
        if let Some(color) = cmd.color {
            command = command.color(clap::ColorChoice::from(color));
        }
        if let Some(styles) = cmd.styles {
            command = command.styles(clap::builder::Styles::from(styles));
        }
        if let Some(term_width) = cmd.term_width {
            command = command.term_width(term_width);
        }
        if let Some(max_term_width) = cmd.max_term_width {
            command = command.max_term_width(max_term_width);
        }
        if let Some(disable_version_flag) = cmd.disable_version_flag {
            command = command.disable_version_flag(disable_version_flag);
        }
        if let Some(propagate_version) = cmd.propagate_version {
            command = command.propagate_version(propagate_version);
        }
        if let Some(next_line_help) = cmd.next_line_help {
            command = command.next_line_help(next_line_help);
        }
        if let Some(disable_help_flag) = cmd.disable_help_flag {
            command = command.disable_help_flag(disable_help_flag);
        }
        if let Some(disable_help_subcommand) = cmd.disable_help_subcommand {
            command = command.disable_help_subcommand(disable_help_subcommand);
        }
        if let Some(disable_colored_help) = cmd.disable_colored_help {
            command = command.disable_colored_help(disable_colored_help);
        }
        if let Some(help_expected) = cmd.help_expected {
            command = command.help_expected(help_expected);
        }
        if let Some(hide_possible_values) = cmd.hide_possible_values {
            command = command.hide_possible_values(hide_possible_values);
        }
        if let Some(infer_long_args) = cmd.infer_long_args {
            command = command.infer_long_args(infer_long_args);
        }
        if let Some(infer_subcommands) = cmd.infer_subcommands {
            command = command.infer_subcommands(infer_subcommands);
        }
        if let Some(bin_name) = cmd.bin_name {
            command = command.bin_name(bin_name);
        }
        if let Some(display_name) = cmd.display_name {
            command = command.display_name(display_name);
        }
        if let Some(author) = cmd.author {
            command = command.author(author);
        }
        if let Some(about) = cmd.about {
            command = command.about(about);
        }
        if let Some(long_about) = cmd.long_about {
            command = command.long_about(long_about);
        }
        if let Some(after_help) = cmd.after_help {
            command = command.after_help(after_help);
        }
        if let Some(after_long_help) = cmd.after_long_help {
            command = command.after_long_help(after_long_help);
        }
        if let Some(before_help) = cmd.before_help {
            command = command.before_help(before_help);
        }
        if let Some(before_long_help) = cmd.before_long_help {
            command = command.before_long_help(before_long_help);
        }
        if let Some(version) = cmd.version {
            command = command.version(version);
        }
        if let Some(long_version) = cmd.long_version {
            command = command.long_version(long_version);
        }
        if let Some(override_usage) = cmd.override_usage {
            command = command.override_usage(override_usage);
        }
        if let Some(override_help) = cmd.override_help {
            command = command.override_help(override_help);
        }
        if let Some(help_template) = cmd.help_template {
            command = command.help_template(help_template);
        }
        if let Some(flatten_help) = cmd.flatten_help {
            command = command.flatten_help(flatten_help);
        }
        if let Some(next_help_heading) = cmd.next_help_heading {
            command = command.next_help_heading(next_help_heading);
        }
        if let Some(next_display_order) = cmd.next_display_order {
            command = command.next_display_order(next_display_order);
        }
        if let Some(arg_required_else_help) = cmd.arg_required_else_help {
            command = command.arg_required_else_help(arg_required_else_help);
        }
        if let Some(allow_missing_positional) = cmd.allow_missing_positional {
            command = command.allow_missing_positional(allow_missing_positional);
        }
        if let Some(short_flag) = cmd.short_flag {
            command = command.short_flag(short_flag);
        }
        if let Some(long_flag) = cmd.long_flag {
            command = command.long_flag(long_flag);
        }
        if let Some(alias) = cmd.alias {
            command = command.alias(alias);
        }
        if let Some(short_flag_alias) = cmd.short_flag_alias {
            command = command.short_flag_alias(short_flag_alias);
        }
        if let Some(long_flag_alias) = cmd.long_flag_alias {
            command = command.long_flag_alias(long_flag_alias);
        }
        if let Some(aliases) = cmd.aliases {
            command = command.aliases(aliases);
        }
        if let Some(short_flag_aliases) = cmd.short_flag_aliases {
            command = command.short_flag_aliases(short_flag_aliases);
        }
        if let Some(long_flag_aliases) = cmd.long_flag_aliases {
            command = command.long_flag_aliases(long_flag_aliases);
        }
        if let Some(visible_alias) = cmd.visible_alias {
            command = command.visible_alias(visible_alias);
        }
        if let Some(visible_short_flag_alias) = cmd.visible_short_flag_alias {
            command = command.visible_short_flag_alias(visible_short_flag_alias);
        }
        if let Some(visible_long_flag_alias) = cmd.visible_long_flag_alias {
            command = command.visible_long_flag_alias(visible_long_flag_alias);
        }
        if let Some(visible_aliases) = cmd.visible_aliases {
            command = command.visible_aliases(visible_aliases);
        }
        if let Some(visible_short_flag_aliases) = cmd.visible_short_flag_aliases {
            command = command.visible_short_flag_aliases(visible_short_flag_aliases);
        }
        if let Some(visible_long_flag_aliases) = cmd.visible_long_flag_aliases {
            command = command.visible_long_flag_aliases(visible_long_flag_aliases);
        }
        if let Some(display_order) = cmd.display_order {
            command = command.display_order(display_order);
        }
        if let Some(hide) = cmd.hide {
            command = command.hide(hide);
        }
        if let Some(subcommand_required) = cmd.subcommand_required {
            command = command.subcommand_required(subcommand_required);
        }
        if let Some(allow_external_subcommands) = cmd.allow_external_subcommands {
            command = command.allow_external_subcommands(allow_external_subcommands);
        }
        if let Some(external_subcommand_value_parser) = cmd.external_subcommand_value_parser {
            command = command.external_subcommand_value_parser(clap::builder::ValueParser::from(
                external_subcommand_value_parser,
            ));
        }
        if let Some(args_conflicts_with_subcommands) = cmd.args_conflicts_with_subcommands {
            command = command.args_conflicts_with_subcommands(args_conflicts_with_subcommands);
        }
        if let Some(subcommand_precedence_over_arg) = cmd.subcommand_precedence_over_arg {
            command = command.subcommand_precedence_over_arg(subcommand_precedence_over_arg);
        }
        if let Some(subcommand_negates_reqs) = cmd.subcommand_negates_reqs {
            command = command.subcommand_negates_reqs(subcommand_negates_reqs);
        }
        if let Some(multicall) = cmd.multicall {
            command = command.multicall(multicall);
        }
        if let Some(subcommand_value_name) = cmd.subcommand_value_name {
            command = command.subcommand_value_name(subcommand_value_name);
        }
        if let Some(subcommand_help_heading) = cmd.subcommand_help_heading {
            command = command.subcommand_help_heading(subcommand_help_heading);
        }
        command = command.args(if let Some(args) = cmd.args {
            args.into_values().map(clap::Arg::from).collect::<Vec<_>>()
        } else {
            Vec::new()
        });
        if let Some(groups) = cmd.groups {
            command = command.groups(groups.into_values().map(clap::ArgGroup::from));
        }
        command = command.subcommands(cmd.subcommands);
        command
    }
}

#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
enum ColorChoice {
    Auto,
    Always,
    Never,
}

impl From<ColorChoice> for clap::ColorChoice {
    fn from(color_choice: ColorChoice) -> Self {
        match color_choice {
            ColorChoice::Auto => Self::Auto,
            ColorChoice::Always => Self::Always,
            ColorChoice::Never => Self::Never,
        }
    }
}