catchr-core 0.1.2

Core library of catchr the testing framework
Documentation
use crate::Section;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::parse::{self, Parse, ParseStream};
use syn::token::{Brace, Mod};
use syn::{AttrStyle, Attribute, Ident, Item, Visibility};

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum CatchrModItem {
    Section(Section),
    Item(Item),
}

impl ToTokens for CatchrModItem {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            CatchrModItem::Item(item) => item.to_tokens(tokens),
            CatchrModItem::Section(section) => section.to_tokens(tokens),
        }
    }
}

impl Parse for CatchrModItem {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        let result = if Section::peek(input) {
            CatchrModItem::Section(input.parse::<Section>()?)
        } else {
            CatchrModItem::Item(input.parse::<Item>()?)
        };

        Ok(result)
    }
}

#[derive(Debug, Clone)]
pub struct CatchrMod {
    attrs: Vec<Attribute>,
    vis: Visibility,
    mod_token: Mod,
    ident: Ident,
    content: (Brace, Vec<CatchrModItem>),
}

impl ToTokens for CatchrMod {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let CatchrMod {
            vis,
            content,
            attrs,
            mod_token,
            ident,
        } = &self;

        let content = &content.1;

        let outer_attrs =
            attrs.iter().filter(|attr| attr.style == AttrStyle::Outer);

        let inner_attrs = attrs.iter().filter(|attr| match attr.style {
            AttrStyle::Inner(_) => true,
            _ => false,
        });

        let q = quote! {
            #(#outer_attrs)*
            #[allow(unused)]
            #vis #mod_token #ident {
                #(#inner_attrs)*
                #(#content)*
            }
        };

        tokens.append_all(q);
    }
}

impl Parse for CatchrMod {
    fn parse(input: ParseStream) -> parse::Result<Self> {
        let mut attrs = Attribute::parse_outer(input)?;

        let vis = Visibility::parse(input)?;

        let mod_token = Mod::parse(input)?;

        let ident = Ident::parse(input)?;

        let content;
        let brace = syn::braced!(content in input);

        let inner_attrs = Attribute::parse_inner(&content)?;

        attrs.extend(inner_attrs);

        let mut items = vec![];

        loop {
            if content.is_empty() {
                break;
            }
            let item = content.parse::<CatchrModItem>()?;

            items.push(item);
        }

        Ok(Self {
            attrs,
            vis,
            mod_token,
            ident,
            content: (brace, items),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_quote() {
        let s = r#"
            #[hello]
            mod whatever {
                #![goodbye]
                use super::*;

                when "whatever" {
                    let x = 1;
                    then "hello" {
                        assert_eq!(x, 1);
                    }
                }
            }"#;

        let catchr_mod = syn::parse_str::<CatchrMod>(s).unwrap();

        let act = catchr_mod.to_token_stream();

        let exp = quote!(
            #[hello]
            #[allow(unused)]
            mod whatever {
                #![goodbye]
                use super::*;

                mod when_whatever {
                    use super::*;

                    #[test]
                    fn then_hello() {
                        {
                            let x = 1;
                            {
                                assert_eq!(x, 1);
                            }
                        }
                    }
                }
            }
        );

        assert_eq!(exp.to_string(), act.to_string());
    }
}