sailfish_compiler/
compiler.rs

1use quote::ToTokens;
2use std::fs;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use syn::Block;
7
8use crate::analyzer::Analyzer;
9use crate::config::Config;
10use crate::error::*;
11use crate::optimizer::Optimizer;
12use crate::parser::Parser;
13use crate::resolver::Resolver;
14use crate::translator::{TranslatedSource, Translator};
15use crate::util::{read_to_string, rustfmt_block};
16
17#[derive(Default)]
18pub struct Compiler {
19    config: Config,
20}
21
22pub struct CompilationReport {
23    pub deps: Vec<PathBuf>,
24}
25
26impl Compiler {
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    pub fn with_config(config: Config) -> Self {
32        Self { config }
33    }
34
35    fn translate_file_contents(&self, input: &Path) -> Result<TranslatedSource, Error> {
36        let parser = Parser::new().delimiter(self.config.delimiter);
37        let translator = Translator::new().escape(self.config.escape);
38        let content = read_to_string(input)
39            .chain_err(|| format!("Failed to open template file: {:?}", input))?;
40
41        let stream = parser.parse(&content);
42        translator.translate(stream)
43    }
44
45    pub fn resolve_file(
46        &self,
47        input: &Path,
48    ) -> Result<(TranslatedSource, CompilationReport), Error> {
49        let include_handler = Arc::new(|child_file: &Path| -> Result<_, Error> {
50            Ok(self.translate_file_contents(child_file)?.ast)
51        });
52
53        let resolver = Resolver::new().include_handler(include_handler);
54        let mut tsource = self.translate_file_contents(input)?;
55        let mut report = CompilationReport { deps: Vec::new() };
56
57        let r = resolver.resolve(input, &mut tsource.ast)?;
58        report.deps = r.deps;
59        Ok((tsource, report))
60    }
61
62    pub fn compile_file(
63        &self,
64        input: &Path,
65        tsource: TranslatedSource,
66        output: &Path,
67    ) -> Result<(), Error> {
68        let analyzer = Analyzer::new();
69        let optimizer = Optimizer::new()
70            .rm_whitespace(self.config.rm_whitespace)
71            .rm_newline(self.config.rm_newline);
72
73        let compile_file = |mut tsource: TranslatedSource,
74                            output: &Path|
75         -> Result<(), Error> {
76            analyzer.analyze(&mut tsource.ast)?;
77            optimizer.optimize(&mut tsource.ast);
78
79            if let Some(parent) = output.parent() {
80                fs::create_dir_all(parent)
81                    .chain_err(|| format!("Failed to save artifacts in {:?}", parent))?;
82            }
83
84            let string = tsource.ast.into_token_stream().to_string();
85
86            let mut f = fs::File::create(output)
87                .chain_err(|| format!("Failed to create artifact: {:?}", output))?;
88            writeln!(f, "// Template compiled from: {}", input.display())
89                .chain_err(|| format!("Failed to write artifact into {:?}", output))?;
90            writeln!(f, "{}", rustfmt_block(&string).unwrap_or(string))
91                .chain_err(|| format!("Failed to write artifact into {:?}", output))?;
92            drop(f);
93
94            Ok(())
95        };
96
97        compile_file(tsource, output)
98            .chain_err(|| "Failed to compile template.")
99            .map_err(|mut e| {
100                e.source = fs::read_to_string(input).ok();
101                e.source_file = Some(input.to_owned());
102                e
103            })
104    }
105
106    pub fn compile_str(&self, input: &str) -> Result<String, Error> {
107        let dummy_path = Path::new(env!("CARGO_MANIFEST_DIR"));
108
109        let include_handler = Arc::new(|_: &Path| -> Result<Block, Error> {
110            Err(make_error!(
111                ErrorKind::AnalyzeError(
112                    "include! macro is not allowed in inline template".to_owned()
113                ),
114                source = input.to_owned()
115            ))
116        });
117
118        let parser = Parser::new().delimiter(self.config.delimiter);
119        let translator = Translator::new().escape(self.config.escape);
120        let resolver = Resolver::new().include_handler(include_handler);
121        let optimizer = Optimizer::new()
122            .rm_whitespace(self.config.rm_whitespace)
123            .rm_newline(self.config.rm_newline);
124
125        let compile = || -> Result<String, Error> {
126            let stream = parser.parse(input);
127            let mut tsource = translator.translate(stream)?;
128            resolver.resolve(dummy_path, &mut tsource.ast)?;
129
130            optimizer.optimize(&mut tsource.ast);
131            Ok(tsource.ast.into_token_stream().to_string())
132        };
133
134        compile()
135            .chain_err(|| "Failed to compile template.")
136            .map_err(|mut e| {
137                e.source = Some(input.to_owned());
138                e
139            })
140    }
141}