use crate::spec::cmd::SpecExample;
use crate::{spec::arg::SpecDoubleDashChoices, SpecArg, SpecChoices, SpecCommand, SpecFlag};
#[derive(Debug, Default, Clone)]
pub struct SpecFlagBuilder {
inner: SpecFlag,
}
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 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 arg(mut self, arg: SpecArg) -> Self {
self.inner.arg = Some(arg);
self
}
pub fn negate(mut self, negate: impl Into<String>) -> Self {
self.inner.negate = Some(negate.into());
self
}
pub fn env(mut self, env: impl Into<String>) -> Self {
self.inner.env = Some(env.into());
self
}
pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
self.inner.deprecated = Some(msg.into());
self
}
#[must_use]
pub fn build(mut self) -> SpecFlag {
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 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 hide(mut self, is_hidden: bool) -> Self {
self.inner.hide = is_hidden;
self
}
pub fn env(mut self, env: impl Into<String>) -> Self {
self.inner.env = Some(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
}
#[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
}
#[must_use]
pub fn build(mut self) -> SpecArg {
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 deprecated(mut self, msg: impl Into<String>) -> Self {
self.inner.deprecated = Some(msg.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_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_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()));
}
}