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
139
140
141
142
143
144
145
#[macro_use] extern crate quote;

extern crate itertools;
extern crate proc_macro;
extern crate syn;

mod ast;
mod generator;
mod parser;
mod scanner;
mod token;

use ast::Ast;
use proc_macro::TokenStream;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

fn user_crate_root() -> PathBuf {
    std::env::current_dir().expect("Unable to get current directory")
}

fn find_attr<'a>(attrs: &'a Vec<syn::Attribute>, name: &str) -> Option<&'a str> {
    attrs.iter()
        .find(|&x| x.name() == name)
        .and_then(|ref attr| match &attr.value {
            &syn::MetaItem::NameValue(_, syn::Lit::Str(ref template, _)) => Some(template),
            _ => None
        })
        .map(|x| x.as_ref())
}

fn buf_file<P: AsRef<Path>>(filename: P) -> String {
    let mut f = File::open(filename)
        .expect("Unable to open file for reading");
    let mut buf = String::new();
    f.read_to_string(&mut buf)
        .expect("Unable to read file");

    buf
}

fn parse_str(input: &str) -> Result<Ast, parser::Error> {
    parser::parse(scanner::sequence(input).unwrap())
}

struct InlinePartialsResolver;
impl generator::PartialsResolver for InlinePartialsResolver {
    fn generate_partial(&mut self, _partial_name: &str) -> quote::Tokens {
        panic!("Partials are unavailable when using template_string");
    }
}

struct FilesystemPartialsResolver<'a> {
    base_dir: PathBuf,
    dependencies: &'a mut Vec<String>,
}

impl<'a> FilesystemPartialsResolver<'a> {
    fn new<T: Into<PathBuf>>(base_dir: T, dependencies: &mut Vec<String>) -> FilesystemPartialsResolver {
        FilesystemPartialsResolver {
            base_dir: base_dir.into(),
            dependencies,
        }
    }
}

impl<'a> generator::PartialsResolver for FilesystemPartialsResolver<'a> {
    fn generate_partial(&mut self, partial_name: &str) -> quote::Tokens {
        let relative_path: PathBuf = partial_name.into();
        let abs_path = match relative_path.has_root() {
            true => user_crate_root().join(relative_path.strip_prefix("/").unwrap()),
            false => self.base_dir.join(partial_name),
        };
        self.dependencies.push(abs_path.to_str().unwrap().to_owned());
        let template = buf_file(&abs_path);
        let parsed = parse_str(&template).unwrap();
        let nested_resolver = &mut FilesystemPartialsResolver::new(abs_path.parent().unwrap(), self.dependencies);
        generator::generate(parsed, 1, nested_resolver)
    }
}

#[proc_macro_derive(BartDisplay, attributes(template, template_string, template_root))]
pub fn bart_display(input: TokenStream) -> TokenStream {
    let s = input.to_string();
    let ast = syn::parse_macro_input(&s).unwrap();

    let mut dependencies = Vec::<String>::new();

    let generated = {
        let (template, mut partials_resolver): (_, Box<generator::PartialsResolver>) =
            match find_attr(&ast.attrs, "template") {
                Some(filename) => {
                    let abs_filename = user_crate_root().join(filename);
                    dependencies.push(abs_filename.to_str().unwrap().to_owned());
                    let resolver = FilesystemPartialsResolver::new(abs_filename.parent().unwrap(), &mut dependencies);
                    (buf_file(filename), Box::new(resolver))
                },
                None => {
                    let template = find_attr(&ast.attrs, "template_string")
                        .map(|x| x.to_owned())
                        .expect("#[derive(BartDisplay)] requires #[template = \"(filename)\"] \
                            or  #[template_string = \"...\"]");
                    (template, Box::new(InlinePartialsResolver))
                }
            };

        let parsed = parse_str(&template).unwrap();
        generator::generate(parsed, 1, &mut *partials_resolver)
    };

    let template_root = syn::Ident::new(find_attr(&ast.attrs, "template_root")
        .map(|x| scanner::segmented_name(&x).expect("Syntax error in template_root"))
        .map(|x| format!("self.{}", x.join(".")))
        .unwrap_or("self".to_owned()));

    let name = &ast.ident;
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    let dummy_const = syn::Ident::new(format!("_IMPL_BART_DISPLAY_FOR_{}", &name));

    let gen = quote! {
        #[allow(non_upper_case_globals, unused_attributes, unused_qualifications, unknown_lints, clippy)]
        const #dummy_const: () = {
            extern crate bart as _bart;

            #[automatically_derived]
            impl #impl_generics ::std::fmt::Display for #name #ty_generics #where_clause {
                fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                    #(
                        let _ = include_bytes!(#dependencies);
                    )*

                    let _s0 = &#template_root;

                    #generated

                    Ok(())
                }
            }
        };
    };

    gen.parse().unwrap()
}