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;
51mod compiler;
52mod block;
53pub mod build_helper;
54
55pub use compiler::*;
56pub use block::*;
57pub use error::*;
58pub use expression::*;
59pub use expression_tokenizer::*;
60
61#[cfg(test)]
62mod tests {
63    use core::str;
64
65    use block::add_builtins;
66    use compiler::{BlockMap, Compiler, Options};
67
68    use crate::*;
69
70    static OPTIONS: Options = Options{
71        root_var_name: Some("self"),
72        write_var_name: "f"
73    };
74
75    fn make_map() -> BlockMap{
76        let mut map = BlockMap::new();
77        add_builtins(&mut map);
78        map
79    }
80
81    fn compile(src: &str) -> String{
82        Compiler::new(OPTIONS, make_map()).compile(src).unwrap().code
83    }
84
85    #[test]
86    fn it_works() {
87        assert_eq!(
88            compile("Hello {{{name}}}!"),
89            "write!(f, \"Hello {}!\", self.name.as_display())?;"
90        );
91    }
92
93    #[test]
94    fn test_if(){
95        let rust = compile("{{#if some}}Hello{{/if}}");
96        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}");
97    }
98
99    #[test]
100    fn test_else(){
101        let rust = compile("{{#if some}}Hello{{else}}World{{/if}}");
102        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}else{write!(f, \"World\")?;}");
103    }
104
105    #[test]
106    fn test_unless(){
107        let rust = compile("{{#unless some}}Hello{{/unless}}");
108        assert_eq!(rust, "if !self.some.as_bool(){write!(f, \"Hello\")?;}");
109    }
110
111    #[test]
112    fn test_each(){
113        let rust = compile("{{#each some}}Hello {{this}}{{/each}}");
114        assert_eq!(rust, "for this_1 in self.some{write!(f, \"Hello {}\", this_1.as_display_html())?;}");
115    }
116
117    #[test]
118    fn test_with(){
119        let rust = compile("{{#with some}}Hello {{name}}{{/with}}");
120        assert_eq!(rust, "{let this_1 = self.some;write!(f, \"Hello {}\", this_1.name.as_display_html())?;}");
121    }
122
123    #[test]
124    fn test_nesting(){
125        let rust = compile("{{#if some}}{{#each some}}Hello {{this}}{{/each}}{{/if}}");
126        assert_eq!(rust, "if self.some.as_bool(){for this_2 in self.some{write!(f, \"Hello {}\", this_2.as_display_html())?;}}");
127    }
128
129    #[test]
130    fn test_as(){
131        let rust = compile("{{#if some}}{{#each some as thing}}Hello {{thing}} {{thing.name}}{{/each}}{{/if}}");
132        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())?;}}");
133    }
134
135    #[test]
136    fn test_comment(){
137        let rust = compile("Note: {{! This is a comment }} and {{!-- {{so is this}} --}}\\{{{{}}");
138        assert_eq!(rust, "write!(f, \"Note:  and {{{{\")?;");
139    }
140
141    #[test]
142    fn test_scoping(){
143        let rust = compile("{{#with some}}{{#with other}}Hello {{name}} {{../company}} {{/with}}{{/with}}");
144        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())?;}}");
145    }
146
147    #[test]
148    fn test_trimming(){
149        let rust = compile("  {{~#if some ~}}   Hello{{~/if~}}");
150        assert_eq!(rust, "if self.some.as_bool(){write!(f, \"Hello\")?;}");
151    }
152
153    #[test]
154    fn test_indexer(){
155        let rust = compile("{{#each things}}Hello{{{@index}}}{{#each things}}{{{lookup other @../index}}}{{{@index}}}{{/each}}{{/each}}");
156        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;}");
157    }
158
159    #[test]
160    fn test_map(){
161        let rust = compile("{{#each things}}Hello{{{@key}}}{{#each @value}}{{#if_some (try_lookup other @../key)}}{{{this}}}{{/if_some}}{{{@value}}}{{/each}}{{/each}}");
162        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())?;}}");
163    }
164
165    #[test]
166    fn test_literals(){
167        let rust = compile("{{#if_some (try_lookup thing \"test\")}}{{this}}{{/if_some}} {{#if_some (try_lookup other_thing 123)}}{{this}}{{/if_some}}");
168        assert_eq!(rust, "if let Some(this_1) = self.thing.get(\"test\"){write!(f, \"{}\", this_1.as_display_html())?;}write!(f, \" \")?;if let Some(this_1) = self.other_thing.get(123){write!(f, \"{}\", this_1.as_display_html())?;}");
169    }
170
171    #[test]
172    fn test_subexpression(){
173        let rust = compile("{{#each things}}{{#with (lookup ../other @index) as |other|}}{{{../name}}}: {{{other}}}{{/with}}{{/each}}");
174        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;}");
175    }
176
177    #[test]
178    fn test_selfless(){
179        let rust = Compiler::new(Options{
180            root_var_name: None,
181            write_var_name: "f"
182        }, make_map()).compile("{{#each things}}{{#with (lookup ../other @index) as |other|}}{{{../name}}}: {{{other}}}{{/with}}{{/each}}").unwrap();
183        assert_eq!(rust.uses("rusty_handlebars").to_string(), "use rusty_handlebars::AsDisplay");
184        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;}");
185    }
186
187    #[test]
188    fn javascript(){
189        let rust = Compiler::new(OPTIONS, make_map()).compile("<script>if (location.href.contains(\"localhost\")){ console.log(\"\\{{{{}}}}\") }</script>").unwrap();
190        assert_eq!(rust.uses("rusty_handlebars").to_string(), "");
191        assert_eq!(rust.code, "write!(f, \"<script>if (location.href.contains(\\\"localhost\\\")){{ console.log(\\\"{{{{}}}}\\\") }}</script>\")?;");
192    }
193
194    #[test]
195    fn if_some(){
196        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}}");
197        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())?;}}");
198    }
199
200    #[test]
201    fn test_escaped(){
202        let rust = compile("{{{{skip}}}}wang doodle {{{{/dandy}}}}{{{{/skip}}}}");
203        assert_eq!(rust, "write!(f, \"wang doodle {{{{{{{{/dandy}}}}}}}}\")?;");
204    }
205
206    #[test]
207    fn test_format_number(){
208        let rust = compile("Price: ${{format \"{:.2}\" price}}");
209        assert_eq!(rust, "write!(f, \"Price: ${:.2}\", self.price)?;");
210    }
211
212    /*#[test]
213    fn test_reports(){
214        const SRC: &str = include_str!("../../examples/templates/reports.hbs");
215        let rust = compile(SRC);
216        assert_eq!(rust, "");
217    }*/
218}