leftwm_macros/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
extern crate proc_macro;

use proc_macro::TokenStream;
use proc_macro2::Span;

use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Error};

macro_rules! derive_error {
    ($string: tt) => {
        Error::new(Span::call_site(), $string)
            .to_compile_error()
            .into()
    };
}

fn parse_enum_doc_comment(attrs: &[syn::Attribute]) -> String {
    let mut ret = String::new();
    for attr in attrs {
        let meta = &attr.meta;
        if let syn::Meta::NameValue(meta) = meta {
            if let syn::Expr::Lit(syn::ExprLit {
                lit: syn::Lit::Str(l),
                ..
            }) = &meta.value
            {
                ret.push_str(&format!("\n    {}", l.value().trim()));
            }
        }
    }

    ret
}

#[proc_macro_derive(EnumDocs)]
/// Returns a const str for the Enum to which it applies.
///
/// # Example:
/// ```
/// #[derive(leftwm_macros::EnumDocs)]
/// enum LeftWm {
///   One,
///   /// Doc comment
///   Two
/// }
///
/// assert_eq!(LeftWm::documentation(), "\nOne\nTwo\n    Doc comment");
/// ```
///
/// The purpose of this macro is for serializing options of the `BaseCommand` for `leftwm-command`
pub fn derive_enum_docs(input: TokenStream) -> TokenStream {
    let input: DeriveInput = parse_macro_input!(input);

    match &input.data {
        // Only if data is an enum, we do parsing
        Data::Enum(data_enum) => {
            // data_enum is of type syn::DataEnum
            // https://doc.servo.org/syn/struct.DataEnum.html

            let mut names = String::new();

            // For each variant, push its name onto `names`
            for variant in &data_enum.variants {
                let doc = parse_enum_doc_comment(&variant.attrs);

                names.push_str(&format!("\n{}{}", variant.ident, doc));
            }

            // The enum's name
            let name = &input.ident;
            let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
            quote! {
                impl #impl_generics #name #ty_generics #where_clause {
                    pub const fn documentation() -> &'static str {
                        #names
                    }
                }
            }
            .into()
        }
        _ => derive_error!("EnumDocs can only be implemented for enums"),
    }
}