1use crate::spec::cmd::SpecExample;
34use crate::spec::effect::SpecCommandEffect;
35use crate::{
36 spec::arg::SpecDoubleDashChoices, SpecAdmonition, SpecArg, SpecChoices, SpecCommand,
37 SpecDefaultIf, SpecFlag, SpecRequiredIfEq, SpecRequiresIf,
38};
39
40#[derive(Debug, Default, Clone)]
42pub struct SpecFlagBuilder {
43 inner: SpecFlag,
44 allow_hyphen_values: bool,
45}
46
47impl SpecFlagBuilder {
48 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn name(mut self, name: impl Into<String>) -> Self {
55 self.inner.name = name.into();
56 self
57 }
58
59 pub fn short(mut self, c: char) -> Self {
61 self.inner.short.push(c);
62 self
63 }
64
65 pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
67 self.inner.short.extend(chars);
68 self
69 }
70
71 pub fn long(mut self, name: impl Into<String>) -> Self {
73 self.inner.long.push(name.into());
74 self
75 }
76
77 pub fn longs<I, S>(mut self, names: I) -> Self
79 where
80 I: IntoIterator<Item = S>,
81 S: Into<String>,
82 {
83 self.inner.long.extend(names.into_iter().map(Into::into));
84 self
85 }
86
87 pub fn default_value(mut self, value: impl Into<String>) -> Self {
89 self.inner.default.push(value.into());
90 self.inner.required = false;
91 self
92 }
93
94 pub fn default_values<I, S>(mut self, values: I) -> Self
96 where
97 I: IntoIterator<Item = S>,
98 S: Into<String>,
99 {
100 self.inner
101 .default
102 .extend(values.into_iter().map(Into::into));
103 if !self.inner.default.is_empty() {
104 self.inner.required = false;
105 }
106 self
107 }
108
109 pub fn help(mut self, text: impl Into<String>) -> Self {
111 self.inner.help = Some(text.into());
112 self
113 }
114
115 pub fn help_long(mut self, text: impl Into<String>) -> Self {
117 self.inner.help_long = Some(text.into());
118 self
119 }
120
121 pub fn help_md(mut self, text: impl Into<String>) -> Self {
123 self.inner.help_md = Some(text.into());
124 self
125 }
126
127 pub fn note(mut self, text: impl Into<String>) -> Self {
129 self.inner.admonitions.push(SpecAdmonition::note(text));
130 self
131 }
132
133 pub fn warning(mut self, text: impl Into<String>) -> Self {
135 self.inner.admonitions.push(SpecAdmonition::warning(text));
136 self
137 }
138
139 pub fn surface(mut self, surface: impl Into<String>) -> Self {
141 self.inner.surface = Some(surface.into());
142 self
143 }
144
145 pub fn available_if(mut self, condition: impl Into<String>) -> Self {
147 self.inner.available_if.push(condition.into());
148 self
149 }
150
151 pub fn var(mut self, is_var: bool) -> Self {
153 self.inner.var = is_var;
154 self
155 }
156
157 pub fn var_min(mut self, min: usize) -> Self {
159 self.inner.var_min = Some(min);
160 self
161 }
162
163 pub fn var_max(mut self, max: usize) -> Self {
165 self.inner.var_max = Some(max);
166 self
167 }
168
169 pub fn required(mut self, is_required: bool) -> Self {
171 self.inner.required = is_required;
172 self
173 }
174
175 pub fn required_if(mut self, flag: impl Into<String>) -> Self {
177 self.inner.required_if.push(flag.into());
178 self
179 }
180
181 pub fn required_if_any<I, S>(mut self, flags: I) -> Self
183 where
184 I: IntoIterator<Item = S>,
185 S: Into<String>,
186 {
187 self.inner
188 .required_if
189 .extend(flags.into_iter().map(Into::into));
190 self
191 }
192
193 pub fn required_if_eq(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
195 self.inner.required_if_eq.push(SpecRequiredIfEq {
196 selector: selector.into(),
197 value: value.into(),
198 });
199 self
200 }
201
202 pub fn required_if_eq_all<I, S, V>(mut self, conditions: I) -> Self
204 where
205 I: IntoIterator<Item = (S, V)>,
206 S: Into<String>,
207 V: Into<String>,
208 {
209 self.inner
210 .required_if_eq_all
211 .extend(
212 conditions
213 .into_iter()
214 .map(|(selector, value)| SpecRequiredIfEq {
215 selector: selector.into(),
216 value: value.into(),
217 }),
218 );
219 self
220 }
221
222 pub fn required_unless(mut self, flag: impl Into<String>) -> Self {
224 self.inner.required_unless.push(flag.into());
225 self
226 }
227
228 pub fn required_unless_any<I, S>(mut self, flags: I) -> Self
230 where
231 I: IntoIterator<Item = S>,
232 S: Into<String>,
233 {
234 self.inner
235 .required_unless
236 .extend(flags.into_iter().map(Into::into));
237 self
238 }
239
240 pub fn required_unless_all<I, S>(mut self, flags: I) -> Self
242 where
243 I: IntoIterator<Item = S>,
244 S: Into<String>,
245 {
246 self.inner
247 .required_unless_all
248 .extend(flags.into_iter().map(Into::into));
249 self
250 }
251
252 pub fn global(mut self, is_global: bool) -> Self {
254 self.inner.global = is_global;
255 self
256 }
257
258 pub fn hide(mut self, is_hidden: bool) -> Self {
260 self.inner.hide = is_hidden;
261 self
262 }
263
264 pub fn count(mut self, is_count: bool) -> Self {
266 self.inner.count = is_count;
267 self
268 }
269
270 pub fn allow_hyphen_values(mut self, allow: bool) -> Self {
272 self.allow_hyphen_values = allow;
273 if let Some(arg) = &mut self.inner.arg {
274 arg.double_dash = if allow {
275 crate::spec::arg::SpecDoubleDashChoices::Automatic
276 } else {
277 crate::spec::arg::SpecDoubleDashChoices::Optional
278 };
279 }
280 self
281 }
282
283 pub fn require_equals(mut self, require: bool) -> Self {
285 self.inner.require_equals = require;
286 self
287 }
288
289 pub fn value_optional(mut self, optional: bool) -> Self {
291 self.inner.value_optional = optional;
292 self
293 }
294
295 pub fn bool_value(mut self, enabled: bool) -> Self {
297 self.inner.bool_value = enabled;
298 self
299 }
300
301 pub fn default_missing(mut self, value: impl Into<String>) -> Self {
303 self.inner.default_missing = Some(value.into());
304 self
305 }
306
307 pub fn arg(mut self, arg: SpecArg) -> Self {
309 self.inner.arg = Some(arg);
310 if self.allow_hyphen_values {
311 if let Some(arg) = &mut self.inner.arg {
312 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
313 }
314 }
315 self
316 }
317
318 pub fn negate(mut self, negate: impl Into<String>) -> Self {
320 self.inner.negate = Some(negate.into());
321 self
322 }
323
324 pub fn override_with(mut self, flag: impl Into<String>) -> Self {
326 self.inner.overrides.push(flag.into());
327 self
328 }
329
330 pub fn overrides_with<I, S>(mut self, flags: I) -> Self
332 where
333 I: IntoIterator<Item = S>,
334 S: Into<String>,
335 {
336 self.inner
337 .overrides
338 .extend(flags.into_iter().map(Into::into));
339 self
340 }
341
342 pub fn require(mut self, flag: impl Into<String>) -> Self {
344 self.inner.requires.push(flag.into());
345 self
346 }
347
348 pub fn requires<I, S>(mut self, flags: I) -> Self
350 where
351 I: IntoIterator<Item = S>,
352 S: Into<String>,
353 {
354 self.inner
355 .requires
356 .extend(flags.into_iter().map(Into::into));
357 self
358 }
359
360 pub fn requires_if(mut self, value: impl Into<String>, flag: impl Into<String>) -> Self {
362 self.inner.requires_if.push(SpecRequiresIf {
363 value: value.into(),
364 requires: flag.into(),
365 });
366 self
367 }
368
369 pub fn requires_ifs<I, V, S>(mut self, requirements: I) -> Self
371 where
372 I: IntoIterator<Item = (V, S)>,
373 V: Into<String>,
374 S: Into<String>,
375 {
376 self.inner
377 .requires_if
378 .extend(
379 requirements
380 .into_iter()
381 .map(|(value, requires)| SpecRequiresIf {
382 value: value.into(),
383 requires: requires.into(),
384 }),
385 );
386 self
387 }
388
389 pub fn default_if(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
393 self.inner.default_if.push(SpecDefaultIf {
394 selector: selector.into(),
395 when: None,
396 value: value.into(),
397 });
398 self
399 }
400
401 pub fn default_if_eq(
405 mut self,
406 selector: impl Into<String>,
407 when: impl Into<String>,
408 value: impl Into<String>,
409 ) -> Self {
410 self.inner.default_if.push(SpecDefaultIf {
411 selector: selector.into(),
412 when: Some(when.into()),
413 value: value.into(),
414 });
415 self
416 }
417
418 pub fn default_ifs<I>(mut self, conditions: I) -> Self
420 where
421 I: IntoIterator<Item = SpecDefaultIf>,
422 {
423 self.inner.default_if.extend(conditions);
424 self
425 }
426
427 pub fn help_heading(mut self, help_heading: impl Into<String>) -> Self {
429 self.inner.help_heading = Some(help_heading.into());
430 self
431 }
432
433 pub fn action(mut self, action: crate::SpecFlagAction) -> Self {
435 self.inner.action = action;
436 self
437 }
438
439 pub fn env(mut self, env: impl Into<String>) -> Self {
440 self.inner.env = Some(env.into());
441 self
442 }
443
444 pub fn env_fallback(mut self, env: impl Into<String>) -> Self {
446 self.inner.env_fallback.push(env.into());
447 self
448 }
449
450 pub fn deprecated_env(mut self, env: impl Into<String>) -> Self {
452 self.inner.deprecated_env.push(env.into());
453 self
454 }
455
456 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
458 self.inner.deprecated = Some(msg.into());
459 self
460 }
461
462 pub fn deprecated_warn_at(mut self, version: impl Into<String>) -> Self {
463 self.inner.deprecated_warn_at = Some(version.into());
464 self
465 }
466
467 pub fn deprecated_remove_at(mut self, version: impl Into<String>) -> Self {
468 self.inner.deprecated_remove_at = Some(version.into());
469 self
470 }
471
472 pub fn usage(mut self, usage: impl Into<String>) -> Self {
474 self.inner.usage = usage.into();
475 self
476 }
477
478 pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
480 self.inner.help_first_line = Some(text.into());
481 self
482 }
483
484 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
486 self.inner.effect = Some(effect);
487 self
488 }
489
490 #[must_use]
492 pub fn build(mut self) -> SpecFlag {
493 if self.allow_hyphen_values {
494 if let Some(arg) = &mut self.inner.arg {
495 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
496 }
497 }
498 if self.inner.default_missing.is_some() {
499 if let Some(arg) = &mut self.inner.arg {
500 arg.required = false;
501 }
502 }
503 self.inner.usage = self.inner.usage();
504 if self.inner.name.is_empty() {
505 if let Some(long) = self.inner.long.first() {
507 self.inner.name = long.clone();
508 } else if let Some(short) = self.inner.short.first() {
509 self.inner.name = short.to_string();
510 }
511 }
512 self.inner
513 }
514}
515
516#[derive(Debug, Default, Clone)]
518pub struct SpecArgBuilder {
519 inner: SpecArg,
520}
521
522impl SpecArgBuilder {
523 pub fn new() -> Self {
525 Self::default()
526 }
527
528 pub fn surface(mut self, surface: impl Into<String>) -> Self {
530 self.inner.surface = Some(surface.into());
531 self
532 }
533
534 pub fn available_if(mut self, condition: impl Into<String>) -> Self {
536 self.inner.available_if.push(condition.into());
537 self
538 }
539
540 pub fn name(mut self, name: impl Into<String>) -> Self {
542 self.inner.name = name.into();
543 self
544 }
545
546 pub fn value_names<I, S>(mut self, names: I) -> Self
548 where
549 I: IntoIterator<Item = S>,
550 S: Into<String>,
551 {
552 self.inner.value_names = names.into_iter().map(Into::into).collect();
553 if let Some(first) = self.inner.value_names.first() {
554 self.inner.name.clone_from(first);
555 }
556 if self.inner.value_names.len() > 1 {
557 let arity = self.inner.value_names.len();
558 self.inner.var = true;
559 self.inner.var_min = Some(arity);
560 self.inner.var_max = Some(arity);
561 }
562 self
563 }
564
565 pub fn default_value(mut self, value: impl Into<String>) -> Self {
567 self.inner.default.push(value.into());
568 self.inner.required = false;
569 self
570 }
571
572 pub fn default_values<I, S>(mut self, values: I) -> Self
574 where
575 I: IntoIterator<Item = S>,
576 S: Into<String>,
577 {
578 self.inner
579 .default
580 .extend(values.into_iter().map(Into::into));
581 if !self.inner.default.is_empty() {
582 self.inner.required = false;
583 }
584 self
585 }
586
587 pub fn help(mut self, text: impl Into<String>) -> Self {
589 self.inner.help = Some(text.into());
590 self
591 }
592
593 pub fn help_long(mut self, text: impl Into<String>) -> Self {
595 self.inner.help_long = Some(text.into());
596 self
597 }
598
599 pub fn help_md(mut self, text: impl Into<String>) -> Self {
601 self.inner.help_md = Some(text.into());
602 self
603 }
604
605 pub fn note(mut self, text: impl Into<String>) -> Self {
607 self.inner.admonitions.push(SpecAdmonition::note(text));
608 self
609 }
610
611 pub fn warning(mut self, text: impl Into<String>) -> Self {
613 self.inner.admonitions.push(SpecAdmonition::warning(text));
614 self
615 }
616
617 pub fn var(mut self, is_var: bool) -> Self {
619 self.inner.var = is_var;
620 self
621 }
622
623 pub fn var_min(mut self, min: usize) -> Self {
625 self.inner.var_min = Some(min);
626 self
627 }
628
629 pub fn var_max(mut self, max: usize) -> Self {
631 self.inner.var_max = Some(max);
632 self
633 }
634
635 pub fn required(mut self, is_required: bool) -> Self {
637 self.inner.required = is_required;
638 self
639 }
640
641 pub fn requires<I, S>(mut self, selectors: I) -> Self
643 where
644 I: IntoIterator<Item = S>,
645 S: Into<String>,
646 {
647 self.inner
648 .requires
649 .extend(selectors.into_iter().map(Into::into));
650 self
651 }
652
653 pub fn required_if_any<I, S>(mut self, selectors: I) -> Self
655 where
656 I: IntoIterator<Item = S>,
657 S: Into<String>,
658 {
659 self.inner
660 .required_if
661 .extend(selectors.into_iter().map(Into::into));
662 self
663 }
664
665 pub fn required_if_eq(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
667 self.inner.required_if_eq.push(SpecRequiredIfEq {
668 selector: selector.into(),
669 value: value.into(),
670 });
671 self
672 }
673
674 pub fn required_if_eq_all<I, S, V>(mut self, conditions: I) -> Self
676 where
677 I: IntoIterator<Item = (S, V)>,
678 S: Into<String>,
679 V: Into<String>,
680 {
681 self.inner
682 .required_if_eq_all
683 .extend(
684 conditions
685 .into_iter()
686 .map(|(selector, value)| SpecRequiredIfEq {
687 selector: selector.into(),
688 value: value.into(),
689 }),
690 );
691 self
692 }
693
694 pub fn required_unless_any<I, S>(mut self, selectors: I) -> Self
696 where
697 I: IntoIterator<Item = S>,
698 S: Into<String>,
699 {
700 self.inner
701 .required_unless
702 .extend(selectors.into_iter().map(Into::into));
703 self
704 }
705
706 pub fn required_unless_all<I, S>(mut self, selectors: I) -> Self
708 where
709 I: IntoIterator<Item = S>,
710 S: Into<String>,
711 {
712 self.inner
713 .required_unless_all
714 .extend(selectors.into_iter().map(Into::into));
715 self
716 }
717
718 pub fn hide(mut self, is_hidden: bool) -> Self {
720 self.inner.hide = is_hidden;
721 self
722 }
723
724 pub fn help_heading(mut self, help_heading: impl Into<String>) -> Self {
727 self.inner.help_heading = Some(help_heading.into());
728 self
729 }
730
731 pub fn env(mut self, env: impl Into<String>) -> Self {
732 self.inner.env = Some(env.into());
733 self
734 }
735
736 pub fn env_fallback(mut self, env: impl Into<String>) -> Self {
738 self.inner.env_fallback.push(env.into());
739 self
740 }
741
742 pub fn deprecated_env(mut self, env: impl Into<String>) -> Self {
744 self.inner.deprecated_env.push(env.into());
745 self
746 }
747
748 pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
750 self.inner.double_dash = behavior;
751 self
752 }
753
754 pub fn choices<I, S>(mut self, choices: I) -> Self
756 where
757 I: IntoIterator<Item = S>,
758 S: Into<String>,
759 {
760 let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
761 #[cfg(feature = "unstable_choices_env")]
762 let env = spec_choices.env().map(ToString::to_string);
763 spec_choices.choices = choices.into_iter().map(Into::into).collect();
764 #[cfg(feature = "unstable_choices_env")]
765 spec_choices.set_env(env);
766 self
767 }
768
769 pub fn validate(mut self, expression: impl Into<String>) -> Self {
771 self.inner.validate = Some(expression.into());
772 self
773 }
774
775 pub fn validate_error(mut self, message: impl Into<String>) -> Self {
777 self.inner.validate_error = Some(message.into());
778 self
779 }
780
781 #[cfg(feature = "unstable_choices_env")]
783 pub fn choices_env(mut self, env: impl Into<String>) -> Self {
784 let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
785 choices.set_env(Some(env.into()));
786 self
787 }
788
789 pub fn usage(mut self, usage: impl Into<String>) -> Self {
791 self.inner.usage = usage.into();
792 self
793 }
794
795 pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
797 self.inner.help_first_line = Some(text.into());
798 self
799 }
800
801 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
803 self.inner.effect = Some(effect);
804 self
805 }
806
807 #[must_use]
809 pub fn build(mut self) -> SpecArg {
810 if self.inner.validate.is_none() {
811 self.inner.validate_error = None;
812 }
813 if self.inner.value_names.len() > 1 {
814 let arity = self.inner.value_names.len();
815 self.inner.var = true;
816 self.inner.var_min = Some(arity);
817 self.inner.var_max = Some(arity);
818 }
819 self.inner.usage = self.inner.usage();
820 self.inner
821 }
822}
823
824#[derive(Debug, Default, Clone)]
826pub struct SpecCommandBuilder {
827 inner: SpecCommand,
828}
829
830impl SpecCommandBuilder {
831 pub fn surface(mut self, surface: impl Into<String>) -> Self {
833 self.inner.surface = Some(surface.into());
834 self
835 }
836
837 pub fn available_if(mut self, condition: impl Into<String>) -> Self {
839 self.inner.available_if.push(condition.into());
840 self
841 }
842 pub fn new() -> Self {
844 Self::default()
845 }
846
847 pub fn name(mut self, name: impl Into<String>) -> Self {
849 self.inner.name = name.into();
850 self
851 }
852
853 pub fn alias(mut self, alias: impl Into<String>) -> Self {
855 self.inner.aliases.push(alias.into());
856 self
857 }
858
859 pub fn aliases<I, S>(mut self, aliases: I) -> Self
861 where
862 I: IntoIterator<Item = S>,
863 S: Into<String>,
864 {
865 self.inner
866 .aliases
867 .extend(aliases.into_iter().map(Into::into));
868 self
869 }
870
871 pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
873 self.inner.hidden_aliases.push(alias.into());
874 self
875 }
876
877 pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
879 where
880 I: IntoIterator<Item = S>,
881 S: Into<String>,
882 {
883 self.inner
884 .hidden_aliases
885 .extend(aliases.into_iter().map(Into::into));
886 self
887 }
888
889 pub fn flag(mut self, flag: SpecFlag) -> Self {
891 self.inner.flags.push(flag);
892 self
893 }
894
895 pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
897 self.inner.flags.extend(flags);
898 self
899 }
900
901 pub fn arg(mut self, arg: SpecArg) -> Self {
903 self.inner.args.push(arg);
904 self
905 }
906
907 pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
909 self.inner.args.extend(args);
910 self
911 }
912
913 pub fn help(mut self, text: impl Into<String>) -> Self {
915 self.inner.help = Some(text.into());
916 self
917 }
918
919 pub fn help_long(mut self, text: impl Into<String>) -> Self {
921 self.inner.help_long = Some(text.into());
922 self
923 }
924
925 pub fn help_md(mut self, text: impl Into<String>) -> Self {
927 self.inner.help_md = Some(text.into());
928 self
929 }
930
931 pub fn hide(mut self, is_hidden: bool) -> Self {
933 self.inner.hide = is_hidden;
934 self
935 }
936
937 pub fn subcommand_required(mut self, required: bool) -> Self {
939 self.inner.subcommand_required = required;
940 self
941 }
942
943 pub fn subcommand_help_heading(mut self, heading: impl Into<String>) -> Self {
945 self.inner.subcommand_help_heading = Some(heading.into());
946 self
947 }
948
949 pub fn subcommand_value_name(mut self, name: impl Into<String>) -> Self {
951 self.inner.subcommand_value_name = Some(name.into());
952 self
953 }
954
955 pub fn term_width(mut self, width: usize) -> Self {
957 self.inner.term_width = Some(width);
958 self
959 }
960
961 pub fn max_term_width(mut self, width: usize) -> Self {
963 self.inner.max_term_width = Some(width);
964 self
965 }
966
967 pub fn external_subcommand(mut self, enabled: bool) -> Self {
969 self.inner.external_subcommand = enabled;
970 self
971 }
972
973 pub fn disable_help_flag(mut self, disabled: bool) -> Self {
975 self.inner.disable_help_flag = disabled;
976 self
977 }
978
979 pub fn disable_help_subcommand(mut self, disabled: bool) -> Self {
981 self.inner.disable_help_subcommand = disabled;
982 self
983 }
984
985 pub fn disable_version_flag(mut self, disabled: bool) -> Self {
987 self.inner.disable_version_flag = disabled;
988 self
989 }
990
991 pub fn args_override_self(mut self, enabled: bool) -> Self {
995 self.inner.args_override_self = enabled;
996 self
997 }
998
999 pub fn subcommand_negates_reqs(mut self, enabled: bool) -> Self {
1001 self.inner.subcommand_negates_reqs = enabled;
1002 self
1003 }
1004
1005 pub fn args_conflicts_with_subcommands(mut self, enabled: bool) -> Self {
1007 self.inner.args_conflicts_with_subcommands = enabled;
1008 self
1009 }
1010
1011 pub fn subcommand_precedence_over_arg(mut self, enabled: bool) -> Self {
1012 self.inner.subcommand_precedence_over_arg = enabled;
1013 self
1014 }
1015
1016 pub fn allow_missing_positional(mut self, enabled: bool) -> Self {
1017 self.inner.allow_missing_positional = enabled;
1018 self
1019 }
1020
1021 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
1023 self.inner.effect = Some(effect);
1024 self
1025 }
1026
1027 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
1029 self.inner.deprecated = Some(msg.into());
1030 self
1031 }
1032
1033 pub fn deprecated_warn_at(mut self, version: impl Into<String>) -> Self {
1034 self.inner.deprecated_warn_at = Some(version.into());
1035 self
1036 }
1037
1038 pub fn deprecated_remove_at(mut self, version: impl Into<String>) -> Self {
1039 self.inner.deprecated_remove_at = Some(version.into());
1040 self
1041 }
1042
1043 pub fn restart_token(mut self, token: impl Into<String>) -> Self {
1046 self.inner.restart_token = Some(token.into());
1047 self
1048 }
1049
1050 pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
1052 self.inner.subcommands.insert(cmd.name.clone(), cmd);
1053 self
1054 }
1055
1056 pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
1058 for cmd in cmds {
1059 self.inner.subcommands.insert(cmd.name.clone(), cmd);
1060 }
1061 self
1062 }
1063
1064 pub fn before_help(mut self, text: impl Into<String>) -> Self {
1066 self.inner.before_help = Some(text.into());
1067 self
1068 }
1069
1070 pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
1072 self.inner.before_help_long = Some(text.into());
1073 self
1074 }
1075
1076 pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
1078 self.inner.before_help_md = Some(text.into());
1079 self
1080 }
1081
1082 pub fn after_help(mut self, text: impl Into<String>) -> Self {
1084 self.inner.after_help = Some(text.into());
1085 self
1086 }
1087
1088 pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
1090 self.inner.after_help_long = Some(text.into());
1091 self
1092 }
1093
1094 pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
1096 self.inner.after_help_md = Some(text.into());
1097 self
1098 }
1099
1100 pub fn example(mut self, code: impl Into<String>) -> Self {
1102 self.inner.examples.push(SpecExample::new(code.into()));
1103 self
1104 }
1105
1106 pub fn example_with_help(
1108 mut self,
1109 code: impl Into<String>,
1110 header: impl Into<String>,
1111 help: impl Into<String>,
1112 ) -> Self {
1113 let mut example = SpecExample::new(code.into());
1114 example.header = Some(header.into());
1115 example.help = Some(help.into());
1116 self.inner.examples.push(example);
1117 self
1118 }
1119
1120 #[must_use]
1122 pub fn build(mut self) -> SpecCommand {
1123 self.inner.usage = self.inner.usage();
1124 self.inner
1125 }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130 use super::*;
1131
1132 #[test]
1133 fn test_flag_builder_basic() {
1134 let flag = SpecFlagBuilder::new()
1135 .name("verbose")
1136 .short('v')
1137 .long("verbose")
1138 .help("Enable verbose output")
1139 .build();
1140
1141 assert_eq!(flag.name, "verbose");
1142 assert_eq!(flag.short, vec!['v']);
1143 assert_eq!(flag.long, vec!["verbose".to_string()]);
1144 assert_eq!(flag.help, Some("Enable verbose output".to_string()));
1145 }
1146
1147 #[test]
1148 fn test_flag_builder_multiple_values() {
1149 let flag = SpecFlagBuilder::new()
1150 .shorts(['v', 'V'])
1151 .longs(["verbose", "loud"])
1152 .default_values(["info", "warn"])
1153 .build();
1154
1155 assert_eq!(flag.short, vec!['v', 'V']);
1156 assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
1157 assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
1158 assert!(!flag.required); }
1160
1161 #[test]
1162 fn test_flag_builder_variadic() {
1163 let flag = SpecFlagBuilder::new()
1164 .long("file")
1165 .var(true)
1166 .var_min(1)
1167 .var_max(10)
1168 .build();
1169
1170 assert!(flag.var);
1171 assert_eq!(flag.var_min, Some(1));
1172 assert_eq!(flag.var_max, Some(10));
1173 }
1174
1175 #[test]
1176 fn test_flag_builder_conditional_requirements() {
1177 let flag = SpecFlagBuilder::new()
1178 .long("config")
1179 .requires_if("special.toml", "--key")
1180 .requires_ifs([("remote.toml", "--token"), ("signed.toml", "--identity")])
1181 .build();
1182
1183 assert_eq!(
1184 flag.requires_if,
1185 [
1186 SpecRequiresIf {
1187 value: "special.toml".into(),
1188 requires: "--key".into(),
1189 },
1190 SpecRequiresIf {
1191 value: "remote.toml".into(),
1192 requires: "--token".into(),
1193 },
1194 SpecRequiresIf {
1195 value: "signed.toml".into(),
1196 requires: "--identity".into(),
1197 },
1198 ]
1199 );
1200 }
1201
1202 #[test]
1203 fn test_flag_builder_conditional_defaults() {
1204 let flag = SpecFlagBuilder::new()
1205 .long("bin-names")
1206 .default_if("--json", "true")
1207 .default_if_eq("--output", "json", "pretty")
1208 .build();
1209
1210 assert_eq!(
1211 flag.default_if,
1212 [
1213 SpecDefaultIf {
1214 selector: "--json".into(),
1215 when: None,
1216 value: "true".into(),
1217 },
1218 SpecDefaultIf {
1219 selector: "--output".into(),
1220 when: Some("json".into()),
1221 value: "pretty".into(),
1222 },
1223 ]
1224 );
1225 }
1226
1227 #[test]
1228 fn test_flag_builder_name_derivation() {
1229 let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
1230
1231 assert_eq!(flag.name, "verbose");
1233
1234 let flag2 = SpecFlagBuilder::new().short('v').build();
1235
1236 assert_eq!(flag2.name, "v");
1238 }
1239
1240 #[test]
1241 fn test_arg_builder_basic() {
1242 let arg = SpecArgBuilder::new()
1243 .name("file")
1244 .help("Input file")
1245 .required(true)
1246 .build();
1247
1248 assert_eq!(arg.name, "file");
1249 assert_eq!(arg.help, Some("Input file".to_string()));
1250 assert!(arg.required);
1251 }
1252
1253 #[test]
1254 fn test_arg_builder_variadic() {
1255 let arg = SpecArgBuilder::new()
1256 .name("files")
1257 .var(true)
1258 .var_min(1)
1259 .var_max(10)
1260 .help("Input files")
1261 .build();
1262
1263 assert_eq!(arg.name, "files");
1264 assert!(arg.var);
1265 assert_eq!(arg.var_min, Some(1));
1266 assert_eq!(arg.var_max, Some(10));
1267 }
1268
1269 #[test]
1270 fn test_arg_builder_defaults() {
1271 let arg = SpecArgBuilder::new()
1272 .name("file")
1273 .default_values(["a.txt", "b.txt"])
1274 .build();
1275
1276 assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
1277 assert!(!arg.required);
1278 }
1279
1280 #[test]
1281 fn test_arg_builder_drops_validation_error_without_expression() {
1282 let arg = SpecArgBuilder::new()
1283 .name("port")
1284 .validate_error("must be a valid port")
1285 .build();
1286
1287 assert!(arg.validate_error.is_none());
1288 }
1289
1290 #[test]
1291 fn test_command_builder_basic() {
1292 let cmd = SpecCommandBuilder::new()
1293 .name("install")
1294 .help("Install packages")
1295 .build();
1296
1297 assert_eq!(cmd.name, "install");
1298 assert_eq!(cmd.help, Some("Install packages".to_string()));
1299 }
1300
1301 #[test]
1302 fn test_command_builder_aliases() {
1303 let cmd = SpecCommandBuilder::new()
1304 .name("install")
1305 .alias("i")
1306 .aliases(["add", "get"])
1307 .hidden_aliases(["inst"])
1308 .build();
1309
1310 assert_eq!(
1311 cmd.aliases,
1312 vec!["i".to_string(), "add".to_string(), "get".to_string()]
1313 );
1314 assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
1315 }
1316
1317 #[test]
1318 fn test_command_builder_with_flags_and_args() {
1319 let flag = SpecFlagBuilder::new().short('f').long("force").build();
1320
1321 let arg = SpecArgBuilder::new().name("package").required(true).build();
1322
1323 let cmd = SpecCommandBuilder::new()
1324 .name("install")
1325 .flag(flag)
1326 .arg(arg)
1327 .build();
1328
1329 assert_eq!(cmd.flags.len(), 1);
1330 assert_eq!(cmd.flags[0].name, "force");
1331 assert_eq!(cmd.args.len(), 1);
1332 assert_eq!(cmd.args[0].name, "package");
1333 }
1334
1335 #[test]
1336 fn test_arg_builder_choices() {
1337 let arg = SpecArgBuilder::new()
1338 .name("format")
1339 .choices(["json", "yaml", "toml"])
1340 .build();
1341
1342 assert!(arg.choices.is_some());
1343 let choices = arg.choices.unwrap();
1344 assert_eq!(
1345 choices.choices,
1346 vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
1347 );
1348 assert_eq!(choices.env(), None);
1349 }
1350
1351 #[cfg(feature = "unstable_choices_env")]
1352 #[test]
1353 fn test_arg_builder_choices_env() {
1354 let arg = SpecArgBuilder::new()
1355 .name("env")
1356 .choices(["local"])
1357 .choices_env("DEPLOY_ENVS")
1358 .build();
1359
1360 let choices = arg.choices.unwrap();
1361 assert_eq!(choices.choices, vec!["local".to_string()]);
1362 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
1363 }
1364
1365 #[cfg(feature = "unstable_choices_env")]
1366 #[test]
1367 fn test_arg_builder_choices_preserves_choices_env() {
1368 let arg = SpecArgBuilder::new()
1369 .name("env")
1370 .choices_env("DEPLOY_ENVS")
1371 .choices(["local"])
1372 .build();
1373
1374 let choices = arg.choices.unwrap();
1375 assert_eq!(choices.choices, vec!["local".to_string()]);
1376 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
1377 }
1378
1379 #[test]
1380 fn test_command_builder_subcommands() {
1381 let sub1 = SpecCommandBuilder::new().name("sub1").build();
1382 let sub2 = SpecCommandBuilder::new().name("sub2").build();
1383
1384 let cmd = SpecCommandBuilder::new()
1385 .name("main")
1386 .subcommand(sub1)
1387 .subcommand(sub2)
1388 .build();
1389
1390 assert_eq!(cmd.subcommands.len(), 2);
1391 assert!(cmd.subcommands.contains_key("sub1"));
1392 assert!(cmd.subcommands.contains_key("sub2"));
1393 }
1394
1395 #[test]
1396 fn test_command_builder_before_after_help() {
1397 let cmd = SpecCommandBuilder::new()
1398 .name("test")
1399 .before_help("Before help text")
1400 .before_help_long("Before help long text")
1401 .after_help("After help text")
1402 .after_help_long("After help long text")
1403 .build();
1404
1405 assert_eq!(cmd.before_help, Some("Before help text".to_string()));
1406 assert_eq!(
1407 cmd.before_help_long,
1408 Some("Before help long text".to_string())
1409 );
1410 assert_eq!(cmd.after_help, Some("After help text".to_string()));
1411 assert_eq!(
1412 cmd.after_help_long,
1413 Some("After help long text".to_string())
1414 );
1415 }
1416
1417 #[test]
1418 fn test_command_builder_examples() {
1419 let cmd = SpecCommandBuilder::new()
1420 .name("test")
1421 .example("mycli run")
1422 .example_with_help("mycli build", "Build example", "Build the project")
1423 .build();
1424
1425 assert_eq!(cmd.examples.len(), 2);
1426 assert_eq!(cmd.examples[0].code, "mycli run");
1427 assert_eq!(cmd.examples[1].code, "mycli build");
1428 assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
1429 assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
1430 }
1431}