Skip to main content

usage/spec/
builder.rs

1//! Builder patterns for ergonomic spec construction
2//!
3//! These builders allow constructing specs without manual Vec allocation,
4//! using variadic-friendly methods.
5//!
6//! # Examples
7//!
8//! ```
9//! use usage::{SpecFlagBuilder, SpecArgBuilder, SpecCommandBuilder};
10//!
11//! let flag = SpecFlagBuilder::new()
12//!     .name("verbose")
13//!     .short('v')
14//!     .long("verbose")
15//!     .help("Enable verbose output")
16//!     .build();
17//!
18//! let arg = SpecArgBuilder::new()
19//!     .name("files")
20//!     .var(true)
21//!     .var_min(1)
22//!     .help("Input files")
23//!     .build();
24//!
25//! let cmd = SpecCommandBuilder::new()
26//!     .name("install")
27//!     .aliases(["i", "add"])
28//!     .flag(flag)
29//!     .arg(arg)
30//!     .build();
31//! ```
32
33use crate::spec::cmd::SpecExample;
34use crate::spec::effect::SpecCommandEffect;
35use crate::{spec::arg::SpecDoubleDashChoices, SpecArg, SpecChoices, SpecCommand, SpecFlag};
36
37/// Builder for SpecFlag
38#[derive(Debug, Default, Clone)]
39pub struct SpecFlagBuilder {
40    inner: SpecFlag,
41    allow_hyphen_values: bool,
42}
43
44impl SpecFlagBuilder {
45    /// Create a new SpecFlagBuilder
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Set the flag name
51    pub fn name(mut self, name: impl Into<String>) -> Self {
52        self.inner.name = name.into();
53        self
54    }
55
56    /// Add a short flag character (can be called multiple times)
57    pub fn short(mut self, c: char) -> Self {
58        self.inner.short.push(c);
59        self
60    }
61
62    /// Add multiple short flags at once
63    pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
64        self.inner.short.extend(chars);
65        self
66    }
67
68    /// Add a long flag name (can be called multiple times)
69    pub fn long(mut self, name: impl Into<String>) -> Self {
70        self.inner.long.push(name.into());
71        self
72    }
73
74    /// Add multiple long flags at once
75    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    /// Add a default value (can be called multiple times for var flags)
85    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    /// Add multiple default values at once
92    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    /// Set help text
107    pub fn help(mut self, text: impl Into<String>) -> Self {
108        self.inner.help = Some(text.into());
109        self
110    }
111
112    /// Set long help text
113    pub fn help_long(mut self, text: impl Into<String>) -> Self {
114        self.inner.help_long = Some(text.into());
115        self
116    }
117
118    /// Set markdown help text
119    pub fn help_md(mut self, text: impl Into<String>) -> Self {
120        self.inner.help_md = Some(text.into());
121        self
122    }
123
124    /// Set as variadic (can be specified multiple times)
125    pub fn var(mut self, is_var: bool) -> Self {
126        self.inner.var = is_var;
127        self
128    }
129
130    /// Set minimum count for variadic flag
131    pub fn var_min(mut self, min: usize) -> Self {
132        self.inner.var_min = Some(min);
133        self
134    }
135
136    /// Set maximum count for variadic flag
137    pub fn var_max(mut self, max: usize) -> Self {
138        self.inner.var_max = Some(max);
139        self
140    }
141
142    /// Set as required
143    pub fn required(mut self, is_required: bool) -> Self {
144        self.inner.required = is_required;
145        self
146    }
147
148    /// Set as global (available to subcommands)
149    pub fn global(mut self, is_global: bool) -> Self {
150        self.inner.global = is_global;
151        self
152    }
153
154    /// Set as hidden
155    pub fn hide(mut self, is_hidden: bool) -> Self {
156        self.inner.hide = is_hidden;
157        self
158    }
159
160    /// Set as count flag
161    pub fn count(mut self, is_count: bool) -> Self {
162        self.inner.count = is_count;
163        self
164    }
165
166    /// Allow this flag's value to start with `-`
167    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    /// Set the argument spec for flags that take values
180    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    /// Set negate string
191    pub fn negate(mut self, negate: impl Into<String>) -> Self {
192        self.inner.negate = Some(negate.into());
193        self
194    }
195
196    /// Set environment variable name
197    pub fn env(mut self, env: impl Into<String>) -> Self {
198        self.inner.env = Some(env.into());
199        self
200    }
201
202    /// Set deprecated message
203    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
204        self.inner.deprecated = Some(msg.into());
205        self
206    }
207
208    /// Set the rendered usage string. `build` derives this when unset.
209    pub fn usage(mut self, usage: impl Into<String>) -> Self {
210        self.inner.usage = usage.into();
211        self
212    }
213
214    /// Set the first line of help text. Derived from `help` when unset.
215    pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
216        self.inner.help_first_line = Some(text.into());
217        self
218    }
219
220    /// Raise the command's effect when this flag is supplied.
221    pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
222        self.inner.effect = Some(effect);
223        self
224    }
225
226    /// Build the final SpecFlag
227    #[must_use]
228    pub fn build(mut self) -> SpecFlag {
229        if self.allow_hyphen_values {
230            if let Some(arg) = &mut self.inner.arg {
231                arg.double_dash = crate::spec::arg::SpecDoubleDashChoices::Automatic;
232            }
233        }
234        self.inner.usage = self.inner.usage();
235        if self.inner.name.is_empty() {
236            // Derive name from long or short flags
237            if let Some(long) = self.inner.long.first() {
238                self.inner.name = long.clone();
239            } else if let Some(short) = self.inner.short.first() {
240                self.inner.name = short.to_string();
241            }
242        }
243        self.inner
244    }
245}
246
247/// Builder for SpecArg
248#[derive(Debug, Default, Clone)]
249pub struct SpecArgBuilder {
250    inner: SpecArg,
251}
252
253impl SpecArgBuilder {
254    /// Create a new SpecArgBuilder
255    pub fn new() -> Self {
256        Self::default()
257    }
258
259    /// Set the argument name
260    pub fn name(mut self, name: impl Into<String>) -> Self {
261        self.inner.name = name.into();
262        self
263    }
264
265    /// Add a default value (can be called multiple times for var args)
266    pub fn default_value(mut self, value: impl Into<String>) -> Self {
267        self.inner.default.push(value.into());
268        self.inner.required = false;
269        self
270    }
271
272    /// Add multiple default values at once
273    pub fn default_values<I, S>(mut self, values: I) -> Self
274    where
275        I: IntoIterator<Item = S>,
276        S: Into<String>,
277    {
278        self.inner
279            .default
280            .extend(values.into_iter().map(Into::into));
281        if !self.inner.default.is_empty() {
282            self.inner.required = false;
283        }
284        self
285    }
286
287    /// Set help text
288    pub fn help(mut self, text: impl Into<String>) -> Self {
289        self.inner.help = Some(text.into());
290        self
291    }
292
293    /// Set long help text
294    pub fn help_long(mut self, text: impl Into<String>) -> Self {
295        self.inner.help_long = Some(text.into());
296        self
297    }
298
299    /// Set markdown help text
300    pub fn help_md(mut self, text: impl Into<String>) -> Self {
301        self.inner.help_md = Some(text.into());
302        self
303    }
304
305    /// Set as variadic (accepts multiple values)
306    pub fn var(mut self, is_var: bool) -> Self {
307        self.inner.var = is_var;
308        self
309    }
310
311    /// Set minimum count for variadic argument
312    pub fn var_min(mut self, min: usize) -> Self {
313        self.inner.var_min = Some(min);
314        self
315    }
316
317    /// Set maximum count for variadic argument
318    pub fn var_max(mut self, max: usize) -> Self {
319        self.inner.var_max = Some(max);
320        self
321    }
322
323    /// Set as required
324    pub fn required(mut self, is_required: bool) -> Self {
325        self.inner.required = is_required;
326        self
327    }
328
329    /// Set as hidden
330    pub fn hide(mut self, is_hidden: bool) -> Self {
331        self.inner.hide = is_hidden;
332        self
333    }
334
335    /// Set environment variable name
336    pub fn env(mut self, env: impl Into<String>) -> Self {
337        self.inner.env = Some(env.into());
338        self
339    }
340
341    /// Set the double-dash behavior
342    pub fn double_dash(mut self, behavior: SpecDoubleDashChoices) -> Self {
343        self.inner.double_dash = behavior;
344        self
345    }
346
347    /// Set choices for this argument
348    pub fn choices<I, S>(mut self, choices: I) -> Self
349    where
350        I: IntoIterator<Item = S>,
351        S: Into<String>,
352    {
353        let spec_choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
354        #[cfg(feature = "unstable_choices_env")]
355        let env = spec_choices.env().map(ToString::to_string);
356        spec_choices.choices = choices.into_iter().map(Into::into).collect();
357        #[cfg(feature = "unstable_choices_env")]
358        spec_choices.set_env(env);
359        self
360    }
361
362    /// Set choices from an environment variable
363    #[cfg(feature = "unstable_choices_env")]
364    pub fn choices_env(mut self, env: impl Into<String>) -> Self {
365        let choices = self.inner.choices.get_or_insert_with(SpecChoices::default);
366        choices.set_env(Some(env.into()));
367        self
368    }
369
370    /// Set the rendered usage string. `build` derives this when unset.
371    pub fn usage(mut self, usage: impl Into<String>) -> Self {
372        self.inner.usage = usage.into();
373        self
374    }
375
376    /// Set the first line of help text. Derived from `help` when unset.
377    pub fn help_first_line(mut self, text: impl Into<String>) -> Self {
378        self.inner.help_first_line = Some(text.into());
379        self
380    }
381
382    /// Raise the command's effect when this argument is supplied.
383    pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
384        self.inner.effect = Some(effect);
385        self
386    }
387
388    /// Build the final SpecArg
389    #[must_use]
390    pub fn build(mut self) -> SpecArg {
391        self.inner.usage = self.inner.usage();
392        self.inner
393    }
394}
395
396/// Builder for SpecCommand
397#[derive(Debug, Default, Clone)]
398pub struct SpecCommandBuilder {
399    inner: SpecCommand,
400}
401
402impl SpecCommandBuilder {
403    /// Create a new SpecCommandBuilder
404    pub fn new() -> Self {
405        Self::default()
406    }
407
408    /// Set the command name
409    pub fn name(mut self, name: impl Into<String>) -> Self {
410        self.inner.name = name.into();
411        self
412    }
413
414    /// Add an alias (can be called multiple times)
415    pub fn alias(mut self, alias: impl Into<String>) -> Self {
416        self.inner.aliases.push(alias.into());
417        self
418    }
419
420    /// Add multiple aliases at once
421    pub fn aliases<I, S>(mut self, aliases: I) -> Self
422    where
423        I: IntoIterator<Item = S>,
424        S: Into<String>,
425    {
426        self.inner
427            .aliases
428            .extend(aliases.into_iter().map(Into::into));
429        self
430    }
431
432    /// Add a hidden alias (can be called multiple times)
433    pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
434        self.inner.hidden_aliases.push(alias.into());
435        self
436    }
437
438    /// Add multiple hidden aliases at once
439    pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
440    where
441        I: IntoIterator<Item = S>,
442        S: Into<String>,
443    {
444        self.inner
445            .hidden_aliases
446            .extend(aliases.into_iter().map(Into::into));
447        self
448    }
449
450    /// Add a flag to the command
451    pub fn flag(mut self, flag: SpecFlag) -> Self {
452        self.inner.flags.push(flag);
453        self
454    }
455
456    /// Add multiple flags at once
457    pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
458        self.inner.flags.extend(flags);
459        self
460    }
461
462    /// Add an argument to the command
463    pub fn arg(mut self, arg: SpecArg) -> Self {
464        self.inner.args.push(arg);
465        self
466    }
467
468    /// Add multiple arguments at once
469    pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
470        self.inner.args.extend(args);
471        self
472    }
473
474    /// Set help text
475    pub fn help(mut self, text: impl Into<String>) -> Self {
476        self.inner.help = Some(text.into());
477        self
478    }
479
480    /// Set long help text
481    pub fn help_long(mut self, text: impl Into<String>) -> Self {
482        self.inner.help_long = Some(text.into());
483        self
484    }
485
486    /// Set markdown help text
487    pub fn help_md(mut self, text: impl Into<String>) -> Self {
488        self.inner.help_md = Some(text.into());
489        self
490    }
491
492    /// Set as hidden
493    pub fn hide(mut self, is_hidden: bool) -> Self {
494        self.inner.hide = is_hidden;
495        self
496    }
497
498    /// Set subcommand required
499    pub fn subcommand_required(mut self, required: bool) -> Self {
500        self.inner.subcommand_required = required;
501        self
502    }
503
504    /// Set what running this command does to the world
505    pub fn effect(mut self, effect: SpecCommandEffect) -> Self {
506        self.inner.effect = Some(effect);
507        self
508    }
509
510    /// Set deprecated message
511    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
512        self.inner.deprecated = Some(msg.into());
513        self
514    }
515
516    /// Set restart token for resetting argument parsing
517    /// e.g., `mise run lint ::: test ::: check` with restart_token=":::"
518    pub fn restart_token(mut self, token: impl Into<String>) -> Self {
519        self.inner.restart_token = Some(token.into());
520        self
521    }
522
523    /// Add a subcommand (can be called multiple times)
524    pub fn subcommand(mut self, cmd: SpecCommand) -> Self {
525        self.inner.subcommands.insert(cmd.name.clone(), cmd);
526        self
527    }
528
529    /// Add multiple subcommands at once
530    pub fn subcommands(mut self, cmds: impl IntoIterator<Item = SpecCommand>) -> Self {
531        for cmd in cmds {
532            self.inner.subcommands.insert(cmd.name.clone(), cmd);
533        }
534        self
535    }
536
537    /// Set before_help text (displayed before the help message)
538    pub fn before_help(mut self, text: impl Into<String>) -> Self {
539        self.inner.before_help = Some(text.into());
540        self
541    }
542
543    /// Set before_help_long text
544    pub fn before_help_long(mut self, text: impl Into<String>) -> Self {
545        self.inner.before_help_long = Some(text.into());
546        self
547    }
548
549    /// Set before_help markdown text
550    pub fn before_help_md(mut self, text: impl Into<String>) -> Self {
551        self.inner.before_help_md = Some(text.into());
552        self
553    }
554
555    /// Set after_help text (displayed after the help message)
556    pub fn after_help(mut self, text: impl Into<String>) -> Self {
557        self.inner.after_help = Some(text.into());
558        self
559    }
560
561    /// Set after_help_long text
562    pub fn after_help_long(mut self, text: impl Into<String>) -> Self {
563        self.inner.after_help_long = Some(text.into());
564        self
565    }
566
567    /// Set after_help markdown text
568    pub fn after_help_md(mut self, text: impl Into<String>) -> Self {
569        self.inner.after_help_md = Some(text.into());
570        self
571    }
572
573    /// Add an example (can be called multiple times)
574    pub fn example(mut self, code: impl Into<String>) -> Self {
575        self.inner.examples.push(SpecExample::new(code.into()));
576        self
577    }
578
579    /// Add an example with header and help text
580    pub fn example_with_help(
581        mut self,
582        code: impl Into<String>,
583        header: impl Into<String>,
584        help: impl Into<String>,
585    ) -> Self {
586        let mut example = SpecExample::new(code.into());
587        example.header = Some(header.into());
588        example.help = Some(help.into());
589        self.inner.examples.push(example);
590        self
591    }
592
593    /// Build the final SpecCommand
594    #[must_use]
595    pub fn build(mut self) -> SpecCommand {
596        self.inner.usage = self.inner.usage();
597        self.inner
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604
605    #[test]
606    fn test_flag_builder_basic() {
607        let flag = SpecFlagBuilder::new()
608            .name("verbose")
609            .short('v')
610            .long("verbose")
611            .help("Enable verbose output")
612            .build();
613
614        assert_eq!(flag.name, "verbose");
615        assert_eq!(flag.short, vec!['v']);
616        assert_eq!(flag.long, vec!["verbose".to_string()]);
617        assert_eq!(flag.help, Some("Enable verbose output".to_string()));
618    }
619
620    #[test]
621    fn test_flag_builder_multiple_values() {
622        let flag = SpecFlagBuilder::new()
623            .shorts(['v', 'V'])
624            .longs(["verbose", "loud"])
625            .default_values(["info", "warn"])
626            .build();
627
628        assert_eq!(flag.short, vec!['v', 'V']);
629        assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
630        assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
631        assert!(!flag.required); // Should be false due to defaults
632    }
633
634    #[test]
635    fn test_flag_builder_variadic() {
636        let flag = SpecFlagBuilder::new()
637            .long("file")
638            .var(true)
639            .var_min(1)
640            .var_max(10)
641            .build();
642
643        assert!(flag.var);
644        assert_eq!(flag.var_min, Some(1));
645        assert_eq!(flag.var_max, Some(10));
646    }
647
648    #[test]
649    fn test_flag_builder_name_derivation() {
650        let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
651
652        // Name should be derived from long flag
653        assert_eq!(flag.name, "verbose");
654
655        let flag2 = SpecFlagBuilder::new().short('v').build();
656
657        // Name should be derived from short flag if no long
658        assert_eq!(flag2.name, "v");
659    }
660
661    #[test]
662    fn test_arg_builder_basic() {
663        let arg = SpecArgBuilder::new()
664            .name("file")
665            .help("Input file")
666            .required(true)
667            .build();
668
669        assert_eq!(arg.name, "file");
670        assert_eq!(arg.help, Some("Input file".to_string()));
671        assert!(arg.required);
672    }
673
674    #[test]
675    fn test_arg_builder_variadic() {
676        let arg = SpecArgBuilder::new()
677            .name("files")
678            .var(true)
679            .var_min(1)
680            .var_max(10)
681            .help("Input files")
682            .build();
683
684        assert_eq!(arg.name, "files");
685        assert!(arg.var);
686        assert_eq!(arg.var_min, Some(1));
687        assert_eq!(arg.var_max, Some(10));
688    }
689
690    #[test]
691    fn test_arg_builder_defaults() {
692        let arg = SpecArgBuilder::new()
693            .name("file")
694            .default_values(["a.txt", "b.txt"])
695            .build();
696
697        assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
698        assert!(!arg.required);
699    }
700
701    #[test]
702    fn test_command_builder_basic() {
703        let cmd = SpecCommandBuilder::new()
704            .name("install")
705            .help("Install packages")
706            .build();
707
708        assert_eq!(cmd.name, "install");
709        assert_eq!(cmd.help, Some("Install packages".to_string()));
710    }
711
712    #[test]
713    fn test_command_builder_aliases() {
714        let cmd = SpecCommandBuilder::new()
715            .name("install")
716            .alias("i")
717            .aliases(["add", "get"])
718            .hidden_aliases(["inst"])
719            .build();
720
721        assert_eq!(
722            cmd.aliases,
723            vec!["i".to_string(), "add".to_string(), "get".to_string()]
724        );
725        assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
726    }
727
728    #[test]
729    fn test_command_builder_with_flags_and_args() {
730        let flag = SpecFlagBuilder::new().short('f').long("force").build();
731
732        let arg = SpecArgBuilder::new().name("package").required(true).build();
733
734        let cmd = SpecCommandBuilder::new()
735            .name("install")
736            .flag(flag)
737            .arg(arg)
738            .build();
739
740        assert_eq!(cmd.flags.len(), 1);
741        assert_eq!(cmd.flags[0].name, "force");
742        assert_eq!(cmd.args.len(), 1);
743        assert_eq!(cmd.args[0].name, "package");
744    }
745
746    #[test]
747    fn test_arg_builder_choices() {
748        let arg = SpecArgBuilder::new()
749            .name("format")
750            .choices(["json", "yaml", "toml"])
751            .build();
752
753        assert!(arg.choices.is_some());
754        let choices = arg.choices.unwrap();
755        assert_eq!(
756            choices.choices,
757            vec!["json".to_string(), "yaml".to_string(), "toml".to_string()]
758        );
759        assert_eq!(choices.env(), None);
760    }
761
762    #[cfg(feature = "unstable_choices_env")]
763    #[test]
764    fn test_arg_builder_choices_env() {
765        let arg = SpecArgBuilder::new()
766            .name("env")
767            .choices(["local"])
768            .choices_env("DEPLOY_ENVS")
769            .build();
770
771        let choices = arg.choices.unwrap();
772        assert_eq!(choices.choices, vec!["local".to_string()]);
773        assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
774    }
775
776    #[cfg(feature = "unstable_choices_env")]
777    #[test]
778    fn test_arg_builder_choices_preserves_choices_env() {
779        let arg = SpecArgBuilder::new()
780            .name("env")
781            .choices_env("DEPLOY_ENVS")
782            .choices(["local"])
783            .build();
784
785        let choices = arg.choices.unwrap();
786        assert_eq!(choices.choices, vec!["local".to_string()]);
787        assert_eq!(choices.env(), Some("DEPLOY_ENVS"));
788    }
789
790    #[test]
791    fn test_command_builder_subcommands() {
792        let sub1 = SpecCommandBuilder::new().name("sub1").build();
793        let sub2 = SpecCommandBuilder::new().name("sub2").build();
794
795        let cmd = SpecCommandBuilder::new()
796            .name("main")
797            .subcommand(sub1)
798            .subcommand(sub2)
799            .build();
800
801        assert_eq!(cmd.subcommands.len(), 2);
802        assert!(cmd.subcommands.contains_key("sub1"));
803        assert!(cmd.subcommands.contains_key("sub2"));
804    }
805
806    #[test]
807    fn test_command_builder_before_after_help() {
808        let cmd = SpecCommandBuilder::new()
809            .name("test")
810            .before_help("Before help text")
811            .before_help_long("Before help long text")
812            .after_help("After help text")
813            .after_help_long("After help long text")
814            .build();
815
816        assert_eq!(cmd.before_help, Some("Before help text".to_string()));
817        assert_eq!(
818            cmd.before_help_long,
819            Some("Before help long text".to_string())
820        );
821        assert_eq!(cmd.after_help, Some("After help text".to_string()));
822        assert_eq!(
823            cmd.after_help_long,
824            Some("After help long text".to_string())
825        );
826    }
827
828    #[test]
829    fn test_command_builder_examples() {
830        let cmd = SpecCommandBuilder::new()
831            .name("test")
832            .example("mycli run")
833            .example_with_help("mycli build", "Build example", "Build the project")
834            .build();
835
836        assert_eq!(cmd.examples.len(), 2);
837        assert_eq!(cmd.examples[0].code, "mycli run");
838        assert_eq!(cmd.examples[1].code, "mycli build");
839        assert_eq!(cmd.examples[1].header, Some("Build example".to_string()));
840        assert_eq!(cmd.examples[1].help, Some("Build the project".to_string()));
841    }
842}