cliargs_derive/
lib.rs

1// Copyright (C) 2024 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5mod errors;
6mod option_field;
7mod scalar_field;
8mod util;
9mod vector_field;
10
11use errors::OptStoreErr;
12use util::identify_field_type;
13
14use proc_macro::TokenStream;
15use std::collections::HashMap;
16
17/// Is attached to a struct which holds command line option values, and automatically implements
18/// its method to generate `OptCfg`s from its fields, and other methods.
19///
20/// This macro automatically implements the method to generates a vector of `OptCfg` from the field
21/// definitions and `opt` field attributes, and this also implements the method that instantiates
22/// the struct using the default values specified in `opt` field attributes, and implements the
23/// method to updates the field values with the values from the passed `HashMap1.
24///
25/// The `opt` field attribute can have the following pairs of name and value: one is `cfg` to
26/// specify `names` and `defaults` of `OptCfg` struct, another is `desc` to specify `desc` of
27/// `OptCfg` struct, and yet another is `arg` to specify `arg_in_help` of `OptCfg` struct.
28///
29/// The format of `cfg` is like `cfg="f,foo=123"`.
30/// The left side of the equal sign is the option name(s), and the right side is the default
31/// value(s).
32/// If there is no equal sign, it is determined that only the option name is specified.
33/// If you want to specify multiple option names, separate them with commas.
34/// If you want to specify multiple default values, separate them with commas and round them with
35/// square brackets, like `[1,2,3]`.
36/// If you want to use your favorite carachter as a separator, you can use it by putting it on the
37/// left side of the open square bracket, like `/[1/2/3]`.
38///
39/// The following code is a sample of a option store struct.
40///
41/// ```rust
42/// extern crate cliargs_derive;
43/// use cliargs_derive::OptStore;
44///
45/// #[derive(OptStore)]
46/// struct MyOptions {
47///     #[opt(cfg="f,foo-bar", desc="The description of foo-bar.")]
48///     foo_bar: bool,
49///
50///     #[opt(cfg="b", desc="The description of baz.", arg="text")]
51///     baz: String,
52///
53///     #[opt(cfg="q=[1,2,3]", desc="The description of qux.", arg="num...")]
54///     qux: Vec<u8>,
55/// }
56/// ```
57#[proc_macro_derive(OptStore, attributes(opt))]
58pub fn opt_store_derive(input: TokenStream) -> TokenStream {
59    let input = &syn::parse_macro_input!(input as syn::DeriveInput);
60
61    match generate_opt_store_impl(input) {
62        Ok(generated) => generated,
63        Err(err) => err.to_compile_error().into(),
64    }
65}
66
67fn generate_opt_store_impl(input: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
68    let struct_name = &input.ident;
69    let (impl_generics, _, where_clause) = &input.generics.split_for_impl();
70
71    let struct_data = match &input.data {
72        syn::Data::Struct(data) => data,
73        _ => return Err(OptStoreErr::MustPutOnStruct.at(input.ident.span())),
74    };
75
76    let struct_span = input.ident.span();
77
78    let mut cfg_vec = Vec::<proc_macro2::TokenStream>::new();
79    let mut init_vec = Vec::<proc_macro2::TokenStream>::new();
80    let mut set_vec = Vec::<proc_macro2::TokenStream>::new();
81    for field in &struct_data.fields {
82        collect_impl_for_field(
83            field,
84            &mut cfg_vec,
85            &mut init_vec,
86            &mut set_vec,
87            struct_span,
88        )?;
89    }
90
91    let expanded = quote::quote! {
92        #[automatically_derived]
93        impl #impl_generics #struct_name #where_clause {
94            pub fn with_defaults() -> #struct_name {
95                #struct_name {
96                    #(#init_vec),*
97                }
98            }
99        }
100
101        #[automatically_derived]
102        impl #impl_generics cliargs::OptStore for  #struct_name #where_clause {
103            fn make_opt_cfgs(&self) -> Vec<cliargs::OptCfg> {
104                vec![
105                    #(#cfg_vec),*
106                ]
107            }
108
109            fn set_field_values(&mut self, m: &std::collections::HashMap<&str, Vec<&str>>) -> Result<(), cliargs::errors::InvalidOption> {
110                #(#set_vec)*
111                Ok(())
112            }
113        }
114    };
115
116    //println!("{}", expanded);
117    Ok(expanded.into())
118}
119
120fn collect_impl_for_field(
121    field: &syn::Field,
122    cfg_vec: &mut Vec<proc_macro2::TokenStream>,
123    init_vec: &mut Vec<proc_macro2::TokenStream>,
124    set_vec: &mut Vec<proc_macro2::TokenStream>,
125    struct_span: proc_macro2::Span,
126) -> Result<(), syn::Error> {
127    let field_name = match field.ident.as_ref() {
128        Some(ident) => ident,
129        None => return Err(OptStoreErr::MustNotPutOnTuple.at(struct_span)),
130    };
131    let field_span = field_name.span();
132
133    let mut attr_map = HashMap::<String, String>::new();
134    let mut attr_span: Option<proc_macro2::Span> = None;
135    for attr in &field.attrs {
136        if attr.path().is_ident("opt") {
137            let span = attr.path().get_ident().unwrap().span();
138            attr_span = Some(span);
139
140            collect_impl_for_field_attr(attr, &mut attr_map, span)?;
141        }
142    }
143
144    if let Some((ty_ident, in_vec, in_opt)) = identify_field_type(&field.ty) {
145        if in_opt {
146            return option_field::collect_impl(
147                field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
148            );
149        } else if in_vec {
150            return vector_field::collect_impl(
151                field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
152            );
153        } else {
154            return scalar_field::collect_impl(
155                field_name, ty_ident, cfg_vec, init_vec, set_vec, &attr_map, attr_span, field_span,
156            );
157        }
158    }
159
160    Err(OptStoreErr::BadFieldType.at(field_span))
161}
162
163fn collect_impl_for_field_attr(
164    attr: &syn::Attribute,
165    attr_map: &mut HashMap<String, String>,
166    attr_span: proc_macro2::Span,
167) -> Result<(), syn::Error> {
168    let nested = attr.parse_args_with(
169        syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
170    )?;
171    for meta in nested {
172        match meta {
173            syn::Meta::NameValue(meta) => {
174                if meta.path.is_ident("cfg") {
175                    match meta.value {
176                        syn::Expr::Lit(lit) => match lit.lit {
177                            syn::Lit::Str(s) => {
178                                let value = s.value();
179                                match &value.split_once("=") {
180                                    Some((lhs, rhs)) => {
181                                        attr_map.insert("names".to_string(), lhs.to_string());
182                                        attr_map.insert("defaults".to_string(), rhs.to_string());
183                                    }
184                                    None => {
185                                        attr_map.insert("names".to_string(), value);
186                                    }
187                                }
188                            }
189                            _ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
190                        },
191                        _ => return Err(OptStoreErr::BadAttrMetaValueCfg.at(attr_span)),
192                    }
193                } else if meta.path.is_ident("desc") {
194                    match meta.value {
195                        syn::Expr::Lit(lit) => match lit.lit {
196                            syn::Lit::Str(s) => {
197                                attr_map.insert("desc".to_string(), s.value());
198                            }
199                            _ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
200                        },
201                        _ => return Err(OptStoreErr::BadAttrMetaValueDesc.at(attr_span)),
202                    }
203                } else if meta.path.is_ident("arg") {
204                    match meta.value {
205                        syn::Expr::Lit(lit) => match lit.lit {
206                            syn::Lit::Str(s) => {
207                                attr_map.insert("arg".to_string(), s.value());
208                            }
209                            _ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
210                        },
211                        _ => return Err(OptStoreErr::BadAttrMetaValueArg.at(attr_span)),
212                    }
213                } else {
214                    return Err(OptStoreErr::BadAttrMetaName.at(attr_span));
215                }
216            }
217            _ => return Err(OptStoreErr::BadAttrMetaName.at(attr_span)),
218        }
219    }
220
221    Ok(())
222}