usage/
macros.rs

1//! Convenience macros for creating specs with minimal boilerplate.
2//!
3//! # Examples
4//!
5//! ```
6//! use usage::{spec_flag, spec_arg, spec_cmd};
7//!
8//! // Create a flag
9//! let verbose = spec_flag!("-v", "--verbose");
10//! let output = spec_flag!("--output" => "<file>"; help = "Output file");
11//!
12//! // Create an argument
13//! let file = spec_arg!("file"; required = true);
14//! let files = spec_arg!("files"; var = true, var_min = 1);
15//!
16//! // Create a command
17//! let cmd = spec_cmd!("install";
18//!     help = "Install packages",
19//!     aliases = ["i", "add"]
20//! );
21//! ```
22
23/// Create a SpecFlag with minimal boilerplate.
24///
25/// # Syntax
26///
27/// ```text
28/// spec_flag!("-s", "--long")
29/// spec_flag!("-s", "--long"; help = "description")
30/// spec_flag!("--long")
31/// spec_flag!("--long"; help = "description", var = true)
32/// spec_flag!("--long" => "<arg>"; help = "description")
33/// ```
34///
35/// # Examples
36///
37/// ```
38/// use usage::spec_flag;
39///
40/// // Simple short and long flag
41/// let f = spec_flag!("-v", "--verbose");
42///
43/// // Flag with help text
44/// let f = spec_flag!("-f", "--force"; help = "Force operation");
45///
46/// // Long flag only
47/// let f = spec_flag!("--all");
48///
49/// // Flag with an argument
50/// let f = spec_flag!("--output" => "<file>"; help = "Output file");
51/// ```
52#[macro_export]
53macro_rules! spec_flag {
54    // Pattern: spec_flag!("-s", "--long")
55    ($short:literal, $long:literal) => {{
56        $crate::SpecFlagBuilder::new()
57            .short($short.chars().nth(1).expect("short flag must be -X format"))
58            .long(&$long[2..])
59            .build()
60    }};
61
62    // Pattern: spec_flag!("-s", "--long"; key = value, ...)
63    ($short:literal, $long:literal; $($key:ident = $value:expr),* $(,)?) => {{
64        let mut builder = $crate::SpecFlagBuilder::new()
65            .short($short.chars().nth(1).expect("short flag must be -X format"))
66            .long(&$long[2..]);
67        $(builder = $crate::__spec_flag_attr!(builder, $key, $value);)*
68        builder.build()
69    }};
70
71    // Pattern: spec_flag!("--long")
72    ($long:literal) => {{
73        $crate::SpecFlagBuilder::new()
74            .long(&$long[2..])
75            .build()
76    }};
77
78    // Pattern: spec_flag!("--long"; key = value, ...)
79    ($long:literal; $($key:ident = $value:expr),* $(,)?) => {{
80        let mut builder = $crate::SpecFlagBuilder::new()
81            .long(&$long[2..]);
82        $(builder = $crate::__spec_flag_attr!(builder, $key, $value);)*
83        builder.build()
84    }};
85
86    // Pattern: spec_flag!("--long" => "<arg>")
87    ($long:literal => $arg:literal) => {{
88        let arg: $crate::SpecArg = $arg.parse().expect("invalid arg format");
89        $crate::SpecFlagBuilder::new()
90            .long(&$long[2..])
91            .arg(arg)
92            .build()
93    }};
94
95    // Pattern: spec_flag!("--long" => "<arg>"; key = value, ...)
96    ($long:literal => $arg:literal; $($key:ident = $value:expr),* $(,)?) => {{
97        let arg: $crate::SpecArg = $arg.parse().expect("invalid arg format");
98        let mut builder = $crate::SpecFlagBuilder::new()
99            .long(&$long[2..])
100            .arg(arg);
101        $(builder = $crate::__spec_flag_attr!(builder, $key, $value);)*
102        builder.build()
103    }};
104
105    // Pattern: spec_flag!("-s", "--long" => "<arg>")
106    ($short:literal, $long:literal => $arg:literal) => {{
107        let arg: $crate::SpecArg = $arg.parse().expect("invalid arg format");
108        $crate::SpecFlagBuilder::new()
109            .short($short.chars().nth(1).expect("short flag must be -X format"))
110            .long(&$long[2..])
111            .arg(arg)
112            .build()
113    }};
114
115    // Pattern: spec_flag!("-s", "--long" => "<arg>"; key = value, ...)
116    ($short:literal, $long:literal => $arg:literal; $($key:ident = $value:expr),* $(,)?) => {{
117        let arg: $crate::SpecArg = $arg.parse().expect("invalid arg format");
118        let mut builder = $crate::SpecFlagBuilder::new()
119            .short($short.chars().nth(1).expect("short flag must be -X format"))
120            .long(&$long[2..])
121            .arg(arg);
122        $(builder = $crate::__spec_flag_attr!(builder, $key, $value);)*
123        builder.build()
124    }};
125}
126
127/// Internal macro for setting flag attributes
128#[macro_export]
129#[doc(hidden)]
130macro_rules! __spec_flag_attr {
131    ($builder:expr, help, $value:expr) => {
132        $builder.help($value)
133    };
134    ($builder:expr, help_long, $value:expr) => {
135        $builder.help_long($value)
136    };
137    ($builder:expr, var, $value:expr) => {
138        $builder.var($value)
139    };
140    ($builder:expr, var_min, $value:expr) => {
141        $builder.var_min($value)
142    };
143    ($builder:expr, var_max, $value:expr) => {
144        $builder.var_max($value)
145    };
146    ($builder:expr, required, $value:expr) => {
147        $builder.required($value)
148    };
149    ($builder:expr, global, $value:expr) => {
150        $builder.global($value)
151    };
152    ($builder:expr, hide, $value:expr) => {
153        $builder.hide($value)
154    };
155    ($builder:expr, count, $value:expr) => {
156        $builder.count($value)
157    };
158    ($builder:expr, env, $value:expr) => {
159        $builder.env($value)
160    };
161}
162
163/// Create a SpecArg with minimal boilerplate.
164///
165/// # Syntax
166///
167/// ```text
168/// spec_arg!("name")
169/// spec_arg!("name"; required = true)
170/// spec_arg!("name"; var = true, var_min = 1, var_max = 10)
171/// ```
172///
173/// # Examples
174///
175/// ```
176/// use usage::spec_arg;
177///
178/// // Simple argument
179/// let a = spec_arg!("file");
180///
181/// // Required argument
182/// let a = spec_arg!("file"; required = true);
183///
184/// // Variadic argument with constraints
185/// let a = spec_arg!("files"; var = true, var_min = 1, help = "Input files");
186/// ```
187#[macro_export]
188macro_rules! spec_arg {
189    // Pattern: spec_arg!("name")
190    ($name:literal) => {{
191        $crate::SpecArgBuilder::new()
192            .name($name)
193            .build()
194    }};
195
196    // Pattern: spec_arg!("name"; key = value, ...)
197    ($name:literal; $($key:ident = $value:expr),* $(,)?) => {{
198        let mut builder = $crate::SpecArgBuilder::new()
199            .name($name);
200        $(builder = $crate::__spec_arg_attr!(builder, $key, $value);)*
201        builder.build()
202    }};
203}
204
205/// Internal macro for setting arg attributes
206#[macro_export]
207#[doc(hidden)]
208macro_rules! __spec_arg_attr {
209    ($builder:expr, help, $value:expr) => {
210        $builder.help($value)
211    };
212    ($builder:expr, help_long, $value:expr) => {
213        $builder.help_long($value)
214    };
215    ($builder:expr, var, $value:expr) => {
216        $builder.var($value)
217    };
218    ($builder:expr, var_min, $value:expr) => {
219        $builder.var_min($value)
220    };
221    ($builder:expr, var_max, $value:expr) => {
222        $builder.var_max($value)
223    };
224    ($builder:expr, required, $value:expr) => {
225        $builder.required($value)
226    };
227    ($builder:expr, hide, $value:expr) => {
228        $builder.hide($value)
229    };
230    ($builder:expr, env, $value:expr) => {
231        $builder.env($value)
232    };
233}
234
235/// Create a SpecCommand with minimal boilerplate.
236///
237/// # Syntax
238///
239/// ```text
240/// spec_cmd!("name")
241/// spec_cmd!("name"; help = "description")
242/// spec_cmd!("name"; help = "description", aliases = ["a", "b"])
243/// ```
244///
245/// # Examples
246///
247/// ```
248/// use usage::spec_cmd;
249///
250/// // Simple command
251/// let c = spec_cmd!("install");
252///
253/// // Command with help
254/// let c = spec_cmd!("install"; help = "Install packages");
255///
256/// // Command with aliases
257/// let c = spec_cmd!("install"; help = "Install packages", aliases = ["i", "add"]);
258/// ```
259#[macro_export]
260macro_rules! spec_cmd {
261    // Pattern: spec_cmd!("name")
262    ($name:literal) => {{
263        $crate::SpecCommandBuilder::new()
264            .name($name)
265            .build()
266    }};
267
268    // Pattern: spec_cmd!("name"; key = value, ...)
269    ($name:literal; $($key:ident = $value:expr),* $(,)?) => {{
270        let mut builder = $crate::SpecCommandBuilder::new()
271            .name($name);
272        $(builder = $crate::__spec_cmd_attr!(builder, $key, $value);)*
273        builder.build()
274    }};
275}
276
277/// Internal macro for setting command attributes
278#[macro_export]
279#[doc(hidden)]
280macro_rules! __spec_cmd_attr {
281    ($builder:expr, help, $value:expr) => {
282        $builder.help($value)
283    };
284    ($builder:expr, help_long, $value:expr) => {
285        $builder.help_long($value)
286    };
287    ($builder:expr, hide, $value:expr) => {
288        $builder.hide($value)
289    };
290    ($builder:expr, subcommand_required, $value:expr) => {
291        $builder.subcommand_required($value)
292    };
293    ($builder:expr, aliases, $value:expr) => {
294        $builder.aliases($value)
295    };
296    ($builder:expr, hidden_aliases, $value:expr) => {
297        $builder.hidden_aliases($value)
298    };
299}
300
301/// Create a Vec<String> from string literals.
302///
303/// # Examples
304///
305/// ```
306/// use usage::defaults;
307///
308/// let values = defaults!["value1", "value2", "value3"];
309/// assert_eq!(values, vec!["value1".to_string(), "value2".to_string(), "value3".to_string()]);
310/// ```
311#[macro_export]
312macro_rules! defaults {
313    [$($value:expr),* $(,)?] => {
314        vec![$($value.to_string()),*]
315    };
316}
317
318/// Create a Vec<char> for short flags.
319///
320/// # Examples
321///
322/// ```
323/// use usage::shorts;
324///
325/// let chars = shorts!['v', 'V', 'd'];
326/// assert_eq!(chars, vec!['v', 'V', 'd']);
327/// ```
328#[macro_export]
329macro_rules! shorts {
330    [$($char:literal),* $(,)?] => {
331        vec![$($char),*]
332    };
333}
334
335/// Create a Vec<String> for long flags.
336///
337/// # Examples
338///
339/// ```
340/// use usage::longs;
341///
342/// let names = longs!["verbose", "debug"];
343/// assert_eq!(names, vec!["verbose".to_string(), "debug".to_string()]);
344/// ```
345#[macro_export]
346macro_rules! longs {
347    [$($name:literal),* $(,)?] => {
348        vec![$($name.to_string()),*]
349    };
350}
351
352/// Create a Vec<String> for command aliases.
353///
354/// # Examples
355///
356/// ```
357/// use usage::aliases;
358///
359/// let als = aliases!["i", "inst", "add"];
360/// assert_eq!(als, vec!["i".to_string(), "inst".to_string(), "add".to_string()]);
361/// ```
362#[macro_export]
363macro_rules! aliases {
364    [$($name:literal),* $(,)?] => {
365        vec![$($name.to_string()),*]
366    };
367}
368
369#[cfg(test)]
370mod tests {
371
372    #[test]
373    fn test_spec_flag_simple() {
374        let f = spec_flag!("-v", "--verbose");
375        assert_eq!(f.short, vec!['v']);
376        assert_eq!(f.long, vec!["verbose".to_string()]);
377    }
378
379    #[test]
380    fn test_spec_flag_with_help() {
381        let f = spec_flag!("-f", "--force"; help = "Force operation");
382        assert_eq!(f.short, vec!['f']);
383        assert_eq!(f.long, vec!["force".to_string()]);
384        assert_eq!(f.help, Some("Force operation".to_string()));
385    }
386
387    #[test]
388    fn test_spec_flag_long_only() {
389        let f = spec_flag!("--all");
390        assert!(f.short.is_empty());
391        assert_eq!(f.long, vec!["all".to_string()]);
392    }
393
394    #[test]
395    fn test_spec_flag_variadic() {
396        let f = spec_flag!("--file"; var = true, var_min = 1, var_max = 10);
397        assert!(f.var);
398        assert_eq!(f.var_min, Some(1));
399        assert_eq!(f.var_max, Some(10));
400    }
401
402    #[test]
403    fn test_spec_flag_with_arg() {
404        let f = spec_flag!("--output" => "<file>"; help = "Output file");
405        assert!(f.arg.is_some());
406        assert_eq!(f.arg.as_ref().unwrap().name, "file");
407        assert_eq!(f.help, Some("Output file".to_string()));
408    }
409
410    #[test]
411    fn test_spec_arg_simple() {
412        let a = spec_arg!("file");
413        assert_eq!(a.name, "file");
414    }
415
416    #[test]
417    fn test_spec_arg_with_options() {
418        let a = spec_arg!("files"; var = true, var_min = 1, help = "Input files");
419        assert_eq!(a.name, "files");
420        assert!(a.var);
421        assert_eq!(a.var_min, Some(1));
422        assert_eq!(a.help, Some("Input files".to_string()));
423    }
424
425    #[test]
426    fn test_spec_cmd_simple() {
427        let c = spec_cmd!("install");
428        assert_eq!(c.name, "install");
429    }
430
431    #[test]
432    fn test_spec_cmd_with_options() {
433        let c = spec_cmd!("install"; help = "Install packages", aliases = ["i", "add"]);
434        assert_eq!(c.name, "install");
435        assert_eq!(c.help, Some("Install packages".to_string()));
436        assert_eq!(c.aliases, vec!["i".to_string(), "add".to_string()]);
437    }
438
439    #[test]
440    fn test_defaults_macro() {
441        let d = defaults!["a", "b", "c"];
442        assert_eq!(d, vec!["a".to_string(), "b".to_string(), "c".to_string()]);
443    }
444
445    #[test]
446    fn test_shorts_macro() {
447        let s = shorts!['a', 'b', 'c'];
448        assert_eq!(s, vec!['a', 'b', 'c']);
449    }
450
451    #[test]
452    fn test_longs_macro() {
453        let l = longs!["verbose", "debug"];
454        assert_eq!(l, vec!["verbose".to_string(), "debug".to_string()]);
455    }
456
457    #[test]
458    fn test_aliases_macro() {
459        let a = aliases!["i", "inst"];
460        assert_eq!(a, vec!["i".to_string(), "inst".to_string()]);
461    }
462}