[go: up one dir, main page]

speculate 0.1.1

An RSpec inspired minimal testing framework for Rust.
Documentation
use proc_macro2::Span;
use syn;
use syn::synom::Synom;
use unicode_xid::UnicodeXID;

pub struct Root(pub(crate) Describe);

impl Synom for Root {
    named!(parse -> Self, do_parse!(
        mut content: many0!(syn!(DescribeBlock)) >>

        ({
            let mut before = vec![];
            let mut after  = vec![];
            let mut blocks = vec![];

            for block in content {
                match block {
                    DescribeBlock::Regular(block) => blocks.push(block),
                    DescribeBlock::Before(block)  => before.push(block),
                    DescribeBlock::After(block)   => after.push(block)
                }
            }

            Root(Describe {
                name: syn::Ident::new("speculate", Span::call_site()),
                before, after, blocks
            })
        })
    ));
}

#[derive(Clone)]
pub enum Block {
    Describe(Describe),
    It(It),
    Bench(Bench),
    Item(syn::Item),
}

impl Synom for Block {
    named!(parse -> Self, alt!(
        syn!(Describe)  => { Block::Describe }
        |
        syn!(It)        => { Block::It }
        |
        syn!(Bench)     => { Block::Bench }
        |
        syn!(syn::Item) => { Block::Item }
    ));
}

enum DescribeBlock {
    Regular(Block),
    Before(syn::Block),
    After(syn::Block),
}

impl Synom for DescribeBlock {
    named!(parse -> Self, alt!(
        do_parse!(
            custom_keyword!(before)             >>
            block: syn!(syn::Block)             >>
            (DescribeBlock::Before(block))      )
        |
        do_parse!(
            custom_keyword!(after)              >>
            block: syn!(syn::Block)             >>
            (DescribeBlock::After(block))       )
        |
        syn!(Block) => { DescribeBlock::Regular }
    ));
}

#[derive(Clone)]
pub struct Describe {
    pub name: syn::Ident,
    pub before: Vec<syn::Block>,
    pub after: Vec<syn::Block>,
    pub blocks: Vec<Block>,
}

impl Synom for Describe {
    named!(parse -> Self, do_parse!(
        alt!(custom_keyword!(describe) | custom_keyword!(context))  >>
        name: syn!(syn::LitStr)                                     >>
        root: braces!(syn!(Root))                                   >>

        ({
            let mut describe = (root.1).0;

            describe.name = litstr_to_ident(&name);
            describe
        })
    ));
}

#[derive(Clone)]
pub struct It {
    pub name: syn::Ident,
    pub attributes: Vec<syn::Attribute>,
    pub block: syn::Block,
}

impl Synom for It {
    named!(parse -> Self, do_parse!(
        attrs:   many0!(call!(syn::Attribute::parse_outer))         >>

        alt!(custom_keyword!(it) | custom_keyword!(test))           >>

        name:    syn!(syn::LitStr)                                  >>
        block:   syn!(syn::Block)                                   >>

        (It {
            name: litstr_to_ident(&name),
            attributes: attrs,
            block
        })
    ));
}

#[derive(Clone)]
pub struct Bench {
    pub name: syn::Ident,
    pub ident: syn::Ident,
    pub block: syn::Block,
}

impl Synom for Bench {
    named!(parse -> Self, do_parse!(
        alt!(custom_keyword!(bench) | custom_keyword!(test))        >>

        name:  syn!(syn::LitStr)                                    >>

        punct!(|)                                                   >>
        ident: syn!(syn::Ident)                                     >>
        punct!(|)                                                   >>

        block: syn!(syn::Block)                                     >>

        (Bench {
            name: litstr_to_ident(&name),
            ident, block
        })
    ));
}

fn litstr_to_ident(l: &syn::LitStr) -> syn::Ident {
    let string = l.value();
    let mut id = String::with_capacity(string.len());

    if string.is_empty() {
        return syn::Ident::new("_", l.span());
    }

    let mut chars = string.chars();
    let mut added_underscore = false;

    let first_ch = chars.next().unwrap();

    if !UnicodeXID::is_xid_start(first_ch) {
        id.push('_');

        if UnicodeXID::is_xid_continue(first_ch) {
            id.push(first_ch);
        } else {
            added_underscore = true;
        }
    } else {
        id.push(first_ch);
    }

    for ch in chars {
        if UnicodeXID::is_xid_continue(ch) {
            id.push(ch);
            added_underscore = false;
        } else if !added_underscore {
            id.push('_');
            added_underscore = true;
        }
    }

    if id.as_bytes()[id.len() - 1] == b'_' {
        id.pop();
    }

    syn::Ident::new(&id, l.span())
}