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
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
extern crate proc_macro;

use inflector::Inflector;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Error, Expr, ExprLit, Ident, Lit, Token, Visibility};
use syn::parse::{Parse, ParseStream, Result};
use syn::punctuated::Punctuated;

// TODO: change this to actually get types, but still be able to make the variant names
struct ActivitypubProperty {
    visibility: Visibility,
    name: Ident,
    functional: bool,
    possible_ty: Punctuated<Ident, Token![,]>,
}

impl Parse for ActivitypubProperty {
    fn parse(input: ParseStream) -> Result<Self> {
        let visibility = input.parse()?;
        let name = input.parse()?;
        input.parse::<Token![,]>()?;
        let functional_expr: Expr = input.parse()?;
        input.parse::<Token![,]>()?;
        let possible_ty = input.parse_terminated(Ident::parse)?;

        let functional = if let Expr::Lit(ExprLit { lit: Lit::Bool(b), .. }) = functional_expr {
            b.value
        } else {
            return Err(Error::new_spanned(functional_expr, "expected true or false"));
        };

        Ok(ActivitypubProperty {
            visibility,
            name,
            possible_ty,
            functional,
        })
    }
}

// Default to using a vec for non-functional fields
#[proc_macro]
pub fn activitypub_property(input: TokenStream) -> TokenStream {
    activitypub_property_vec(input)
}

#[proc_macro]
pub fn activitypub_property_vec(input: TokenStream) -> TokenStream {
    let ActivitypubProperty {
        visibility,
        name,
        possible_ty,
        functional
    } = parse_macro_input!(input as ActivitypubProperty);

    let variants = possible_ty.iter().map(|ty| {
        let singular_name = format!("{}", ty).to_pascal_case();
        let plural_name = format!("{}", ty).to_pascal_case().to_plural();
        let singular_ident = Ident::new(&singular_name, ty.span());
        let plural_ident = Ident::new(&plural_name, ty.span());
        if functional {
            quote! {
                #singular_ident(Box<#ty>)
            }
        } else {
            quote! {
                #singular_ident(Box<#ty>),
                #plural_ident(Vec<#ty>)
            }
        }
    });

    let expanded = quote! {
        #[derive(Clone, Debug, Serialize, Deserialize)]
        #[serde(untagged)]
        #visibility enum #name {
            #(#variants,)*
        }
    };

    expanded.into()
}

#[proc_macro]
pub fn activitypub_property_hashmap(input: TokenStream) -> TokenStream {
    let ActivitypubProperty {
        visibility,
        name,
        possible_ty,
        functional
    } = parse_macro_input!(input as ActivitypubProperty);

    let variants = possible_ty.iter().map(|ty| {
        let singular_name = format!("{}", ty).to_pascal_case();
        let plural_name = format!("{}", ty).to_pascal_case().to_plural();
        let singular_ident = Ident::new(&singular_name, ty.span());
        let plural_ident = Ident::new(&plural_name, ty.span());
        if functional {
            quote! {
                #singular_ident(Box<#ty>)
            }
        } else {
            quote! {
                #singular_ident(Box<#ty>),
                #plural_ident(HashMap<String, #ty>)
            }
        }
    });

    let expanded = quote! {
        #[derive(Clone, Debug, Serialize, Deserialize)]
        #[serde(untagged)]
        #visibility enum #name {
            #(#variants,)*
        }
    };

    expanded.into()
}