termcolor_output_impl 1.0.0

Implementation crate for colored! macro.
Documentation
use proc_macro::{
    Delimiter::*, Group, Ident, Literal, Punct, Spacing::*, Span, TokenStream, TokenTree,
};

use crate::*;

macro_rules! tt {
    (Literal::$ty:tt($($args:expr),*)) => {
        TokenTree::Literal(Literal::$ty($($args),*))
    };
    ($ty:tt($($args:expr),*)) => {
        TokenTree::$ty($ty::new($($args),*))
    };
}

macro_rules! ts {
    () => { TokenStream::new() };
    (let $let:ident = $($tok:tt)+) => {
        let $let: TokenStream = ts!($($tok)+);
    };
    ($($tok:tt)+) => {
        vec![$($tok)+].into_iter().collect()
    };
}

pub fn macro_wrapper(body: TokenStream) -> TokenStream {
    ts![
        tt!(Ident("macro_rules", Span::call_site())),
        tt!(Punct('!', Alone)),
        tt!(Ident("colored_impl", Span::call_site())),
        tt!(Group(
            Brace,
            ts![
                tt!(Group(Parenthesis, TokenStream::new())),
                tt!(Punct('=', Joint)),
                tt!(Punct('>', Alone)),
                tt!(Group(Brace, body))
            ]
        )),
    ]
}

pub fn closure_wrapper(body: TokenStream) -> TokenStream {
    ts!(
        tt!(Punct('|', Alone)),
        tt!(Punct('|', Alone)),
        tt!(Punct('-', Joint)),
        tt!(Punct('>', Alone)),
        tt!(Ident("std", Span::call_site())),
        tt!(Punct(':', Joint)),
        tt!(Punct(':', Alone)),
        tt!(Ident("io", Span::call_site())),
        tt!(Punct(':', Joint)),
        tt!(Punct(':', Alone)),
        tt!(Ident("Result", Span::call_site())),
        tt!(Punct('<', Alone)),
        tt!(Group(Parenthesis, ts!())),
        tt!(Punct('>', Alone)),
        tt!(Group(
            Brace,
            body.into_iter()
                .chain(
                    vec![
                        tt!(Ident("Ok", Span::call_site())),
                        tt!(Group(Parenthesis, ts!(tt!(Group(Parenthesis, ts!())))))
                    ]
                    .into_iter()
                )
                .collect()
        )),
        tt!(Group(Parenthesis, ts!())),
    )
}

pub fn compile_error((start, end, error): CompileError) -> TokenStream {
    let mut bang = Punct::new('!', Alone);
    bang.set_span(start);
    let mut inner = Group::new(Parenthesis, ts!(tt!(Literal::string(error))));
    inner.set_span(end);
    let mut semi = Punct::new(';', Alone);
    semi.set_span(end);
    vec![
        tt!(Ident("compile_error", start)),
        TokenTree::Punct(bang),
        TokenTree::Group(inner),
        TokenTree::Punct(semi),
    ]
    .into_iter()
    .collect()
}

pub fn guard(writer: TokenStream) -> TokenStream {
    ts!(
        tt!(Ident("let", Span::call_site())),
        tt!(Ident("__writer__", Span::call_site())),
        tt!(Punct(':', Alone)),
        tt!(Punct('&', Alone)),
        tt!(Ident("mut", Span::call_site())),
        tt!(Ident("_", Span::call_site())),
        tt!(Punct('=', Alone)),
        tt!(Group(Parenthesis, writer.clone())),
        tt!(Punct('.', Alone)),
        tt!(Ident(
            "guard",
            writer
                .into_iter()
                .next()
                .map_or(Span::call_site(), |t| t.span())
        )),
        tt!(Group(Parenthesis, ts!())),
        tt!(Punct(';', Alone)),
    )
}

pub fn spec_init() -> TokenStream {
    ts!(
        tt!(Ident("let", Span::call_site())),
        tt!(Ident("mut", Span::call_site())),
        tt!(Ident("__spec__", Span::call_site())),
        tt!(Punct('=', Alone)),
        tt!(Ident("termcolor", Span::call_site())),
        tt!(Punct(':', Joint)),
        tt!(Punct(':', Alone)),
        tt!(Ident("ColorSpec", Span::call_site())),
        tt!(Punct(':', Joint)),
        tt!(Punct(':', Alone)),
        tt!(Ident("new", Span::call_site())),
        tt!(Group(Parenthesis, ts!())),
        tt!(Punct(';', Alone))
    )
}

pub fn output(entry: OutputItem) -> TokenStream {
    match entry {
        OutputItem::Ctrl(seq) => control(seq),
        OutputItem::Raw(entry) => raw(entry),
    }
}

fn raw(entry: RawOutput) -> TokenStream {
    use std::iter::once;
    let (fmt, items) = entry;
    let inner = vec![
        tt!(Ident("__writer__", Span::call_site())),
        tt!(Punct(',', Alone)),
    ]
    .into_iter()
    .chain(
        format!("\"{}\"", fmt).parse::<TokenStream>()
            .expect(concat!("Unable to parse format string as token stream in codegen; this is supposed to be unreachable. Please report this case to ", env!("CARGO_PKG_REPOSITORY"), "/issues"))
            .into_iter(),
    )
    .chain(
        items
            .into_iter()
            .flat_map(|stream| once(tt!(Punct(',', Alone))).chain(stream.into_iter())),
    )
    .collect();
    ts!(
        tt!(Ident("write", Span::call_site())),
        tt!(Punct('!', Alone)),
        tt!(Group(Parenthesis, inner)),
        tt!(Punct('?', Alone)),
        tt!(Punct(';', Alone)),
    )
}

fn control(seq: ControlSeq) -> TokenStream {
    ts!(let head =
        tt!(Ident("__spec__", Span::call_site())),
        tt!(Punct('.', Alone)),
    );
    let change_spec: TokenStream = match seq {
        ControlSeq::Reset => ts!(
            tt!(Ident("clear", Span::call_site())),
            tt!(Group(Parenthesis, ts!())),
            tt!(Punct(';', Alone)),
        ),
        ControlSeq::Command(cmd, inner) => ts!(
            tt!(Ident(&(String::from("set_") + &cmd), Span::call_site())),
            tt!(Group(Parenthesis, inner)),
            tt!(Punct(';', Alone)),
        ),
    };
    ts!(let set_spec =
        tt!(Ident("__writer__", Span::call_site())),
        tt!(Punct('.', Alone)),
        tt!(Ident("set_color", Span::call_site())),
        tt!(Group(Parenthesis, ts!(
            tt!(Punct('&', Alone)),
            tt!(Ident("__spec__", Span::call_site()))
        ))),
        tt!(Punct('?', Alone)),
        tt!(Punct(';', Alone)),
    );
    head.into_iter()
        .chain(change_spec.into_iter())
        .chain(set_spec.into_iter())
        .collect()
}