1use crate::spec::cmd::SpecExample;
34use crate::spec::effect::SpecCommandEffect;
35use crate::{spec::arg::SpecDoubleDashChoices, SpecArg, SpecChoices, SpecCommand, SpecFlag};
36
37#[derive(Debug, Default, Clone)]
39pub struct SpecFlagBuilder {
40 inner: SpecFlag,
41 allow_hyphen_values: bool,
42}
43
44impl SpecFlagBuilder {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn name(mut self, name: impl Into<String>) -> Self {
52 self.inner.name = name.into();
53 self
54 }
55
56 pub fn short(mut self, c: char) -> Self {
58 self.inner.short.push(c);
59 self
60 }
61
62 pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
64 self.inner.short.extend(chars);
65 self
66 }
67
68 pub fn long(mut self, name: impl Into<String>) -> Self {
70 self.inner.long.push(name.into());
71 self
72 }
73
74 pub fn longs<I, S>(mut self, names: I) -> Self
76 where
77 I: IntoIterator<Item = S>,
78 S: Into<String>,
79 {
80 self.inner.long.extend(names.into_iter().map(Into::into));
81 self
82 }
83
84 pub fn default_value(mut self, value: impl Into<String>) -> Self {
86 self.inner.default.push(value.into());
87 self.inner.required = false;
88 self
89 }
90
91 pub fn default_values<I, S>(mut self, values: I) -> Self
93 where
94 I: IntoIterator<Item = S>,
95 S: Into<String>,
96 {
97 self.inner
98 .default
99 .extend(values.into_iter().map(Into::into));
100 if !self.inner.default.is_empty() {
101 self.inner.required = false;
102 }
103 self
104 }
105
106 pub fn help(mut self, text: impl Into<String>) -> Self {
108 self.inner.help = Some(text.into());
109 self
110 }
111
112 pub fn help_long(mut self, text: impl Into<String>) -> Self {
114 self.inner.help_long = Some(text.into());
115 self
116 }
117
118 pub fn help_md(mut self, text: impl Into<String>) -> Self {
120 self.inner.help_md = Some(text.into());
121 self
122 }
123
124 pub fn var(mut self, is_var: bool) -> Self {
126 self.inner.var = is_var;
127 self
128 }
129
130 pub fn var_min(mut self, min: usize) -> Self {
132 self.inner.var_min = Some(min);
133 self
134 }
135
136 pub fn var_max(mut self, max: usize) -> Self {
138 self.inner.var_max = Some(max);
139 self
140 }
141
142 pub fn required(mut self, is_required: bool) -> Self {
144 self.inner.required = is_required;
145 self
146 }
147
148 pub fn global(mut self, is_global: bool) -> Self {
150 self.inner.global = is_global;
151 self
152 }
153
154 pub fn hide(mut self, is_hidden: bool) -> Self {
156 self.inner.hide = is_hidden;
157 self
158 }
159
160 pub fn count(mut self, is_count: bool) -> Self {
162 self.inner.count = is_count;
163 self
164 }
165
166 pub fn allow_hyphen_values(mut self, allow: bool) -> Self {
168 self.allow_hyphen_values = allow;
169 if let Some(arg) = &mut self.inner.arg {
170 arg.double_dash = if allow {
171 crate::spec::arg::SpecDoubleDashChoices::Automatic
172 } else {
173 crate::spec::arg::SpecDoubleDashChoices::Optional
174 };
175 }
176 self
177 }
178
179 pub fn arg(mut self, arg: SpecArg) -> Self {
181 self.inner.arg = Some(arg);
182 if self.allow_hyphen_values {
183 if let Some(arg) = &mut self.inner.arg {
184 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
185 }
186 }
187 self
188 }
189
190 pub fn negate(mut self, negate: impl Into<String>) -> Self {
192 self.inner.negate = Some(negate.into());
193 self
194 }
195
196 pub fn env(mut self, env: impl Into<String>) -> Self {
198 self.inner.env = Some(env.into());
199 self
200 }
201
202 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
204 self.inner.deprecated = Some(msg.into());
205 self
206 }
207
208 pub fn usage(mut self, usage: impl Into<String>) -> Self {
210 self.inner.usage = usage.into();
211 self
212 }
213
214 pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
216 self.inner.help_first_line = Some(text.into());
217 self
218 }
219
220 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
222 self.inner.effect = Some(effect);
223 self
224 }
225
226 #[must_use]
228 pub fn build(mut self) -> SpecFlag {
229 if self.allow_hyphen_values {
230 if let Some(arg) = &mut self.inner.arg {
231 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
232 }
233 }
234 self.inner.usage = self.inner.usage();
235 if self.inner.name.is_empty() {
236 if let Some(long) = self.inner.long.first() {
238 self.inner.name = long.clone();
239 } else if let Some(short) = self.inner.short.first() {
240 self.inner.name = short.to_string();
241 }
242 }
243 self.inner
244 }
245}
246
247#[derive(Debug, Default, Clone)]
249pub struct SpecArgBuilder {
250 inner: SpecArg,
251}
252
253impl SpecArgBuilder {
254 pub fn new() -> Self {
256 Self::default()
257 }
258
259 pub fn name(mut self, name: impl Into<String>) -> Self {
261 self.inner.name = name.into();
262 self
263 }
264
265 pub fn default_value(mut self, value: impl Into<String>) -> Self {
267 self.inner.default.push(value.into());
268 self.inner.required = false;
269 self
270 }
271
272 pub fn default_values<I, S>(mut self, values: I) -> Self
274 where
275 I: IntoIterator<Item = S>,
276 S: Into<String>,
277 {
278 self.inner
279 .default
280 .extend(values.into_iter().map(Into::into));
281 if !self.inner.default.is_empty() {
282 self.inner.required = false;
283 }
284 self
285 }
286
287 pub fn help(mut self, text: impl Into<String>) -> Self {
289 self.inner.help = Some(text.into());
290 self
291 }
292
293 pub fn help_long(mut self, text: impl Into<String>) -> Self {
295 self.inner.help_long = Some(text.into());
296 self
297 }
298
299 pub fn help_md(mut self, text: impl Into<String>) -> Self {
301 self.inner.help_md = Some(text.into());
302 self
303 }
304
305 pub fn var(mut self, is_var: bool) -> Self {
307 self.inner.var = is_var;
308 self
309 }
310
311 pub fn var_min(mut self, min: usize) -> Self {
313 self.inner.var_min = Some(min);
314 self
315 }
316
317 pub fn var_max(mut self, max: usize) -> Self {
319 self.inner.var_max = Some(max);
320 self
321 }
322
323 pub fn required(mut self, is_required: bool) -> Self {
325 self.inner.required = is_required;
326 self
327 }
328
329 pub fn hide(mut self, is_hidden: bool) -> Self {
331 self.inner.hide = is_hidden;
332 self
333 }
334
335 pub fn env(mut self, env: impl Into<String>) -> Self {
337 self.inner.env = Some(env.into());
338 self
339 }
340
341 pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
343 self.inner.double_dash = behavior;
344 self
345 }
346
347 pub fn choices<I, S>(mut self, choices: I) -> Self
349 where
350 I: IntoIterator<Item = S>,
351 S: Into<String>,
352 {
353 let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
354 #[cfg(feature = "unstable_choices_env")]
355 let env = spec_choices.env().map(ToString::to_string);
356 spec_choices.choices = choices.into_iter().map(Into::into).collect();
357 #[cfg(feature = "unstable_choices_env")]
358 spec_choices.set_env(env);
359 self
360 }
361
362 #[cfg(feature = "unstable_choices_env")]
364 pub fn choices_env(mut self, env: impl Into<String>) -> Self {
365 let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
366 choices.set_env(Some(env.into()));
367 self
368 }
369
370 pub fn usage(mut self, usage: impl Into<String>) -> Self {
372 self.inner.usage = usage.into();
373 self
374 }
375
376 pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
378 self.inner.help_first_line = Some(text.into());
379 self
380 }
381
382 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
384 self.inner.effect = Some(effect);
385 self
386 }
387
388 #[must_use]
390 pub fn build(mut self) -> SpecArg {
391 self.inner.usage = self.inner.usage();
392 self.inner
393 }
394}
395
396#[derive(Debug, Default, Clone)]
398pub struct SpecCommandBuilder {
399 inner: SpecCommand,
400}
401
402impl SpecCommandBuilder {
403 pub fn new() -> Self {
405 Self::default()
406 }
407
408 pub fn name(mut self, name: impl Into<String>) -> Self {
410 self.inner.name = name.into();
411 self
412 }
413
414 pub fn alias(mut self, alias: impl Into<String>) -> Self {
416 self.inner.aliases.push(alias.into());
417 self
418 }
419
420 pub fn aliases<I, S>(mut self, aliases: I) -> Self
422 where
423 I: IntoIterator<Item = S>,
424 S: Into<String>,
425 {
426 self.inner
427 .aliases
428 .extend(aliases.into_iter().map(Into::into));
429 self
430 }
431
432 pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
434 self.inner.hidden_aliases.push(alias.into());
435 self
436 }
437
438 pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
440 where
441 I: IntoIterator<Item = S>,
442 S: Into<String>,
443 {
444 self.inner
445 .hidden_aliases
446 .extend(aliases.into_iter().map(Into::into));
447 self
448 }
449
450 pub fn flag(mut self, flag: SpecFlag) -> Self {
452 self.inner.flags.push(flag);
453 self
454 }
455
456 pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
458 self.inner.flags.extend(flags);
459 self
460 }
461
462 pub fn arg(mut self, arg: SpecArg) -> Self {
464 self.inner.args.push(arg);
465 self
466 }
467
468 pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
470 self.inner.args.extend(args);
471 self
472 }
473
474 pub fn help(mut self, text: impl Into<String>) -> Self {
476 self.inner.help = Some(text.into());
477 self
478 }
479
480 pub fn help_long(mut self, text: impl Into<String>) -> Self {
482 self.inner.help_long = Some(text.into());
483 self
484 }
485
486 pub fn help_md(mut self, text: impl Into<String>) -> Self {
488 self.inner.help_md = Some(text.into());
489 self
490 }
491
492 pub fn hide(mut self, is_hidden: bool) -> Self {
494 self.inner.hide = is_hidden;
495 self
496 }
497
498 pub fn subcommand_required(mut self, required: bool) -> Self {
500 self.inner.subcommand_required = required;
501 self
502 }
503
504 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
506 self.inner.effect = Some(effect);
507 self
508 }
509
510 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
512 self.inner.deprecated = Some(msg.into());
513 self
514 }
515
516 pub fn restart_token(mut self, token: impl Into<String>) -> Self {
519 self.inner.restart_token = Some(token.into());
520 self
521 }
522
523 pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
525 self.inner.subcommands.insert(cmd.name.clone(), cmd);
526 self
527 }
528
529 pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
531 for cmd in cmds {
532 self.inner.subcommands.insert(cmd.name.clone(), cmd);
533 }
534 self
535 }
536
537 pub fn before_help(mut self, text: impl Into<String>) -> Self {
539 self.inner.before_help = Some(text.into());
540 self
541 }
542
543 pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
545 self.inner.before_help_long = Some(text.into());
546 self
547 }
548
549 pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
551 self.inner.before_help_md = Some(text.into());
552 self
553 }
554
555 pub fn after_help(mut self, text: impl Into<String>) -> Self {
557 self.inner.after_help = Some(text.into());
558 self
559 }
560
561 pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
563 self.inner.after_help_long = Some(text.into());
564 self
565 }
566
567 pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
569 self.inner.after_help_md = Some(text.into());
570 self
571 }
572
573 pub fn example(mut self, code: impl Into<String>) -> Self {
575 self.inner.examples.push(SpecExample::new(code.into()));
576 self
577 }
578
579 pub fn example_with_help(
581 mut self,
582 code: impl Into<String>,
583 header: impl Into<String>,
584 help: impl Into<String>,
585 ) -> Self {
586 let mut example = SpecExample::new(code.into());
587 example.header = Some(header.into());
588 example.help = Some(help.into());
589 self.inner.examples.push(example);
590 self
591 }
592
593 #[must_use]
595 pub fn build(mut self) -> SpecCommand {
596 self.inner.usage = self.inner.usage();
597 self.inner
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
606 fn test_flag_builder_basic() {
607 let flag = SpecFlagBuilder::new()
608 .name("verbose")
609 .short('v')
610 .long("verbose")
611 .help("Enable verbose output")
612 .build();
613
614 assert_eq!(flag.name, "verbose");
615 assert_eq!(flag.short, vec!['v']);
616 assert_eq!(flag.long, vec!["verbose".to_string()]);
617 assert_eq!(flag.help, Some("Enable verbose output".to_string()));
618 }
619
620 #[test]
621 fn test_flag_builder_multiple_values() {
622 let flag = SpecFlagBuilder::new()
623 .shorts(['v', 'V'])
624 .longs(["verbose", "loud"])
625 .default_values(["info", "warn"])
626 .build();
627
628 assert_eq!(flag.short, vec!['v', 'V']);
629 assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
630 assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
631 assert!(!flag.required); }
633
634 #[test]
635 fn test_flag_builder_variadic() {
636 let flag = SpecFlagBuilder::new()
637 .long("file")
638 .var(true)
639 .var_min(1)
640 .var_max(10)
641 .build();
642
643 assert!(flag.var);
644 assert_eq!(flag.var_min, Some(1));
645 assert_eq!(flag.var_max, Some(10));
646 }
647
648 #[test]
649 fn test_flag_builder_name_derivation() {
650 let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
651
652 assert_eq!(flag.name, "verbose");
654
655 let flag2 = SpecFlagBuilder::new().short('v').build();
656
657 assert_eq!(flag2.name, "v");
659 }
660
661 #[test]
662 fn test_arg_builder_basic() {
663 let arg = SpecArgBuilder::new()
664 .name("file")
665 .help("Input file")
666 .required(true)
667 .build();
668
669 assert_eq!(arg.name, "file");
670 assert_eq!(arg.help, Some("Input file".to_string()));
671 assert!(arg.required);
672 }
673
674 #[test]
675 fn test_arg_builder_variadic() {
676 let arg = SpecArgBuilder::new()
677 .name("files")
678 .var(true)
679 .var_min(1)
680 .var_max(10)
681 .help("Input files")
682 .build();
683
684 assert_eq!(arg.name, "files");
685 assert!(arg.var);
686 assert_eq!(arg.var_min, Some(1));
687 assert_eq!(arg.var_max, Some(10));
688 }
689
690 #[test]
691 fn test_arg_builder_defaults() {
692 let arg = SpecArgBuilder::new()
693 .name("file")
694 .default_values(["a.txt", "b.txt"])
695 .build();
696
697 assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
698 assert!(!arg.required);
699 }
700
701 #[test]
702 fn test_command_builder_basic() {
703 let cmd = SpecCommandBuilder::new()
704 .name("install")
705 .help("Install packages")
706 .build();
707
708 assert_eq!(cmd.name, "install");
709 assert_eq!(cmd.help, Some("Install packages".to_string()));
710 }
711
712 #[test]
713 fn test_command_builder_aliases() {
714 let cmd = SpecCommandBuilder::new()
715 .name("install")
716 .alias("i")
717 .aliases(["add", "get"])
718 .hidden_aliases(["inst"])
719 .build();
720
721 assert_eq!(
722 cmd.aliases,
723 vec!["i".to_string(), "add".to_string(), "get".to_string()]
724 );
725 assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
726 }
727
728 #[test]
729 fn test_command_builder_with_flags_and_args() {
730 let flag = SpecFlagBuilder::new().short('f').long("force").build();
731
732 let arg = SpecArgBuilder::new().name("package").required(true).build();
733
734 let cmd = SpecCommandBuilder::new()
735 .name("install")
736 .flag(flag)
737 .arg(arg)
738 .build();
739
740 assert_eq!(cmd.flags.len(), 1);
741 assert_eq!(cmd.flags[0].name, "force");
742 assert_eq!(cmd.args.len(), 1);
743 assert_eq!(cmd.args[0].name, "package");
744 }
745
746 #[test]
747 fn test_arg_builder_choices() {
748 let arg = SpecArgBuilder::new()
749 .name("format")
750 .choices(["json", "yaml", "toml"])
751 .build();
752
753 assert!(arg.choices.is_some());
754 let choices = arg.choices.unwrap();
755 assert_eq!(
756 choices.choices,
757 vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
758 );
759 assert_eq!(choices.env(), None);
760 }
761
762 #[cfg(feature = "unstable_choices_env")]
763 #[test]
764 fn test_arg_builder_choices_env() {
765 let arg = SpecArgBuilder::new()
766 .name("env")
767 .choices(["local"])
768 .choices_env("DEPLOY_ENVS")
769 .build();
770
771 let choices = arg.choices.unwrap();
772 assert_eq!(choices.choices, vec!["local".to_string()]);
773 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
774 }
775
776 #[cfg(feature = "unstable_choices_env")]
777 #[test]
778 fn test_arg_builder_choices_preserves_choices_env() {
779 let arg = SpecArgBuilder::new()
780 .name("env")
781 .choices_env("DEPLOY_ENVS")
782 .choices(["local"])
783 .build();
784
785 let choices = arg.choices.unwrap();
786 assert_eq!(choices.choices, vec!["local".to_string()]);
787 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
788 }
789
790 #[test]
791 fn test_command_builder_subcommands() {
792 let sub1 = SpecCommandBuilder::new().name("sub1").build();
793 let sub2 = SpecCommandBuilder::new().name("sub2").build();
794
795 let cmd = SpecCommandBuilder::new()
796 .name("main")
797 .subcommand(sub1)
798 .subcommand(sub2)
799 .build();
800
801 assert_eq!(cmd.subcommands.len(), 2);
802 assert!(cmd.subcommands.contains_key("sub1"));
803 assert!(cmd.subcommands.contains_key("sub2"));
804 }
805
806 #[test]
807 fn test_command_builder_before_after_help() {
808 let cmd = SpecCommandBuilder::new()
809 .name("test")
810 .before_help("Before help text")
811 .before_help_long("Before help long text")
812 .after_help("After help text")
813 .after_help_long("After help long text")
814 .build();
815
816 assert_eq!(cmd.before_help, Some("Before help text".to_string()));
817 assert_eq!(
818 cmd.before_help_long,
819 Some("Before help long text".to_string())
820 );
821 assert_eq!(cmd.after_help, Some("After help text".to_string()));
822 assert_eq!(
823 cmd.after_help_long,
824 Some("After help long text".to_string())
825 );
826 }
827
828 #[test]
829 fn test_command_builder_examples() {
830 let cmd = SpecCommandBuilder::new()
831 .name("test")
832 .example("mycli run")
833 .example_with_help("mycli build", "Build example", "Build the project")
834 .build();
835
836 assert_eq!(cmd.examples.len(), 2);
837 assert_eq!(cmd.examples[0].code, "mycli run");
838 assert_eq!(cmd.examples[1].code, "mycli build");
839 assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
840 assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
841 }
842}