├── .gitignore ├── .gitmodules ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── examples └── vector_iteration.rs ├── rustfmt.toml ├── src ├── builder.rs ├── compiler.rs ├── encoder.rs ├── error.rs ├── lib.rs ├── macros.rs ├── parser.rs └── template.rs └── tests ├── builder.rs ├── macros.rs ├── template.rs ├── test-data ├── base.foo.mustache ├── base.mustache ├── test.mustache ├── user.mustache └── username.mustache └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.dylib 3 | *.dSYM 4 | bin/ 5 | target/ 6 | lib/ 7 | .rust/ 8 | Cargo.lock 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spec"] 2 | path = spec 3 | url = https://github.com/mustache/spec 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | 4 | cache: 5 | directories: 6 | - target 7 | 8 | rust: 9 | - 1.16.0 10 | - 1.17.0 11 | - stable 12 | - nightly 13 | - beta 14 | 15 | before_script: 16 | - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH 17 | 18 | script: 19 | - | 20 | travis-cargo build && 21 | travis-cargo test && 22 | travis-cargo doc && 23 | echo "Testing README" && 24 | rustdoc --test README.md -L dependency=./target/debug/deps --extern mustache=./target/debug/libmustache.rlib --extern rustc_serialize=./target/debug/deps/librustc_serialize 25 | 26 | after_success: 27 | - | 28 | travis-cargo --only stable doc-upload 29 | 30 | env: 31 | global: 32 | secure: dKGat/Zb9K7hjdKnQGeKIDlBkBPA+vGZsc9h43WC9SyCQM8srwg2+A0q/Bu59T35BuTXoT1J1mo4f5HztTK0cRS3eJX6nBflTy7g/mrkrsTT8wYNfcZUu0FyXV8xozOohBXW4xMSSLUmaz3VGrbWrHH31Wb8RexVBDmWWGyv810= 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mustache" 3 | description = "Rust implementation of Mustache" 4 | repository = "https://github.com/nickel-org/rust-mustache" 5 | documentation = "http://nickel-org.github.io/rust-mustache" 6 | version = "0.9.0" 7 | authors = ["erick.tryzelaar@gmail.com"] 8 | license = "MIT/Apache-2.0" 9 | 10 | [features] 11 | unstable = [] 12 | 13 | [dependencies] 14 | log = "0.3.5" 15 | serde = "1.0.0" 16 | 17 | [dev-dependencies] 18 | serde_derive = "1.0.0" 19 | serde_json = "1.0.0" 20 | tempdir = "0.3.4" 21 | 22 | [[test]] 23 | name = "test" 24 | path = "tests/test.rs" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Erick Tryzelaar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | 23 | Inspired by https://github.com/vspy/mustache: 24 | 25 | Copyright (c) 2010 Victor Bilyk 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining 28 | a copy of this software and associated documentation files (the 29 | "Software"), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to 32 | permit persons to whom the Software is furnished to do so, subject to 33 | the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be 36 | included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 39 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 41 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 42 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 43 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 44 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mustache [![Ohloh statistics](http://www.ohloh.net/p/rust-mustache/widgets/project_thin_badge.gif)](https://www.ohloh.net/p/rust-mustache) [![Build Status](http://travis-ci.org/nickel-org/rust-mustache.png?branch=master)](https://travis-ci.org/nickel-org/rust-mustache) [![](http://meritbadge.herokuapp.com/mustache)](https://crates.io/crates/mustache) 2 | ======== 3 | 4 | Inspired by [ctemplate][1] and [et][2], [Mustache][3] is a framework-agnostic way 5 | to render logic-free views. 6 | 7 | As ctemplates says, "It emphasizes separating logic from presentation: it is 8 | impossible to embed application logic in this template language." 9 | 10 | rust-mustache is a rust implementation of Mustache. 11 | 12 | ## Documentation 13 | 14 | The different Mustache tags are documented at [mustache(5)][4]. 15 | 16 | Documentation for this library is [here][5]. 17 | 18 | ## Install 19 | 20 | Install it through Cargo! 21 | 22 | ```toml 23 | [dependencies] 24 | mustache = "*" 25 | ``` 26 | 27 | # Basic example 28 | 29 | ```rust 30 | extern crate mustache; 31 | extern crate serde; 32 | 33 | use std::io; 34 | use mustache::MapBuilder; 35 | 36 | #[derive(Serde)] 37 | struct Planet { 38 | name: String, 39 | } 40 | 41 | fn main() { 42 | // First the string needs to be compiled. 43 | let template = mustache::compile_str("hello {{name}}").unwrap(); 44 | 45 | // You can either use an encodable type to print "hello Mercury". 46 | let planet = Planet { name: "Mercury".into() }; 47 | template.render(&mut io::stdout(), &planet).unwrap(); 48 | println!(""); 49 | 50 | // ... or you can use a builder to print "hello Venus". 51 | let data = MapBuilder::new() 52 | .insert_str("name", "Venus") 53 | .build(); 54 | 55 | template.render_data(&mut io::stdout(), &data).unwrap(); 56 | println!(""); 57 | 58 | // ... you can even use closures. 59 | let mut planets = vec!("Jupiter", "Mars", "Earth"); 60 | 61 | let data = MapBuilder::new() 62 | .insert_fn("name", move |_| { 63 | planets.pop().unwrap().into() 64 | }) 65 | .build(); 66 | 67 | // prints "hello Earth" 68 | template.render_data(&mut io::stdout(), &data).unwrap(); 69 | println!(""); 70 | 71 | // prints "hello Mars" 72 | template.render_data(&mut io::stdout(), &data).unwrap(); 73 | println!(""); 74 | 75 | // prints "hello Jupiter" 76 | template.render_data(&mut io::stdout(), &data).unwrap(); 77 | println!(""); 78 | } 79 | ``` 80 | 81 | ## Testing 82 | 83 | Simply clone and run: 84 | 85 | ```bash 86 | cargo test 87 | ``` 88 | 89 | If you want to run the test cases, you'll need the spec as well. 90 | 91 | ```bash 92 | git submodule init 93 | git submodule update 94 | cargo test 95 | ``` 96 | 97 | [1]: http://code.google.com/p/google-ctemplate/ 98 | [2]: http://www.ivan.fomichev.name/2008/05/erlang-template-engine-prototype.html 99 | [3]: https://mustache.github.io/ 100 | [4]: http://mustache.github.com/mustache.5.html 101 | [5]: http://nickel.rs/rust-mustache/mustache/index.html 102 | 103 | # License 104 | 105 | See LICENSE File 106 | -------------------------------------------------------------------------------- /examples/vector_iteration.rs: -------------------------------------------------------------------------------- 1 | extern crate serde; 2 | #[macro_use] 3 | extern crate serde_derive; 4 | 5 | extern crate mustache; 6 | 7 | use std::str; 8 | use std::collections::HashMap; 9 | 10 | #[derive(Serialize)] 11 | struct User { 12 | name: String 13 | } 14 | 15 | fn main() { 16 | let template ="{{#users}}\ 17 | Hello {{name}}! 18 | {{/users}}"; 19 | 20 | let template = mustache::compile_str(template).expect("Failed to compile"); 21 | 22 | let users = vec![ 23 | User { name: "Harry".into() }, 24 | User { name: "Samantha".into() } 25 | ]; 26 | 27 | let mut data = HashMap::new(); 28 | data.insert("users", users); 29 | 30 | let mut bytes = vec![]; 31 | template.render(&mut bytes, &data).expect("Failed to render"); 32 | 33 | assert_eq!(str::from_utf8(&bytes), Ok("Hello Harry!\nHello Samantha!\n")) 34 | } 35 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | write_mode = "Overwrite" 2 | where_indent = "Inherit" 3 | take_source_hints = false 4 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::string::ToString; 3 | use std::collections::HashMap; 4 | use serde::Serialize; 5 | 6 | use encoder::Error; 7 | use super::{Data, to_data}; 8 | 9 | /// `MapBuilder` is a helper type that construct `Data` types. 10 | #[derive(Default)] 11 | pub struct MapBuilder { 12 | data: HashMap, 13 | } 14 | 15 | impl MapBuilder { 16 | /// Create a `MapBuilder` 17 | #[inline] 18 | pub fn new() -> MapBuilder { 19 | MapBuilder::default() 20 | } 21 | 22 | /// Add an `Encodable` to the `MapBuilder`. 23 | /// 24 | /// ```rust 25 | /// use mustache::MapBuilder; 26 | /// let data = MapBuilder::new() 27 | /// .insert("name", &("Jane Austen")).expect("Failed to encode name") 28 | /// .insert("age", &41usize).expect("Failed to encode age") 29 | /// .build(); 30 | /// ``` 31 | #[inline] 32 | pub fn insert(self, key: K, value: &T) -> Result 33 | where 34 | K: Into, 35 | T: Serialize, 36 | { 37 | let MapBuilder { mut data } = self; 38 | let value = to_data(value)?; 39 | data.insert(key.into(), value); 40 | Ok(MapBuilder { data: data }) 41 | } 42 | 43 | /// Add a `String` to the `MapBuilder`. 44 | /// 45 | /// ```rust 46 | /// use mustache::MapBuilder; 47 | /// let data = MapBuilder::new() 48 | /// .insert_str("name", "Jane Austen") 49 | /// .build(); 50 | /// ``` 51 | #[inline] 52 | pub fn insert_str(self, key: K, value: V) -> MapBuilder 53 | where 54 | K: Into, 55 | V: Into, 56 | { 57 | let MapBuilder { mut data } = self; 58 | data.insert(key.into(), Data::String(value.into())); 59 | MapBuilder { data: data } 60 | } 61 | 62 | /// Add a `bool` to the `MapBuilder`. 63 | /// 64 | /// ```rust 65 | /// use mustache::MapBuilder; 66 | /// let data = MapBuilder::new() 67 | /// .insert_bool("show", true) 68 | /// .build(); 69 | /// ``` 70 | #[inline] 71 | pub fn insert_bool(self, key: K, value: bool) -> MapBuilder 72 | where 73 | K: Into, 74 | { 75 | let MapBuilder { mut data } = self; 76 | data.insert(key.into(), Data::Bool(value)); 77 | MapBuilder { data: data } 78 | } 79 | 80 | /// Add a `Vec` to the `MapBuilder`. 81 | /// 82 | /// ```rust 83 | /// use mustache::MapBuilder; 84 | /// let data = MapBuilder::new() 85 | /// .insert_vec("authors", |builder| { 86 | /// builder 87 | /// .push_str("Jane Austen") 88 | /// .push_str("Lewis Carroll") 89 | /// }) 90 | /// .build(); 91 | /// ``` 92 | #[inline] 93 | pub fn insert_vec(self, key: K, mut f: F) -> MapBuilder 94 | where K: Into, 95 | F: FnMut(VecBuilder) -> VecBuilder 96 | { 97 | let MapBuilder { mut data } = self; 98 | let builder = f(VecBuilder::new()); 99 | data.insert(key.into(), builder.build()); 100 | MapBuilder { data: data } 101 | } 102 | 103 | /// Add a `Map` to the `MapBuilder`. 104 | /// 105 | /// ```rust 106 | /// use mustache::MapBuilder; 107 | /// let data = MapBuilder::new() 108 | /// .insert_map("person1", |builder| { 109 | /// builder 110 | /// .insert_str("first_name", "Jane") 111 | /// .insert_str("last_name", "Austen") 112 | /// }) 113 | /// .insert_map("person2", |builder| { 114 | /// builder 115 | /// .insert_str("first_name", "Lewis") 116 | /// .insert_str("last_name", "Carroll") 117 | /// }) 118 | /// .build(); 119 | /// ``` 120 | #[inline] 121 | pub fn insert_map(self, key: K, mut f: F) -> MapBuilder 122 | where 123 | K: Into, 124 | F: FnMut(MapBuilder) -> MapBuilder, 125 | { 126 | let MapBuilder { mut data } = self; 127 | let builder = f(MapBuilder::new()); 128 | data.insert(key.into(), builder.build()); 129 | MapBuilder { data: data } 130 | } 131 | 132 | /// Add a function to the `MapBuilder`. 133 | /// 134 | /// ```rust 135 | /// use mustache::MapBuilder; 136 | /// let mut count = 0; 137 | /// let data = MapBuilder::new() 138 | /// .insert_fn("increment", move |_| { 139 | /// count += 1usize; 140 | /// count.to_string() 141 | /// }) 142 | /// .build(); 143 | /// ``` 144 | #[inline] 145 | pub fn insert_fn(self, key: K, f: F) -> MapBuilder 146 | where F: FnMut(String) -> String + Send + 'static 147 | { 148 | let MapBuilder { mut data } = self; 149 | data.insert(key.to_string(), Data::Fun(RefCell::new(Box::new(f)))); 150 | MapBuilder { data: data } 151 | } 152 | 153 | /// Return the built `Data`. 154 | #[inline] 155 | pub fn build(self) -> Data { 156 | Data::Map(self.data) 157 | } 158 | } 159 | 160 | #[derive(Default)] 161 | pub struct VecBuilder { 162 | data: Vec, 163 | } 164 | 165 | impl VecBuilder { 166 | /// Create a `VecBuilder` 167 | #[inline] 168 | pub fn new() -> VecBuilder { 169 | VecBuilder::default() 170 | } 171 | 172 | /// Add an `Encodable` to the `VecBuilder`. 173 | /// 174 | /// ```rust 175 | /// use mustache::{VecBuilder, Data}; 176 | /// let data: Data = VecBuilder::new() 177 | /// .push(& &"Jane Austen").unwrap() 178 | /// .push(&41usize).unwrap() 179 | /// .build(); 180 | /// ``` 181 | #[inline] 182 | pub fn push(self, value: &T) -> Result { 183 | let VecBuilder { mut data } = self; 184 | let value = to_data(value)?; 185 | data.push(value); 186 | Ok(VecBuilder { data: data }) 187 | } 188 | 189 | /// Add a `String` to the `VecBuilder`. 190 | /// 191 | /// ```rust 192 | /// use mustache::VecBuilder; 193 | /// let data = VecBuilder::new() 194 | /// .push_str("Jane Austen") 195 | /// .push_str("Lewis Carroll") 196 | /// .build(); 197 | /// ``` 198 | #[inline] 199 | pub fn push_str(self, value: T) -> VecBuilder { 200 | let VecBuilder { mut data } = self; 201 | data.push(Data::String(value.to_string())); 202 | VecBuilder { data: data } 203 | } 204 | 205 | /// Add a `bool` to the `VecBuilder`. 206 | /// 207 | /// ```rust 208 | /// use mustache::VecBuilder; 209 | /// let data = VecBuilder::new() 210 | /// .push_bool(false) 211 | /// .push_bool(true) 212 | /// .build(); 213 | /// ``` 214 | #[inline] 215 | pub fn push_bool(self, value: bool) -> VecBuilder { 216 | let VecBuilder { mut data } = self; 217 | data.push(Data::Bool(value)); 218 | VecBuilder { data: data } 219 | } 220 | 221 | /// Add a `Vec` to the `MapBuilder`. 222 | /// 223 | /// ```rust 224 | /// use mustache::VecBuilder; 225 | /// let data = VecBuilder::new() 226 | /// .push_vec(|builder| { 227 | /// builder 228 | /// .push_str("Jane Austen".to_string()) 229 | /// .push_str("Lewis Carroll".to_string()) 230 | /// }) 231 | /// .build(); 232 | /// ``` 233 | #[inline] 234 | pub fn push_vec(self, mut f: F) -> VecBuilder 235 | where F: FnMut(VecBuilder) -> VecBuilder 236 | { 237 | let VecBuilder { mut data } = self; 238 | let builder = f(VecBuilder::new()); 239 | data.push(builder.build()); 240 | VecBuilder { data: data } 241 | } 242 | 243 | /// Add a `Map` to the `VecBuilder`. 244 | /// 245 | /// ```rust 246 | /// use mustache::VecBuilder; 247 | /// let data = VecBuilder::new() 248 | /// .push_map(|builder| { 249 | /// builder 250 | /// .insert_str("first_name".to_string(), "Jane".to_string()) 251 | /// .insert_str("last_name".to_string(), "Austen".to_string()) 252 | /// }) 253 | /// .push_map(|builder| { 254 | /// builder 255 | /// .insert_str("first_name".to_string(), "Lewis".to_string()) 256 | /// .insert_str("last_name".to_string(), "Carroll".to_string()) 257 | /// }) 258 | /// .build(); 259 | /// ``` 260 | #[inline] 261 | pub fn push_map(self, mut f: F) -> VecBuilder 262 | where F: FnMut(MapBuilder) -> MapBuilder 263 | { 264 | let VecBuilder { mut data } = self; 265 | let builder = f(MapBuilder::new()); 266 | data.push(builder.build()); 267 | VecBuilder { data: data } 268 | } 269 | 270 | /// Add a function to the `VecBuilder`. 271 | /// 272 | /// ```rust 273 | /// use mustache::VecBuilder; 274 | /// let mut count = 0; 275 | /// let data = VecBuilder::new() 276 | /// .push_fn(move |s| { 277 | /// count += 1usize; 278 | /// s + &count.to_string() 279 | /// }) 280 | /// .build(); 281 | /// ``` 282 | #[inline] 283 | pub fn push_fn(self, f: F) -> VecBuilder 284 | where F: FnMut(String) -> String + Send + 'static 285 | { 286 | let VecBuilder { mut data } = self; 287 | data.push(Data::Fun(RefCell::new(Box::new(f)))); 288 | VecBuilder { data: data } 289 | } 290 | 291 | #[inline] 292 | pub fn build(self) -> Data { 293 | Data::Vec(self.data) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::io::ErrorKind::NotFound; 3 | use std::io::Read; 4 | use std::fs::File; 5 | 6 | use parser::{Parser, Token}; 7 | use super::Context; 8 | 9 | use Result; 10 | 11 | pub type PartialsMap = HashMap>; 12 | 13 | /// `Compiler` is a object that compiles a string into a `Vec`. 14 | pub struct Compiler { 15 | ctx: Context, 16 | reader: T, 17 | partials: PartialsMap, 18 | otag: String, 19 | ctag: String, 20 | } 21 | 22 | impl> Compiler { 23 | /// Construct a default compiler. 24 | pub fn new(ctx: Context, reader: T) -> Compiler { 25 | Compiler { 26 | ctx: ctx, 27 | reader: reader, 28 | partials: HashMap::new(), 29 | otag: "{{".to_string(), 30 | ctag: "}}".to_string(), 31 | } 32 | } 33 | 34 | /// Construct a default compiler. 35 | pub fn new_with(ctx: Context, 36 | reader: T, 37 | partials: PartialsMap, 38 | otag: String, 39 | ctag: String) 40 | -> Compiler { 41 | Compiler { 42 | ctx: ctx, 43 | reader: reader, 44 | partials: partials, 45 | otag: otag, 46 | ctag: ctag, 47 | } 48 | } 49 | 50 | /// Compiles a template into a series of tokens. 51 | pub fn compile(mut self) -> Result<(Vec, PartialsMap)> { 52 | let (tokens, partials) = { 53 | let parser = Parser::new(&mut self.reader, &self.otag, &self.ctag); 54 | try!(parser.parse()) 55 | }; 56 | 57 | // Compile the partials if we haven't done so already. 58 | for name in partials.into_iter() { 59 | let path = 60 | self.ctx.template_path.join(&(name.clone() + "." + &self.ctx.template_extension)); 61 | 62 | if !self.partials.contains_key(&name) { 63 | // Insert a placeholder so we don't recurse off to infinity. 64 | self.partials.insert(name.to_string(), Vec::new()); 65 | 66 | match File::open(&path) { 67 | Ok(mut file) => { 68 | let mut string = String::new(); 69 | try!(file.read_to_string(&mut string)); 70 | 71 | let compiler = Compiler { 72 | ctx: self.ctx.clone(), 73 | reader: string.chars(), 74 | partials: self.partials.clone(), 75 | otag: "{{".to_string(), 76 | ctag: "}}".to_string(), 77 | }; 78 | 79 | let (tokens, subpartials) = try!(compiler.compile()); 80 | 81 | // Include subpartials 82 | self.partials.extend(subpartials.into_iter()); 83 | 84 | // Set final compiled tokens for *this* partial 85 | self.partials.insert(name, tokens); 86 | } 87 | // Ignore missing files. 88 | Err(ref e) if e.kind() == NotFound => {}, 89 | Err(e) => return Err(e.into()), 90 | } 91 | } 92 | } 93 | 94 | let Compiler { partials, .. } = self; 95 | 96 | Ok((tokens, partials)) 97 | } 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use std::path::PathBuf; 103 | 104 | use parser::Token; 105 | use compiler::Compiler; 106 | use context::Context; 107 | 108 | fn compile_str(template: &str) -> Vec { 109 | let ctx = Context::new(PathBuf::from(".")); 110 | let (tokens, _) = Compiler::new(ctx, template.chars()) 111 | .compile() 112 | .expect("Failed to compile"); 113 | tokens 114 | } 115 | 116 | fn check_tokens(actual: Vec, expected: &[Token]) { 117 | assert_eq!(actual, expected); 118 | } 119 | 120 | #[test] 121 | fn test_compile_texts() { 122 | check_tokens(compile_str("hello world"), 123 | &[Token::Text("hello world".to_string())]); 124 | check_tokens(compile_str("hello {world"), 125 | &[Token::Text("hello {world".to_string())]); 126 | check_tokens(compile_str("hello world}"), 127 | &[Token::Text("hello world}".to_string())]); 128 | check_tokens(compile_str("hello world}}"), 129 | &[Token::Text("hello world}}".to_string())]); 130 | } 131 | 132 | #[test] 133 | fn test_compile_etags() { 134 | check_tokens(compile_str("{{ name }}"), 135 | &[Token::EscapedTag(vec!["name".to_string()], "{{ name }}".to_string())]); 136 | 137 | check_tokens(compile_str("before {{name}} after"), 138 | &[Token::Text("before ".to_string()), 139 | Token::EscapedTag(vec!["name".to_string()], "{{name}}".to_string()), 140 | Token::Text(" after".to_string())]); 141 | 142 | check_tokens(compile_str("before {{name}}"), 143 | &[Token::Text("before ".to_string()), 144 | Token::EscapedTag(vec!["name".to_string()], "{{name}}".to_string())]); 145 | 146 | check_tokens(compile_str("{{name}} after"), 147 | &[Token::EscapedTag(vec!["name".to_string()], "{{name}}".to_string()), 148 | Token::Text(" after".to_string())]); 149 | } 150 | 151 | #[test] 152 | fn test_compile_utags() { 153 | check_tokens(compile_str("{{{name}}}"), 154 | &[Token::UnescapedTag(vec!["name".to_string()], "{{{name}}}".to_string())]); 155 | 156 | check_tokens(compile_str("before {{{name}}} after"), 157 | &[Token::Text("before ".to_string()), 158 | Token::UnescapedTag(vec!["name".to_string()], "{{{name}}}".to_string()), 159 | Token::Text(" after".to_string())]); 160 | 161 | check_tokens(compile_str("before {{{name}}}"), 162 | &[Token::Text("before ".to_string()), 163 | Token::UnescapedTag(vec!["name".to_string()], "{{{name}}}".to_string())]); 164 | 165 | check_tokens(compile_str("{{{name}}} after"), 166 | &[Token::UnescapedTag(vec!["name".to_string()], "{{{name}}}".to_string()), 167 | Token::Text(" after".to_string())]); 168 | } 169 | 170 | #[test] 171 | fn test_compile_sections() { 172 | check_tokens(compile_str("{{# name}}{{/name}}"), 173 | &[Token::Section(vec!["name".to_string()], 174 | false, 175 | Vec::new(), 176 | "{{".to_string(), 177 | "{{# name}}".to_string(), 178 | "".to_string(), 179 | "{{/name}}".to_string(), 180 | "}}".to_string())]); 181 | 182 | check_tokens(compile_str("before {{^name}}{{/name}} after"), 183 | &[Token::Text("before ".to_string()), 184 | Token::Section(vec!["name".to_string()], 185 | true, 186 | Vec::new(), 187 | "{{".to_string(), 188 | "{{^name}}".to_string(), 189 | "".to_string(), 190 | "{{/name}}".to_string(), 191 | "}}".to_string()), 192 | Token::Text(" after".to_string())]); 193 | 194 | check_tokens(compile_str("before {{#name}}{{/name}}"), 195 | &[Token::Text("before ".to_string()), 196 | Token::Section( 197 | vec!["name".to_string()], 198 | false, 199 | Vec::new(), 200 | "{{".to_string(), 201 | "{{#name}}".to_string(), 202 | "".to_string(), 203 | "{{/name}}".to_string(), 204 | "}}".to_string())]); 205 | 206 | check_tokens(compile_str("{{#name}}{{/name}} after"), 207 | &[Token::Section( 208 | vec!["name".to_string()], 209 | false, 210 | Vec::new(), 211 | "{{".to_string(), 212 | "{{#name}}".to_string(), 213 | "".to_string(), 214 | "{{/name}}".to_string(), 215 | "}}".to_string()), 216 | Token::Text(" after".to_string())]); 217 | 218 | check_tokens(compile_str("before {{#a}} 1 {{^b}} 2 {{/b}} {{/a}} after"), 219 | &[Token::Text("before ".to_string()), 220 | Token::Section( 221 | vec!["a".to_string()], 222 | false, 223 | vec![ 224 | Token::Text(" 1 ".to_string()), 225 | Token::Section( 226 | vec!["b".to_string()], 227 | true, 228 | vec![Token::Text(" 2 ".to_string())], 229 | "{{".to_string(), 230 | "{{^b}}".to_string(), 231 | " 2 ".to_string(), 232 | "{{/b}}".to_string(), 233 | "}}".to_string(), 234 | ), 235 | Token::Text(" ".to_string()) 236 | ], 237 | "{{".to_string(), 238 | "{{#a}}".to_string(), 239 | " 1 {{^b}} 2 {{/b}} ".to_string(), 240 | "{{/a}}".to_string(), 241 | "}}".to_string()), 242 | Token::Text(" after".to_string())]); 243 | } 244 | 245 | #[test] 246 | fn test_compile_partials() { 247 | check_tokens(compile_str("{{> test}}"), 248 | &[Token::Partial("test".to_string(), "".to_string(), "{{> test}}".to_string())]); 249 | 250 | check_tokens(compile_str("before {{>test}} after"), 251 | &[Token::Text("before ".to_string()), 252 | Token::Partial("test".to_string(), "".to_string(), "{{>test}}".to_string()), 253 | Token::Text(" after".to_string())]); 254 | 255 | check_tokens(compile_str("before {{> test}}"), 256 | &[Token::Text("before ".to_string()), 257 | Token::Partial("test".to_string(), "".to_string(), "{{> test}}".to_string())]); 258 | 259 | check_tokens(compile_str("{{>test}} after"), 260 | &[Token::Partial("test".to_string(), "".to_string(), "{{>test}}".to_string()), 261 | Token::Text(" after".to_string())]); 262 | } 263 | 264 | #[test] 265 | fn test_compile_delimiters() { 266 | check_tokens(compile_str("before {{=<% %>=}}<%name%> after"), 267 | &[Token::Text("before ".to_string()), 268 | Token::EscapedTag(vec!["name".to_string()], "<%name%>".to_string()), 269 | Token::Text(" after".to_string())]); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/encoder.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error; 3 | use std::fmt::{self, Display}; 4 | use std::result; 5 | 6 | use serde::{self, Serialize, ser}; 7 | 8 | use super::{Data, to_data}; 9 | 10 | /// Error type to represent encoding failure. 11 | /// 12 | /// This type is not intended to be matched exhaustively as new variants 13 | /// may be added in future without a version bump. 14 | #[derive(Debug)] 15 | pub enum Error { 16 | NestedOptions, 17 | UnsupportedType, 18 | MissingElements, 19 | KeyIsNotString, 20 | NoDataToEncode, 21 | Message(String), 22 | 23 | #[doc(hidden)] 24 | __Nonexhaustive, 25 | } 26 | 27 | impl serde::ser::Error for Error { 28 | fn custom(msg: T) -> Self { 29 | Error::Message(msg.to_string()) 30 | } 31 | } 32 | 33 | /// Alias for a `Result` with the error type `mustache::encoder::Error`. 34 | pub type Result = result::Result; 35 | 36 | impl fmt::Display for Error { 37 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 38 | use std::error::Error; 39 | self.description().fmt(f) 40 | } 41 | } 42 | 43 | impl error::Error for Error { 44 | fn description(&self) -> &str { 45 | match *self { 46 | Error::NestedOptions => "nested Option types are not supported", 47 | Error::UnsupportedType => "unsupported type", 48 | Error::MissingElements => "no elements in value", 49 | Error::KeyIsNotString => "key is not a string", 50 | Error::NoDataToEncode => "the encodable type created no data", 51 | Error::Message(ref s) => s, 52 | Error::__Nonexhaustive => unreachable!(), 53 | } 54 | } 55 | } 56 | 57 | #[derive(Default)] 58 | pub struct Encoder; 59 | 60 | impl Encoder { 61 | pub fn new() -> Encoder { 62 | Encoder::default() 63 | } 64 | } 65 | 66 | impl serde::Serializer for Encoder { 67 | type Ok = Data; 68 | type Error = Error; 69 | 70 | type SerializeSeq = SerializeVec; 71 | type SerializeTuple = SerializeVec; 72 | type SerializeTupleStruct = SerializeVec; 73 | type SerializeTupleVariant = SerializeTupleVariant; 74 | type SerializeMap = SerializeMap; 75 | type SerializeStruct = SerializeMap; 76 | type SerializeStructVariant = SerializeStructVariant; 77 | 78 | fn serialize_bool(self, v: bool) -> Result { 79 | Ok(Data::Bool(v)) 80 | } 81 | 82 | fn serialize_char(self, v: char) -> Result { 83 | Ok(Data::String(v.to_string())) 84 | } 85 | 86 | fn serialize_u8(self, v: u8) -> Result { 87 | Ok(Data::String(v.to_string())) 88 | } 89 | 90 | fn serialize_i8(self, v: i8) -> Result { 91 | Ok(Data::String(v.to_string())) 92 | } 93 | 94 | fn serialize_u16(self, v: u16) -> Result { 95 | Ok(Data::String(v.to_string())) 96 | } 97 | 98 | fn serialize_i16(self, v: i16) -> Result { 99 | Ok(Data::String(v.to_string())) 100 | } 101 | 102 | fn serialize_u32(self, v: u32) -> Result { 103 | Ok(Data::String(v.to_string())) 104 | } 105 | 106 | fn serialize_i32(self, v: i32) -> Result { 107 | Ok(Data::String(v.to_string())) 108 | } 109 | 110 | fn serialize_i64(self, v: i64) -> Result { 111 | Ok(Data::String(v.to_string())) 112 | } 113 | 114 | fn serialize_u64(self, v: u64) -> Result { 115 | Ok(Data::String(v.to_string())) 116 | } 117 | 118 | fn serialize_f32(self, v: f32) -> Result { 119 | Ok(Data::String(v.to_string())) 120 | } 121 | 122 | fn serialize_f64(self, v: f64) -> Result { 123 | Ok(Data::String(v.to_string())) 124 | } 125 | 126 | fn serialize_str(self, v: &str) -> Result { 127 | Ok(Data::String(v.to_string())) 128 | } 129 | 130 | fn serialize_unit_struct(self, _name: &'static str) -> Result { 131 | // FIXME: Perhaps this could be relaxed to just 'do nothing' 132 | Err(Error::UnsupportedType) 133 | } 134 | 135 | fn serialize_unit_variant( 136 | self, 137 | _name: &'static str, 138 | _variant_index: u32, 139 | variant: &'static str, 140 | ) -> Result 141 | { 142 | // FIXME: Perhaps this could be relaxed to just 'do nothing' 143 | Ok(Data::String(variant.to_string())) 144 | } 145 | 146 | fn serialize_unit(self) -> Result { 147 | Ok(Data::Null) 148 | } 149 | 150 | fn serialize_none(self) -> Result { 151 | Ok(Data::Null) 152 | } 153 | 154 | fn serialize_some(self, value: &T) -> Result 155 | where 156 | T: Serialize 157 | { 158 | value.serialize(self) 159 | } 160 | 161 | fn serialize_struct( 162 | self, 163 | _name: &'static str, 164 | len: usize, 165 | ) -> Result { 166 | self.serialize_map(Some(len)) 167 | } 168 | 169 | fn serialize_struct_variant( 170 | self, 171 | _name: &'static str, 172 | _variant_index: u32, 173 | variant: &'static str, 174 | len: usize, 175 | ) -> Result { 176 | Ok(SerializeStructVariant { 177 | name: String::from(variant), 178 | map: HashMap::with_capacity(len), 179 | }) 180 | } 181 | 182 | fn serialize_newtype_struct( 183 | self, 184 | _name: &'static str, 185 | value: &T, 186 | ) -> Result 187 | where 188 | T: Serialize, 189 | { 190 | // Ignore newtype name 191 | value.serialize(self) 192 | } 193 | 194 | fn serialize_newtype_variant( 195 | self, 196 | _name: &'static str, 197 | _variant_index: u32, 198 | _variant: &'static str, 199 | value: &T, 200 | ) -> Result 201 | where 202 | T: Serialize, 203 | { 204 | // Ignore newtype name 205 | value.serialize(self) 206 | } 207 | 208 | fn serialize_bytes(self, value: &[u8]) -> Result { 209 | let vec = value.iter() 210 | .map(|&b| Data::String(b.to_string())) 211 | .collect(); 212 | 213 | Ok(Data::Vec(vec)) 214 | } 215 | 216 | fn serialize_seq(self, len: Option) -> Result { 217 | Ok(SerializeVec { 218 | vec: Vec::with_capacity(len.unwrap_or(0)), 219 | }) 220 | } 221 | 222 | fn serialize_tuple(self, len: usize) -> Result { 223 | self.serialize_seq(Some(len)) 224 | } 225 | 226 | fn serialize_tuple_struct( 227 | self, 228 | _name: &'static str, 229 | len: usize, 230 | ) -> Result { 231 | self.serialize_seq(Some(len)) 232 | } 233 | 234 | fn serialize_tuple_variant( 235 | self, 236 | _name: &'static str, 237 | _variant_index: u32, 238 | variant: &'static str, 239 | len: usize, 240 | ) -> Result { 241 | Ok(SerializeTupleVariant { 242 | name: String::from(variant), 243 | vec: Vec::with_capacity(len), 244 | }) 245 | } 246 | 247 | fn serialize_map(self, len: Option) -> Result { 248 | Ok(SerializeMap { 249 | map: HashMap::with_capacity(len.unwrap_or(0)), 250 | next_key: None, 251 | }) 252 | } 253 | } 254 | 255 | #[doc(hidden)] 256 | pub struct SerializeVec { 257 | vec: Vec, 258 | } 259 | 260 | #[doc(hidden)] 261 | pub struct SerializeTupleVariant { 262 | name: String, 263 | vec: Vec, 264 | } 265 | 266 | #[doc(hidden)] 267 | pub struct SerializeMap { 268 | map: HashMap, 269 | next_key: Option, 270 | } 271 | 272 | #[doc(hidden)] 273 | pub struct SerializeStructVariant { 274 | name: String, 275 | map: HashMap, 276 | } 277 | 278 | impl ser::SerializeSeq for SerializeVec { 279 | type Ok = Data; 280 | type Error = Error; 281 | 282 | fn serialize_element(&mut self, value: &T) -> Result<()> 283 | where T: Serialize 284 | { 285 | self.vec.push(to_data(&value)?); 286 | Ok(()) 287 | } 288 | 289 | fn end(self) -> Result { 290 | Ok(Data::Vec(self.vec)) 291 | } 292 | } 293 | 294 | impl ser::SerializeTuple for SerializeVec { 295 | type Ok = Data; 296 | type Error = Error; 297 | 298 | fn serialize_element(&mut self, value: &T) -> Result<()> 299 | where T: Serialize 300 | { 301 | ser::SerializeSeq::serialize_element(self, value) 302 | } 303 | 304 | fn end(self) -> Result { 305 | ser::SerializeSeq::end(self) 306 | } 307 | } 308 | 309 | impl ser::SerializeTupleStruct for SerializeVec { 310 | type Ok = Data; 311 | type Error = Error; 312 | 313 | fn serialize_field(&mut self, value: &T) -> Result<()> 314 | where T: Serialize 315 | { 316 | ser::SerializeSeq::serialize_element(self, value) 317 | } 318 | 319 | fn end(self) -> Result { 320 | ser::SerializeSeq::end(self) 321 | } 322 | } 323 | 324 | impl ser::SerializeTupleVariant for SerializeTupleVariant { 325 | type Ok = Data; 326 | type Error = Error; 327 | 328 | fn serialize_field(&mut self, value: &T) -> Result<()> 329 | where T: Serialize 330 | { 331 | self.vec.push(try!(to_data(&value))); 332 | Ok(()) 333 | } 334 | 335 | fn end(self) -> Result { 336 | let mut object = HashMap::new(); 337 | 338 | object.insert(self.name, Data::Vec(self.vec)); 339 | 340 | Ok(Data::Map(object)) 341 | } 342 | } 343 | 344 | impl ser::SerializeMap for SerializeMap { 345 | type Ok = Data; 346 | type Error = Error; 347 | 348 | fn serialize_key(&mut self, key: &T) -> Result<()> 349 | where 350 | T: Serialize 351 | { 352 | match to_data(key)? { 353 | Data::String(s) => { 354 | self.next_key = Some(s); 355 | Ok(()) 356 | } 357 | _ => Err(Error::KeyIsNotString), 358 | } 359 | } 360 | 361 | fn serialize_value(&mut self, value: &T) -> Result<()> 362 | where 363 | T: Serialize 364 | { 365 | let key = self.next_key.take(); 366 | // Panic because this indicates a bug in the program rather than an 367 | // expected failure. 368 | let key = key.expect("serialize_value called before serialize_key"); 369 | self.map.insert(key, try!(to_data(&value))); 370 | Ok(()) 371 | } 372 | 373 | fn end(self) -> Result { 374 | Ok(Data::Map(self.map)) 375 | } 376 | } 377 | 378 | impl ser::SerializeStruct for SerializeMap { 379 | type Ok = Data; 380 | type Error = Error; 381 | 382 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> 383 | where 384 | T: Serialize 385 | { 386 | ser::SerializeMap::serialize_key(self, key)?; 387 | ser::SerializeMap::serialize_value(self, value) 388 | } 389 | 390 | fn end(self) -> Result { 391 | ser::SerializeMap::end(self) 392 | } 393 | } 394 | 395 | impl ser::SerializeStructVariant for SerializeStructVariant { 396 | type Ok = Data; 397 | type Error = Error; 398 | 399 | fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> 400 | where 401 | T: Serialize, 402 | { 403 | self.map.insert(String::from(key), to_data(&value)?); 404 | Ok(()) 405 | } 406 | 407 | fn end(self) -> Result { 408 | let mut object = HashMap::new(); 409 | 410 | object.insert(self.name, Data::Map(self.map)); 411 | 412 | Ok(Data::Map(object)) 413 | } 414 | } 415 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as StdError; 2 | use std::fmt; 3 | use std::io::Error as StdIoError; 4 | use std::result; 5 | 6 | use parser; 7 | use encoder; 8 | 9 | /// Error type for any error within this library. 10 | /// 11 | /// This type is not intended to be matched exhaustively as new variants 12 | /// may be added in future without a version bump. 13 | #[derive(Debug)] 14 | pub enum Error { 15 | InvalidStr, 16 | NoFilename, 17 | Io(StdIoError), 18 | Parser(parser::Error), 19 | Encoder(encoder::Error), 20 | 21 | #[doc(hidden)] 22 | __Nonexhaustive, 23 | } 24 | 25 | pub type Result = result::Result; 26 | 27 | impl fmt::Display for Error { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | self.description().fmt(f) 30 | } 31 | } 32 | 33 | impl StdError for Error { 34 | fn description(&self) -> &str { 35 | match *self { 36 | Error::InvalidStr => "invalid str", 37 | Error::NoFilename => "a filename must be provided", 38 | Error::Io(ref err) => err.description(), 39 | Error::Parser(ref err) => err.description(), 40 | Error::Encoder(ref err) => err.description(), 41 | Error::__Nonexhaustive => unreachable!(), 42 | } 43 | } 44 | } 45 | 46 | impl From for Error { 47 | fn from(err: StdIoError) -> Error { 48 | Error::Io(err) 49 | } 50 | } 51 | 52 | impl From for Error { 53 | fn from(err: parser::Error) -> Error { 54 | Error::Parser(err) 55 | } 56 | } 57 | 58 | impl From for Error { 59 | fn from(err: encoder::Error) -> Error { 60 | Error::Encoder(err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | extern crate serde; 3 | 4 | use std::str; 5 | use std::path::{PathBuf, Path}; 6 | use std::result; 7 | 8 | #[macro_use] 9 | mod macros; 10 | 11 | mod builder; 12 | mod compiler; 13 | mod context; 14 | mod data; 15 | mod encoder; 16 | mod error; 17 | mod parser; 18 | mod template; 19 | 20 | pub use builder::{MapBuilder, VecBuilder}; 21 | pub use context::Context; 22 | pub use data::Data; 23 | pub use encoder::Encoder; 24 | pub use encoder::Error as EncoderError; 25 | pub use encoder::{SerializeVec, SerializeTupleVariant, SerializeMap, SerializeStructVariant}; 26 | pub use error::{Error, Result}; 27 | pub use parser::Error as ParserError; 28 | pub use template::Template; 29 | 30 | pub fn to_data(value: T) -> result::Result 31 | where 32 | T: serde::Serialize, 33 | { 34 | value.serialize(Encoder) 35 | } 36 | 37 | /// Compiles a template from an `Iterator`. 38 | pub fn compile_iter>(iter: T) -> Result