rusty_handlebars_parser/
lib.rs

1//! Handlebars template parser and compiler
2//!
3//! This crate provides the core functionality for parsing and compiling Handlebars templates
4//! into Rust code. It's used internally by the `rusty-handlebars` crate to process templates
5//! at compile time.
6//!
7//! # Features
8//!
9//! - Handlebars template parsing
10//! - Template compilation to Rust code
11//! - Support for all standard Handlebars features:
12//!   - Variables and expressions
13//!   - Block helpers (if, unless, each, with)
14//!   - Partials
15//!   - Comments
16//!   - HTML escaping
17//!   - Whitespace control
18//!   - Subexpressions
19//!   - Lookup helpers
20//!
21//! # Example
22//!
23//! ```rust
24//! use rusty_handlebars_parser::{Compiler, Options, BlockMap};
25//! use rusty_handlebars_parser::block::add_builtins;
26//!
27//! let mut factories = BlockMap::new();
28//! add_builtins(&mut factories);
29//!
30//! let compiler = Compiler::new(Options {
31//!     write_var_name: "f",
32//!     root_var_name: Some("self")
33//! }, factories);
34//!
35//! let template = "Hello {{name}}!";
36//! let rust_code = compiler.compile(template).unwrap();
37//! ```
38//!
39//! # Module Structure
40//!
41//! - `compiler.rs`: Main compiler implementation
42//! - `block.rs`: Block helper implementations
43//! - `expression.rs`: Expression parsing and evaluation
44//! - `expression_tokenizer.rs`: Tokenization of expressions
45//! - `error.rs`: Error types and handling
46//! - `build_helper.rs`: Helper functions for template building
47
48mod error;
49mod expression;
50mod expression_tokenizer;
51//mod optimizer;
52mod compiler;
53mod block;
54pub mod build_helper;
55
56pub use compiler::*;
57pub use block::*;
58pub use error::*;
59pub use expression::*;
60pub use expression_tokenizer::*;
61
62#[cfg(test)]
63mod tests {
64    use core::str;
65
66    use block::add_builtins;
67    use compiler::{BlockMap, Compiler, Options};
68
69    use crate::*;
70
71    static OPTIONS: Options = Options{
72        root_var_name: Some("self"),
73        write_var_name: "f"
74    };
75
76    fn make_map() -> BlockMap{
77        let mut map = BlockMap::new();
78        add_builtins(&mut map);
79        map
80    }
81
82    fn compile(src: &str) -> String{
83        Compiler::new(OPTIONS, make_map()).compile(src).unwrap().code
84    }
85
86    #[test]
87    fn it_works() {
88        assert_eq!(
89            compile("Hello {{{name}}}!"),
90            "write!(f, \"Hello {}!\", self.name.as_display())?;"
91        );
92    }
93
94    #[test]
95    fn test_if(){
96        let rust = compile("{{#if some}}Hello{{/if}}");
97        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}");
98    }
99
100    #[test]
101    fn test_else(){
102        let rust = compile("{{#if some}}Hello{{else}}World{{/if}}");
103        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}else{write!(f, \"World\")?;}");
104    }
105
106    #[test]
107    fn test_unless(){
108        let rust = compile("{{#unless some}}Hello{{/unless}}");
109        assert_eq!(rust, "if !self.some.as_bool(){write!(f, \"Hello\")?;}");
110    }
111
112    #[test]
113    fn test_each(){
114        let rust = compile("{{#each some}}Hello {{this}}{{/each}}");
115        assert_eq!(rust, "for this_1 in self.some{write!(f, \"Hello {}\", this_1.as_display_html())?;}");
116    }
117
118    #[test]
119    fn test_with(){
120        let rust = compile("{{#with some}}Hello {{name}}{{/with}}");
121        assert_eq!(rust, "{let this_1 = self.some;write!(f, \"Hello {}\", this_1.name.as_display_html())?;}");
122    }
123
124    #[test]
125    fn test_nesting(){
126        let rust = compile("{{#if some}}{{#each some}}Hello {{this}}{{/each}}{{/if}}");
127        assert_eq!(rust, "if self.some.as_bool(){for this_2 in self.some{write!(f, \"Hello {}\", this_2.as_display_html())?;}}");
128    }
129
130    #[test]
131    fn test_as(){
132        let rust = compile("{{#if some}}{{#each some as thing}}Hello {{thing}} {{thing.name}}{{/each}}{{/if}}");
133        assert_eq!(rust, "if self.some.as_bool(){for thing_2 in self.some{write!(f, \"Hello {} {}\", thing_2.as_display_html(), thing_2.name.as_display_html())?;}}");
134    }
135
136    #[test]
137    fn test_comment(){
138        let rust = compile("Note: {{! This is a comment }} and {{!-- {{so is this}} --}}\\{{{{}}");
139        assert_eq!(rust, "write!(f, \"Note:  and {{{{\")?;");
140    }
141
142    #[test]
143    fn test_scoping(){
144        let rust = compile("{{#with some}}{{#with other}}Hello {{name}} {{../company}} {{/with}}{{/with}}");
145        assert_eq!(rust, "{let this_1 = self.some;{let this_2 = this_1.other;write!(f, \"Hello {} {} \", this_2.name.as_display_html(), this_1.company.as_display_html())?;}}");
146    }
147
148    #[test]
149    fn test_trimming(){
150        let rust = compile("  {{~#if some ~}}   Hello{{~/if~}}");
151        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}");
152    }
153
154    #[test]
155    fn test_indexer(){
156        let rust = compile("{{#each things}}Hello{{{@index}}}{{#each things}}{{{lookup other @../index}}}{{{@index}}}{{/each}}{{/each}}");
157        assert_eq!(rust, "let mut i_1 = 0;for this_1 in self.things{write!(f, \"Hello{}\", i_1.as_display())?;let mut i_2 = 0;for this_2 in this_1.things{write!(f, \"{}{}\", this_2.other[i_1].as_display(), i_2.as_display())?;i_2+=1;}i_1+=1;}");
158    }
159
160    #[test]
161    fn test_map(){
162        let rust = compile("{{#each things}}Hello{{{@key}}}{{#each @value}}{{#if_some (try_lookup other @../key)}}{{{this}}}{{/if_some}}{{{@value}}}{{/each}}{{/each}}");
163        assert_eq!(rust, "for this_1 in self.things{write!(f, \"Hello{}\", this_1.0.as_display())?;for this_2 in this_1.1{if let Some(this_3) = this_2.other.get(this_1.0){write!(f, \"{}\", this_3.as_display())?;}write!(f, \"{}\", this_2.1.as_display())?;}}");
164    }
165
166    #[test]
167    fn test_subexpression(){
168        let rust = compile("{{#each things}}{{#with (lookup ../other @index) as |other|}}{{{../name}}}: {{{other}}}{{/with}}{{/each}}");
169        assert_eq!(rust, "let mut i_1 = 0;for this_1 in self.things{{let other_2 = self.other[i_1];write!(f, \"{}: {}\", this_1.name.as_display(), other_2.as_display())?;}i_1+=1;}");
170    }
171
172    #[test]
173    fn test_selfless(){
174        let rust = Compiler::new(Options{
175            root_var_name: None,
176            write_var_name: "f"
177        }, make_map()).compile("{{#each things}}{{#with (lookup ../other @index) as |other|}}{{{../name}}}: {{{other}}}{{/with}}{{/each}}").unwrap();
178        assert_eq!(rust.uses().to_string(), "use rusty_handlebars::AsDisplay");
179        assert_eq!(rust.code, "let mut i_1 = 0;for this_1 in things{{let other_2 = other[i_1];write!(f, \"{}: {}\", this_1.name.as_display(), other_2.as_display())?;}i_1+=1;}");
180    }
181
182    #[test]
183    fn javascript(){
184        let rust = Compiler::new(OPTIONS, make_map()).compile("<script>if (location.href.contains(\"localhost\")){ console.log(\"\\{{{{}}}}\") }</script>").unwrap();
185        assert_eq!(rust.uses().to_string(), "");
186        assert_eq!(rust.code, "write!(f, \"<script>if (location.href.contains(\\\"localhost\\\")){{ console.log(\\\"{{{{}}}}\\\") }}</script>\")?;");
187    }
188
189    #[test]
190    fn if_some(){
191        let rust = compile("{{#if_some some}}Hello {{name}}{{else}}Oh dear{{/if_some}}{{#if some}}{{#if_some_ref ../some as |other|}}Hello {{other.name}}{{/if_some}}{{/if}}");
192        assert_eq!(rust, "if let Some(this_1) = self.some{write!(f, \"Hello {}\", this_1.name.as_display_html())?;}else{write!(f, \"Oh dear\")?;}if self.some.as_bool(){if let Some(other_2) = &self.some{write!(f, \"Hello {}\", other_2.name.as_display_html())?;}}");
193    }
194
195    #[test]
196    fn test_escaped(){
197        let rust = compile("{{{{skip}}}}wang doodle {{{{/dandy}}}}{{{{/skip}}}}");
198        assert_eq!(rust, "write!(f, \"wang doodle {{{{{{{{/dandy}}}}}}}}\")?;");
199    }
200}