Skip to main content

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, subcommand_help_heading, $value:expr) => {
294        $builder.subcommand_help_heading($value)
295    };
296    ($builder:expr, subcommand_value_name, $value:expr) => {
297        $builder.subcommand_value_name($value)
298    };
299    ($builder:expr, external_subcommand, $value:expr) => {
300        $builder.external_subcommand($value)
301    };
302    ($builder:expr, aliases, $value:expr) => {
303        $builder.aliases($value)
304    };
305    ($builder:expr, hidden_aliases, $value:expr) => {
306        $builder.hidden_aliases($value)
307    };
308}
309
310/// Create a `Vec<String>` from string literals.
311///
312/// # Examples
313///
314/// ```
315/// use usage::defaults;
316///
317/// let values = defaults!["value1", "value2", "value3"];
318/// assert_eq!(values, vec!["value1".to_string(), "value2".to_string(), "value3".to_string()]);
319/// ```
320#[macro_export]
321macro_rules! defaults {
322    [$($value:expr),* $(,)?] => {
323        vec![$($value.to_string()),*]
324    };
325}
326
327/// Create a `Vec<char>` for short flags.
328///
329/// # Examples
330///
331/// ```
332/// use usage::shorts;
333///
334/// let chars = shorts!['v', 'V', 'd'];
335/// assert_eq!(chars, vec!['v', 'V', 'd']);
336/// ```
337#[macro_export]
338macro_rules! shorts {
339    [$($char:literal),* $(,)?] => {
340        vec![$($char),*]
341    };
342}
343
344/// Create a `Vec<String>` for long flags.
345///
346/// # Examples
347///
348/// ```
349/// use usage::longs;
350///
351/// let names = longs!["verbose", "debug"];
352/// assert_eq!(names, vec!["verbose".to_string(), "debug".to_string()]);
353/// ```
354#[macro_export]
355macro_rules! longs {
356    [$($name:literal),* $(,)?] => {
357        vec![$($name.to_string()),*]
358    };
359}
360
361/// Create a `Vec<String>` for command aliases.
362///
363/// # Examples
364///
365/// ```
366/// use usage::aliases;
367///
368/// let als = aliases!["i", "inst", "add"];
369/// assert_eq!(als, vec!["i".to_string(), "inst".to_string(), "add".to_string()]);
370/// ```
371#[macro_export]
372macro_rules! aliases {
373    [$($name:literal),* $(,)?] => {
374        vec![$($name.to_string()),*]
375    };
376}
377
378#[cfg(test)]
379mod tests {
380
381    #[test]
382    fn test_spec_flag_simple() {
383        let f = spec_flag!("-v", "--verbose");
384        assert_eq!(f.short, vec!['v']);
385        assert_eq!(f.long, vec!["verbose".to_string()]);
386    }
387
388    #[test]
389    fn test_spec_flag_with_help() {
390        let f = spec_flag!("-f", "--force"; help = "Force operation");
391        assert_eq!(f.short, vec!['f']);
392        assert_eq!(f.long, vec!["force".to_string()]);
393        assert_eq!(f.help, Some("Force operation".to_string()));
394    }
395
396    #[test]
397    fn test_spec_flag_long_only() {
398        let f = spec_flag!("--all");
399        assert!(f.short.is_empty());
400        assert_eq!(f.long, vec!["all".to_string()]);
401    }
402
403    #[test]
404    fn test_spec_flag_variadic() {
405        let f = spec_flag!("--file"; var = true, var_min = 1, var_max = 10);
406        assert!(f.var);
407        assert_eq!(f.var_min, Some(1));
408        assert_eq!(f.var_max, Some(10));
409    }
410
411    #[test]
412    fn test_spec_flag_with_arg() {
413        let f = spec_flag!("--output" => "<file>"; help = "Output file");
414        assert!(f.arg.is_some());
415        assert_eq!(f.arg.as_ref().unwrap().name, "file");
416        assert_eq!(f.help, Some("Output file".to_string()));
417    }
418
419    #[test]
420    fn test_spec_arg_simple() {
421        let a = spec_arg!("file");
422        assert_eq!(a.name, "file");
423    }
424
425    #[test]
426    fn test_spec_arg_with_options() {
427        let a = spec_arg!("files"; var = true, var_min = 1, help = "Input files");
428        assert_eq!(a.name, "files");
429        assert!(a.var);
430        assert_eq!(a.var_min, Some(1));
431        assert_eq!(a.help, Some("Input files".to_string()));
432    }
433
434    #[test]
435    fn test_spec_cmd_simple() {
436        let c = spec_cmd!("install");
437        assert_eq!(c.name, "install");
438    }
439
440    #[test]
441    fn test_spec_cmd_with_options() {
442        let c = spec_cmd!("install"; help = "Install packages", aliases = ["i", "add"]);
443        assert_eq!(c.name, "install");
444        assert_eq!(c.help, Some("Install packages".to_string()));
445        assert_eq!(c.aliases, vec!["i".to_string(), "add".to_string()]);
446    }
447
448    #[test]
449    fn test_defaults_macro() {
450        let d = defaults!["a", "b", "c"];
451        assert_eq!(d, vec!["a".to_string(), "b".to_string(), "c".to_string()]);
452    }
453
454    #[test]
455    fn test_shorts_macro() {
456        let s = shorts!['a', 'b', 'c'];
457        assert_eq!(s, vec!['a', 'b', 'c']);
458    }
459
460    #[test]
461    fn test_longs_macro() {
462        let l = longs!["verbose", "debug"];
463        assert_eq!(l, vec!["verbose".to_string(), "debug".to_string()]);
464    }
465
466    #[test]
467    fn test_aliases_macro() {
468        let a = aliases!["i", "inst"];
469        assert_eq!(a, vec!["i".to_string(), "inst".to_string()]);
470    }
471}