cargo_generate_license_impl_license/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{parse::Parse, Ident, Token, Visibility};
4
5struct UnitStruct {
6    vis: Visibility,
7    name: Ident,
8}
9
10impl Parse for UnitStruct {
11    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
12        let vis: Visibility = input.parse()?;
13        let _: Token!(struct) = input.parse()?;
14        let name = input.parse()?;
15        let _: Token!(;) = input.parse()?;
16        Ok(Self { vis, name })
17    }
18}
19struct Attrs {
20    path: syn::LitStr,
21    author: bool,
22    year: bool,
23    project: bool,
24}
25
26impl Parse for Attrs {
27    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
28        let path = input.parse()?;
29        let mut me = Self {
30            path,
31            author: false,
32            project: false,
33            year: false,
34        };
35        for _ in 0..=2 {
36            let Ok(_): syn::Result<Token!(,)> = input.parse() else { return Ok(me);};
37            // up to 3x, check for format args
38            let Ok(ident): syn::Result<Ident> = input.parse()  else { return Ok(me);};
39            match ident.to_string().as_ref() {
40                "AUTHOR" => me.author = true,
41                "YEAR" => me.year = true,
42                "PROJECT" => me.project = true,
43                _ => {
44                    return Err(syn::Error::new_spanned(
45                        ident,
46                        "expected fields AUTHOR, YEAR, and/or PROJECT",
47                    ))
48                }
49            };
50        }
51        Ok(me)
52    }
53}
54
55fn derive_license_impl(attr: Attrs, struct_def: UnitStruct) -> TokenStream {
56    let type_name = &struct_def.name;
57    let path = attr.path;
58    let vis = &struct_def.vis;
59    let mut format_arguments = vec![];
60    if attr.author {
61        format_arguments.push(quote!(AUTHOR = name));
62    }
63    if attr.year {
64        format_arguments.push(quote!(YEAR = year));
65    }
66    if attr.project {
67        format_arguments.push(quote!(PROJECT = project));
68    }
69    quote!(
70        #vis struct #type_name;
71
72        impl License for #type_name {
73            fn notice(&self, year: u32, name: &str, project: &str) -> String {
74                format!(include_str!(#path) #(, #format_arguments)*)
75            }
76        }
77    )
78    .into()
79}
80
81#[proc_macro_attribute]
82pub fn impl_license(attrs: TokenStream, item: TokenStream) -> TokenStream {
83    derive_license_impl(
84        syn::parse(attrs).expect("parse attrs"),
85        syn::parse(item).expect("parse struct def"),
86    )
87}
88
89#[cfg(test)]
90mod tests {
91    use quote::ToTokens;
92
93    use super::*;
94
95    #[test]
96    fn test_attrs_parse() {
97        let attrs: Attrs = syn::parse_str(r#""path value", AUTHOR"#).expect("parse");
98        assert_eq!(
99            attrs.path.into_token_stream().to_string(),
100            r#""path value""#
101        );
102        assert!(attrs.author);
103        assert!(!attrs.project);
104        assert!(!attrs.year);
105    }
106}