stache/ruby/
mod.rs

1extern crate regex;
2
3use regex::Regex;
4use std::collections::HashSet;
5use std::io::{self, Write};
6
7use super::{Compile, Name, ParseError, Path, Statement, Template};
8use self::runtime::RUNTIME;
9
10mod runtime;
11
12/// A program is the final result of Mustache AST to Ruby extension source
13/// translation that is presented to the main compiler driver for output.
14///
15/// It contains all external rendering functions generated by the translator,
16/// which are exposed to Ruby code after the extension is compiled.
17#[derive(Debug)]
18pub struct Program {
19    global: Scope,
20}
21
22impl Program {
23    fn new() -> Self {
24        Program { global: Scope::new(Name::new("global")) }
25    }
26
27    fn merge(&mut self, scope: Scope) -> &mut Self {
28        self.global.merge(scope);
29        self
30    }
31}
32
33impl Compile for Program {
34    /// Writes the final translated source code to an output buffer.
35    ///
36    /// This emits fully-formed Ruby extension source code that may be input
37    /// into a mkmf build process, creating a dynamically loadable shared
38    /// object file.
39    fn emit(&self, buf: &mut Write) -> io::Result<()> {
40        // Emit runtime preamble.
41        writeln!(buf, "{}", RUNTIME)?;
42
43        // Emit function declarations.
44        for fun in &self.global.functions {
45            writeln!(buf, "{};", fun.decl)?;
46        }
47
48        writeln!(buf, "")?;
49
50        // Emit function definitions.
51        for fun in &self.global.functions {
52            fun.emit(buf)?
53        }
54
55        // Emit main function.
56        write!(buf,
57               r#"void Init_stache() {{
58                      VALUE Stache = rb_define_module("Stache");
59                      VALUE Templates = rb_define_class_under(Stache, "Templates", rb_cObject);
60                 "#)?;
61
62        for fun in &self.global.functions {
63            if let Some(ref export) = fun.export {
64                writeln!(buf,
65                         "rb_define_method(Templates, \"{}\", {}, 1);",
66                         export,
67                         fun.name)?;
68            }
69        }
70
71        // Emit cached constants.
72        writeln!(buf,
73                 r#"rb_require("cgi");
74                    cCGI = rb_const_get(rb_cObject, rb_intern("CGI"));
75                    id_escape_html = rb_intern("escapeHTML");
76                    id_key_p = rb_intern("key?");
77                    id_to_s = rb_intern("to_s");
78                 "#)?;
79
80        writeln!(buf, "}}")
81    }
82}
83
84/// A store for functions created by the translation process of an input
85/// template to source code output.
86///
87/// Scopes have name generators that are used in function naming, providing a
88/// stable name that other scopes may rely on for partial template function
89/// calls.
90///
91/// After each template is translated into a scope they are merged into a
92/// Program's global scope for final output.
93#[derive(Debug)]
94struct Scope {
95    name: Name,
96    functions: Vec<Function>,
97}
98
99impl Scope {
100    fn new(name: Name) -> Self {
101        Scope {
102            name: name,
103            functions: Vec::new(),
104        }
105    }
106
107    /// Combines this scope's function definitions with another's.
108    fn merge(&mut self, mut other: Scope) -> &mut Self {
109        self.functions.append(&mut other.functions);
110        self
111    }
112
113    /// Advances the scope's name generator to the next unique identifier. This
114    /// should be called before descending another level in the recursive
115    /// tree translation process.
116    fn next(&mut self) -> &mut Self {
117        self.name.next();
118        self
119    }
120
121    /// Adds a function to this scope.
122    fn register(&mut self, fun: Function) {
123        self.functions.push(fun);
124    }
125}
126
127#[derive(Debug)]
128struct Function {
129    name: String,
130    decl: String,
131    body: Vec<String>,
132    export: Option<String>,
133}
134
135impl Function {
136    fn emit(&self, buf: &mut Write) -> io::Result<()> {
137        writeln!(buf, "{} {{", self.decl)?;
138        for node in &self.body {
139            writeln!(buf, "{}", node)?;
140        }
141        writeln!(buf, "}}\n")
142    }
143}
144
145/// Recursively walks the AST, translating Mustache statement tree nodes into
146/// the corresponding Ruby extension source code.
147///
148/// Sections are extracted into top-level functions paired with a function
149/// call at the location the section appeared in the template. Partials are
150/// similarly translated into a function call which is expected to be provided
151/// by another template in the final tree.
152fn transform(scope: &mut Scope, node: &Statement) -> Option<String> {
153    match *node {
154        Statement::Program(ref block) => {
155            let id = scope.name.id();
156
157            // Build private render function.
158            let children = block.statements
159                .iter()
160                .filter_map(|stmt| transform(scope.next(), stmt))
161                .collect();
162
163            let internal = Function {
164                name: format!("render_{}", id),
165                decl: format!("static void render_{}(VALUE buf, VALUE stack)", id),
166                body: children,
167                export: None,
168            };
169
170            // Build public template function.
171            let body = format!("VALUE buf = rb_str_buf_new(0);
172                                VALUE stack = rb_ary_new_from_args(1, context);
173                                render_{}(buf, stack);
174                                return buf;",
175                               id);
176            let external = Function {
177                name: format!("{}_template", id),
178                decl: format!("static VALUE {}_template(VALUE self, VALUE context)", id),
179                body: vec![body],
180                export: Some(id),
181            };
182
183            scope.register(internal);
184            scope.register(external);
185
186            None
187        }
188        Statement::Section(ref path, ref block) => {
189            let children = block.statements
190                .iter()
191                .filter_map(|stmt| transform(scope.next(), stmt))
192                .collect();
193
194            let fun = Function {
195                name: format!("section_{}", scope.name),
196                decl: format!("static void section_{}(VALUE buf, VALUE stack)", scope.name),
197                body: children,
198                export: None,
199            };
200
201            let call = format!("{{ {} section(buf, stack, path, {}); }}",
202                               path_ary(path),
203                               fun.name);
204
205            scope.register(fun);
206            Some(call)
207        }
208        Statement::Inverted(ref path, ref block) => {
209            let children = block.statements
210                .iter()
211                .filter_map(|stmt| transform(scope.next(), stmt))
212                .collect();
213
214            let fun = Function {
215                name: format!("section_{}", scope.name),
216                decl: format!("static void section_{}(VALUE buf, VALUE stack)", scope.name),
217                body: children,
218                export: None,
219            };
220
221            let call = format!("{{ {} inverted(buf, stack, path, {}); }}",
222                               path_ary(path),
223                               fun.name);
224
225            scope.register(fun);
226            Some(call)
227        }
228        Statement::Partial(ref name) => {
229            let name = Name::new(name);
230            Some(format!("render_{}(buf, stack);", name.id()))
231        }
232        Statement::Comment(_) => None,
233        Statement::Content(ref text) => {
234            let text = clean(text);
235            Some(format!("rb_str_cat_cstr(buf, \"{}\");", text))
236        }
237        Statement::Variable(ref path) => {
238            let path = path_ary(path);
239            Some(format!("{{ {} append_value(buf, stack, path, true); }}", path))
240        }
241        Statement::Html(ref path) => {
242            let path = path_ary(path);
243            Some(format!("{{ {} append_value(buf, stack, path, false); }}", path))
244        }
245    }
246}
247
248/// Transforms the AST of each parsed template into a source code tree
249/// and links each template together into a single executable program.
250pub fn link(templates: &Vec<Template>) -> Result<Program, ParseError> {
251    validate(templates)?;
252
253    let mut program = Program::new();
254    templates.iter()
255        .map(|template| {
256            let mut scope = Scope::new(template.name());
257            transform(&mut scope, &template.tree);
258            scope
259        })
260        .fold(&mut program, |program, scope| program.merge(scope));
261
262    Ok(program)
263}
264
265/// Ensures all templates may be linked together into an executable.
266///
267/// This method checks that all partial template paths are provided by
268/// another template. For example, a `{{>include/header}}` partial invocation
269/// must be provided by an `include/header.mustache` template file.
270///
271/// Partials can be considered function calls, so the function must be defined.
272fn validate(templates: &Vec<Template>) -> Result<(), ParseError> {
273    let all: HashSet<_> = templates.iter().map(|temp| &temp.name).collect();
274
275    for template in templates {
276        let names: HashSet<_> = template.tree.partials().into_iter().collect();
277        let missing = &names - &all;
278        if !missing.is_empty() {
279            let name = missing.into_iter().next().unwrap();
280            return Err(ParseError::UnknownPartial(name.clone(), template.path.clone()));
281        }
282    }
283
284    Ok(())
285}
286
287/// Replaces string literal characters considered invalid inside a cstr with
288/// their escaped counterparts.
289fn clean(text: &str) -> String {
290    let re = Regex::new(r"\r").unwrap();
291    let value = re.replace_all(text, "\\r");
292
293    let re = Regex::new(r"\n").unwrap();
294    let value = re.replace_all(&value, "\\n");
295
296    let re = Regex::new(r#"["]"#).unwrap();
297    re.replace_all(&value, "\\\"")
298}
299
300/// Transforms a Mustache variable key path into the source code to build a
301/// Ruby array. At runtime, each key in the array is recursively processed to
302/// find the replacement text for a Mustache expression.
303fn path_ary(path: &Path) -> String {
304    let args = path.keys
305        .iter()
306        .map(|key| format!("rb_str_new_cstr(\"{}\")", key))
307        .collect::<Vec<String>>()
308        .join(", ");
309
310    format!("VALUE path = rb_ary_new_from_args({}, {});",
311            path.keys.len(),
312            args)
313}
314
315#[cfg(test)]
316mod tests {
317    use super::{link, transform, Scope};
318    use super::super::{Name, ParseError, Statement, Template};
319    use std::path::{Path, PathBuf};
320
321    #[test]
322    fn validates_valid_partial_reference() {
323        let base = PathBuf::from("app/templates");
324        let path = PathBuf::from("app/templates/machines/robots.mustache");
325        let tree = Statement::Partial(String::from("machines/robot"));
326        let master = Template::new(&base, path, tree);
327
328        let path = PathBuf::from("app/templates/machines/robot.mustache");
329        let tree = Statement::Content(String::from("hubot"));
330        let detail = Template::new(&base, path, tree);
331
332        let templates = vec![master, detail];
333        match link(&templates) {
334            Ok(_) => (),
335            Err(e) => panic!("Must link valid partials: {}", e),
336        }
337    }
338
339    #[test]
340    fn validates_invalid_partial_reference() {
341        let base = PathBuf::from("app/templates");
342        let path = PathBuf::from("app/templates/machines/robots.mustache");
343        let tree = Statement::Partial(String::from("machines/unknown"));
344        let master = Template::new(&base, path, tree);
345
346        let path = PathBuf::from("app/templates/machines/robot.mustache");
347        let tree = Statement::Content(String::from("hubot"));
348        let detail = Template::new(&base, path, tree);
349
350        let templates = vec![master, detail];
351        match link(&templates) {
352            Err(ParseError::UnknownPartial(ref name, ref path)) => {
353                assert_eq!("machines/unknown", name);
354                assert_eq!(Path::new("app/templates/machines/robots.mustache"), path);
355            }
356            _ => panic!("Must enforce partial references"),
357        }
358    }
359
360    #[test]
361    fn transforms_tree_into_functions() {
362        let text = "
363            {{> includes/header }}
364            <ul>
365                {{# robots}}
366                    <li>{{ name.first }}</li>
367                {{/ robots}}
368                {{^ robots}}
369                    {{! else clause }}
370                    No robots
371                {{/ robots}}
372            </ul>
373            {{> includes/footer }}
374            {{{ unescaped.html }}}
375        ";
376
377        match Statement::parse(text) {
378            Ok(tree) => {
379                let mut scope = Scope::new(Name::new("machines/robot"));
380                transform(&mut scope, &tree);
381
382                // One for each section, private render, and exported template function.
383                let names: Vec<_> = scope.functions.iter().map(|fun| &fun.name).collect();
384                assert_eq!(vec!["section_machines_robot7",
385                                "section_machines_robot12",
386                                "render_machines_robot",
387                                "machines_robot_template"],
388                           names);
389
390                // Single exported function name.
391                let exports: Vec<_> =
392                    scope.functions.iter().filter_map(|fun| fun.export.as_ref()).collect();
393                assert_eq!(vec!["machines_robot"], exports);
394            }
395            Err(e) => panic!("Failed to parse tree: {}", e),
396        }
397    }
398}