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