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 #[must_use]
210 pub fn build(mut self) -> SpecFlag {
211 if self.allow_hyphen_values {
212 if let Some(arg) = &mut self.inner.arg {
213 arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
214 }
215 }
216 self.inner.usage = self.inner.usage();
217 if self.inner.name.is_empty() {
218 if let Some(long) = self.inner.long.first() {
220 self.inner.name = long.clone();
221 } else if let Some(short) = self.inner.short.first() {
222 self.inner.name = short.to_string();
223 }
224 }
225 self.inner
226 }
227}
228
229#[derive(Debug, Default, Clone)]
231pub struct SpecArgBuilder {
232 inner: SpecArg,
233}
234
235impl SpecArgBuilder {
236 pub fn new() -> Self {
238 Self::default()
239 }
240
241 pub fn name(mut self, name: impl Into<String>) -> Self {
243 self.inner.name = name.into();
244 self
245 }
246
247 pub fn default_value(mut self, value: impl Into<String>) -> Self {
249 self.inner.default.push(value.into());
250 self.inner.required = false;
251 self
252 }
253
254 pub fn default_values<I, S>(mut self, values: I) -> Self
256 where
257 I: IntoIterator<Item = S>,
258 S: Into<String>,
259 {
260 self.inner
261 .default
262 .extend(values.into_iter().map(Into::into));
263 if !self.inner.default.is_empty() {
264 self.inner.required = false;
265 }
266 self
267 }
268
269 pub fn help(mut self, text: impl Into<String>) -> Self {
271 self.inner.help = Some(text.into());
272 self
273 }
274
275 pub fn help_long(mut self, text: impl Into<String>) -> Self {
277 self.inner.help_long = Some(text.into());
278 self
279 }
280
281 pub fn help_md(mut self, text: impl Into<String>) -> Self {
283 self.inner.help_md = Some(text.into());
284 self
285 }
286
287 pub fn var(mut self, is_var: bool) -> Self {
289 self.inner.var = is_var;
290 self
291 }
292
293 pub fn var_min(mut self, min: usize) -> Self {
295 self.inner.var_min = Some(min);
296 self
297 }
298
299 pub fn var_max(mut self, max: usize) -> Self {
301 self.inner.var_max = Some(max);
302 self
303 }
304
305 pub fn required(mut self, is_required: bool) -> Self {
307 self.inner.required = is_required;
308 self
309 }
310
311 pub fn hide(mut self, is_hidden: bool) -> Self {
313 self.inner.hide = is_hidden;
314 self
315 }
316
317 pub fn env(mut self, env: impl Into<String>) -> Self {
319 self.inner.env = Some(env.into());
320 self
321 }
322
323 pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
325 self.inner.double_dash = behavior;
326 self
327 }
328
329 pub fn choices<I, S>(mut self, choices: I) -> Self
331 where
332 I: IntoIterator<Item = S>,
333 S: Into<String>,
334 {
335 let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
336 #[cfg(feature = "unstable_choices_env")]
337 let env = spec_choices.env().map(ToString::to_string);
338 spec_choices.choices = choices.into_iter().map(Into::into).collect();
339 #[cfg(feature = "unstable_choices_env")]
340 spec_choices.set_env(env);
341 self
342 }
343
344 #[cfg(feature = "unstable_choices_env")]
346 pub fn choices_env(mut self, env: impl Into<String>) -> Self {
347 let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
348 choices.set_env(Some(env.into()));
349 self
350 }
351
352 #[must_use]
354 pub fn build(mut self) -> SpecArg {
355 self.inner.usage = self.inner.usage();
356 self.inner
357 }
358}
359
360#[derive(Debug, Default, Clone)]
362pub struct SpecCommandBuilder {
363 inner: SpecCommand,
364}
365
366impl SpecCommandBuilder {
367 pub fn new() -> Self {
369 Self::default()
370 }
371
372 pub fn name(mut self, name: impl Into<String>) -> Self {
374 self.inner.name = name.into();
375 self
376 }
377
378 pub fn alias(mut self, alias: impl Into<String>) -> Self {
380 self.inner.aliases.push(alias.into());
381 self
382 }
383
384 pub fn aliases<I, S>(mut self, aliases: I) -> Self
386 where
387 I: IntoIterator<Item = S>,
388 S: Into<String>,
389 {
390 self.inner
391 .aliases
392 .extend(aliases.into_iter().map(Into::into));
393 self
394 }
395
396 pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
398 self.inner.hidden_aliases.push(alias.into());
399 self
400 }
401
402 pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
404 where
405 I: IntoIterator<Item = S>,
406 S: Into<String>,
407 {
408 self.inner
409 .hidden_aliases
410 .extend(aliases.into_iter().map(Into::into));
411 self
412 }
413
414 pub fn flag(mut self, flag: SpecFlag) -> Self {
416 self.inner.flags.push(flag);
417 self
418 }
419
420 pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
422 self.inner.flags.extend(flags);
423 self
424 }
425
426 pub fn arg(mut self, arg: SpecArg) -> Self {
428 self.inner.args.push(arg);
429 self
430 }
431
432 pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
434 self.inner.args.extend(args);
435 self
436 }
437
438 pub fn help(mut self, text: impl Into<String>) -> Self {
440 self.inner.help = Some(text.into());
441 self
442 }
443
444 pub fn help_long(mut self, text: impl Into<String>) -> Self {
446 self.inner.help_long = Some(text.into());
447 self
448 }
449
450 pub fn help_md(mut self, text: impl Into<String>) -> Self {
452 self.inner.help_md = Some(text.into());
453 self
454 }
455
456 pub fn hide(mut self, is_hidden: bool) -> Self {
458 self.inner.hide = is_hidden;
459 self
460 }
461
462 pub fn subcommand_required(mut self, required: bool) -> Self {
464 self.inner.subcommand_required = required;
465 self
466 }
467
468 pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
470 self.inner.effect = Some(effect);
471 self
472 }
473
474 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
476 self.inner.deprecated = Some(msg.into());
477 self
478 }
479
480 pub fn restart_token(mut self, token: impl Into<String>) -> Self {
483 self.inner.restart_token = Some(token.into());
484 self
485 }
486
487 pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
489 self.inner.subcommands.insert(cmd.name.clone(), cmd);
490 self
491 }
492
493 pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
495 for cmd in cmds {
496 self.inner.subcommands.insert(cmd.name.clone(), cmd);
497 }
498 self
499 }
500
501 pub fn before_help(mut self, text: impl Into<String>) -> Self {
503 self.inner.before_help = Some(text.into());
504 self
505 }
506
507 pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
509 self.inner.before_help_long = Some(text.into());
510 self
511 }
512
513 pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
515 self.inner.before_help_md = Some(text.into());
516 self
517 }
518
519 pub fn after_help(mut self, text: impl Into<String>) -> Self {
521 self.inner.after_help = Some(text.into());
522 self
523 }
524
525 pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
527 self.inner.after_help_long = Some(text.into());
528 self
529 }
530
531 pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
533 self.inner.after_help_md = Some(text.into());
534 self
535 }
536
537 pub fn example(mut self, code: impl Into<String>) -> Self {
539 self.inner.examples.push(SpecExample::new(code.into()));
540 self
541 }
542
543 pub fn example_with_help(
545 mut self,
546 code: impl Into<String>,
547 header: impl Into<String>,
548 help: impl Into<String>,
549 ) -> Self {
550 let mut example = SpecExample::new(code.into());
551 example.header = Some(header.into());
552 example.help = Some(help.into());
553 self.inner.examples.push(example);
554 self
555 }
556
557 #[must_use]
559 pub fn build(mut self) -> SpecCommand {
560 self.inner.usage = self.inner.usage();
561 self.inner
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568
569 #[test]
570 fn test_flag_builder_basic() {
571 let flag = SpecFlagBuilder::new()
572 .name("verbose")
573 .short('v')
574 .long("verbose")
575 .help("Enable verbose output")
576 .build();
577
578 assert_eq!(flag.name, "verbose");
579 assert_eq!(flag.short, vec!['v']);
580 assert_eq!(flag.long, vec!["verbose".to_string()]);
581 assert_eq!(flag.help, Some("Enable verbose output".to_string()));
582 }
583
584 #[test]
585 fn test_flag_builder_multiple_values() {
586 let flag = SpecFlagBuilder::new()
587 .shorts(['v', 'V'])
588 .longs(["verbose", "loud"])
589 .default_values(["info", "warn"])
590 .build();
591
592 assert_eq!(flag.short, vec!['v', 'V']);
593 assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
594 assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
595 assert!(!flag.required); }
597
598 #[test]
599 fn test_flag_builder_variadic() {
600 let flag = SpecFlagBuilder::new()
601 .long("file")
602 .var(true)
603 .var_min(1)
604 .var_max(10)
605 .build();
606
607 assert!(flag.var);
608 assert_eq!(flag.var_min, Some(1));
609 assert_eq!(flag.var_max, Some(10));
610 }
611
612 #[test]
613 fn test_flag_builder_name_derivation() {
614 let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
615
616 assert_eq!(flag.name, "verbose");
618
619 let flag2 = SpecFlagBuilder::new().short('v').build();
620
621 assert_eq!(flag2.name, "v");
623 }
624
625 #[test]
626 fn test_arg_builder_basic() {
627 let arg = SpecArgBuilder::new()
628 .name("file")
629 .help("Input file")
630 .required(true)
631 .build();
632
633 assert_eq!(arg.name, "file");
634 assert_eq!(arg.help, Some("Input file".to_string()));
635 assert!(arg.required);
636 }
637
638 #[test]
639 fn test_arg_builder_variadic() {
640 let arg = SpecArgBuilder::new()
641 .name("files")
642 .var(true)
643 .var_min(1)
644 .var_max(10)
645 .help("Input files")
646 .build();
647
648 assert_eq!(arg.name, "files");
649 assert!(arg.var);
650 assert_eq!(arg.var_min, Some(1));
651 assert_eq!(arg.var_max, Some(10));
652 }
653
654 #[test]
655 fn test_arg_builder_defaults() {
656 let arg = SpecArgBuilder::new()
657 .name("file")
658 .default_values(["a.txt", "b.txt"])
659 .build();
660
661 assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
662 assert!(!arg.required);
663 }
664
665 #[test]
666 fn test_command_builder_basic() {
667 let cmd = SpecCommandBuilder::new()
668 .name("install")
669 .help("Install packages")
670 .build();
671
672 assert_eq!(cmd.name, "install");
673 assert_eq!(cmd.help, Some("Install packages".to_string()));
674 }
675
676 #[test]
677 fn test_command_builder_aliases() {
678 let cmd = SpecCommandBuilder::new()
679 .name("install")
680 .alias("i")
681 .aliases(["add", "get"])
682 .hidden_aliases(["inst"])
683 .build();
684
685 assert_eq!(
686 cmd.aliases,
687 vec!["i".to_string(), "add".to_string(), "get".to_string()]
688 );
689 assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
690 }
691
692 #[test]
693 fn test_command_builder_with_flags_and_args() {
694 let flag = SpecFlagBuilder::new().short('f').long("force").build();
695
696 let arg = SpecArgBuilder::new().name("package").required(true).build();
697
698 let cmd = SpecCommandBuilder::new()
699 .name("install")
700 .flag(flag)
701 .arg(arg)
702 .build();
703
704 assert_eq!(cmd.flags.len(), 1);
705 assert_eq!(cmd.flags[0].name, "force");
706 assert_eq!(cmd.args.len(), 1);
707 assert_eq!(cmd.args[0].name, "package");
708 }
709
710 #[test]
711 fn test_arg_builder_choices() {
712 let arg = SpecArgBuilder::new()
713 .name("format")
714 .choices(["json", "yaml", "toml"])
715 .build();
716
717 assert!(arg.choices.is_some());
718 let choices = arg.choices.unwrap();
719 assert_eq!(
720 choices.choices,
721 vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
722 );
723 assert_eq!(choices.env(), None);
724 }
725
726 #[cfg(feature = "unstable_choices_env")]
727 #[test]
728 fn test_arg_builder_choices_env() {
729 let arg = SpecArgBuilder::new()
730 .name("env")
731 .choices(["local"])
732 .choices_env("DEPLOY_ENVS")
733 .build();
734
735 let choices = arg.choices.unwrap();
736 assert_eq!(choices.choices, vec!["local".to_string()]);
737 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
738 }
739
740 #[cfg(feature = "unstable_choices_env")]
741 #[test]
742 fn test_arg_builder_choices_preserves_choices_env() {
743 let arg = SpecArgBuilder::new()
744 .name("env")
745 .choices_env("DEPLOY_ENVS")
746 .choices(["local"])
747 .build();
748
749 let choices = arg.choices.unwrap();
750 assert_eq!(choices.choices, vec!["local".to_string()]);
751 assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
752 }
753
754 #[test]
755 fn test_command_builder_subcommands() {
756 let sub1 = SpecCommandBuilder::new().name("sub1").build();
757 let sub2 = SpecCommandBuilder::new().name("sub2").build();
758
759 let cmd = SpecCommandBuilder::new()
760 .name("main")
761 .subcommand(sub1)
762 .subcommand(sub2)
763 .build();
764
765 assert_eq!(cmd.subcommands.len(), 2);
766 assert!(cmd.subcommands.contains_key("sub1"));
767 assert!(cmd.subcommands.contains_key("sub2"));
768 }
769
770 #[test]
771 fn test_command_builder_before_after_help() {
772 let cmd = SpecCommandBuilder::new()
773 .name("test")
774 .before_help("Before help text")
775 .before_help_long("Before help long text")
776 .after_help("After help text")
777 .after_help_long("After help long text")
778 .build();
779
780 assert_eq!(cmd.before_help, Some("Before help text".to_string()));
781 assert_eq!(
782 cmd.before_help_long,
783 Some("Before help long text".to_string())
784 );
785 assert_eq!(cmd.after_help, Some("After help text".to_string()));
786 assert_eq!(
787 cmd.after_help_long,
788 Some("After help long text".to_string())
789 );
790 }
791
792 #[test]
793 fn test_command_builder_examples() {
794 let cmd = SpecCommandBuilder::new()
795 .name("test")
796 .example("mycli run")
797 .example_with_help("mycli build", "Build example", "Build the project")
798 .build();
799
800 assert_eq!(cmd.examples.len(), 2);
801 assert_eq!(cmd.examples[0].code, "mycli run");
802 assert_eq!(cmd.examples[1].code, "mycli build");
803 assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
804 assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
805 }
806}