derive_path_static_str.rs

 1use proc_macro::TokenStream;
 2use quote::quote;
 3use syn::{Attribute, Data, DeriveInput, Lit, Meta, NestedMeta, parse_macro_input};
 4
 5pub fn derive_path_static_str(input: TokenStream) -> TokenStream {
 6    let input = parse_macro_input!(input as DeriveInput);
 7    let name = &input.ident;
 8
 9    let prefix = get_attr_value(&input.attrs, "prefix").unwrap_or_else(|| "".to_string());
10    let suffix = get_attr_value(&input.attrs, "suffix").unwrap_or_else(|| "".to_string());
11    let delimiter = get_attr_value(&input.attrs, "delimiter").unwrap_or_else(|| "/".to_string());
12
13    let path_str_impl = impl_path_str(name, &input.data, &prefix, &suffix, &delimiter);
14
15    let expanded = quote! {
16        impl #name {
17            pub fn path_str(&self) -> &'static str {
18                #path_str_impl
19            }
20        }
21    };
22
23    TokenStream::from(expanded)
24}
25
26fn impl_path_str(
27    name: &syn::Ident,
28    data: &Data,
29    prefix: &str,
30    suffix: &str,
31    delimiter: &str,
32) -> proc_macro2::TokenStream {
33    match *data {
34        Data::Enum(ref data) => {
35            let match_arms = data.variants.iter().map(|variant| {
36                let ident = &variant.ident;
37                let path = format!("{}{}{}{}{}", prefix, delimiter, ident, delimiter, suffix);
38                quote! {
39                    #name::#ident => #path,
40                }
41            });
42
43            quote! {
44                match self {
45                    #(#match_arms)*
46                }
47            }
48        }
49        _ => panic!("DerivePathStr only supports enums"),
50    }
51}
52
53fn get_attr_value(attrs: &[Attribute], key: &str) -> Option<String> {
54    attrs
55        .iter()
56        .filter(|attr| attr.path.is_ident("derive_path_static_str"))
57        .find_map(|attr| {
58            if let Ok(Meta::List(meta_list)) = attr.parse_meta() {
59                meta_list.nested.iter().find_map(|nested_meta| {
60                    if let NestedMeta::Meta(Meta::NameValue(name_value)) = nested_meta {
61                        if name_value.path.is_ident(key) {
62                            if let Lit::Str(lit_str) = &name_value.lit {
63                                return Some(lit_str.value());
64                            }
65                        }
66                    }
67                    None
68                })
69            } else {
70                None
71            }
72        })
73}