test-case-derive 0.2.3

Provides #[test_case(...)] procedural macro attribute for generating parametrized test cases easily
Documentation
use prelude::*;

pub struct TestCaseSuit {
    attrs: Vec<Vec<TokenTree>>,
    body:  Vec<TokenTree>,
    name:  String
}

impl TestCaseSuit {
    pub fn body_tokens(&self) -> Tokens {
        let body = &self.body;

        quote! { #(#body)* }
    }

    fn name_token(&self) -> Token {
        TIdent((&self.name as &str).into())
    }

    pub fn gen_test_cases(&self) -> Tokens {
        let name = self.name_token();
        let body = self.body_tokens();
        let test_cases = 
            self.attrs
                .iter()
                .map(|tt| {
                    gen_test_case(self.name_token(), tt)
                })
                .collect::<Vec<_>>();

        quote! { 
            mod #name {
                #[allow(unused_imports)]
                use super::*;

                #body

                #(#test_cases)*
            }
        }
    }
}

impl From<Vec<TokenTree>> for TestCaseSuit {
    fn from(token_tree: Vec<TokenTree>) -> Self {
        let mut attrs     = Vec::new();
        let mut leftover  = Vec::new();
        let mut name      = None;
        let mut iter      = token_tree.into_iter().peekable();
        let mut skip_next = false;

        while let Some(token) = iter.next() {
            if skip_next { skip_next = false; continue }

            let next_token = iter.peek();

            if let Some(attributes) = try_get_test_case(&token, next_token) { 
                attrs.push(attributes.clone());
                skip_next = true;
                continue;
            }
            
            name = name.or(try_parse_fn_name(&token, next_token));
            
            leftover.push(token);
        }

        Self {
            attrs: attrs,
            body:  leftover,
            name:  name.expect("Couldn't find test function name")
        }
    }
}

fn try_get_test_case<'a>(token: &'a TokenTree, next_token: Option<&'a TokenTree>) -> Option<&'a Vec<TokenTree>> {
    if token == &TTToken(TPound) {
        if let Some(&TTDelimited(ref delimited)) = next_token {
            if is_test_case(delimited) {
                if let TTDelimited(ref inner_delimited) = delimited.tts[1] {
                    return Some(&inner_delimited.tts);
                }
            }
        }
    }

    None
}

fn try_parse_fn_name(token: &TokenTree, next_token: Option<&TokenTree>) -> Option<String> {
    if token == &TTToken(TIdent("fn".into())) {
        if let Some(&TTToken(TIdent(ref ident))) = next_token {
            return Some(ident.to_string());
        }
    }

    None
}