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
use quote::ToTokens;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use syn::Block;

use crate::config::Config;
use crate::error::*;
use crate::optimizer::Optimizer;
use crate::parser::Parser;
use crate::resolver::Resolver;
use crate::translator::{TranslatedSource, Translator};
use crate::util::{read_to_string, rustfmt_block};

#[derive(Default)]
pub struct Compiler {
    config: Config,
}

pub struct CompilationReport {
    pub deps: Vec<PathBuf>,
}

impl Compiler {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_config(config: Config) -> Self {
        Self { config }
    }

    fn translate_file_contents(&self, input: &Path) -> Result<TranslatedSource, Error> {
        let parser = Parser::new().delimiter(self.config.delimiter);
        let translator = Translator::new().escape(self.config.escape);
        let content = read_to_string(input)
            .chain_err(|| format!("Failed to open template file: {:?}", input))?;

        let stream = parser.parse(&*content);
        translator.translate(stream)
    }

    pub fn compile_file(
        &self,
        input: &Path,
        output: &Path,
    ) -> Result<CompilationReport, Error> {
        // TODO: introduce cache system

        let input = input
            .canonicalize()
            .map_err(|_| format!("Template file not found: {:?}", input))?;

        let include_handler = Arc::new(|child_file: &Path| -> Result<_, Error> {
            Ok(self.translate_file_contents(&*child_file)?.ast)
        });

        let resolver = Resolver::new().include_handler(include_handler);
        let optimizer = Optimizer::new().rm_whitespace(self.config.rm_whitespace);

        let compile_file = |input: &Path,
                            output: &Path|
         -> Result<CompilationReport, Error> {
            let mut tsource = self.translate_file_contents(input)?;
            let mut report = CompilationReport { deps: Vec::new() };

            let r = resolver.resolve(&*input, &mut tsource.ast)?;
            report.deps = r.deps;

            optimizer.optimize(&mut tsource.ast);

            if let Some(parent) = output.parent() {
                fs::create_dir_all(parent)
                    .chain_err(|| format!("Failed to save artifacts in {:?}", parent))?;
            }

            let string = tsource.ast.into_token_stream().to_string();

            let mut f = fs::File::create(output)
                .chain_err(|| format!("Failed to create artifact: {:?}", output))?;
            writeln!(f, "{}", rustfmt_block(&*string).unwrap_or(string))
                .chain_err(|| format!("Failed to write artifact into {:?}", output))?;
            Ok(report)
        };

        compile_file(&*input, &*output)
            .chain_err(|| "Failed to compile template.")
            .map_err(|mut e| {
                e.source = fs::read_to_string(&*input).ok();
                e.source_file = Some(input.to_owned());
                e
            })
    }

    pub fn compile_str(&self, input: &str) -> Result<String, Error> {
        let dummy_path = Path::new(env!("CARGO_MANIFEST_DIR"));

        let include_handler = Arc::new(|_: &Path| -> Result<Block, Error> {
            Err(make_error!(
                ErrorKind::AnalyzeError(
                    "include! macro is not allowed in inline template".to_owned()
                ),
                source = input.to_owned()
            ))
        });

        let parser = Parser::new().delimiter(self.config.delimiter);
        let translator = Translator::new().escape(self.config.escape);
        let resolver = Resolver::new().include_handler(include_handler);
        let optimizer = Optimizer::new().rm_whitespace(self.config.rm_whitespace);

        let compile = || -> Result<String, Error> {
            let stream = parser.parse(input);
            let mut tsource = translator.translate(stream)?;
            resolver.resolve(dummy_path, &mut tsource.ast)?;

            optimizer.optimize(&mut tsource.ast);
            Ok(tsource.ast.into_token_stream().to_string())
        };

        compile()
            .chain_err(|| "Failed to compile template.")
            .map_err(|mut e| {
                e.source = Some(input.to_owned());
                e
            })
    }
}