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::{SpecArg, SpecCommand, SpecFlag};
34
35/// Builder for SpecFlag
36#[derive(Debug, Default, Clone)]
37pub struct SpecFlagBuilder {
38    inner: SpecFlag,
39}
40
41impl SpecFlagBuilder {
42    /// Create a new SpecFlagBuilder
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Set the flag name
48    pub fn name(mut self, name: impl Into<String>) -> Self {
49        self.inner.name = name.into();
50        self
51    }
52
53    /// Add a short flag character (can be called multiple times)
54    pub fn short(mut self, c: char) -> Self {
55        self.inner.short.push(c);
56        self
57    }
58
59    /// Add multiple short flags at once
60    pub fn shorts(mut self, chars: impl IntoIterator<Item = char>) -> Self {
61        self.inner.short.extend(chars);
62        self
63    }
64
65    /// Add a long flag name (can be called multiple times)
66    pub fn long(mut self, name: impl Into<String>) -> Self {
67        self.inner.long.push(name.into());
68        self
69    }
70
71    /// Add multiple long flags at once
72    pub fn longs<I, S>(mut self, names: I) -> Self
73    where
74        I: IntoIterator<Item = S>,
75        S: Into<String>,
76    {
77        self.inner.long.extend(names.into_iter().map(Into::into));
78        self
79    }
80
81    /// Add a default value (can be called multiple times for var flags)
82    pub fn default_value(mut self, value: impl Into<String>) -> Self {
83        self.inner.default.push(value.into());
84        self.inner.required = false;
85        self
86    }
87
88    /// Add multiple default values at once
89    pub fn default_values<I, S>(mut self, values: I) -> Self
90    where
91        I: IntoIterator<Item = S>,
92        S: Into<String>,
93    {
94        self.inner
95            .default
96            .extend(values.into_iter().map(Into::into));
97        if !self.inner.default.is_empty() {
98            self.inner.required = false;
99        }
100        self
101    }
102
103    /// Set help text
104    pub fn help(mut self, text: impl Into<String>) -> Self {
105        self.inner.help = Some(text.into());
106        self
107    }
108
109    /// Set long help text
110    pub fn help_long(mut self, text: impl Into<String>) -> Self {
111        self.inner.help_long = Some(text.into());
112        self
113    }
114
115    /// Set markdown help text
116    pub fn help_md(mut self, text: impl Into<String>) -> Self {
117        self.inner.help_md = Some(text.into());
118        self
119    }
120
121    /// Set as variadic (can be specified multiple times)
122    pub fn var(mut self, is_var: bool) -> Self {
123        self.inner.var = is_var;
124        self
125    }
126
127    /// Set minimum count for variadic flag
128    pub fn var_min(mut self, min: usize) -> Self {
129        self.inner.var_min = Some(min);
130        self
131    }
132
133    /// Set maximum count for variadic flag
134    pub fn var_max(mut self, max: usize) -> Self {
135        self.inner.var_max = Some(max);
136        self
137    }
138
139    /// Set as required
140    pub fn required(mut self, is_required: bool) -> Self {
141        self.inner.required = is_required;
142        self
143    }
144
145    /// Set as global (available to subcommands)
146    pub fn global(mut self, is_global: bool) -> Self {
147        self.inner.global = is_global;
148        self
149    }
150
151    /// Set as hidden
152    pub fn hide(mut self, is_hidden: bool) -> Self {
153        self.inner.hide = is_hidden;
154        self
155    }
156
157    /// Set as count flag
158    pub fn count(mut self, is_count: bool) -> Self {
159        self.inner.count = is_count;
160        self
161    }
162
163    /// Set the argument spec for flags that take values
164    pub fn arg(mut self, arg: SpecArg) -> Self {
165        self.inner.arg = Some(arg);
166        self
167    }
168
169    /// Set negate string
170    pub fn negate(mut self, negate: impl Into<String>) -> Self {
171        self.inner.negate = Some(negate.into());
172        self
173    }
174
175    /// Set environment variable name
176    pub fn env(mut self, env: impl Into<String>) -> Self {
177        self.inner.env = Some(env.into());
178        self
179    }
180
181    /// Set deprecated message
182    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
183        self.inner.deprecated = Some(msg.into());
184        self
185    }
186
187    /// Build the final SpecFlag
188    pub fn build(mut self) -> SpecFlag {
189        self.inner.usage = self.inner.usage();
190        if self.inner.name.is_empty() {
191            // Derive name from long or short flags
192            if let Some(long) = self.inner.long.first() {
193                self.inner.name = long.clone();
194            } else if let Some(short) = self.inner.short.first() {
195                self.inner.name = short.to_string();
196            }
197        }
198        self.inner
199    }
200}
201
202/// Builder for SpecArg
203#[derive(Debug, Default, Clone)]
204pub struct SpecArgBuilder {
205    inner: SpecArg,
206}
207
208impl SpecArgBuilder {
209    /// Create a new SpecArgBuilder
210    pub fn new() -> Self {
211        Self::default()
212    }
213
214    /// Set the argument name
215    pub fn name(mut self, name: impl Into<String>) -> Self {
216        self.inner.name = name.into();
217        self
218    }
219
220    /// Add a default value (can be called multiple times for var args)
221    pub fn default_value(mut self, value: impl Into<String>) -> Self {
222        self.inner.default.push(value.into());
223        self.inner.required = false;
224        self
225    }
226
227    /// Add multiple default values at once
228    pub fn default_values<I, S>(mut self, values: I) -> Self
229    where
230        I: IntoIterator<Item = S>,
231        S: Into<String>,
232    {
233        self.inner
234            .default
235            .extend(values.into_iter().map(Into::into));
236        if !self.inner.default.is_empty() {
237            self.inner.required = false;
238        }
239        self
240    }
241
242    /// Set help text
243    pub fn help(mut self, text: impl Into<String>) -> Self {
244        self.inner.help = Some(text.into());
245        self
246    }
247
248    /// Set long help text
249    pub fn help_long(mut self, text: impl Into<String>) -> Self {
250        self.inner.help_long = Some(text.into());
251        self
252    }
253
254    /// Set markdown help text
255    pub fn help_md(mut self, text: impl Into<String>) -> Self {
256        self.inner.help_md = Some(text.into());
257        self
258    }
259
260    /// Set as variadic (accepts multiple values)
261    pub fn var(mut self, is_var: bool) -> Self {
262        self.inner.var = is_var;
263        self
264    }
265
266    /// Set minimum count for variadic argument
267    pub fn var_min(mut self, min: usize) -> Self {
268        self.inner.var_min = Some(min);
269        self
270    }
271
272    /// Set maximum count for variadic argument
273    pub fn var_max(mut self, max: usize) -> Self {
274        self.inner.var_max = Some(max);
275        self
276    }
277
278    /// Set as required
279    pub fn required(mut self, is_required: bool) -> Self {
280        self.inner.required = is_required;
281        self
282    }
283
284    /// Set as hidden
285    pub fn hide(mut self, is_hidden: bool) -> Self {
286        self.inner.hide = is_hidden;
287        self
288    }
289
290    /// Set environment variable name
291    pub fn env(mut self, env: impl Into<String>) -> Self {
292        self.inner.env = Some(env.into());
293        self
294    }
295
296    /// Build the final SpecArg
297    pub fn build(mut self) -> SpecArg {
298        self.inner.usage = self.inner.usage();
299        self.inner
300    }
301}
302
303/// Builder for SpecCommand
304#[derive(Debug, Default, Clone)]
305pub struct SpecCommandBuilder {
306    inner: SpecCommand,
307}
308
309impl SpecCommandBuilder {
310    /// Create a new SpecCommandBuilder
311    pub fn new() -> Self {
312        Self::default()
313    }
314
315    /// Set the command name
316    pub fn name(mut self, name: impl Into<String>) -> Self {
317        self.inner.name = name.into();
318        self
319    }
320
321    /// Add an alias (can be called multiple times)
322    pub fn alias(mut self, alias: impl Into<String>) -> Self {
323        self.inner.aliases.push(alias.into());
324        self
325    }
326
327    /// Add multiple aliases at once
328    pub fn aliases<I, S>(mut self, aliases: I) -> Self
329    where
330        I: IntoIterator<Item = S>,
331        S: Into<String>,
332    {
333        self.inner
334            .aliases
335            .extend(aliases.into_iter().map(Into::into));
336        self
337    }
338
339    /// Add a hidden alias (can be called multiple times)
340    pub fn hidden_alias(mut self, alias: impl Into<String>) -> Self {
341        self.inner.hidden_aliases.push(alias.into());
342        self
343    }
344
345    /// Add multiple hidden aliases at once
346    pub fn hidden_aliases<I, S>(mut self, aliases: I) -> Self
347    where
348        I: IntoIterator<Item = S>,
349        S: Into<String>,
350    {
351        self.inner
352            .hidden_aliases
353            .extend(aliases.into_iter().map(Into::into));
354        self
355    }
356
357    /// Add a flag to the command
358    pub fn flag(mut self, flag: SpecFlag) -> Self {
359        self.inner.flags.push(flag);
360        self
361    }
362
363    /// Add multiple flags at once
364    pub fn flags(mut self, flags: impl IntoIterator<Item = SpecFlag>) -> Self {
365        self.inner.flags.extend(flags);
366        self
367    }
368
369    /// Add an argument to the command
370    pub fn arg(mut self, arg: SpecArg) -> Self {
371        self.inner.args.push(arg);
372        self
373    }
374
375    /// Add multiple arguments at once
376    pub fn args(mut self, args: impl IntoIterator<Item = SpecArg>) -> Self {
377        self.inner.args.extend(args);
378        self
379    }
380
381    /// Set help text
382    pub fn help(mut self, text: impl Into<String>) -> Self {
383        self.inner.help = Some(text.into());
384        self
385    }
386
387    /// Set long help text
388    pub fn help_long(mut self, text: impl Into<String>) -> Self {
389        self.inner.help_long = Some(text.into());
390        self
391    }
392
393    /// Set markdown help text
394    pub fn help_md(mut self, text: impl Into<String>) -> Self {
395        self.inner.help_md = Some(text.into());
396        self
397    }
398
399    /// Set as hidden
400    pub fn hide(mut self, is_hidden: bool) -> Self {
401        self.inner.hide = is_hidden;
402        self
403    }
404
405    /// Set subcommand required
406    pub fn subcommand_required(mut self, required: bool) -> Self {
407        self.inner.subcommand_required = required;
408        self
409    }
410
411    /// Set deprecated message
412    pub fn deprecated(mut self, msg: impl Into<String>) -> Self {
413        self.inner.deprecated = Some(msg.into());
414        self
415    }
416
417    /// Build the final SpecCommand
418    pub fn build(mut self) -> SpecCommand {
419        self.inner.usage = self.inner.usage();
420        self.inner
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use super::*;
427
428    #[test]
429    fn test_flag_builder_basic() {
430        let flag = SpecFlagBuilder::new()
431            .name("verbose")
432            .short('v')
433            .long("verbose")
434            .help("Enable verbose output")
435            .build();
436
437        assert_eq!(flag.name, "verbose");
438        assert_eq!(flag.short, vec!['v']);
439        assert_eq!(flag.long, vec!["verbose".to_string()]);
440        assert_eq!(flag.help, Some("Enable verbose output".to_string()));
441    }
442
443    #[test]
444    fn test_flag_builder_multiple_values() {
445        let flag = SpecFlagBuilder::new()
446            .shorts(['v', 'V'])
447            .longs(["verbose", "loud"])
448            .default_values(["info", "warn"])
449            .build();
450
451        assert_eq!(flag.short, vec!['v', 'V']);
452        assert_eq!(flag.long, vec!["verbose".to_string(), "loud".to_string()]);
453        assert_eq!(flag.default, vec!["info".to_string(), "warn".to_string()]);
454        assert!(!flag.required); // Should be false due to defaults
455    }
456
457    #[test]
458    fn test_flag_builder_variadic() {
459        let flag = SpecFlagBuilder::new()
460            .long("file")
461            .var(true)
462            .var_min(1)
463            .var_max(10)
464            .build();
465
466        assert!(flag.var);
467        assert_eq!(flag.var_min, Some(1));
468        assert_eq!(flag.var_max, Some(10));
469    }
470
471    #[test]
472    fn test_flag_builder_name_derivation() {
473        let flag = SpecFlagBuilder::new().short('v').long("verbose").build();
474
475        // Name should be derived from long flag
476        assert_eq!(flag.name, "verbose");
477
478        let flag2 = SpecFlagBuilder::new().short('v').build();
479
480        // Name should be derived from short flag if no long
481        assert_eq!(flag2.name, "v");
482    }
483
484    #[test]
485    fn test_arg_builder_basic() {
486        let arg = SpecArgBuilder::new()
487            .name("file")
488            .help("Input file")
489            .required(true)
490            .build();
491
492        assert_eq!(arg.name, "file");
493        assert_eq!(arg.help, Some("Input file".to_string()));
494        assert!(arg.required);
495    }
496
497    #[test]
498    fn test_arg_builder_variadic() {
499        let arg = SpecArgBuilder::new()
500            .name("files")
501            .var(true)
502            .var_min(1)
503            .var_max(10)
504            .help("Input files")
505            .build();
506
507        assert_eq!(arg.name, "files");
508        assert!(arg.var);
509        assert_eq!(arg.var_min, Some(1));
510        assert_eq!(arg.var_max, Some(10));
511    }
512
513    #[test]
514    fn test_arg_builder_defaults() {
515        let arg = SpecArgBuilder::new()
516            .name("file")
517            .default_values(["a.txt", "b.txt"])
518            .build();
519
520        assert_eq!(arg.default, vec!["a.txt".to_string(), "b.txt".to_string()]);
521        assert!(!arg.required);
522    }
523
524    #[test]
525    fn test_command_builder_basic() {
526        let cmd = SpecCommandBuilder::new()
527            .name("install")
528            .help("Install packages")
529            .build();
530
531        assert_eq!(cmd.name, "install");
532        assert_eq!(cmd.help, Some("Install packages".to_string()));
533    }
534
535    #[test]
536    fn test_command_builder_aliases() {
537        let cmd = SpecCommandBuilder::new()
538            .name("install")
539            .alias("i")
540            .aliases(["add", "get"])
541            .hidden_aliases(["inst"])
542            .build();
543
544        assert_eq!(
545            cmd.aliases,
546            vec!["i".to_string(), "add".to_string(), "get".to_string()]
547        );
548        assert_eq!(cmd.hidden_aliases, vec!["inst".to_string()]);
549    }
550
551    #[test]
552    fn test_command_builder_with_flags_and_args() {
553        let flag = SpecFlagBuilder::new().short('f').long("force").build();
554
555        let arg = SpecArgBuilder::new().name("package").required(true).build();
556
557        let cmd = SpecCommandBuilder::new()
558            .name("install")
559            .flag(flag)
560            .arg(arg)
561            .build();
562
563        assert_eq!(cmd.flags.len(), 1);
564        assert_eq!(cmd.flags[0].name, "force");
565        assert_eq!(cmd.args.len(), 1);
566        assert_eq!(cmd.args[0].name, "package");
567    }
568}