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