solarsail-macros 0.1.0

macros for the solarsail crate
Documentation
use std::iter::{self, Once};

use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
use proc_macro_error::{abort, abort_call_site, proc_macro_error};

#[proc_macro]
#[proc_macro_error]
pub fn get(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::GET".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn head(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::HEAD".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn post(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::POST".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn put(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::PUT".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn delete(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::DELETE".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn patch(path: TokenStream) -> TokenStream {
    let method: TokenStream = "&::solarsail::http::Method::PATCH".parse().unwrap();
    method_macro(method, path)
}

#[proc_macro]
#[proc_macro_error]
pub fn route(path: TokenStream) -> TokenStream {
    let mut path = path.into_iter();
    let method = (&mut path)
        .take_while(|token| {
            match token {
                TokenTree::Punct(p) => {
                    match p.as_char() {
                        ',' => false,
                        '/' => abort_call_site!(
                            "route! macro must contain a pattern matching expected methods as its first argument"
                        ),
                        _ => true
                    }
                }
                _ => true
            }
        })
        .collect();
    method_macro(method, path.collect())
}

fn method_macro(method: TokenStream, path: TokenStream) -> TokenStream {
    let mut expect_slash = false;
    let path: TokenStream = if path.is_empty() {
        iter::once(TokenTree::Ident(Ident::new("_", Span::call_site()))).collect()
    } else {
        punct_once('&')
            .chain(iter::once(TokenTree::Group(Group::new(
                Delimiter::Bracket,
                path.into_iter()
                    .enumerate()
                    .filter_map(|(i, item)| {
                        Some(match item {
                            TokenTree::Group(g) => abort!(g.span(), "unexpected group"),
                            item @ TokenTree::Ident(_) => {
                                expect_slash = true;
                                item
                            }
                            TokenTree::Punct(p) if p.as_char() == '/' => {
                                if i == 0 {
                                    return None;
                                }

                                if !expect_slash {
                                    abort!(p.span(), "unexpected forward slash")
                                }

                                expect_slash = false;

                                TokenTree::Punct(Punct::new(',', Spacing::Alone))
                            }
                            TokenTree::Punct(p) => {
                                if p.spacing() == Spacing::Alone {
                                    expect_slash = true;
                                }
                                TokenTree::Punct(p)
                            }
                            literal @ TokenTree::Literal(_) => {
                                expect_slash = true;
                                literal
                            }
                        })
                    })
                    .collect(),
            ))))
            .collect()
    };

    let result: TokenStream = TokenTree::Group(Group::new(
        Delimiter::Parenthesis,
        method
            .into_iter()
            .chain(punct_once(','))
            .chain(path)
            .collect(),
    ))
    .into();

    result
}

fn punct_once(ch: char) -> Once<TokenTree> {
    iter::once(TokenTree::Punct(Punct::new(ch, Spacing::Alone)))
}