clap_duration/
lib.rs

1#![deny(
2    clippy::all,
3    clippy::pedantic,
4    clippy::cargo,
5    clippy::nursery,
6    clippy::unwrap_in_result,
7    clippy::unwrap_used,
8    clippy::expect_used
9)]
10use duration_human::DurationHumanValidator;
11use proc_macro::TokenStream;
12use quote::{format_ident, quote};
13use syn::{braced, parse::Parse, parse_macro_input, Ident, Token};
14
15/// macro
16///
17/// ## Example
18/// ```rust
19/// use clap::Parser;
20/// use clap_duration::assign_duration_range_validator;
21/// use duration_human::{DurationHuman, DurationHumanValidator};
22///
23/// assign_duration_range_validator!( LIFETIME_RANGE = {default: 333s, min: 1min, max: 60day});
24///
25/// #[derive(Parser)]
26/// struct ServerOptions {
27///    
28///     #[arg(
29///         long,
30///         help = format!("What lifetime will it have, between {}", LIFETIME_RANGE),
31///         default_value = LIFETIME_RANGE.default,
32///         value_parser = {|lifetime: &str|LIFETIME_RANGE.parse_and_validate(lifetime)}
33///     )]
34///     lifetime: DurationHuman,
35/// }
36///
37///  let opts = ServerOptions::parse();
38///  assert_eq!(format!("{:#}",opts.lifetime), format!("5min 33s"));
39///  assert_eq!(opts.lifetime.to_string(), "333s".to_string());
40// ```
41#[proc_macro]
42pub fn assign_duration_range_validator(input: TokenStream) -> TokenStream {
43    let assignment = parse_macro_input!(input as DurationRangeAssignment);
44
45    let range = &assignment.range;
46
47    let ident = format_ident!("{}", assignment.name.to_ascii_uppercase());
48    let (minimal_ms, default_ms, maximal_ms): (u64, u64, u64) = range.into();
49
50    let s = format!(
51        "Validator to check that a human-friendly duration is between {min} and {max}, or defaults to {default:#}.",
52        min = &assignment.range.min,
53        default = &assignment.range.default,
54        max = &assignment.range.max
55    );
56
57    TokenStream::from(quote! {
58        #[doc=#s]
59        const #ident: DurationHumanValidator = DurationHumanValidator::new(#minimal_ms, #default_ms, #maximal_ms);
60    })
61}
62
63#[proc_macro]
64pub fn duration_range_validator(input: TokenStream) -> TokenStream {
65    let validator = parse_macro_input!(input as DurationHumanValidator);
66
67    let (minimal_ms, default_ms, maximal_ms): (u64, u64, u64) = (&validator).into();
68
69    TokenStream::from(quote! {
70        DurationHumanValidator::new(#minimal_ms, #default_ms, #maximal_ms)
71    })
72}
73
74/// macro for use as `value_parse` parameter in a clap arg attribute
75///
76/// ## Example
77/// ```rust
78/// # use clap::Parser;
79/// # use clap_duration::duration_range_value_parse;
80/// # use duration_human::{DurationHuman, DurationHumanValidator};
81/// #
82/// # #[derive(Parser)]
83/// struct SampleOptions {
84///     #[arg(
85///         long, default_value="666000ms",
86///         value_parser = duration_range_value_parse!(min: 10min, max: 1h)
87///     )]
88///     interval: DurationHuman,
89/// }
90///
91/// let opts = SampleOptions::parse();
92/// assert_eq!(format!("{:#}",opts.interval), format!("11min 6s"));
93/// assert_eq!(opts.interval.to_string(), "666s".to_string())
94///
95// ```
96#[proc_macro]
97pub fn duration_range_value_parse(input: TokenStream) -> TokenStream {
98    let validator = parse_macro_input!(input as DurationHumanValidator);
99
100    let (minimal_ms, default_ms, maximal_ms): (u64, u64, u64) = (&validator).into();
101
102    TokenStream::from(quote! {
103        {|interval: &str|DurationHumanValidator::new(#minimal_ms, #default_ms, #maximal_ms).parse_and_validate(interval)}
104    })
105}
106
107struct DurationRangeAssignment {
108    name: String,
109    range: DurationHumanValidator,
110}
111
112impl Parse for DurationRangeAssignment {
113    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
114        let name: Ident = input.parse()?;
115        let _punc: Token![=] = input.parse()?;
116        let inner;
117        braced!(inner in input);
118        let range: DurationHumanValidator = inner.parse()?;
119        Ok(Self {
120            name: name.to_string(),
121            range,
122        })
123    }
124}