1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use mime_guess::from_ext;
use proc_macro2::TokenStream;
use quote::quote;

use yarte_hir::{Each, IfElse, Mode, Struct, HIR};

mod html;
mod text;
pub mod wasm;

pub use self::{
    html::{HTMLCodeGen, HTMLMinCodeGen},
    text::TextCodeGen,
};

pub trait CodeGen {
    fn gen(&mut self, v: Vec<HIR>) -> TokenStream;
}

pub struct FmtCodeGen<'a, T: CodeGen> {
    codegen: T,
    s: &'a Struct<'a>,
}

impl<'a, T: CodeGen> FmtCodeGen<'a, T> {
    pub fn new<'n>(codegen: T, s: &'n Struct) -> FmtCodeGen<'n, T> {
        FmtCodeGen { codegen, s }
    }

    fn get_mime(&self) -> String {
        let ext = match self.s.mode {
            Mode::Text => match self.s.path.extension() {
                Some(s) => s.to_str().unwrap(),
                None => "txt",
            },
            _ => "html",
        };

        from_ext(ext).first_or_text_plain().to_string()
    }

    fn template(&self, size_hint: usize, tokens: &mut TokenStream) {
        let mut body = quote!(
            fn size_hint() -> usize {
                #size_hint
            }
        );
        if cfg!(feature = "actix-web") {
            let mime = self.get_mime() + "; charset=utf-8";
            body.extend(quote!(fn mime() -> &'static str { #mime }))
        }

        tokens.extend(self.s.implement_head(quote!(Template), &body));
    }

    fn display(&mut self, nodes: Vec<HIR>, tokens: &mut TokenStream) -> usize {
        let nodes = self.codegen.gen(nodes);
        // heuristic based on https://github.com/lfairy/maud
        let size_hint = nodes.to_string().len();
        let func = quote!(
            fn fmt(&self, _fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                #nodes
                Ok(())
            }
        );

        tokens.extend(self.s.implement_head(quote!(::std::fmt::Display), &func));

        size_hint
    }

    fn responder(&self, tokens: &mut TokenStream) {
        let err_msg = &self.s.err_msg;

        let body = quote!(
            type Error = ::yarte::aw::Error;
            type Future = ::yarte::aw::Ready<::std::result::Result<::yarte::aw::HttpResponse, Self::Error>>;

            #[inline]
            fn respond_to(self, _req: &::yarte::aw::HttpRequest) -> Self::Future {
                match self.call() {
                    Ok(body) => {
                        ::yarte::aw::ok(::yarte::aw::HttpResponse::Ok().content_type(Self::mime()).body(body))
                    }
                    Err(_) => {
                        ::yarte::aw::err(::yarte::aw::ErrorInternalServerError(#err_msg))
                    }
                }
            }
        );

        tokens.extend(self.s.implement_head(quote!(::yarte::aw::Responder), &body));
    }
}

impl<'a, T: CodeGen> CodeGen for FmtCodeGen<'a, T> {
    fn gen(&mut self, v: Vec<HIR>) -> TokenStream {
        let mut tokens = TokenStream::new();

        let size_hint = self.display(v, &mut tokens);
        self.template(size_hint, &mut tokens);

        if cfg!(feature = "actix-web") {
            self.responder(&mut tokens);
        }

        tokens
    }
}

pub trait EachCodeGen: CodeGen {
    fn gen_each(&mut self, Each { args, body, expr }: Each) -> TokenStream {
        let body = self.gen(body);
        quote!(for #expr in #args { #body })
    }
}

pub trait IfElseCodeGen: CodeGen {
    fn gen_if_else(&mut self, IfElse { ifs, if_else, els }: IfElse) -> TokenStream {
        let mut tokens = TokenStream::new();

        let (args, body) = ifs;
        let body = self.gen(body);
        tokens.extend(quote!(if #args { #body }));

        for (args, body) in if_else {
            let body = self.gen(body);
            tokens.extend(quote!(else if #args { #body }));
        }

        if let Some(body) = els {
            let body = self.gen(body);
            tokens.extend(quote!(else { #body }));
        }

        tokens
    }
}