1use crate::spec::cmd::SpecExample;
34use crate::spec::effect::SpecCommandEffect;
35use crate::{
36 SpecAdmonition, SpecArg, SpecChoices, SpecCommand, SpecComplete, SpecDefaultIf, SpecFlag,
37 SpecRequiredIfEq, SpecRequiresIf, spec::arg::SpecDoubleDashChoices,
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 && let Some(arg) = &mut self.inner.arg
312 {
313 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
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 && let Some(arg) = &mut self.inner.arg
495 {
496 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
497 }
498 if self.inner.default_missing.is_some()
499 && let Some(arg) = &mut self.inner.arg
500 {
501 arg.required = false;
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 sigil(mut self, sigil: impl Into<String>) -> Self {
548 self.inner.sigil = Some(sigil.into());
549 self
550 }
551
552 pub fn value_names<I, S>(mut self, names: I) -> Self
554 where
555 I: IntoIterator<Item = S>,
556 S: Into<String>,
557 {
558 self.inner.value_names = names.into_iter().map(Into::into).collect();
559 if let Some(first) = self.inner.value_names.first() {
560 self.inner.name.clone_from(first);
561 }
562 if self.inner.value_names.len() > 1 {
563 let arity = self.inner.value_names.len();
564 self.inner.var = true;
565 self.inner.var_min = Some(arity);
566 self.inner.var_max = Some(arity);
567 }
568 self
569 }
570
571 pub fn default_value(mut self, value: impl Into<String>) -> Self {
573 self.inner.default.push(value.into());
574 self.inner.required = false;
575 self
576 }
577
578 pub fn default_values<I, S>(mut self, values: I) -> Self
580 where
581 I: IntoIterator<Item = S>,
582 S: Into<String>,
583 {
584 self.inner
585 .default
586 .extend(values.into_iter().map(Into::into));
587 if !self.inner.default.is_empty() {
588 self.inner.required = false;
589 }
590 self
591 }
592
593 pub fn help(mut self, text: impl Into<String>) -> Self {
595 self.inner.help = Some(text.into());
596 self
597 }
598
599 pub fn help_long(mut self, text: impl Into<String>) -> Self {
601 self.inner.help_long = Some(text.into());
602 self
603 }
604
605 pub fn help_md(mut self, text: impl Into<String>) -> Self {
607 self.inner.help_md = Some(text.into());
608 self
609 }
610
611 pub fn note(mut self, text: impl Into<String>) -> Self {
613 self.inner.admonitions.push(SpecAdmonition::note(text));
614 self
615 }
616
617 pub fn warning(mut self, text: impl Into<String>) -> Self {
619 self.inner.admonitions.push(SpecAdmonition::warning(text));
620 self
621 }
622
623 pub fn var(mut self, is_var: bool) -> Self {
625 self.inner.var = is_var;
626 self
627 }
628
629 pub fn var_min(mut self, min: usize) -> Self {
631 self.inner.var_min = Some(min);
632 self
633 }
634
635 pub fn var_max(mut self, max: usize) -> Self {
637 self.inner.var_max = Some(max);
638 self
639 }
640
641 pub fn required(mut self, is_required: bool) -> Self {
643 self.inner.required = is_required;
644 self
645 }
646
647 pub fn requires<I, S>(mut self, selectors: I) -> Self
649 where
650 I: IntoIterator<Item = S>,
651 S: Into<String>,
652 {
653 self.inner
654 .requires
655 .extend(selectors.into_iter().map(Into::into));
656 self
657 }
658
659 pub fn required_if_any<I, S>(mut self, selectors: I) -> Self
661 where
662 I: IntoIterator<Item = S>,
663 S: Into<String>,
664 {
665 self.inner
666 .required_if
667 .extend(selectors.into_iter().map(Into::into));
668 self
669 }
670
671 pub fn required_if_eq(mut self, selector: impl Into<String>, value: impl Into<String>) -> Self {
673 self.inner.required_if_eq.push(SpecRequiredIfEq {
674 selector: selector.into(),
675 value: value.into(),
676 });
677 self
678 }
679
680 pub fn required_if_eq_all<I, S, V>(mut self, conditions: I) -> Self
682 where
683 I: IntoIterator<Item = (S, V)>,
684 S: Into<String>,
685 V: Into<String>,
686 {
687 self.inner
688 .required_if_eq_all
689 .extend(
690 conditions
691 .into_iter()
692 .map(|(selector, value)| SpecRequiredIfEq {
693 selector: selector.into(),
694 value: value.into(),
695 }),
696 );
697 self
698 }
699
700 pub fn required_unless_any<I, S>(mut self, selectors: I) -> Self
702 where
703 I: IntoIterator<Item = S>,
704 S: Into<String>,
705 {
706 self.inner
707 .required_unless
708 .extend(selectors.into_iter().map(Into::into));
709 self
710 }
711
712 pub fn required_unless_all<I, S>(mut self, selectors: I) -> Self
714 where
715 I: IntoIterator<Item = S>,
716 S: Into<String>,
717 {
718 self.inner
719 .required_unless_all
720 .extend(selectors.into_iter().map(Into::into));
721 self
722 }
723
724 pub fn hide(mut self, is_hidden: bool) -> Self {
726 self.inner.hide = is_hidden;
727 self
728 }
729
730 pub fn help_heading(mut self, help_heading: impl Into<String>) -> Self {
733 self.inner.help_heading = Some(help_heading.into());
734 self
735 }
736
737 pub fn env(mut self, env: impl Into<String>) -> Self {
738 self.inner.env = Some(env.into());
739 self
740 }
741
742 pub fn env_fallback(mut self, env: impl Into<String>) -> Self {
744 self.inner.env_fallback.push(env.into());
745 self
746 }
747
748 pub fn deprecated_env(mut self, env: impl Into<String>) -> Self {
750 self.inner.deprecated_env.push(env.into());
751 self
752 }
753
754 pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
756 self.inner.double_dash = behavior;
757 self
758 }
759
760 pub fn choices<I, S>(mut self, choices: I) -> Self
762 where
763 I: IntoIterator<Item = S>,
764 S: Into<String>,
765 {
766 let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
767 #[cfg(feature = "unstable_choices_env")]
768 let env = spec_choices.env().map(ToString::to_string);
769 spec_choices.choices = choices.into_iter().map(Into::into).collect();
770 #[cfg(feature = "unstable_choices_env")]
771 spec_choices.set_env(env);
772 self
773 }
774
775 pub fn validate(mut self, expression: impl Into<String>) -> Self {
777 self.inner.validate = Some(expression.into());
778 self
779 }
780
781 pub fn validate_error(mut self, message: impl Into<String>) -> Self {
783 self.inner.validate_error = Some(message.into());
784 self
785 }
786
787 pub fn choices_run(mut self, run: impl Into<String>) -> Self {
792 let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
793 choices.run = Some(run.into());
794 self
795 }
796
797 #[cfg(feature = "unstable_choices_env")]
799 pub fn choices_env(mut self, env: impl Into<String>) -> Self {
800 let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
801 choices.set_env(Some(env.into()));
802 self
803 }
804
805 pub fn usage(mut self, usage: impl Into<String>) -> Self {
807 self.inner.usage = usage.into();
808 self
809 }
810
811 pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
813 self.inner.help_first_line = Some(text.into());
814 self
815 }
816
817 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
819 self.inner.effect = Some(effect);
820 self
821 }
822
823 pub fn complete(mut self, complete: SpecComplete) -> Self {
827 self.inner.complete = Some(complete);
828 self
829 }
830
831 #[must_use]
833 pub fn build(mut self) -> SpecArg {
834 if self.inner.validate.is_none() {
835 self.inner.validate_error = None;
836 }
837 if let Some(complete) = &mut self.inner.complete {
840 complete.name = self.inner.name.to_lowercase();
841 }
842 if self.inner.value_names.len() > 1 {
843 let arity = self.inner.value_names.len();
844 self.inner.var = true;
845 self.inner.var_min = Some(arity);
846 self.inner.var_max = Some(arity);
847 }
848 self.inner.usage = self.inner.usage();
849 self.inner
850 }
851}
852
853#[derive(Debug, Default, Clone)]
855pub struct SpecCommandBuilder {
856 inner: SpecCommand,
857}
858
859impl SpecCommandBuilder {
860 pub fn surface(mut self, surface: impl Into<String>) -> Self {
862 self.inner.surface = Some(surface.into());
863 self
864 }
865
866 pub fn available_if(mut self, condition: impl Into<String>) -> Self {
868 self.inner.available_if.push(condition.into());
869 self
870 }
871 pub fn new() -> Self {
873 Self::default()
874 }
875
876 pub fn name(mut self, name: impl Into<String>) -> Self {
878 self.inner.name = name.into();
879 self
880 }
881
882 pub fn alias(mut self, alias: impl Into<String>) -> Self {
884 self.inner.aliases.push(alias.into());
885 self
886 }
887
888 pub fn aliases<I, S>(mut self, aliases: I) -> Self
890 where
891 I: IntoIterator<Item = S>,
892 S: Into<String>,
893 {
894 self.inner
895 .aliases
896 .extend(aliases.into_iter().map(Into::into));
897 self
898 }
899
900 pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
902 self.inner.hidden_aliases.push(alias.into());
903 self
904 }
905
906 pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
908 where
909 I: IntoIterator<Item = S>,
910 S: Into<String>,
911 {
912 self.inner
913 .hidden_aliases
914 .extend(aliases.into_iter().map(Into::into));
915 self
916 }
917
918 pub fn flag(mut self, flag: SpecFlag) -> Self {
920 self.inner.flags.push(flag);
921 self
922 }
923
924 pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
926 self.inner.flags.extend(flags);
927 self
928 }
929
930 pub fn arg(mut self, arg: SpecArg) -> Self {
932 self.inner.args.push(arg);
933 self
934 }
935
936 pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
938 self.inner.args.extend(args);
939 self
940 }
941
942 pub fn help(mut self, text: impl Into<String>) -> Self {
944 self.inner.help = Some(text.into());
945 self
946 }
947
948 pub fn help_long(mut self, text: impl Into<String>) -> Self {
950 self.inner.help_long = Some(text.into());
951 self
952 }
953
954 pub fn help_md(mut self, text: impl Into<String>) -> Self {
956 self.inner.help_md = Some(text.into());
957 self
958 }
959
960 pub fn hide(mut self, is_hidden: bool) -> Self {
962 self.inner.hide = is_hidden;
963 self
964 }
965
966 pub fn subcommand_required(mut self, required: bool) -> Self {
968 self.inner.subcommand_required = required;
969 self
970 }
971
972 pub fn subcommand_help_heading(mut self, heading: impl Into<String>) -> Self {
974 self.inner.subcommand_help_heading = Some(heading.into());
975 self
976 }
977
978 pub fn subcommand_value_name(mut self, name: impl Into<String>) -> Self {
980 self.inner.subcommand_value_name = Some(name.into());
981 self
982 }
983
984 pub fn term_width(mut self, width: usize) -> Self {
986 self.inner.term_width = Some(width);
987 self
988 }
989
990 pub fn max_term_width(mut self, width: usize) -> Self {
992 self.inner.max_term_width = Some(width);
993 self
994 }
995
996 pub fn external_subcommand(mut self, enabled: bool) -> Self {
998 self.inner.external_subcommand = enabled;
999 self
1000 }
1001
1002 pub fn disable_help_flag(mut self, disabled: bool) -> Self {
1004 self.inner.disable_help_flag = disabled;
1005 self
1006 }
1007
1008 pub fn disable_help_subcommand(mut self, disabled: bool) -> Self {
1010 self.inner.disable_help_subcommand = disabled;
1011 self
1012 }
1013
1014 pub fn disable_version_flag(mut self, disabled: bool) -> Self {
1016 self.inner.disable_version_flag = disabled;
1017 self
1018 }
1019
1020 pub fn args_override_self(mut self, enabled: bool) -> Self {
1024 self.inner.args_override_self = enabled;
1025 self
1026 }
1027
1028 pub fn subcommand_negates_reqs(mut self, enabled: bool) -> Self {
1030 self.inner.subcommand_negates_reqs = enabled;
1031 self
1032 }
1033
1034 pub fn args_conflicts_with_subcommands(mut self, enabled: bool) -> Self {
1036 self.inner.args_conflicts_with_subcommands = enabled;
1037 self
1038 }
1039
1040 pub fn subcommand_precedence_over_arg(mut self, enabled: bool) -> Self {
1041 self.inner.subcommand_precedence_over_arg = enabled;
1042 self
1043 }
1044
1045 pub fn allow_missing_positional(mut self, enabled: bool) -> Self {
1046 self.inner.allow_missing_positional = enabled;
1047 self
1048 }
1049
1050 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
1052 self.inner.effect = Some(effect);
1053 self
1054 }
1055
1056 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
1058 self.inner.deprecated = Some(msg.into());
1059 self
1060 }
1061
1062 pub fn deprecated_warn_at(mut self, version: impl Into<String>) -> Self {
1063 self.inner.deprecated_warn_at = Some(version.into());
1064 self
1065 }
1066
1067 pub fn deprecated_remove_at(mut self, version: impl Into<String>) -> Self {
1068 self.inner.deprecated_remove_at = Some(version.into());
1069 self
1070 }
1071
1072 pub fn restart_token(mut self, token: impl Into<String>) -> Self {
1075 self.inner.restart_token = Some(token.into());
1076 self
1077 }
1078
1079 pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
1081 self.inner.subcommands.insert(cmd.name.clone(), cmd);
1082 self
1083 }
1084
1085 pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
1087 for cmd in cmds {
1088 self.inner.subcommands.insert(cmd.name.clone(), cmd);
1089 }
1090 self
1091 }
1092
1093 pub fn before_help(mut self, text: impl Into<String>) -> Self {
1095 self.inner.before_help = Some(text.into());
1096 self
1097 }
1098
1099 pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
1101 self.inner.before_help_long = Some(text.into());
1102 self
1103 }
1104
1105 pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
1107 self.inner.before_help_md = Some(text.into());
1108 self
1109 }
1110
1111 pub fn after_help(mut self, text: impl Into<String>) -> Self {
1113 self.inner.after_help = Some(text.into());
1114 self
1115 }
1116
1117 pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
1119 self.inner.after_help_long = Some(text.into());
1120 self
1121 }
1122
1123 pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
1125 self.inner.after_help_md = Some(text.into());
1126 self
1127 }
1128
1129 pub fn example(mut self, code: impl Into<String>) -> Self {
1131 self.inner.examples.push(SpecExample::new(code.into()));
1132 self
1133 }
1134
1135 pub fn example_with_help(
1137 mut self,
1138 code: impl Into<String>,
1139 header: impl Into<String>,
1140 help: impl Into<String>,
1141 ) -> Self {
1142 let mut example = SpecExample::new(code.into());
1143 example.header = Some(header.into());
1144 example.help = Some(help.into());
1145 self.inner.examples.push(example);
1146 self
1147 }
1148
1149 #[must_use]
1151 pub fn build(mut self) -> SpecCommand {
1152 self.inner.usage = self.inner.usage();
1153 self.inner
1154 }
1155}
1156
1157#[cfg(test)]
1158mod tests {
1159 use super::*;
1160
1161 #[test]
1162 fn test_flag_builder_basic() {
1163 let flag = SpecFlagBuilder::new()
1164 .name("verbose")
1165 .short('v')
1166 .long("verbose")
1167 .help("Enable verbose output")
1168 .build();
1169
1170 assert_eq!(flag.name, "verbose");
1171 assert_eq!(flag.short, vec!['v']);
1172 assert_eq!(flag.long, vec!["verbose".to_string()]);
1173 assert_eq!(flag.help, Some("Enable verbose output".to_string()));
1174 }
1175
1176 #[test]
1177 fn test_flag_builder_multiple_values() {
1178 let flag = SpecFlagBuilder::new()
1179 .shorts(['v', 'V'])
1180 .longs(["verbose", "loud"])
1181 .default_values(["info", "warn"])
1182 .build();
1183
1184 assert_eq!(flag.short, vec!['v', 'V']);
1185 assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
1186 assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
1187 assert!(!flag.required); }
1189
1190 #[test]
1191 fn test_flag_builder_variadic() {
1192 let flag = SpecFlagBuilder::new()
1193 .long("file")
1194 .var(true)
1195 .var_min(1)
1196 .var_max(10)
1197 .build();
1198
1199 assert!(flag.var);
1200 assert_eq!(flag.var_min, Some(1));
1201 assert_eq!(flag.var_max, Some(10));
1202 }
1203
1204 #[test]
1205 fn test_flag_builder_conditional_requirements() {
1206 let flag = SpecFlagBuilder::new()
1207 .long("config")
1208 .requires_if("special.toml", "--key")
1209 .requires_ifs([("remote.toml", "--token"), ("signed.toml", "--identity")])
1210 .build();
1211
1212 assert_eq!(
1213 flag.requires_if,
1214 [
1215 SpecRequiresIf {
1216 value: "special.toml".into(),
1217 requires: "--key".into(),
1218 },
1219 SpecRequiresIf {
1220 value: "remote.toml".into(),
1221 requires: "--token".into(),
1222 },
1223 SpecRequiresIf {
1224 value: "signed.toml".into(),
1225 requires: "--identity".into(),
1226 },
1227 ]
1228 );
1229 }
1230
1231 #[test]
1232 fn test_flag_builder_conditional_defaults() {
1233 let flag = SpecFlagBuilder::new()
1234 .long("bin-names")
1235 .default_if("--json", "true")
1236 .default_if_eq("--output", "json", "pretty")
1237 .build();
1238
1239 assert_eq!(
1240 flag.default_if,
1241 [
1242 SpecDefaultIf {
1243 selector: "--json".into(),
1244 when: None,
1245 value: "true".into(),
1246 },
1247 SpecDefaultIf {
1248 selector: "--output".into(),
1249 when: Some("json".into()),
1250 value: "pretty".into(),
1251 },
1252 ]
1253 );
1254 }
1255
1256 #[test]
1257 fn test_flag_builder_name_derivation() {
1258 let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
1259
1260 assert_eq!(flag.name, "verbose");
1262
1263 let flag2 = SpecFlagBuilder::new().short('v').build();
1264
1265 assert_eq!(flag2.name, "v");
1267 }
1268
1269 #[test]
1270 fn test_arg_builder_basic() {
1271 let arg = SpecArgBuilder::new()
1272 .name("file")
1273 .help("Input file")
1274 .required(true)
1275 .build();
1276
1277 assert_eq!(arg.name, "file");
1278 assert_eq!(arg.help, Some("Input file".to_string()));
1279 assert!(arg.required);
1280 }
1281
1282 #[test]
1283 fn test_arg_builder_variadic() {
1284 let arg = SpecArgBuilder::new()
1285 .name("files")
1286 .var(true)
1287 .var_min(1)
1288 .var_max(10)
1289 .help("Input files")
1290 .build();
1291
1292 assert_eq!(arg.name, "files");
1293 assert!(arg.var);
1294 assert_eq!(arg.var_min, Some(1));
1295 assert_eq!(arg.var_max, Some(10));
1296 }
1297
1298 #[test]
1299 fn test_arg_builder_defaults() {
1300 let arg = SpecArgBuilder::new()
1301 .name("file")
1302 .default_values(["a.txt", "b.txt"])
1303 .build();
1304
1305 assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
1306 assert!(!arg.required);
1307 }
1308
1309 #[test]
1310 fn test_arg_builder_drops_validation_error_without_expression() {
1311 let arg = SpecArgBuilder::new()
1312 .name("port")
1313 .validate_error("must be a valid port")
1314 .build();
1315
1316 assert!(arg.validate_error.is_none());
1317 }
1318
1319 #[test]
1320 fn test_command_builder_basic() {
1321 let cmd = SpecCommandBuilder::new()
1322 .name("install")
1323 .help("Install packages")
1324 .build();
1325
1326 assert_eq!(cmd.name, "install");
1327 assert_eq!(cmd.help, Some("Install packages".to_string()));
1328 }
1329
1330 #[test]
1331 fn test_command_builder_aliases() {
1332 let cmd = SpecCommandBuilder::new()
1333 .name("install")
1334 .alias("i")
1335 .aliases(["add", "get"])
1336 .hidden_aliases(["inst"])
1337 .build();
1338
1339 assert_eq!(
1340 cmd.aliases,
1341 vec!["i".to_string(), "add".to_string(), "get".to_string()]
1342 );
1343 assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
1344 }
1345
1346 #[test]
1347 fn test_command_builder_with_flags_and_args() {
1348 let flag = SpecFlagBuilder::new().short('f').long("force").build();
1349
1350 let arg = SpecArgBuilder::new().name("package").required(true).build();
1351
1352 let cmd = SpecCommandBuilder::new()
1353 .name("install")
1354 .flag(flag)
1355 .arg(arg)
1356 .build();
1357
1358 assert_eq!(cmd.flags.len(), 1);
1359 assert_eq!(cmd.flags[0].name, "force");
1360 assert_eq!(cmd.args.len(), 1);
1361 assert_eq!(cmd.args[0].name, "package");
1362 }
1363
1364 #[test]
1365 fn test_arg_builder_choices() {
1366 let arg = SpecArgBuilder::new()
1367 .name("format")
1368 .choices(["json", "yaml", "toml"])
1369 .build();
1370
1371 assert!(arg.choices.is_some());
1372 let choices = arg.choices.unwrap();
1373 assert_eq!(
1374 choices.choices,
1375 vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
1376 );
1377 assert_eq!(choices.env(), None);
1378 }
1379
1380 #[test]
1381 fn test_arg_builder_choices_run_keeps_the_declared_values() {
1382 let arg = SpecArgBuilder::new()
1383 .name("service")
1384 .choices_run("printf 'app\\ndatabase\\n'")
1385 .choices(["local"])
1386 .build();
1387 let choices = arg.choices.unwrap();
1388 assert_eq!(choices.choices, vec!["local".to_string()]);
1389 assert_eq!(choices.run(), Some("printf 'app\\ndatabase\\n'"));
1390 }
1391
1392 #[cfg(feature = "unstable_choices_env")]
1393 #[test]
1394 fn test_arg_builder_choices_env() {
1395 let arg = SpecArgBuilder::new()
1396 .name("env")
1397 .choices(["local"])
1398 .choices_env("DEPLOY_ENVS")
1399 .build();
1400
1401 let choices = arg.choices.unwrap();
1402 assert_eq!(choices.choices, vec!["local".to_string()]);
1403 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
1404 }
1405
1406 #[cfg(feature = "unstable_choices_env")]
1407 #[test]
1408 fn test_arg_builder_choices_preserves_choices_env() {
1409 let arg = SpecArgBuilder::new()
1410 .name("env")
1411 .choices_env("DEPLOY_ENVS")
1412 .choices(["local"])
1413 .build();
1414
1415 let choices = arg.choices.unwrap();
1416 assert_eq!(choices.choices, vec!["local".to_string()]);
1417 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
1418 }
1419
1420 #[test]
1421 fn test_command_builder_subcommands() {
1422 let sub1 = SpecCommandBuilder::new().name("sub1").build();
1423 let sub2 = SpecCommandBuilder::new().name("sub2").build();
1424
1425 let cmd = SpecCommandBuilder::new()
1426 .name("main")
1427 .subcommand(sub1)
1428 .subcommand(sub2)
1429 .build();
1430
1431 assert_eq!(cmd.subcommands.len(), 2);
1432 assert!(cmd.subcommands.contains_key("sub1"));
1433 assert!(cmd.subcommands.contains_key("sub2"));
1434 }
1435
1436 #[test]
1437 fn test_command_builder_before_after_help() {
1438 let cmd = SpecCommandBuilder::new()
1439 .name("test")
1440 .before_help("Before help text")
1441 .before_help_long("Before help long text")
1442 .after_help("After help text")
1443 .after_help_long("After help long text")
1444 .build();
1445
1446 assert_eq!(cmd.before_help, Some("Before help text".to_string()));
1447 assert_eq!(
1448 cmd.before_help_long,
1449 Some("Before help long text".to_string())
1450 );
1451 assert_eq!(cmd.after_help, Some("After help text".to_string()));
1452 assert_eq!(
1453 cmd.after_help_long,
1454 Some("After help long text".to_string())
1455 );
1456 }
1457
1458 #[test]
1459 fn test_command_builder_examples() {
1460 let cmd = SpecCommandBuilder::new()
1461 .name("test")
1462 .example("mycli run")
1463 .example_with_help("mycli build", "Build example", "Build the project")
1464 .build();
1465
1466 assert_eq!(cmd.examples.len(), 2);
1467 assert_eq!(cmd.examples[0].code, "mycli run");
1468 assert_eq!(cmd.examples[1].code, "mycli build");
1469 assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
1470 assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
1471 }
1472}