hanger 0.1.0

Attribute macro to put hooks around a function.
Documentation
extern crate proc_macro;

#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use syn::parse;
use syn::punctuated::Punctuated;

#[proc_macro_attribute]
pub fn hook(attr: TokenStream, input: TokenStream) -> TokenStream {
    let hooks = syn::parse_macro_input!(attr as Hooks);
    let mut function = syn::parse_macro_input!(input as syn::ItemFn);

    let before: Box<syn::Block> = if let Some(func) = hooks.before {
        Box::new(syn::parse_quote!({ #func() }))
    } else {
        Box::new(syn::parse_quote!({}))
    };
    let after: Box<syn::Block> = if let Some(func) = hooks.after {
        Box::new(syn::parse_quote!({ #func() }))
    } else {
        Box::new(syn::parse_quote!({}))
    };

    let body = function.block;
    function.block = Box::new(syn::parse_quote!({
        #before { #body } #after
    }));
    TokenStream::from(quote!(#function))
}

struct Hooks {
    before: Option<syn::TypePath>,
    after: Option<syn::TypePath>,
}

mod pk {
    use super::*;

    syn::custom_keyword!(before);
    syn::custom_keyword!(after);

    pub enum Arg {
        Before {
            b: before,
            eq: syn::Token![=],
            func: syn::TypePath,
        },
        After {
            a: after,
            eq: syn::Token![=],
            func: syn::TypePath,
        },
    }

    impl parse::Parse for Arg {
        fn parse(input: parse::ParseStream) -> parse::Result<Self> {
            let lookahead = input.lookahead1();
            if lookahead.peek(before) {
                Ok(Arg::Before {
                    b: input.parse::<before>()?,
                    eq: input.parse::<syn::Token![=]>()?,
                    func: input.parse()?,
                })
            } else {
                Ok(Arg::After {
                    a: input.parse::<after>()?,
                    eq: input.parse::<syn::Token![=]>()?,
                    func: input.parse()?,
                })
            }
        }
    }
}

impl parse::Parse for Hooks {
    fn parse(input: parse::ParseStream) -> parse::Result<Self> {
        let mut hb = None;
        let mut ha = None;
        let parser = Punctuated::<pk::Arg, syn::Token![,]>::parse_terminated;
        if let Ok(args) = parser(input) {
            for arg in args.iter() {
                match arg {
                    pk::Arg::After { func, .. } => ha = Some(func.clone()),
                    pk::Arg::Before { func, .. } => hb = Some(func.clone()),
                }
            }
        }

        if hb.is_none() && ha.is_none() {
            hb = Some(input.parse::<syn::TypePath>()?);
        }

        Ok(Hooks {
            after: ha,
            before: hb,
        })
    }
}