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 self.inner.choices = Some(SpecChoices {
311 choices: choices.into_iter().map(Into::into).collect(),
312 });
313 self
314 }
315
316 #[must_use]
318 pub fn build(mut self) -> SpecArg {
319 self.inner.usage = self.inner.usage();
320 self.inner
321 }
322}
323
324#[derive(Debug, Default, Clone)]
326pub struct SpecCommandBuilder {
327 inner: SpecCommand,
328}
329
330impl SpecCommandBuilder {
331 pub fn new() -> Self {
333 Self::default()
334 }
335
336 pub fn name(mut self, name: impl Into<String>) -> Self {
338 self.inner.name = name.into();
339 self
340 }
341
342 pub fn alias(mut self, alias: impl Into<String>) -> Self {
344 self.inner.aliases.push(alias.into());
345 self
346 }
347
348 pub fn aliases<I, S>(mut self, aliases: I) -> Self
350 where
351 I: IntoIterator<Item = S>,
352 S: Into<String>,
353 {
354 self.inner
355 .aliases
356 .extend(aliases.into_iter().map(Into::into));
357 self
358 }
359
360 pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
362 self.inner.hidden_aliases.push(alias.into());
363 self
364 }
365
366 pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
368 where
369 I: IntoIterator<Item = S>,
370 S: Into<String>,
371 {
372 self.inner
373 .hidden_aliases
374 .extend(aliases.into_iter().map(Into::into));
375 self
376 }
377
378 pub fn flag(mut self, flag: SpecFlag) -> Self {
380 self.inner.flags.push(flag);
381 self
382 }
383
384 pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
386 self.inner.flags.extend(flags);
387 self
388 }
389
390 pub fn arg(mut self, arg: SpecArg) -> Self {
392 self.inner.args.push(arg);
393 self
394 }
395
396 pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
398 self.inner.args.extend(args);
399 self
400 }
401
402 pub fn help(mut self, text: impl Into<String>) -> Self {
404 self.inner.help = Some(text.into());
405 self
406 }
407
408 pub fn help_long(mut self, text: impl Into<String>) -> Self {
410 self.inner.help_long = Some(text.into());
411 self
412 }
413
414 pub fn help_md(mut self, text: impl Into<String>) -> Self {
416 self.inner.help_md = Some(text.into());
417 self
418 }
419
420 pub fn hide(mut self, is_hidden: bool) -> Self {
422 self.inner.hide = is_hidden;
423 self
424 }
425
426 pub fn subcommand_required(mut self, required: bool) -> Self {
428 self.inner.subcommand_required = required;
429 self
430 }
431
432 pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
434 self.inner.deprecated = Some(msg.into());
435 self
436 }
437
438 pub fn restart_token(mut self, token: impl Into<String>) -> Self {
441 self.inner.restart_token = Some(token.into());
442 self
443 }
444
445 pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
447 self.inner.subcommands.insert(cmd.name.clone(), cmd);
448 self
449 }
450
451 pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
453 for cmd in cmds {
454 self.inner.subcommands.insert(cmd.name.clone(), cmd);
455 }
456 self
457 }
458
459 pub fn before_help(mut self, text: impl Into<String>) -> Self {
461 self.inner.before_help = Some(text.into());
462 self
463 }
464
465 pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
467 self.inner.before_help_long = Some(text.into());
468 self
469 }
470
471 pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
473 self.inner.before_help_md = Some(text.into());
474 self
475 }
476
477 pub fn after_help(mut self, text: impl Into<String>) -> Self {
479 self.inner.after_help = Some(text.into());
480 self
481 }
482
483 pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
485 self.inner.after_help_long = Some(text.into());
486 self
487 }
488
489 pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
491 self.inner.after_help_md = Some(text.into());
492 self
493 }
494
495 pub fn example(mut self, code: impl Into<String>) -> Self {
497 self.inner.examples.push(SpecExample::new(code.into()));
498 self
499 }
500
501 pub fn example_with_help(
503 mut self,
504 code: impl Into<String>,
505 header: impl Into<String>,
506 help: impl Into<String>,
507 ) -> Self {
508 let mut example = SpecExample::new(code.into());
509 example.header = Some(header.into());
510 example.help = Some(help.into());
511 self.inner.examples.push(example);
512 self
513 }
514
515 #[must_use]
517 pub fn build(mut self) -> SpecCommand {
518 self.inner.usage = self.inner.usage();
519 self.inner
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526
527 #[test]
528 fn test_flag_builder_basic() {
529 let flag = SpecFlagBuilder::new()
530 .name("verbose")
531 .short('v')
532 .long("verbose")
533 .help("Enable verbose output")
534 .build();
535
536 assert_eq!(flag.name, "verbose");
537 assert_eq!(flag.short, vec!['v']);
538 assert_eq!(flag.long, vec!["verbose".to_string()]);
539 assert_eq!(flag.help, Some("Enable verbose output".to_string()));
540 }
541
542 #[test]
543 fn test_flag_builder_multiple_values() {
544 let flag = SpecFlagBuilder::new()
545 .shorts(['v', 'V'])
546 .longs(["verbose", "loud"])
547 .default_values(["info", "warn"])
548 .build();
549
550 assert_eq!(flag.short, vec!['v', 'V']);
551 assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
552 assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
553 assert!(!flag.required); }
555
556 #[test]
557 fn test_flag_builder_variadic() {
558 let flag = SpecFlagBuilder::new()
559 .long("file")
560 .var(true)
561 .var_min(1)
562 .var_max(10)
563 .build();
564
565 assert!(flag.var);
566 assert_eq!(flag.var_min, Some(1));
567 assert_eq!(flag.var_max, Some(10));
568 }
569
570 #[test]
571 fn test_flag_builder_name_derivation() {
572 let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
573
574 assert_eq!(flag.name, "verbose");
576
577 let flag2 = SpecFlagBuilder::new().short('v').build();
578
579 assert_eq!(flag2.name, "v");
581 }
582
583 #[test]
584 fn test_arg_builder_basic() {
585 let arg = SpecArgBuilder::new()
586 .name("file")
587 .help("Input file")
588 .required(true)
589 .build();
590
591 assert_eq!(arg.name, "file");
592 assert_eq!(arg.help, Some("Input file".to_string()));
593 assert!(arg.required);
594 }
595
596 #[test]
597 fn test_arg_builder_variadic() {
598 let arg = SpecArgBuilder::new()
599 .name("files")
600 .var(true)
601 .var_min(1)
602 .var_max(10)
603 .help("Input files")
604 .build();
605
606 assert_eq!(arg.name, "files");
607 assert!(arg.var);
608 assert_eq!(arg.var_min, Some(1));
609 assert_eq!(arg.var_max, Some(10));
610 }
611
612 #[test]
613 fn test_arg_builder_defaults() {
614 let arg = SpecArgBuilder::new()
615 .name("file")
616 .default_values(["a.txt", "b.txt"])
617 .build();
618
619 assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
620 assert!(!arg.required);
621 }
622
623 #[test]
624 fn test_command_builder_basic() {
625 let cmd = SpecCommandBuilder::new()
626 .name("install")
627 .help("Install packages")
628 .build();
629
630 assert_eq!(cmd.name, "install");
631 assert_eq!(cmd.help, Some("Install packages".to_string()));
632 }
633
634 #[test]
635 fn test_command_builder_aliases() {
636 let cmd = SpecCommandBuilder::new()
637 .name("install")
638 .alias("i")
639 .aliases(["add", "get"])
640 .hidden_aliases(["inst"])
641 .build();
642
643 assert_eq!(
644 cmd.aliases,
645 vec!["i".to_string(), "add".to_string(), "get".to_string()]
646 );
647 assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
648 }
649
650 #[test]
651 fn test_command_builder_with_flags_and_args() {
652 let flag = SpecFlagBuilder::new().short('f').long("force").build();
653
654 let arg = SpecArgBuilder::new().name("package").required(true).build();
655
656 let cmd = SpecCommandBuilder::new()
657 .name("install")
658 .flag(flag)
659 .arg(arg)
660 .build();
661
662 assert_eq!(cmd.flags.len(), 1);
663 assert_eq!(cmd.flags[0].name, "force");
664 assert_eq!(cmd.args.len(), 1);
665 assert_eq!(cmd.args[0].name, "package");
666 }
667
668 #[test]
669 fn test_arg_builder_choices() {
670 let arg = SpecArgBuilder::new()
671 .name("format")
672 .choices(["json", "yaml", "toml"])
673 .build();
674
675 assert!(arg.choices.is_some());
676 let choices = arg.choices.unwrap();
677 assert_eq!(
678 choices.choices,
679 vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
680 );
681 }
682
683 #[test]
684 fn test_command_builder_subcommands() {
685 let sub1 = SpecCommandBuilder::new().name("sub1").build();
686 let sub2 = SpecCommandBuilder::new().name("sub2").build();
687
688 let cmd = SpecCommandBuilder::new()
689 .name("main")
690 .subcommand(sub1)
691 .subcommand(sub2)
692 .build();
693
694 assert_eq!(cmd.subcommands.len(), 2);
695 assert!(cmd.subcommands.contains_key("sub1"));
696 assert!(cmd.subcommands.contains_key("sub2"));
697 }
698
699 #[test]
700 fn test_command_builder_before_after_help() {
701 let cmd = SpecCommandBuilder::new()
702 .name("test")
703 .before_help("Before help text")
704 .before_help_long("Before help long text")
705 .after_help("After help text")
706 .after_help_long("After help long text")
707 .build();
708
709 assert_eq!(cmd.before_help, Some("Before help text".to_string()));
710 assert_eq!(
711 cmd.before_help_long,
712 Some("Before help long text".to_string())
713 );
714 assert_eq!(cmd.after_help, Some("After help text".to_string()));
715 assert_eq!(
716 cmd.after_help_long,
717 Some("After help long text".to_string())
718 );
719 }
720
721 #[test]
722 fn test_command_builder_examples() {
723 let cmd = SpecCommandBuilder::new()
724 .name("test")
725 .example("mycli run")
726 .example_with_help("mycli build", "Build example", "Build the project")
727 .build();
728
729 assert_eq!(cmd.examples.len(), 2);
730 assert_eq!(cmd.examples[0].code, "mycli run");
731 assert_eq!(cmd.examples[1].code, "mycli build");
732 assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
733 assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
734 }
735}