use crate::spec::cmd::SpecExample;
use crate::spec::effect::SpecCommandEffect;
use crate::{
spec::arg::SpecDoubleDashChoices, SpecArg, SpecChoices, SpecCommand, SpecDefaultIf, SpecFlag,
SpecRequiredIfEq, SpecRequiresIf,
};
#[derive(Debug, Default, Clone)]
pub struct SpecFlagBuilder {
inner: SpecFlag,
allow_hyphen_values: bool,
}
impl SpecFlagBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.inner.name = name.into();
self
}
pub fn short(mut self, c: char) -> Self {
self.inner.short.push(c);
self
}
pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
self.inner.short.extend(chars);
self
}
pub fn long(mut self, name: impl Into<String>) -> Self {
self.inner.long.push(name.into());
self
}
pub fn longs<I, S>(mut self, names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.long.extend(names.into_iter().map(Into::into));
self
}
pub fn default_value(mut self, value: impl Into<String>) -> Self {
self.inner.default.push(value.into());
self.inner.required = false;
self
}
pub fn default_values<I, S>(mut self, values: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.default
.extend(values.into_iter().map(Into::into));
if !self.inner.default.is_empty() {
self.inner.required = false;
}
self
}
pub fn help(mut self, text: impl Into<String>) -> Self {
self.inner.help = Some(text.into());
self
}
pub fn help_long(mut self, text: impl Into<String>) -> Self {
self.inner.help_long = Some(text.into());
self
}
pub fn help_md(mut self, text: impl Into<String>) -> Self {
self.inner.help_md = Some(text.into());
self
}
pub fn var(mut self, is_var: bool) -> Self {
self.inner.var = is_var;
self
}
pub fn var_min(mut self, min: usize) -> Self {
self.inner.var_min = Some(min);
self
}
pub fn var_max(mut self, max: usize) -> Self {
self.inner.var_max = Some(max);
self
}
pub fn required(mut self, is_required: bool) -> Self {
self.inner.required = is_required;
self
}
pub fn required_if(mut self, flag: impl Into<String>) -> Self {
self.inner.required_if.push(flag.into());
self
}
pub fn required_if_any<I, S>(mut self, flags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_if
.extend(flags.into_iter().map(Into::into));
self
}
pub fn required_if_eq(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
self.inner.required_if_eq.push(SpecRequiredIfEq {
selector: selector.into(),
value: value.into(),
});
self
}
pub fn required_if_eq_all<I, S, V>(mut self, conditions: I) -> Self
where
I: IntoIterator<Item = (S, V)>,
S: Into<String>,
V: Into<String>,
{
self.inner
.required_if_eq_all
.extend(
conditions
.into_iter()
.map(|(selector, value)| SpecRequiredIfEq {
selector: selector.into(),
value: value.into(),
}),
);
self
}
pub fn required_unless(mut self, flag: impl Into<String>) -> Self {
self.inner.required_unless.push(flag.into());
self
}
pub fn required_unless_any<I, S>(mut self, flags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_unless
.extend(flags.into_iter().map(Into::into));
self
}
pub fn required_unless_all<I, S>(mut self, flags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_unless_all
.extend(flags.into_iter().map(Into::into));
self
}
pub fn global(mut self, is_global: bool) -> Self {
self.inner.global = is_global;
self
}
pub fn hide(mut self, is_hidden: bool) -> Self {
self.inner.hide = is_hidden;
self
}
pub fn count(mut self, is_count: bool) -> Self {
self.inner.count = is_count;
self
}
pub fn allow_hyphen_values(mut self, allow: bool) -> Self {
self.allow_hyphen_values = allow;
if let Some(arg) = &mut self.inner.arg {
arg.double_dash = if allow {
crate::spec::arg::SpecDoubleDashChoices::Automatic
} else {
crate::spec::arg::SpecDoubleDashChoices::Optional
};
}
self
}
pub fn require_equals(mut self, require: bool) -> Self {
self.inner.require_equals = require;
self
}
pub fn value_optional(mut self, optional: bool) -> Self {
self.inner.value_optional = optional;
self
}
pub fn bool_value(mut self, enabled: bool) -> Self {
self.inner.bool_value = enabled;
self
}
pub fn default_missing(mut self, value: impl Into<String>) -> Self {
self.inner.default_missing = Some(value.into());
self
}
pub fn arg(mut self, arg: SpecArg) -> Self {
self.inner.arg = Some(arg);
if self.allow_hyphen_values {
if let Some(arg) = &mut self.inner.arg {
arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
}
}
self
}
pub fn negate(mut self, negate: impl Into<String>) -> Self {
self.inner.negate = Some(negate.into());
self
}
pub fn override_with(mut self, flag: impl Into<String>) -> Self {
self.inner.overrides.push(flag.into());
self
}
pub fn overrides_with<I, S>(mut self, flags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.overrides
.extend(flags.into_iter().map(Into::into));
self
}
pub fn require(mut self, flag: impl Into<String>) -> Self {
self.inner.requires.push(flag.into());
self
}
pub fn requires<I, S>(mut self, flags: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.requires
.extend(flags.into_iter().map(Into::into));
self
}
pub fn requires_if(mut self, value: impl Into<String>, flag: impl Into<String>) -> Self {
self.inner.requires_if.push(SpecRequiresIf {
value: value.into(),
requires: flag.into(),
});
self
}
pub fn requires_ifs<I, V, S>(mut self, requirements: I) -> Self
where
I: IntoIterator<Item = (V, S)>,
V: Into<String>,
S: Into<String>,
{
self.inner
.requires_if
.extend(
requirements
.into_iter()
.map(|(value, requires)| SpecRequiresIf {
value: value.into(),
requires: requires.into(),
}),
);
self
}
pub fn default_if(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
self.inner.default_if.push(SpecDefaultIf {
selector: selector.into(),
when: None,
value: value.into(),
});
self
}
pub fn default_if_eq(
mut self,
selector: impl Into<String>,
when: impl Into<String>,
value: impl Into<String>,
) -> Self {
self.inner.default_if.push(SpecDefaultIf {
selector: selector.into(),
when: Some(when.into()),
value: value.into(),
});
self
}
pub fn default_ifs<I>(mut self, conditions: I) -> Self
where
I: IntoIterator<Item = SpecDefaultIf>,
{
self.inner.default_if.extend(conditions);
self
}
pub fn help_heading(mut self, help_heading: impl Into<String>) -> Self {
self.inner.help_heading = Some(help_heading.into());
self
}
pub fn action(mut self, action: crate::SpecFlagAction) -> Self {
self.inner.action = action;
self
}
pub fn env(mut self, env: impl Into<String>) -> Self {
self.inner.env = Some(env.into());
self
}
pub fn env_fallback(mut self, env: impl Into<String>) -> Self {
self.inner.env_fallback.push(env.into());
self
}
pub fn deprecated_env(mut self, env: impl Into<String>) -> Self {
self.inner.deprecated_env.push(env.into());
self
}
pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
self.inner.deprecated = Some(msg.into());
self
}
pub fn deprecated_warn_at(mut self, version: impl Into<String>) -> Self {
self.inner.deprecated_warn_at = Some(version.into());
self
}
pub fn deprecated_remove_at(mut self, version: impl Into<String>) -> Self {
self.inner.deprecated_remove_at = Some(version.into());
self
}
pub fn usage(mut self, usage: impl Into<String>) -> Self {
self.inner.usage = usage.into();
self
}
pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
self.inner.help_first_line = Some(text.into());
self
}
pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
self.inner.effect = Some(effect);
self
}
#[must_use]
pub fn build(mut self) -> SpecFlag {
if self.allow_hyphen_values {
if let Some(arg) = &mut self.inner.arg {
arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
}
}
if self.inner.default_missing.is_some() {
if let Some(arg) = &mut self.inner.arg {
arg.required = false;
}
}
self.inner.usage = self.inner.usage();
if self.inner.name.is_empty() {
if let Some(long) = self.inner.long.first() {
self.inner.name = long.clone();
} else if let Some(short) = self.inner.short.first() {
self.inner.name = short.to_string();
}
}
self.inner
}
}
#[derive(Debug, Default, Clone)]
pub struct SpecArgBuilder {
inner: SpecArg,
}
impl SpecArgBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.inner.name = name.into();
self
}
pub fn value_names<I, S>(mut self, names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.value_names = names.into_iter().map(Into::into).collect();
if let Some(first) = self.inner.value_names.first() {
self.inner.name.clone_from(first);
}
if self.inner.value_names.len() > 1 {
let arity = self.inner.value_names.len();
self.inner.var = true;
self.inner.var_min = Some(arity);
self.inner.var_max = Some(arity);
}
self
}
pub fn default_value(mut self, value: impl Into<String>) -> Self {
self.inner.default.push(value.into());
self.inner.required = false;
self
}
pub fn default_values<I, S>(mut self, values: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.default
.extend(values.into_iter().map(Into::into));
if !self.inner.default.is_empty() {
self.inner.required = false;
}
self
}
pub fn help(mut self, text: impl Into<String>) -> Self {
self.inner.help = Some(text.into());
self
}
pub fn help_long(mut self, text: impl Into<String>) -> Self {
self.inner.help_long = Some(text.into());
self
}
pub fn help_md(mut self, text: impl Into<String>) -> Self {
self.inner.help_md = Some(text.into());
self
}
pub fn var(mut self, is_var: bool) -> Self {
self.inner.var = is_var;
self
}
pub fn var_min(mut self, min: usize) -> Self {
self.inner.var_min = Some(min);
self
}
pub fn var_max(mut self, max: usize) -> Self {
self.inner.var_max = Some(max);
self
}
pub fn required(mut self, is_required: bool) -> Self {
self.inner.required = is_required;
self
}
pub fn requires<I, S>(mut self, selectors: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.requires
.extend(selectors.into_iter().map(Into::into));
self
}
pub fn required_if_any<I, S>(mut self, selectors: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_if
.extend(selectors.into_iter().map(Into::into));
self
}
pub fn required_if_eq(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
self.inner.required_if_eq.push(SpecRequiredIfEq {
selector: selector.into(),
value: value.into(),
});
self
}
pub fn required_if_eq_all<I, S, V>(mut self, conditions: I) -> Self
where
I: IntoIterator<Item = (S, V)>,
S: Into<String>,
V: Into<String>,
{
self.inner
.required_if_eq_all
.extend(
conditions
.into_iter()
.map(|(selector, value)| SpecRequiredIfEq {
selector: selector.into(),
value: value.into(),
}),
);
self
}
pub fn required_unless_any<I, S>(mut self, selectors: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_unless
.extend(selectors.into_iter().map(Into::into));
self
}
pub fn required_unless_all<I, S>(mut self, selectors: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.required_unless_all
.extend(selectors.into_iter().map(Into::into));
self
}
pub fn hide(mut self, is_hidden: bool) -> Self {
self.inner.hide = is_hidden;
self
}
pub fn help_heading(mut self, help_heading: impl Into<String>) -> Self {
self.inner.help_heading = Some(help_heading.into());
self
}
pub fn env(mut self, env: impl Into<String>) -> Self {
self.inner.env = Some(env.into());
self
}
pub fn env_fallback(mut self, env: impl Into<String>) -> Self {
self.inner.env_fallback.push(env.into());
self
}
pub fn deprecated_env(mut self, env: impl Into<String>) -> Self {
self.inner.deprecated_env.push(env.into());
self
}
pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
self.inner.double_dash = behavior;
self
}
pub fn choices<I, S>(mut self, choices: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
#[cfg(feature = "unstable_choices_env")]
let env = spec_choices.env().map(ToString::to_string);
spec_choices.choices = choices.into_iter().map(Into::into).collect();
#[cfg(feature = "unstable_choices_env")]
spec_choices.set_env(env);
self
}
pub fn validate(mut self, expression: impl Into<String>) -> Self {
self.inner.validate = Some(expression.into());
self
}
pub fn validate_error(mut self, message: impl Into<String>) -> Self {
self.inner.validate_error = Some(message.into());
self
}
#[cfg(feature = "unstable_choices_env")]
pub fn choices_env(mut self, env: impl Into<String>) -> Self {
let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
choices.set_env(Some(env.into()));
self
}
pub fn usage(mut self, usage: impl Into<String>) -> Self {
self.inner.usage = usage.into();
self
}
pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
self.inner.help_first_line = Some(text.into());
self
}
pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
self.inner.effect = Some(effect);
self
}
#[must_use]
pub fn build(mut self) -> SpecArg {
if self.inner.validate.is_none() {
self.inner.validate_error = None;
}
if self.inner.value_names.len() > 1 {
let arity = self.inner.value_names.len();
self.inner.var = true;
self.inner.var_min = Some(arity);
self.inner.var_max = Some(arity);
}
self.inner.usage = self.inner.usage();
self.inner
}
}
#[derive(Debug, Default, Clone)]
pub struct SpecCommandBuilder {
inner: SpecCommand,
}
impl SpecCommandBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn name(mut self, name: impl Into<String>) -> Self {
self.inner.name = name.into();
self
}
pub fn alias(mut self, alias: impl Into<String>) -> Self {
self.inner.aliases.push(alias.into());
self
}
pub fn aliases<I, S>(mut self, aliases: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.aliases
.extend(aliases.into_iter().map(Into::into));
self
}
pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
self.inner.hidden_aliases.push(alias.into());
self
}
pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner
.hidden_aliases
.extend(aliases.into_iter().map(Into::into));
self
}
pub fn flag(mut self, flag: SpecFlag) -> Self {
self.inner.flags.push(flag);
self
}
pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
self.inner.flags.extend(flags);
self
}
pub fn arg(mut self, arg: SpecArg) -> Self {
self.inner.args.push(arg);
self
}
pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
self.inner.args.extend(args);
self
}
pub fn help(mut self, text: impl Into<String>) -> Self {
self.inner.help = Some(text.into());
self
}
pub fn help_long(mut self, text: impl Into<String>) -> Self {
self.inner.help_long = Some(text.into());
self
}
pub fn help_md(mut self, text: impl Into<String>) -> Self {
self.inner.help_md = Some(text.into());
self
}
pub fn hide(mut self, is_hidden: bool) -> Self {
self.inner.hide = is_hidden;
self
}
pub fn subcommand_required(mut self, required: bool) -> Self {
self.inner.subcommand_required = required;
self
}
pub fn subcommand_help_heading(mut self, heading: impl Into<String>) -> Self {
self.inner.subcommand_help_heading = Some(heading.into());
self
}
pub fn subcommand_value_name(mut self, name: impl Into<String>) -> Self {
self.inner.subcommand_value_name = Some(name.into());
self
}
pub fn term_width(mut self, width: usize) -> Self {
self.inner.term_width = Some(width);
self
}
pub fn max_term_width(mut self, width: usize) -> Self {
self.inner.max_term_width = Some(width);
self
}
pub fn external_subcommand(mut self, enabled: bool) -> Self {
self.inner.external_subcommand = enabled;
self
}
pub fn disable_help_flag(mut self, disabled: bool) -> Self {
self.inner.disable_help_flag = disabled;
self
}
pub fn disable_help_subcommand(mut self, disabled: bool) -> Self {
self.inner.disable_help_subcommand = disabled;
self
}
pub fn disable_version_flag(mut self, disabled: bool) -> Self {
self.inner.disable_version_flag = disabled;
self
}
pub fn args_override_self(mut self, enabled: bool) -> Self {
self.inner.args_override_self = enabled;
self
}
pub fn subcommand_negates_reqs(mut self, enabled: bool) -> Self {
self.inner.subcommand_negates_reqs = enabled;
self
}
pub fn args_conflicts_with_subcommands(mut self, enabled: bool) -> Self {
self.inner.args_conflicts_with_subcommands = enabled;
self
}
pub fn subcommand_precedence_over_arg(mut self, enabled: bool) -> Self {
self.inner.subcommand_precedence_over_arg = enabled;
self
}
pub fn allow_missing_positional(mut self, enabled: bool) -> Self {
self.inner.allow_missing_positional = enabled;
self
}
pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
self.inner.effect = Some(effect);
self
}
pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
self.inner.deprecated = Some(msg.into());
self
}
pub fn deprecated_warn_at(mut self, version: impl Into<String>) -> Self {
self.inner.deprecated_warn_at = Some(version.into());
self
}
pub fn deprecated_remove_at(mut self, version: impl Into<String>) -> Self {
self.inner.deprecated_remove_at = Some(version.into());
self
}
pub fn restart_token(mut self, token: impl Into<String>) -> Self {
self.inner.restart_token = Some(token.into());
self
}
pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
self.inner.subcommands.insert(cmd.name.clone(), cmd);
self
}
pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
for cmd in cmds {
self.inner.subcommands.insert(cmd.name.clone(), cmd);
}
self
}
pub fn before_help(mut self, text: impl Into<String>) -> Self {
self.inner.before_help = Some(text.into());
self
}
pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
self.inner.before_help_long = Some(text.into());
self
}
pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
self.inner.before_help_md = Some(text.into());
self
}
pub fn after_help(mut self, text: impl Into<String>) -> Self {
self.inner.after_help = Some(text.into());
self
}
pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
self.inner.after_help_long = Some(text.into());
self
}
pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
self.inner.after_help_md = Some(text.into());
self
}
pub fn example(mut self, code: impl Into<String>) -> Self {
self.inner.examples.push(SpecExample::new(code.into()));
self
}
pub fn example_with_help(
mut self,
code: impl Into<String>,
header: impl Into<String>,
help: impl Into<String>,
) -> Self {
let mut example = SpecExample::new(code.into());
example.header = Some(header.into());
example.help = Some(help.into());
self.inner.examples.push(example);
self
}
#[must_use]
pub fn build(mut self) -> SpecCommand {
self.inner.usage = self.inner.usage();
self.inner
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_flag_builder_basic() {
let flag = SpecFlagBuilder::new()
.name("verbose")
.short('v')
.long("verbose")
.help("Enable verbose output")
.build();
assert_eq!(flag.name, "verbose");
assert_eq!(flag.short, vec!['v']);
assert_eq!(flag.long, vec!["verbose".to_string()]);
assert_eq!(flag.help, Some("Enable verbose output".to_string()));
}
#[test]
fn test_flag_builder_multiple_values() {
let flag = SpecFlagBuilder::new()
.shorts(['v', 'V'])
.longs(["verbose", "loud"])
.default_values(["info", "warn"])
.build();
assert_eq!(flag.short, vec!['v', 'V']);
assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
assert!(!flag.required); }
#[test]
fn test_flag_builder_variadic() {
let flag = SpecFlagBuilder::new()
.long("file")
.var(true)
.var_min(1)
.var_max(10)
.build();
assert!(flag.var);
assert_eq!(flag.var_min, Some(1));
assert_eq!(flag.var_max, Some(10));
}
#[test]
fn test_flag_builder_conditional_requirements() {
let flag = SpecFlagBuilder::new()
.long("config")
.requires_if("special.toml", "--key")
.requires_ifs([("remote.toml", "--token"), ("signed.toml", "--identity")])
.build();
assert_eq!(
flag.requires_if,
[
SpecRequiresIf {
value: "special.toml".into(),
requires: "--key".into(),
},
SpecRequiresIf {
value: "remote.toml".into(),
requires: "--token".into(),
},
SpecRequiresIf {
value: "signed.toml".into(),
requires: "--identity".into(),
},
]
);
}
#[test]
fn test_flag_builder_conditional_defaults() {
let flag = SpecFlagBuilder::new()
.long("bin-names")
.default_if("--json", "true")
.default_if_eq("--output", "json", "pretty")
.build();
assert_eq!(
flag.default_if,
[
SpecDefaultIf {
selector: "--json".into(),
when: None,
value: "true".into(),
},
SpecDefaultIf {
selector: "--output".into(),
when: Some("json".into()),
value: "pretty".into(),
},
]
);
}
#[test]
fn test_flag_builder_name_derivation() {
let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
assert_eq!(flag.name, "verbose");
let flag2 = SpecFlagBuilder::new().short('v').build();
assert_eq!(flag2.name, "v");
}
#[test]
fn test_arg_builder_basic() {
let arg = SpecArgBuilder::new()
.name("file")
.help("Input file")
.required(true)
.build();
assert_eq!(arg.name, "file");
assert_eq!(arg.help, Some("Input file".to_string()));
assert!(arg.required);
}
#[test]
fn test_arg_builder_variadic() {
let arg = SpecArgBuilder::new()
.name("files")
.var(true)
.var_min(1)
.var_max(10)
.help("Input files")
.build();
assert_eq!(arg.name, "files");
assert!(arg.var);
assert_eq!(arg.var_min, Some(1));
assert_eq!(arg.var_max, Some(10));
}
#[test]
fn test_arg_builder_defaults() {
let arg = SpecArgBuilder::new()
.name("file")
.default_values(["a.txt", "b.txt"])
.build();
assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
assert!(!arg.required);
}
#[test]
fn test_arg_builder_drops_validation_error_without_expression() {
let arg = SpecArgBuilder::new()
.name("port")
.validate_error("must be a valid port")
.build();
assert!(arg.validate_error.is_none());
}
#[test]
fn test_command_builder_basic() {
let cmd = SpecCommandBuilder::new()
.name("install")
.help("Install packages")
.build();
assert_eq!(cmd.name, "install");
assert_eq!(cmd.help, Some("Install packages".to_string()));
}
#[test]
fn test_command_builder_aliases() {
let cmd = SpecCommandBuilder::new()
.name("install")
.alias("i")
.aliases(["add", "get"])
.hidden_aliases(["inst"])
.build();
assert_eq!(
cmd.aliases,
vec!["i".to_string(), "add".to_string(), "get".to_string()]
);
assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
}
#[test]
fn test_command_builder_with_flags_and_args() {
let flag = SpecFlagBuilder::new().short('f').long("force").build();
let arg = SpecArgBuilder::new().name("package").required(true).build();
let cmd = SpecCommandBuilder::new()
.name("install")
.flag(flag)
.arg(arg)
.build();
assert_eq!(cmd.flags.len(), 1);
assert_eq!(cmd.flags[0].name, "force");
assert_eq!(cmd.args.len(), 1);
assert_eq!(cmd.args[0].name, "package");
}
#[test]
fn test_arg_builder_choices() {
let arg = SpecArgBuilder::new()
.name("format")
.choices(["json", "yaml", "toml"])
.build();
assert!(arg.choices.is_some());
let choices = arg.choices.unwrap();
assert_eq!(
choices.choices,
vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
);
assert_eq!(choices.env(), None);
}
#[cfg(feature = "unstable_choices_env")]
#[test]
fn test_arg_builder_choices_env() {
let arg = SpecArgBuilder::new()
.name("env")
.choices(["local"])
.choices_env("DEPLOY_ENVS")
.build();
let choices = arg.choices.unwrap();
assert_eq!(choices.choices, vec!["local".to_string()]);
assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
}
#[cfg(feature = "unstable_choices_env")]
#[test]
fn test_arg_builder_choices_preserves_choices_env() {
let arg = SpecArgBuilder::new()
.name("env")
.choices_env("DEPLOY_ENVS")
.choices(["local"])
.build();
let choices = arg.choices.unwrap();
assert_eq!(choices.choices, vec!["local".to_string()]);
assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
}
#[test]
fn test_command_builder_subcommands() {
let sub1 = SpecCommandBuilder::new().name("sub1").build();
let sub2 = SpecCommandBuilder::new().name("sub2").build();
let cmd = SpecCommandBuilder::new()
.name("main")
.subcommand(sub1)
.subcommand(sub2)
.build();
assert_eq!(cmd.subcommands.len(), 2);
assert!(cmd.subcommands.contains_key("sub1"));
assert!(cmd.subcommands.contains_key("sub2"));
}
#[test]
fn test_command_builder_before_after_help() {
let cmd = SpecCommandBuilder::new()
.name("test")
.before_help("Before help text")
.before_help_long("Before help long text")
.after_help("After help text")
.after_help_long("After help long text")
.build();
assert_eq!(cmd.before_help, Some("Before help text".to_string()));
assert_eq!(
cmd.before_help_long,
Some("Before help long text".to_string())
);
assert_eq!(cmd.after_help, Some("After help text".to_string()));
assert_eq!(
cmd.after_help_long,
Some("After help long text".to_string())
);
}
#[test]
fn test_command_builder_examples() {
let cmd = SpecCommandBuilder::new()
.name("test")
.example("mycli run")
.example_with_help("mycli build", "Build example", "Build the project")
.build();
assert_eq!(cmd.examples.len(), 2);
assert_eq!(cmd.examples[0].code, "mycli run");
assert_eq!(cmd.examples[1].code, "mycli build");
assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
}
}