Skip to main content

getopts_macro/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!("../README.md")]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5pub use getopts;
6use getopts::{HasArg, Occur};
7
8/// [`getopts_options`] `*?` and `+?` support
9pub trait GetOptsExt {
10    fn optflagreqopt(
11        &mut self,
12        short_name: &str,
13        long_name: &str,
14        desc: &str,
15        hint: &str,
16    ) -> &mut Self;
17    fn optflagmultiopt(
18        &mut self,
19        short_name: &str,
20        long_name: &str,
21        desc: &str,
22        hint: &str,
23    ) -> &mut Self;
24}
25impl GetOptsExt for getopts::Options {
26    fn optflagreqopt(
27        &mut self,
28        short_name: &str,
29        long_name: &str,
30        desc: &str,
31        hint: &str,
32    ) -> &mut Self {
33        self.opt(short_name, long_name, desc, hint, HasArg::Maybe, Occur::Req)
34    }
35
36    fn optflagmultiopt(
37        &mut self,
38        short_name: &str,
39        long_name: &str,
40        desc: &str,
41        hint: &str,
42    ) -> &mut Self {
43        self.opt(short_name, long_name, desc, hint, HasArg::Maybe, Occur::Multi)
44    }
45}
46
47/// Quick create [`getopts::Options`]
48///
49/// # Syntax
50///
51/// `[...]` is optional syntax
52///
53/// *Occur* (default optional):
54///
55/// - `*` is require multi
56/// - `+` is require one
57///
58/// *HasArg* (default no arg):
59///
60/// - `=` is has arg
61/// - `?=` is has optional arg
62///
63/// *Option*:
64///
65/// - `-` *ident*
66/// - `--` *ident* \*(`-` *ident*)
67/// - `-` *ident* \[`,`] `--` *ident* \*(`-` *ident*)
68///
69/// *Desc*: *any-token*
70///
71/// *Syntax*: *Option* \[*Occur*] \[*HasArg* *Desc*] *Desc* `;` \[*Syntax*]
72///
73/// # Examples
74///
75/// ```
76/// use getopts_macro::{
77///     getopts_options,
78///     getopts::ParsingStyle,
79/// };
80///
81/// let _options = getopts_options! {
82///     -f --file=FILE              "input from file";
83///     -p --parse-config*=CONFIG   "parse config";
84///     -h --help*                  "help messages";
85///        --help-long*             "long help messages";
86///     .parsing_style(ParsingStyle::StopAtFirstFree)
87/// };
88/// ```
89///
90/// **NOTE**: Expressions within parentheses, e.g `-h   (text.help);`
91#[macro_export]
92macro_rules! getopts_options {
93    (-$($t:tt)*) => {{
94        let mut options = $crate::getopts::Options::new();
95        $crate::getopts_options!(@with(options) -$($t)*);
96        options
97    }};
98    (@long $first:ident $($rest:ident)*) => {
99        ::core::concat!(
100            ::core::stringify!($first),
101            $(
102                "-",
103                ::core::stringify!($rest),
104            )*
105        )
106    };
107
108    (@with($o:ident)) => {};
109    (@with($o:ident) . $($t:tt)+) => {
110        $o.$($t)+;
111    };
112    (@with($o:ident) $t1:tt $t2:tt $desc:tt; $($rest:tt)*) => {
113        $crate::getopts_options!(@impl($o, $desc) $t1 $t2);
114        $crate::getopts_options!(@with($o) $($rest)*);
115    };
116    (@with($o:ident) $t1:tt $t2:tt $t3:tt $desc:tt; $($rest:tt)*) => {
117        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3);
118        $crate::getopts_options!(@with($o) $($rest)*);
119    };
120    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $desc:tt; $($rest:tt)*) => {
121        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4);
122        $crate::getopts_options!(@with($o) $($rest)*);
123    };
124    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $desc:tt; $($rest:tt)*) => {
125        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5);
126        $crate::getopts_options!(@with($o) $($rest)*);
127    };
128    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $desc:tt; $($rest:tt)*) => {
129        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6);
130        $crate::getopts_options!(@with($o) $($rest)*);
131    };
132    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $desc:tt; $($rest:tt)*) => {
133        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7);
134        $crate::getopts_options!(@with($o) $($rest)*);
135    };
136    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $desc:tt; $($rest:tt)*) => {
137        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8);
138        $crate::getopts_options!(@with($o) $($rest)*);
139    };
140    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $desc:tt; $($rest:tt)*) => {
141        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9);
142        $crate::getopts_options!(@with($o) $($rest)*);
143    };
144    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $desc:tt; $($rest:tt)*) => {
145        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10);
146        $crate::getopts_options!(@with($o) $($rest)*);
147    };
148    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $desc:tt; $($rest:tt)*) => {
149        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11);
150        $crate::getopts_options!(@with($o) $($rest)*);
151    };
152    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $desc:tt; $($rest:tt)*) => {
153        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12);
154        $crate::getopts_options!(@with($o) $($rest)*);
155    };
156    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $desc:tt; $($rest:tt)*) => {
157        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13);
158        $crate::getopts_options!(@with($o) $($rest)*);
159    };
160    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $desc:tt; $($rest:tt)*) => {
161        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14);
162        $crate::getopts_options!(@with($o) $($rest)*);
163    };
164    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $t15:tt $desc:tt; $($rest:tt)*) => {
165        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14 $t15);
166        $crate::getopts_options!(@with($o) $($rest)*);
167    };
168    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $t15:tt $t16:tt $desc:tt; $($rest:tt)*) => {
169        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14 $t15 $t16);
170        $crate::getopts_options!(@with($o) $($rest)*);
171    };
172    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $t15:tt $t16:tt $t17:tt $desc:tt; $($rest:tt)*) => {
173        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14 $t15 $t16 $t17);
174        $crate::getopts_options!(@with($o) $($rest)*);
175    };
176    (@with($o:ident) $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt $t6:tt $t7:tt $t8:tt $t9:tt $t10:tt $t11:tt $t12:tt $t13:tt $t14:tt $t15:tt $t16:tt $t17:tt $t18:tt $desc:tt; $($rest:tt)*) => {
177        $crate::getopts_options!(@impl($o, $desc) $t1 $t2 $t3 $t4 $t5 $t6 $t7 $t8 $t9 $t10 $t11 $t12 $t13 $t14 $t15 $t16 $t17 $t18);
178        $crate::getopts_options!(@with($o) $($rest)*);
179    };
180
181    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+) => {
182        $o.optflag(
183            ::core::stringify!($short),
184            $crate::getopts_options!(@long $($long)+),
185            $desc,
186        );
187    };
188    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ *) => {
189        $o.optflagmulti(
190            ::core::stringify!($short),
191            $crate::getopts_options!(@long $($long)+),
192            $desc,
193        );
194    };
195    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ = $hint:ident) => {
196        $o.optopt(
197            ::core::stringify!($short),
198            $crate::getopts_options!(@long $($long)+),
199            $desc,
200            ::core::stringify!($hint),
201        );
202    };
203    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ * = $hint:ident) => {
204        $o.optmulti(
205            ::core::stringify!($short),
206            $crate::getopts_options!(@long $($long)+),
207            $desc,
208            ::core::stringify!($hint),
209        );
210    };
211    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ *= $hint:ident) => {
212        $o.optmulti(
213            ::core::stringify!($short),
214            $crate::getopts_options!(@long $($long)+),
215            $desc,
216            ::core::stringify!($hint),
217        );
218    };
219    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ + = $hint:ident) => {
220        $o.reqopt(
221            ::core::stringify!($short),
222            $crate::getopts_options!(@long $($long)+),
223            $desc,
224            ::core::stringify!($hint),
225        );
226    };
227    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ += $hint:ident) => {
228        $o.reqopt(
229            ::core::stringify!($short),
230            $crate::getopts_options!(@long $($long)+),
231            $desc,
232            ::core::stringify!($hint),
233        );
234    };
235    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ ?= $hint:ident) => {
236        $o.optflagopt(
237            ::core::stringify!($short),
238            $crate::getopts_options!(@long $($long)+),
239            $desc,
240            ::core::stringify!($hint),
241        );
242    };
243    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ *?= $hint:ident) => {
244        $crate::GetOptsExt::optflagmultiopt(
245            &mut $o,
246            ::core::stringify!($short),
247            $crate::getopts_options!(@long $($long)+),
248            $desc,
249            ::core::stringify!($hint),
250        )
251    };
252    (@impl($o:ident, $desc:tt) -$short:ident $(,)? --$($long:ident)-+ +?= $hint:ident) => {
253        $crate::GetOptsExt::optflagreqopt(
254            &mut $o,
255            ::core::stringify!($short),
256            $crate::getopts_options!(@long $($long)+),
257            $desc,
258            ::core::stringify!($hint),
259        )
260    };
261
262
263    (@impl($o:ident, $desc:tt) -$short:ident) => {
264        $o.optflag(
265            ::core::stringify!($short),
266            "",
267            $desc,
268        );
269    };
270    (@impl($o:ident, $desc:tt) -$short:ident *) => {
271        $o.optflagmulti(
272            ::core::stringify!($short),
273            "",
274            $desc,
275        );
276    };
277    (@impl($o:ident, $desc:tt) -$short:ident = $hint:ident) => {
278        $o.optopt(
279            ::core::stringify!($short),
280            "",
281            $desc,
282            ::core::stringify!($hint),
283        );
284    };
285    (@impl($o:ident, $desc:tt) -$short:ident * = $hint:ident) => {
286        $o.optmulti(
287            ::core::stringify!($short),
288            "",
289            $desc,
290            ::core::stringify!($hint),
291        );
292    };
293    (@impl($o:ident, $desc:tt) -$short:ident *= $hint:ident) => {
294        $o.optmulti(
295            ::core::stringify!($short),
296            "",
297            $desc,
298            ::core::stringify!($hint),
299        );
300    };
301    (@impl($o:ident, $desc:tt) -$short:ident + = $hint:ident) => {
302        $o.reqopt(
303            ::core::stringify!($short),
304            "",
305            $desc,
306            ::core::stringify!($hint),
307        );
308    };
309    (@impl($o:ident, $desc:tt) -$short:ident += $hint:ident) => {
310        $o.reqopt(
311            ::core::stringify!($short),
312            "",
313            $desc,
314            ::core::stringify!($hint),
315        );
316    };
317    (@impl($o:ident, $desc:tt) -$short:ident ?= $hint:ident) => {
318        $o.optflagopt(
319            ::core::stringify!($short),
320            "",
321            $desc,
322            ::core::stringify!($hint),
323        );
324    };
325    (@impl($o:ident, $desc:tt) -$short:ident *?= $hint:ident) => {
326        $crate::GetOptsExt::optflagmultiopt(
327            &mut $o,
328            ::core::stringify!($short),
329            "",
330            $desc,
331            ::core::stringify!($hint),
332        )
333    };
334    (@impl($o:ident, $desc:tt) -$short:ident +?= $hint:ident) => {
335        $crate::GetOptsExt::optflagreqopt(
336            &mut $o,
337            ::core::stringify!($short),
338            "",
339            $desc,
340            ::core::stringify!($hint),
341        )
342    };
343
344
345    (@impl($o:ident, $desc:tt) --$($long:ident)-+) => {
346        $o.optflag(
347            "",
348            $crate::getopts_options!(@long $($long)+),
349            $desc,
350        );
351    };
352    (@impl($o:ident, $desc:tt) --$($long:ident)-+ *) => {
353        $o.optflagmulti(
354            "",
355            $crate::getopts_options!(@long $($long)+),
356            $desc,
357        );
358    };
359    (@impl($o:ident, $desc:tt) --$($long:ident)-+ = $hint:ident) => {
360        $o.optopt(
361            "",
362            $crate::getopts_options!(@long $($long)+),
363            $desc,
364            ::core::stringify!($hint),
365        );
366    };
367    (@impl($o:ident, $desc:tt) --$($long:ident)-+ * = $hint:ident) => {
368        $o.optmulti(
369            "",
370            $crate::getopts_options!(@long $($long)+),
371            $desc,
372            ::core::stringify!($hint),
373        );
374    };
375    (@impl($o:ident, $desc:tt) --$($long:ident)-+ *= $hint:ident) => {
376        $o.optmulti(
377            "",
378            $crate::getopts_options!(@long $($long)+),
379            $desc,
380            ::core::stringify!($hint),
381        );
382    };
383    (@impl($o:ident, $desc:tt) --$($long:ident)-+ + = $hint:ident) => {
384        $o.reqopt(
385            "",
386            $crate::getopts_options!(@long $($long)+),
387            $desc,
388            ::core::stringify!($hint),
389        );
390    };
391    (@impl($o:ident, $desc:tt) --$($long:ident)-+ += $hint:ident) => {
392        $o.reqopt(
393            "",
394            $crate::getopts_options!(@long $($long)+),
395            $desc,
396            ::core::stringify!($hint),
397        );
398    };
399    (@impl($o:ident, $desc:tt) --$($long:ident)-+ ?= $hint:ident) => {
400        $o.optflagopt(
401            "",
402            $crate::getopts_options!(@long $($long)+),
403            $desc,
404            ::core::stringify!($hint),
405        );
406    };
407    (@impl($o:ident, $desc:tt) --$($long:ident)-+ *?= $hint:ident) => {
408        $crate::GetOptsExt::optflagmultiopt(
409            &mut $o,
410            "",
411            $crate::getopts_options!(@long $($long)+),
412            $desc,
413            ::core::stringify!($hint),
414        )
415    };
416    (@impl($o:ident, $desc:tt) --$($long:ident)-+ +?= $hint:ident) => {
417        $crate::GetOptsExt::optflagreqopt(
418            &mut $o,
419            "",
420            $crate::getopts_options!(@long $($long)+),
421            $desc,
422            ::core::stringify!($hint),
423        )
424    };
425}
426
427
428/// Simple parsing and handling of `--help`
429///
430/// When the arguments is incorrect, with 2 exit code.
431///
432/// # Examples
433///
434/// ```
435/// # fn fake_main() {
436/// use getopts_macro::{getopts_options, simple_parse};
437///
438/// let options = getopts_options! {
439///     -h, --help          "show help message";
440/// };
441/// let matches = simple_parse(&options, "", 1, "<FILE..>");
442/// println!("files: {:?}", matches.free)
443/// # }
444/// ```
445#[cfg(feature = "std")]
446#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
447#[track_caller]
448pub fn simple_parse(
449    options: &getopts::Options,
450    prog_desc: &str,
451    min_free: usize,
452    free_desc: &str,
453) -> getopts::Matches {
454    let mut args = std::env::args_os();
455    let prog_name = args.next().unwrap_or_else(|| "<unknown>".into());
456    let prog_name = prog_name.display();
457    let matches = match options.parse(args) {
458        Ok(it) => it,
459        Err(e) => {
460            eprintln!("{prog_name}: {e}");
461            std::process::exit(2);
462        },
463    };
464
465    debug_assert!(matches.opt_defined("help"));
466    if matches.opt_present("help") {
467        let desc = if prog_desc.is_empty() { "" } else { &format!("\n{prog_desc}") };
468        let free_desc = if free_desc.is_empty() { "" } else { &format!(" {free_desc}") };
469
470        let brief = format!("Usage: {prog_name} [Options]{free_desc}{desc}");
471        let usage = options.usage(&brief);
472        println!("{}", usage.trim_end());
473        std::process::exit(0);
474    }
475
476    let found = matches.free.len();
477    if found < min_free {
478        eprintln!("{prog_name}: missing argument, requires {min_free} arguments, provide {found}");
479        std::process::exit(2);
480    }
481
482    matches
483}
484
485#[test]
486fn test() {
487    let usage = getopts_options! {
488        -c --center-rule            "...";
489        -i --ignore*=NAME           "...";
490        -I --ignore-partial*=NAME   "...";
491        -S --fake-source-from=SRC   "...";
492        -s --sep?=PATTERN           "...";
493        -k --keep*?=PATTERN         "...";
494        -h,--help*                  "...";
495        -m*                         "...";
496           --long                   "...";
497           --long-arg=A             "...";
498        -j+=J                       "...";
499        .parsing_style(getopts::ParsingStyle::StopAtFirstFree)
500    }.short_usage("prog");
501    assert_eq!(usage, "Usage: prog [-c] [-i NAME].. [-I NAME].. [-S SRC] [-s [PATTERN]] [-k [PATTERN]].. [-h].. [-m].. [--long] [--long-arg A] -j J");
502}