├── .gitignore ├── .template.rs ├── .travis.yml ├── AUTHORS.md ├── COMPATIBILITY.md ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── api ├── error │ ├── macros.rs │ └── mod.rs ├── ext.rs ├── lexer │ ├── error.rs │ ├── job │ │ ├── cursor.rs │ │ └── mod.rs │ ├── mod.rs │ └── options.rs ├── mod.rs ├── node │ └── mod.rs ├── parser │ ├── error.rs │ ├── expression_parser.rs │ ├── job │ │ ├── cursor.rs │ │ └── mod.rs │ ├── mod.rs │ └── options.rs └── token │ ├── error.rs │ ├── mod.rs │ └── stream.rs ├── engine ├── error.rs ├── extension_registry.rs ├── mod.rs ├── options.rs └── template_cache.rs ├── error.rs ├── extension ├── core │ └── mod.rs ├── debug │ └── mod.rs ├── escaper │ └── mod.rs ├── mod.rs └── optimizer │ └── mod.rs ├── lib.rs ├── loader ├── error.rs └── mod.rs ├── setup └── mod.rs └── template ├── compiled └── mod.rs ├── mod.rs └── raw └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | *~ 3 | Cargo.lock 4 | target 5 | 6 | tags 7 | -------------------------------------------------------------------------------- /.template.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | /// Twig library for rust 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | rust: 4 | - stable 5 | - beta 6 | env: 7 | global: 8 | - RUST_BACKTRACE=1 9 | # encrypted github token for doc+coveralls upload 10 | - secure: "K6yfXslYKoELg7DgLleLOdfEjOmblLZlxUQXtHD4Vx6PKR+hFm3t+XS8KxjXDrPf/Hop/GYgh5S67GSmjvgtu3dMD+M6moaFocRP5MAxc5jM1tCY5TRdRYDbCAMuN4kj8kzOHTbctMHpM0FMSnvdPkcCXfuMIOuO3Vt3J4f5llE8FQI54h+8qoYeE3SZnVQ4a6IXHPS6tLBpowdQB3D8pf44g5nNpHDd7kkOs3VlE0lPX1ENQXp6WGPmFpWPXwfY9xOebZDkJ0EZxGLUC9JF81mwPKxEQ+t7EecXx2saNQgggSxyc5lLVX628kj3Fnlkn/jw5pxIuWPRuQ3v8/CFXV+wD5LVVfpv15DluMz3zxw8MRLC7OmMrsnW+ws16Kj7gaRJ966Q1QJX286Lq1IN0MG5e+BqLeibCEVPVCFeaxH0u47ULFSTsNRu0ognaS60smnvkM2iKMNndaob/4fMo+p5bKFEyKre0N52IqiM18IlWzmtfupoK8nRIP46RI34ghRP9NBVJRQtHD+4pE8MAcSJqZAkFjur3sfRkLy1oY0QyZzTb7b1D25REvjKDXJcDmBwmeSoJ+i4F0cTGUhg8DNZATtQ1FTZWtwSCFyNjYnW4jK/Q4i0aStblIlh9BLLXncJX5aVKDrMb+nSGrvmn8g34HLl5xOEWsfVB4IkuUQ=" 11 | addons: 12 | apt: 13 | packages: 14 | - libcurl4-openssl-dev 15 | - libelf-dev 16 | - libdw-dev 17 | - binutils-dev 18 | before_script: | 19 | # load travis cargo 20 | # - https://github.com/huonw/travis-cargo 21 | pip install 'travis-cargo<0.2' --user && 22 | export PATH=$LOCAL/bin:$PATH 23 | script: | 24 | travis-cargo build && 25 | travis-cargo test && 26 | travis-cargo bench && 27 | travis-cargo doc 28 | after_success: | 29 | # upload the documentation from the build with stable (automatically only actually 30 | # runs on the master branch, not individual PRs) 31 | travis-cargo --only stable doc-upload 2> /dev/null # anti-credential leakage 32 | 33 | # measure code coverage and upload to coveralls.io (the verify 34 | # argument mitigates kcov crashes due to malformed debuginfo, at the 35 | # cost of some speed ) 36 | travis-cargo coveralls --no-sudo --verify 2> /dev/null # anti-credential leakage 37 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Rust version of Twig was developed by (in alphabetical order): 2 | 3 | * Colin Kiegel 4 | * Nerijus Arlauskas 5 | 6 | The Rust Twig Project would not have been possible without original Twig. 7 | -------------------------------------------------------------------------------- /COMPATIBILITY.md: -------------------------------------------------------------------------------- 1 | # Differences and Compatibility 2 | 3 | Despite all efforts for compatibility, [rust-web/twig][documentation] and [twigPHP] differ in the following aspects: 4 | 5 | ## Twig Templates 6 | 7 | * **Encoding** - Twig Rust only supports UTF8 encoded templates. Templates must be converted prior usage. 8 | 9 | * **Not yet implemented** - the following functionality is not yet implemented, but about to come. 10 | * ... almost everything. But stay tuned. 11 | 12 | ## Twig Extensions 13 | 14 | * **Rust** - twig-rust extensions must be written in rust, instead of php. (see `src/api` in the [documentation] for details) 15 | 16 | [twigphp]: http://twig.sensiolabs.org/documentation 17 | [documentation]: http://rust-web.github.io/twig 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "twig" 3 | version = "0.1.0" 4 | description = "Twig templating engine for Rust; work in progress." 5 | documentation = "https://rust-web.github.io/twig" 6 | repository = "https://github.com/rust-web/twig" 7 | authors = [ 8 | "Rust Twig Team", 9 | "Nerijus Arlauskas ", 10 | "Colin Kiegel " 11 | ] 12 | license = "BSD-3-Clause" 13 | keywords = ["twig", "template", "templating", "engine", "web", "application", 14 | "html", "language"] 15 | readme = "README.md" 16 | 17 | [dependencies] 18 | regex = "0.1" 19 | quick-error = "0.*" 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 by the rust-web/twig team. 2 | Copyright (c) 2009-2014 by the Twig Team. 3 | 4 | Some rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are 8 | met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following 15 | disclaimer in the documentation and/or other materials provided 16 | with the distribution. 17 | 18 | * The names of the contributors may not be used to endorse or 19 | promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 25 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 26 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 27 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 28 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/rust-web/twig.svg)](https://travis-ci.org/rust-web/twig) 2 | [![Coverage Status](https://coveralls.io/repos/rust-web/twig/badge.svg?branch=master&service=github)](https://coveralls.io/github/rust-web/twig?branch=master) 3 | 4 | # Twig-Rust 5 | 6 | A **template engine** for everyone writing web applications with Rust. By design Twig is 7 | * flexible 8 | * fast 9 | * and secure 10 | 11 | This library is derived from [Twig (for PHP)][twigphp] and intended to become a [_fully compatible_][compatibility] port - as far as it makes sense. 12 | 13 | ## Documentation 14 | 15 | The library code is documented [here][documentation]. 16 | 17 | ## Current Status 18 | 19 | This is an effort to merge two alpha-grade implementations and to proceed from there on 20 | * https://github.com/Nercury/twig-rs 21 | * https://github.com/colin-kiegel/twig-rust 22 | 23 | see issues and pull-requests for more information about next steps. 24 | 25 | [twigphp]: http://twig.sensiolabs.org/documentation 26 | [documentation]: http://rust-web.github.io/twig 27 | [compatibility]: https://github.com/rust-web/twig/blob/master/COMPATIBILITY.md 28 | -------------------------------------------------------------------------------- /src/api/error/macros.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Twig (ported to Rust). 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig macros for error handling 7 | 8 | /// Creates a traced error for the location from which it was invoked. 9 | /// 10 | /// For internal use within the twig library and extensions. 11 | /// 12 | /// * argument type `T` must implement `std::error::Error` 13 | /// * expanded expression has type `Result<_,Traced>` 14 | /// 15 | /// # Examples 16 | /// 17 | /// ```rust,macro_test 18 | /// # #[macro_use] extern crate twig; 19 | /// # fn main() { 20 | /// use twig::api::error::Traced; 21 | /// use std::env::VarError; 22 | /// 23 | /// let result: Result<(), Traced> = traced_err!(VarError::NotPresent); 24 | /// if let Err(error) = result { 25 | /// assert_eq!(error.to_string(), "environment variable not found at :6:53\n"); 26 | /// } 27 | /// # } 28 | /// ``` 29 | #[macro_export] 30 | macro_rules! traced_err { 31 | ( $error:expr ) => ({ 32 | Err($crate::api::error::Traced::new($error, loc!())) 33 | }); 34 | } 35 | /// Expands to the location from which it was invoked. 36 | /// 37 | /// For internal use within the twig library and extensions. 38 | /// 39 | /// * expanded expression has type `twig::api::error::Location` 40 | /// * the returned location is not the invocation of the `loc!()` macro itself, 41 | /// but rather the first macro invocation leading up to the invocation of the `loc!()` macro. 42 | /// 43 | /// # Examples 44 | /// 45 | /// ``` 46 | /// # #[macro_use] extern crate twig; 47 | /// # fn main() { 48 | /// use twig::api::error; 49 | /// use twig::api::error::Location; 50 | /// 51 | /// let here: Location = loc!(); 52 | /// println!("called from: {:?}", here); 53 | /// # } 54 | /// ``` 55 | #[macro_export] 56 | macro_rules! loc { 57 | () => ({ 58 | $crate::api::error::Location { 59 | filename : file!(), 60 | line : line!(), 61 | column : column!(), 62 | } 63 | }); 64 | } 65 | 66 | /// Equivalent to `try!` for traced errors. 67 | /// 68 | /// For internal use within the twig library and extensions. 69 | /// 70 | /// `try_traced!` pushes the current code location to the backtrace of `Traced`. 71 | /// In Twig library and extensions this (or something equivalent) should be used everywhere, 72 | /// where a function is supposed to return early in case of some traced error. 73 | /// 74 | /// If the internal error type `T` implements `Into`, then `try_traced!` will do implicit 75 | /// conversions according to the expected result type of the current function (similar to `try!`). 76 | /// 77 | /// # Examples 78 | /// 79 | /// ```rust,macro_test 80 | /// #[macro_use] extern crate twig; 81 | /// 82 | /// use twig::api::error::{Traced, ErrorExt, Location}; 83 | /// use std::env::VarError; 84 | /// 85 | /// fn foo() -> Result<(), Traced> { 86 | /// let traced = VarError::NotPresent.at(loc!()); 87 | /// 88 | /// // The trace contains one code location. 89 | /// assert_eq!(traced.backtrace().len(), 1); 90 | /// 91 | /// try_traced!(Err(traced)) 92 | /// } 93 | /// 94 | /// 95 | /// fn main() { 96 | /// let traced = foo().unwrap_err(); 97 | /// assert_eq!(traced.backtrace().len(), 2); 98 | /// } 99 | /// ``` 100 | #[macro_export] 101 | macro_rules! try_traced { 102 | ( $result:expr ) => ({ 103 | use $crate::api::error::Traced; 104 | 105 | match $result { 106 | Ok(value) => value, 107 | Err(traced) => return Err(Traced::trace(traced, loc!())), 108 | } 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /src/api/error/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Twig (ported to Rust). 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig Error Handling 7 | //! 8 | //! # Examples 9 | //! 10 | //! ```rust,macro_test 11 | //! #[macro_use] extern crate twig; // Macros must be imported *explicitly* 12 | //! 13 | //! use std::fmt; 14 | //! use twig::api::error::Traced; 15 | //! use std::error::Error; 16 | //! 17 | //! // Implement a custom error code. 18 | //! type MyTracedSimpleError = Traced; 19 | //! 20 | //! #[derive(Debug)] 21 | //! enum MySimpleError { 22 | //! Critical, 23 | //! Recoverable 24 | //! } 25 | //! 26 | //! impl Error for MySimpleError { 27 | //! fn description(&self) -> &str { 28 | //! match *self { 29 | //! MySimpleError::Critical => "Critical error.", 30 | //! MySimpleError::Recoverable => "Recoverable error." 31 | //! } 32 | //! } 33 | //! } 34 | //! 35 | //! impl fmt::Display for MySimpleError { 36 | //! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 37 | //! write!(f, "{} With human readable details", self.description()) 38 | //! } 39 | //! } 40 | //! 41 | //! fn main() { 42 | //! // Create a twig error, wrapping this error code + code location. 43 | //! let result: Result<(), Traced> = traced_err!(MySimpleError::Critical); 44 | //! 45 | //! if let Err(error) = result { 46 | //! assert!(error.to_string().starts_with("Critical error. With human readable details at :")); 47 | //! } 48 | //! } 49 | //! ``` 50 | 51 | #[macro_use] mod macros; 52 | use std::fmt::{self, Debug, Display}; 53 | use std::error::Error; // use std Error-trait to improve cross-crate compatibility 54 | use std::ops::{Deref, DerefMut}; 55 | 56 | /// Extension trait for std::error::Error 57 | /// 58 | /// adds conversion to `Traced` error 59 | pub trait ErrorExt: Error { 60 | /// Returns generic twig error for this error code. 61 | /// You must provide the location, where the error occured. 62 | fn at(self, location: Location) -> Traced 63 | where Self: Sized 64 | { 65 | Traced::new(self, location) 66 | } 67 | } 68 | 69 | impl ErrorExt for T where T: Error {} 70 | 71 | /// Record current state of complex objects 72 | /// 73 | /// The purpose of a dump is to be wrapped in error codes instead of complex objects. 74 | /// This abstraction allows to 75 | /// * keep error codes free of any lifetimes 76 | /// * maintain the ability of receiver of error codes to decide about verbosity 77 | /// 78 | /// The Dump::Data type may not contain lifetimes. 79 | /// In practice this means cloning all referenced data into the dump. 80 | /// 81 | /// For a type `X: Dump` you can reference the associated dump type via `::Data`. 82 | pub trait Dump { 83 | type Data: Debug + Display + 'static; 84 | 85 | fn dump(&self) -> Self::Data; 86 | } 87 | 88 | /// Generic error with backtrace. 89 | /// 90 | /// Wrapper around some error type `T` implementing `std::error::Error`. 91 | /// * Adds support for a backtrace. 92 | pub struct Traced 93 | where T: Error 94 | { 95 | error: T, 96 | trace: Trace, 97 | } 98 | 99 | impl Traced where 100 | T: Error 101 | { 102 | /// Create a new twig error out of some generic error code. 103 | /// 104 | /// # Examples 105 | /// 106 | /// ``` 107 | /// # #[macro_use] extern crate twig; 108 | /// # fn main() { 109 | /// use twig::api::error; 110 | /// use std::env::VarError; 111 | /// 112 | /// error::Traced::new(VarError::NotPresent, loc!()); // shorthand: `trace!(VarError::NotPresent)` 113 | /// # } 114 | /// ``` 115 | pub fn new(error: T, location: Location) -> Traced { 116 | Traced { 117 | error: error, 118 | trace: Trace(vec![location]), 119 | } 120 | } 121 | 122 | /// Return the first location the error occured. 123 | pub fn location(&self) -> Option<&Location> { 124 | self.trace.first() 125 | } 126 | 127 | pub fn backtrace(&self) -> &[Location] { 128 | &self.trace 129 | } 130 | 131 | // should not name it `at`, because `Traced` derefs to `Error` which impl `ErrorExt::at()` 132 | pub fn trace(self, loc: Location) -> Traced 133 | where T: Into, 134 | R: Error 135 | { 136 | let Traced { error, mut trace } = self; 137 | trace.push(loc); 138 | 139 | Traced { 140 | error: error.into(), 141 | trace: trace 142 | } 143 | } 144 | 145 | /// Creates an iterator to iterate along the error cause-chain. 146 | pub fn iter(&self) -> ErrorIter { 147 | ErrorIter { 148 | next: Some(&self.error), 149 | } 150 | } 151 | } 152 | 153 | impl Error for Traced where 154 | T: Error 155 | { 156 | fn description(&self) -> &str { 157 | self.error.description() 158 | } 159 | 160 | fn cause(&self) -> Option<&Error> { 161 | self.error.cause() 162 | } 163 | } 164 | 165 | /// Iterator to iterate along the error cause-chain. 166 | pub struct ErrorIter<'a> { 167 | next: Option<&'a Error> 168 | } 169 | 170 | impl<'a> Iterator for ErrorIter<'a> { 171 | type Item = &'a Error; 172 | 173 | fn next(&mut self) -> Option { 174 | return match self.next { 175 | Some(err) => { 176 | self.next = err.cause(); 177 | Some(err) 178 | } 179 | None => None, 180 | } 181 | } 182 | } 183 | 184 | impl Debug for Traced where 185 | T: Error 186 | { 187 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 188 | f.debug_struct("Traced") 189 | .field("error", &self.error) 190 | .field("at", &self.trace) 191 | .finish() 192 | } 193 | } 194 | 195 | impl Display for Traced where 196 | T: Error 197 | { 198 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 199 | write!(f, "{error} at {trace}\n", 200 | error = self.error, 201 | trace = &self.trace) 202 | } 203 | } 204 | 205 | /// Trace wraps `Vec` with specialized `Display` and `Debug` impls. 206 | /// 207 | /// For everything else it just derefs to `Vec`. 208 | pub struct Trace(Vec); 209 | 210 | impl Deref for Trace { 211 | type Target = Vec; 212 | 213 | fn deref(&self) -> &Self::Target { 214 | let Trace(ref trace) = *self; 215 | 216 | trace 217 | } 218 | } 219 | 220 | impl DerefMut for Trace { 221 | fn deref_mut(&mut self) -> &mut Self::Target { 222 | let Trace(ref mut trace) = *self; 223 | 224 | trace 225 | } 226 | } 227 | 228 | impl Debug for Trace { 229 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 230 | self.iter().enumerate().fold( 231 | &mut f.debug_struct("Trace"), 232 | |s, i| s.field(&format!("#{}", i.0), i.1) 233 | ).finish() 234 | } 235 | } 236 | 237 | impl Display for Trace { 238 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 239 | match self.first() { 240 | Some(location) => write!(f, "{:?}", location), 241 | None => write!(f, "unknown (no backtrace)") 242 | } 243 | } 244 | } 245 | 246 | /// Location in rust source code 247 | /// 248 | /// Debug::fmt() output is formatted in a compact way: `"{filename}:{line}:{column}"`. 249 | #[derive(PartialEq)] 250 | pub struct Location { 251 | pub filename : &'static str, // e.g. /src/lexer/job/state/shared_traits.rs 252 | pub line : u32, 253 | pub column : u32, 254 | } 255 | 256 | impl Debug for Location { 257 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 258 | write!(f, "{filename}:{line}:{column}", 259 | filename = self.filename, 260 | line = self.line, 261 | column = self.column) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/api/ext.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig Extension API 7 | //! 8 | //! Extensions can define new behavior during the compilation process, via **token parser** and 9 | //! **node visitor**. They can also define new *node types* in the abstract syntax tree, like 10 | //! **test**, **unary operator**, **binary operator**, **function**, **filter**, **global** 11 | //! 12 | //! See `twig::extension::core` for example implementations. 13 | //! Note: The core extension is not yet fully implemented (see [CHANGELOG][changelog]). 14 | 15 | use std::fmt; 16 | use engine; 17 | use api::parser::{Job, ParserError}; 18 | use api::token::stream::Item; 19 | use api::Node; 20 | use api::error::Traced; 21 | 22 | 23 | /// Extends the Twig Engine with new behaviour. 24 | pub trait Extension : fmt::Debug { 25 | /// Get the name of the extension. 26 | fn name(&self) -> &'static str; 27 | 28 | /// Initialize and register the extension. 29 | /// 30 | /// This method is supposed to push all filters, functions etc. of the extension to the 31 | /// extension registry builder. 32 | fn init(&mut self, registry: &mut engine::extension_registry::Builder, options: &engine::Options) 33 | -> Result<(), Traced>; // TODO: add error handling ??? 34 | } 35 | 36 | // Abstract extension traits + structs - TODO: check what needs to be trait / can be struct 37 | 38 | /// Can modify the result of variable expressions. 39 | /// 40 | /// E.g. the `default` filter returns the result of the variable expression if it is defined, 41 | /// otherwise it returns the default value. The `escaper` filter escapes the result according 42 | /// to the output channel (html, html attribute, css, js, url, ..) 43 | pub trait Filter : fmt::Debug {} 44 | 45 | /// Can be used to perform complex computations. 46 | /// 47 | /// E.g. the `round` function rounds a floating number with a given precision. 48 | pub trait Function : fmt::Debug {} 49 | 50 | /// Can be used to define global constants. 51 | /// 52 | /// Templates can test for these global constants to trigger conditional behavior, or use 53 | /// them as argument for functions, etc 54 | pub trait Global : fmt::Debug {} 55 | 56 | /// Modifies the abstract syntax tree immediately after parsing. 57 | /// 58 | /// E.g. `twig::extension::optimizer` defines the `optimizeRawFilter` node visitor which strips all "raw" filters from the syntax tree. 59 | pub trait NodeVisitor : fmt::Debug {} 60 | 61 | /// Can be used in conditional Twig statements. 62 | /// 63 | /// E.g. the `defined` test checks if a variable is defined in the current context. 64 | pub trait Test : fmt::Debug {} 65 | 66 | /// Transforms a sub-sequence from the token stream (=lexed template) to nodes in the abstract syntax tree. 67 | /// 68 | /// E.g. the `TokenParserIf` parses complex if-statements (if, elseif, else, endif) and creates the if-node with according child nodes for each test and conditional branch. 69 | /// 70 | /// Note: Token parser are also called 'tag handler' by twig parser. 71 | pub trait TokenParser : fmt::Debug { 72 | fn tag(&self) -> &'static str; 73 | 74 | fn parse(&self, job: &mut Job, item: &Item) -> Result, Traced>; 75 | } 76 | 77 | pub mod token_parser { 78 | use api::token::stream::Item; 79 | 80 | pub type Test = Fn(&Item) -> TestResult; 81 | 82 | #[derive(Debug)] 83 | pub enum TestResult { 84 | Continue, // orig: no_match 85 | KeepToken, // orig: is_match + dropNeedle == false 86 | DropToken, // orig: is_match + dropNeedle == true 87 | } 88 | } 89 | 90 | /// Can be used in variable expressions to process results. 91 | /// 92 | /// E.g. the `-` (neg) operator inverts the sign of a numeric result. 93 | #[derive(Debug, PartialEq)] 94 | pub struct UnaryOperator { 95 | pub repr: String, // token representation like "-" 96 | pub ext: op::Extension, 97 | pub prec: op::Precedence, 98 | pub op: op::Operation, 99 | } 100 | 101 | /// Can be used in variable expressions to combine two results. 102 | /// 103 | /// E.g. the `**` (power) operator takes one number to the power of another number. 104 | #[derive(Debug, PartialEq)] 105 | pub struct BinaryOperator { 106 | pub repr: String, // token representation like "!=" 107 | pub ext: op::Extension, 108 | pub prec: op::Precedence, 109 | pub op: op::Operation, 110 | pub assoc: op::Assoc, 111 | } 112 | 113 | pub mod op { 114 | #[derive(Debug, PartialEq)] 115 | pub struct Extension(String); // might switch to ID for faster lookups 116 | 117 | #[derive(Debug, PartialEq, PartialOrd)] 118 | pub struct Precedence(pub usize); 119 | 120 | #[derive(Debug, PartialEq)] 121 | pub enum Operation { 122 | Class(Class), 123 | Callable(Function) 124 | } 125 | 126 | /// Associativity 127 | #[derive(Debug, PartialEq)] 128 | pub enum Assoc { 129 | Left, 130 | Right, 131 | } 132 | 133 | #[derive(Debug, PartialEq)] 134 | pub struct Function { 135 | name: String 136 | } 137 | 138 | #[derive(Debug, PartialEq)] 139 | pub struct Class { 140 | name: String 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/api/lexer/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Typisation of lexer and syntax errors. 7 | 8 | use std::fmt::{self, Display}; 9 | use std::error::Error; 10 | 11 | use api::token; 12 | use api::lexer::job::cursor; 13 | 14 | #[derive(Debug)] 15 | pub enum SyntaxError { 16 | UnexpectedCharacter { 17 | character: char, 18 | cursor: cursor::CursorDump, 19 | }, 20 | UnexpectedBracket { 21 | bracket: token::BracketType, 22 | cursor: cursor::CursorDump, 23 | }, 24 | UnexpectedEof { 25 | reason: &'static str, 26 | cursor: cursor::CursorDump, 27 | }, 28 | UnclosedBracket { 29 | bracket: token::BracketType, 30 | bracket_before: token::BracketType, 31 | line_before: usize, 32 | cursor: cursor::CursorDump, 33 | }, 34 | UnclosedComment { 35 | cursor: cursor::CursorDump, 36 | }, 37 | UnclosedBlock { 38 | cursor: cursor::CursorDump, 39 | }, 40 | UnclosedVariable { 41 | cursor: cursor::CursorDump 42 | }, 43 | } 44 | 45 | impl Error for SyntaxError { 46 | fn description(&self) -> &str { 47 | match *self { 48 | SyntaxError::UnexpectedCharacter{..} => "Unexpected character.", 49 | SyntaxError::UnexpectedBracket{..} => "Unexpected bracket.", 50 | SyntaxError::UnexpectedEof{..} => "Unexpected end of template.", 51 | SyntaxError::UnclosedBracket{..} => "Unclosed bracket.", 52 | SyntaxError::UnclosedComment{..} => "Unclosed comment.", 53 | SyntaxError::UnclosedBlock{..} => "Unclosed block.", 54 | SyntaxError::UnclosedVariable{..} => "Unclosed variable.", 55 | } 56 | } 57 | } 58 | 59 | impl Display for SyntaxError { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | try!(write!(f, "{}", self.description())); 62 | 63 | match *self { 64 | SyntaxError::UnexpectedCharacter { 65 | character, ref cursor 66 | } => { 67 | write!(f, " found '{c}' at {cursor}.", 68 | c = character, cursor = cursor) 69 | }, 70 | SyntaxError::UnexpectedBracket { 71 | ref cursor, ref bracket 72 | } => { 73 | write!(f, " Unexpected {bracket:?} at {cursor}.", 74 | cursor = cursor, bracket = bracket) 75 | }, 76 | SyntaxError::UnexpectedEof { 77 | reason: ref r, 78 | cursor: ref c 79 | } => { 80 | write!(f, " {reason} at {cursor}.", 81 | reason = r, 82 | cursor = c) 83 | }, 84 | SyntaxError::UnclosedBracket { 85 | ref cursor, ref bracket, ref bracket_before, line_before 86 | } => { 87 | write!(f, " Unclosed {b_before:?} from line\ 88 | {line_before} but found {b:?} at {cursor}.", 89 | cursor = cursor, 90 | b = bracket, 91 | b_before = bracket_before, 92 | line_before = line_before) 93 | }, 94 | SyntaxError::UnclosedComment { 95 | ref cursor 96 | } => { 97 | write!(f, " At {cursor}.", 98 | cursor = cursor) 99 | }, 100 | SyntaxError::UnclosedBlock { 101 | ref cursor 102 | } => { 103 | write!(f, " At {cursor}.", 104 | cursor = cursor) 105 | }, 106 | SyntaxError::UnclosedVariable { 107 | ref cursor 108 | } => { 109 | write!(f, " At {cursor}.", 110 | cursor = cursor) 111 | }, 112 | } 113 | } 114 | } 115 | 116 | #[derive(Debug, PartialEq)] 117 | pub enum LexerError { 118 | PatternRegexError, 119 | _InvalidPatternMatch, 120 | InvalidValue { 121 | value: String 122 | }, 123 | } 124 | 125 | impl Error for LexerError { 126 | fn description(&self) -> &str { 127 | match *self { 128 | LexerError::PatternRegexError => "Could not initialize lexer due to invalid regular expression.", 129 | LexerError::_InvalidPatternMatch => "Invalid pattern match.", 130 | LexerError::InvalidValue{..} => "Invalid value.", 131 | } 132 | } 133 | } 134 | 135 | impl Display for LexerError { 136 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 | try!(write!(f, "{}", self.description())); 138 | 139 | match *self { 140 | LexerError::PatternRegexError => Ok(()), 141 | LexerError::_InvalidPatternMatch => Ok(()), 142 | LexerError::InvalidValue { 143 | ref value 144 | } => { 145 | write!(f, " Found value {}", value) 146 | }, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/api/lexer/job/cursor.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Position within a raw template. 7 | 8 | use std::fmt; 9 | use template; 10 | use api::error::Dump; 11 | 12 | pub type Position = usize; 13 | pub type Line = usize; 14 | 15 | #[derive(Debug)] 16 | pub struct Cursor<'a> { 17 | pos: Position, // 0,.. 18 | end: Position, // 0,.. 19 | line: Line, // 1,.. 20 | template: &'a template::Raw, 21 | } 22 | 23 | impl<'a> fmt::Display for Cursor<'a> { 24 | fn fmt(&self, _f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 25 | unimplemented!() 26 | } 27 | } 28 | 29 | impl<'a> Dump for Cursor<'a> { 30 | type Data = CursorDump; 31 | 32 | fn dump(&self) -> Self::Data { 33 | CursorDump { 34 | pos: self.pos, 35 | end: self.end, 36 | line: self.line, 37 | template: (*self.template).clone() 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct CursorDump { 44 | pos: Position, // 0,.. 45 | end: Position, // 0,.. 46 | line: Line, // 1,.. 47 | template: template::Raw, 48 | } 49 | 50 | impl fmt::Display for CursorDump { 51 | fn fmt(&self, _f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 52 | unimplemented!() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/api/lexer/job/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! A lexer job - modeled as a FSM (Finite State Machine). 7 | 8 | pub mod cursor; 9 | pub use self::cursor::Cursor; 10 | 11 | // Finite State Machine loosely inspired by 12 | // * http://www.huffingtonpost.com/damien-radtke/rustic-state-machines-for_b_4466566.html 13 | 14 | pub struct Job; 15 | -------------------------------------------------------------------------------- /src/api/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Lexes a template string. 7 | 8 | use template; 9 | use api::token; 10 | use api::error::Traced; 11 | 12 | pub mod job; 13 | pub mod error; 14 | pub mod options; 15 | pub use self::error::{LexerError, SyntaxError}; 16 | pub use self::options::Options; 17 | 18 | 19 | #[derive(PartialEq, Debug, Default)] 20 | pub struct Lexer; 21 | 22 | impl Lexer { 23 | pub fn new(_opt: Options) -> Result> { 24 | unimplemented!() 25 | } 26 | 27 | #[allow(dead_code)] // TODO: testcase 28 | pub fn tokenize<'a, 't> (&'a self, _template: &'t template::Raw) -> Result, Traced> 29 | where 't: 'a // the template must outlive the Lexer 30 | { 31 | unimplemented!() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/api/lexer/options.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Options of the lexer. 7 | 8 | use regex; 9 | 10 | #[derive(Debug, PartialEq)] 11 | pub struct OptionData { 12 | raw: String, 13 | quoted: String, 14 | } 15 | 16 | #[derive(Debug, PartialEq)] 17 | pub struct Options { 18 | pub interpolation_start: OptionData, 19 | pub interpolation_end: OptionData, 20 | pub tag_block_start: OptionData, 21 | pub tag_block_end: OptionData, 22 | pub tag_comment_start: OptionData, 23 | pub tag_comment_end: OptionData, 24 | pub tag_expression_start: OptionData, 25 | pub tag_variable_end: OptionData, 26 | pub whitespace_trim: OptionData, 27 | } 28 | 29 | impl<'a> Into for &'a str { 30 | fn into(self) -> OptionData { 31 | OptionData { 32 | raw: self.to_string(), 33 | quoted: regex::quote(&self), 34 | } 35 | } 36 | } 37 | 38 | impl OptionData { 39 | pub fn raw(&self) -> &str { 40 | self.raw.as_ref() 41 | } 42 | 43 | pub fn quoted(&self) -> &str { 44 | self.quoted.as_ref() 45 | } 46 | } 47 | 48 | impl Default for Options { 49 | fn default() -> Options { 50 | Options { 51 | interpolation_start : "#{".into(), 52 | interpolation_end : "}".into(), 53 | tag_block_start : "{%".into(), 54 | tag_block_end : "%}".into(), 55 | tag_comment_start : "{#".into(), 56 | tag_comment_end : "#}".into(), 57 | tag_expression_start : "{{".into(), 58 | tag_variable_end : "}}".into(), 59 | whitespace_trim : "-".into(), 60 | } 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod test { 66 | use super::*; 67 | 68 | #[test] 69 | pub fn default() { 70 | let opt_o = Options::default(); 71 | let opt_x = Options { 72 | interpolation_start : OptionData { raw: "#{".into(), quoted: r"\#\{".into()}, 73 | interpolation_end : OptionData { raw: "}".into(), quoted: r"\}".into()}, 74 | tag_block_start : OptionData { raw: "{%".into(), quoted: r"\{%".into()}, 75 | tag_block_end : OptionData { raw: "%}".into(), quoted: r"%\}".into()}, 76 | tag_comment_start : OptionData { raw: "{#".into(), quoted: r"\{\#".into()}, 77 | tag_comment_end : OptionData { raw: "#}".into(), quoted: r"\#\}".into()}, 78 | tag_expression_start : OptionData { raw: "{{".into(), quoted: r"\{\{".into()}, 79 | tag_variable_end : OptionData { raw: "}}".into(), quoted: r"\}\}".into()}, 80 | whitespace_trim : OptionData { raw: "-".into(), quoted: r"-".into()}, 81 | }; 82 | 83 | assert_eq!(opt_o, opt_x); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! The extensions API for the Twig Template Engine. 7 | 8 | #[macro_use] pub mod error; 9 | pub mod ext; 10 | pub mod lexer; 11 | pub mod node; 12 | pub mod parser; 13 | pub mod token; 14 | pub use self::ext::Extension; 15 | pub use self::lexer::Lexer; 16 | pub use self::parser::Parser; 17 | pub use self::token::Token; 18 | pub use self::node::Node; 19 | -------------------------------------------------------------------------------- /src/api/node/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Node. 7 | 8 | use std::fmt::Debug; 9 | use api::token::stream::Position; 10 | 11 | pub trait Node : Debug { 12 | fn tag(&self) -> &str; 13 | fn position(&self) -> &Position; 14 | fn children(&self) -> &Vec>; 15 | fn children_mut(&mut self) -> &mut Vec>; 16 | } 17 | -------------------------------------------------------------------------------- /src/api/parser/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Typisation of parser errors. 7 | 8 | use std::fmt::{self, Display}; 9 | use std::error::Error; 10 | 11 | use api::parser::job::{self, cursor}; 12 | use api::token; 13 | 14 | #[allow(dead_code)] 15 | #[derive(Debug)] 16 | pub enum ParserError { 17 | TokenParserError { 18 | tag: &'static str, // known at compile-time 19 | error: String, 20 | job: job::JobDump, 21 | }, 22 | NoTagHandler { 23 | tag: String, // only known at runtime 24 | position: token::stream::Position, 25 | job: job::JobDump, 26 | }, 27 | UnexpectedBinaryOperator { 28 | name: String, 29 | job: job::JobDump, 30 | }, 31 | UnexpectedToken { 32 | reason: Option<&'static str>, 33 | expected: token::PatternDump, 34 | found: token::stream::Item, 35 | }, 36 | UnexpectedEof { 37 | reason: Option<&'static str>, 38 | expected: Option, 39 | cursor: cursor::CursorDump, 40 | }, 41 | } 42 | 43 | impl Error for ParserError { 44 | fn description(&self) -> &str { 45 | match *self { 46 | ParserError::TokenParserError{..} => "Token parser error.", 47 | ParserError::NoTagHandler{..} => "There is no registered tag handler for named block.", 48 | ParserError::UnexpectedBinaryOperator{..} => "Unexpected Binary Operator.", 49 | ParserError::UnexpectedToken{..} => "Unexpected Token.", 50 | ParserError::UnexpectedEof{..} => "Unexpected end of token stream.", 51 | } 52 | } 53 | } 54 | 55 | impl Display for ParserError { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | try!(write!(f, "{}", self.description())); 58 | 59 | match *self { 60 | ParserError::TokenParserError { 61 | tag, ref error, ref job 62 | } => { 63 | write!(f, " {tag:?}-block: {error} for job {job}.", 64 | tag = tag, error = error, job = job) 65 | }, 66 | ParserError::NoTagHandler { 67 | tag: ref t, position: ref p, job: ref j 68 | } => { 69 | write!(f, " Found block {tag} at {pos} for job {job}.", 70 | tag = t, pos = p, job = j) 71 | }, 72 | ParserError::UnexpectedBinaryOperator { 73 | name: ref n, job: ref j 74 | } => { 75 | write!(f, " The binary operator {name:?} is unknown to the engine for job {job}", 76 | name = n, 77 | job = j) 78 | }, 79 | ParserError::UnexpectedToken { 80 | reason: r, expected: ref x, found: ref i 81 | } => { 82 | try!(write!(f, " Expected token {x:?} but found {t:?} at {p:?}.", 83 | x = x, t = i.token(), p = i.position())); 84 | 85 | if let Some(reason) = r { 86 | try!(write!(f, " {}", reason)); 87 | } 88 | 89 | Ok(()) 90 | }, 91 | ParserError::UnexpectedEof { 92 | reason, ref expected, ref cursor 93 | } => { 94 | if let Some(ref expected) = *expected { 95 | try!(write!(f, " Expected token to match {:?}.", expected)); 96 | } 97 | 98 | if let Some(reason) = reason { 99 | try!(write!(f, " {}", reason)) 100 | } 101 | 102 | write!(f, " For {}.", cursor) 103 | } 104 | } 105 | } 106 | } 107 | 108 | #[allow(dead_code)] 109 | #[derive(Debug)] 110 | pub enum NodeError { 111 | AttributeNotFound { 112 | key: String, 113 | node_tag: String 114 | } 115 | } 116 | 117 | impl Error for NodeError { 118 | fn description(&self) -> &str { 119 | match *self { 120 | NodeError::AttributeNotFound{..} => "Attribute not found.", 121 | } 122 | } 123 | } 124 | 125 | impl Display for NodeError { 126 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 127 | try!(write!(f, "{}", self.description())); 128 | 129 | match *self { 130 | NodeError::AttributeNotFound{ 131 | ref key, ref node_tag 132 | } => { 133 | write!(f, " Attribute {key:?} does not exist for Node {node:?}.", 134 | key = key, node = node_tag) 135 | }, 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/api/parser/expression_parser.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Expression parser. 7 | 8 | use engine::ExtensionRegistry; 9 | use std::rc::Rc; 10 | 11 | #[derive(Debug)] 12 | pub struct Expression; // dummy 13 | 14 | #[derive(Debug)] 15 | #[allow(dead_code)] 16 | pub struct ExpressionParser { 17 | ext: Rc, 18 | } 19 | -------------------------------------------------------------------------------- /src/api/parser/job/cursor.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Position within a token stream. 7 | 8 | use api::token::stream::{self, Stream}; 9 | use std::fmt; 10 | use api::error::Dump; 11 | 12 | pub type Position = usize; 13 | 14 | #[derive(Debug)] 15 | pub struct Cursor<'stream> { 16 | next: Position, // 0,.. 17 | end: Position, // 0,.. 18 | stream: &'stream Stream<'stream>, // inner lifetime: 'template 19 | } 20 | 21 | impl<'stream> fmt::Display for Cursor<'stream> { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 23 | write!(f, "cursor (next: {next}/{end}) for {tokens:?}", 24 | next = self.next, 25 | end = self.end, 26 | tokens = self.stream) 27 | } 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct CursorDump { 32 | next: Position, 33 | end: Position, 34 | stream_dump: stream::StreamDump, 35 | } 36 | 37 | impl<'stream> Dump for Cursor<'stream> { 38 | type Data = CursorDump; 39 | 40 | fn dump(&self) -> Self::Data { 41 | CursorDump { 42 | next: self.next, 43 | end: self.end, 44 | stream_dump: self.stream.dump(), 45 | } 46 | } 47 | } 48 | 49 | impl fmt::Display for CursorDump { 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | write!(f, "Cursor (next: {next}/{end}) for {stream_dump}", 52 | next = self.next, 53 | end = self.end, 54 | stream_dump = self.stream_dump) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/api/parser/job/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! A parser job. 7 | 8 | pub mod cursor; 9 | pub use self::cursor::Cursor; 10 | 11 | pub struct Job; 12 | pub type JobDump = String; 13 | -------------------------------------------------------------------------------- /src/api/parser/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Parser 7 | 8 | use template; 9 | use api::token; 10 | use api::error::Traced; 11 | 12 | pub mod options; 13 | pub mod error; 14 | pub mod expression_parser; 15 | pub mod job; 16 | pub use self::job::Job; 17 | pub use self::error::ParserError; 18 | pub use self::options::Options; 19 | 20 | #[derive(Debug, Default)] 21 | pub struct Parser; 22 | 23 | impl Parser { 24 | pub fn new(_opt: Options) -> Result> { 25 | unimplemented!() 26 | } 27 | 28 | #[allow(dead_code)] // TODO: testcase 29 | pub fn parse<'a, 't> (&'a self, _stream: &'t token::Stream<'t>) -> Result> 30 | where 't: 'a // the token stream must outlive the Parser 31 | { 32 | unimplemented!() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/api/parser/options.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Options of the parser. 7 | 8 | #[derive(Debug, PartialEq)] 9 | pub struct Options; 10 | 11 | //... should these Options structures have public members, or not? 12 | // PRO: Public members would allow composition / decomposition outside of the respective module without getter/setter-bloat. 13 | // CON: I think public members should not be mixed with logic - i.e. we shouldn't have any impl Options {...} in this case. 14 | // 15 | // My tendency is to use opaque structs with setters+getters 16 | -------------------------------------------------------------------------------- /src/api/token/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Typisation of syntax errors. 7 | 8 | use std::fmt::{self, Display}; 9 | use std::error::Error; 10 | use api::token; 11 | 12 | #[derive(Debug)] 13 | pub enum TokenError { 14 | _NoValue, 15 | UnexpectedTokenAtItem { 16 | reason: Option<&'static str>, 17 | expected: token::PatternDump, 18 | found: token::stream::Item, 19 | } 20 | } 21 | 22 | impl Error for TokenError { 23 | fn description(&self) -> &str { 24 | match *self { 25 | TokenError::_NoValue => "No value.", 26 | TokenError::UnexpectedTokenAtItem{..} => "Unexpected token.", 27 | } 28 | } 29 | } 30 | 31 | impl Display for TokenError { 32 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 33 | try!(write!(f, "{}", self.description())); 34 | 35 | match *self { 36 | TokenError::_NoValue => Ok(()), 37 | TokenError::UnexpectedTokenAtItem { 38 | reason, ref expected, ref found 39 | } => { 40 | try!(write!(f, " Expected token matching {x:?} but found item {t:?} at {p:?}", 41 | x = expected, t = found.token(), p = found.position())); 42 | 43 | if let Some(reason) = reason { 44 | try!(write!(f, " {}", reason)) 45 | } 46 | 47 | Ok(()) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/api/token/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Represents a Token 7 | 8 | use std::fmt; 9 | use api::error::Dump; 10 | 11 | pub mod error; 12 | pub mod stream; 13 | pub use self::stream::Stream; 14 | pub use self::error::TokenError; 15 | 16 | #[derive(PartialEq, Clone)] 17 | pub enum Token { 18 | _Eof, 19 | Text(String), 20 | // .. 21 | } 22 | 23 | #[derive(Debug, PartialEq, Clone)] 24 | pub enum Punctuation { 25 | Dot, 26 | Comma, 27 | Colon, 28 | VerticalBar, 29 | QuestionMark, 30 | OpeningBracket(BracketType), 31 | ClosingBracket(BracketType), 32 | } 33 | 34 | #[derive(Debug, PartialEq, Clone)] 35 | pub enum BracketType { 36 | Round, 37 | Square, 38 | Curly, 39 | DoubleQuote, // Pseudo-Bracket - never being pushed to a real token Stream 40 | // but used as a temporary state of the lexer 41 | } 42 | 43 | #[derive(PartialEq)] 44 | /// Used to define patterns about expected tokens. 45 | /// 46 | /// Example: `token::Type::Text` 47 | pub enum Type { 48 | Eof, 49 | Text, 50 | // .. 51 | } 52 | 53 | #[allow(unused_variables)] 54 | impl Token { 55 | // TODO store String representation for numbers and Punctuation? 56 | // NOTE: Because of Number Types we need to return `String` copys instead of `&'a str` 57 | pub fn value<'a>(&'a self) -> Option { 58 | match *self { 59 | Token::_Eof => None, 60 | Token::Text(ref x) => Some(x.to_string()), 61 | // .. 62 | } 63 | } 64 | 65 | // NOTE: Does *not* yield number types - use value() instead. 66 | pub fn value_as_str<'a>(&'a self) -> Option<&str> { 67 | match *self { 68 | Token::_Eof => None, 69 | Token::Text(ref x) => Some(x), 70 | // .. 71 | } 72 | } 73 | 74 | pub fn get_type(&self) -> Type { 75 | match *self { 76 | Token::_Eof => Type::Eof, 77 | Token::Text(_) => Type::Text, 78 | // .. 79 | } 80 | } 81 | 82 | #[allow(dead_code)] // TODO: testcase 83 | pub fn is_type(&self, typ: Type) -> bool { 84 | self.get_type() == typ 85 | } 86 | } 87 | 88 | impl fmt::Display for Token { 89 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 90 | write!(f, "{}", self.get_type().name()) 91 | } 92 | } 93 | 94 | impl fmt::Debug for Token { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 96 | let typ = self.get_type().name(); 97 | match self.value() { 98 | Some(ref val) => write!(f, "{typ}({val:?})", typ = typ, val = val), 99 | None => write!(f, "{typ}", typ = typ), 100 | } 101 | } 102 | } 103 | 104 | pub type TokenDump = Token; // may change as soon as we use RefTokens 105 | 106 | impl Dump for Token { 107 | type Data = TokenDump; 108 | 109 | fn dump(&self) -> Self::Data { 110 | self.clone() 111 | } 112 | } 113 | 114 | #[allow(unused_variables)] 115 | impl Type { 116 | /// Returns the name of the token type (internal representation). 117 | pub fn name(&self) -> &'static str { 118 | match *self { 119 | Type::Eof => "EOF", 120 | Type::Text => "TEXT", 121 | // .. 122 | } 123 | } 124 | 125 | /// Returns the description of the token type in plain english. 126 | pub fn _description(&self) -> &'static str { 127 | match *self { 128 | Type::Eof => "end of template", 129 | Type::Text => "text", 130 | // .. 131 | } 132 | } 133 | } 134 | 135 | impl fmt::Display for Type { 136 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 137 | write!(f, "{}", self.name()) 138 | } 139 | } 140 | 141 | impl fmt::Debug for Type { 142 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 143 | write!(f, "{}", self.name()) 144 | } 145 | } 146 | 147 | pub trait Pattern: fmt::Debug + fmt::Display { 148 | fn matches(&self, &Token) -> bool; 149 | } 150 | 151 | impl Pattern for Token { 152 | fn matches(&self, token: &Token) -> bool { 153 | *self == *token 154 | } 155 | } 156 | 157 | impl Pattern for Type { 158 | fn matches(&self, token: &Token) -> bool { 159 | *self == token.get_type() 160 | } 161 | } 162 | 163 | pub type PatternDump = String; 164 | 165 | impl Dump for Pattern { 166 | type Data = PatternDump; 167 | 168 | fn dump(&self) -> Self::Data { 169 | format!("{:?}", self) 170 | } 171 | } 172 | 173 | #[cfg(test)] 174 | mod test { 175 | use super::{Token, Type}; 176 | 177 | #[test] 178 | fn new_token() { 179 | let token = Token::Text("Hello World!".to_string()); 180 | assert_eq!(token.value().unwrap(), "Hello World!".to_string()); 181 | assert!(token.is_type(Type::Text)); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/api/token/stream.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Represents a token stream. 7 | 8 | use std::fmt; 9 | use api::token::{self, Token}; 10 | use template; 11 | use api::token::TokenError; 12 | use api::error::{Traced, Dump}; 13 | 14 | #[derive(Debug, Default, Clone)] 15 | pub struct Position { 16 | pub line: usize, 17 | pub column: usize, 18 | } 19 | 20 | impl fmt::Display for Position { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 22 | write!(f, "{line}:{column}", 23 | line = self.line, 24 | column = self.column) 25 | } 26 | } 27 | 28 | #[derive(Debug)] 29 | pub struct Item { 30 | token: Token, 31 | position: Position, 32 | } 33 | 34 | impl Item { 35 | pub fn token(&self) -> &Token { 36 | &self.token 37 | } 38 | 39 | pub fn position(&self) -> &Position { 40 | &self.position 41 | } 42 | 43 | pub fn expect(&self, pattern: T, reason: Option<&'static str>) -> Result<&Item, Traced> 44 | where T: token::Pattern + 'static 45 | { 46 | if pattern.matches(self.token()) { 47 | Ok(&self) 48 | } else { 49 | traced_err!(TokenError::UnexpectedTokenAtItem { 50 | reason: reason, 51 | expected: ::dump(&pattern), 52 | found: self.dump(), 53 | }) 54 | } 55 | } 56 | } 57 | 58 | pub type ItemDump = Item; // may change as soon as we use RefTokens 59 | 60 | impl Dump for Item { 61 | type Data = ItemDump; 62 | 63 | fn dump(&self) -> Self::Data { 64 | ItemDump { 65 | token: self.token.dump(), 66 | position: self.position.clone() 67 | } 68 | } 69 | } 70 | 71 | impl fmt::Display for Item { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | write!(f, "token {t} at position {p}", 74 | t = self.token, 75 | p = self.position) 76 | } 77 | } 78 | 79 | //#[derive(Default)] 80 | pub struct Stream<'a> { 81 | items: Vec, 82 | _template: &'a template::Raw, 83 | } 84 | 85 | impl<'a> fmt::Display for Stream<'a> { 86 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 87 | let v: Vec = self.items.iter().map(|i| format!("{}", i.token)).collect(); 88 | write!(f, "[\n\t{}\n]", v.join("\n\t")) 89 | } 90 | } 91 | 92 | impl<'a> fmt::Debug for Stream<'a> { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 94 | let v: Vec = self.items.iter().map(|i| format!("{:?}", i.token)).collect(); 95 | write!(f, "[\n\t{}\n]", v.join("\n\t")) 96 | } 97 | } 98 | 99 | #[derive(Debug)] 100 | pub struct StreamDump { 101 | pub template_str: String, 102 | pub items_str: String, 103 | } 104 | 105 | impl<'a> Dump for Stream<'a> { 106 | type Data = StreamDump; 107 | 108 | fn dump(&self) -> Self::Data { 109 | unimplemented!() 110 | } 111 | } 112 | 113 | impl fmt::Display for StreamDump { 114 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 115 | write!(f, " StreamDump{{ template: {template:?}, items: {items:?}}}", 116 | template = self.template_str, 117 | items = self.items_str) 118 | } 119 | } 120 | 121 | impl<'a> IntoIterator for Stream<'a> { 122 | type Item = self::Item; 123 | type IntoIter = as IntoIterator>::IntoIter; 124 | 125 | fn into_iter(self) -> Self::IntoIter { 126 | self.items.into_iter() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/engine/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Typisation of syntax errors. 7 | 8 | use std::fmt::{self, Display}; 9 | use std::error::Error; 10 | 11 | use loader::LoaderError; 12 | use api::parser::ParserError; 13 | use api::lexer::LexerError; 14 | use api::ext; 15 | use std::convert::From; 16 | 17 | quick_error! { 18 | #[derive(Debug)] 19 | pub enum TwigError { 20 | Loader(cause: LoaderError) { 21 | description("Twig loader error") 22 | display(me) -> ("{}: {}", me.description(), cause) 23 | from() 24 | cause(&*cause) 25 | } 26 | LoaderNotInitialized { 27 | description("The template loader must be initializied prior usage.") 28 | } 29 | Lexer(cause: LexerError) { 30 | description("Twig lexer error") 31 | display(me) -> ("{}: {}", me.description(), cause) 32 | from() 33 | cause(&*cause) 34 | } 35 | LexerNotInitialized { 36 | description("The template lexer must be initializied prior usage.") 37 | } 38 | Parser(cause: ParserError) { 39 | description("Twig parser error") 40 | display(me) -> ("{}: {}", me.description(), cause) 41 | from() 42 | cause(&*cause) 43 | } 44 | Runtime { 45 | description("Twig runtime error.") 46 | } 47 | ExtensionRegistry(cause: ExtensionRegistryError) { 48 | description("Twig extension registry error") 49 | display(me) -> ("{}: {}", me.description(), cause) 50 | from() 51 | cause(&*cause) 52 | } 53 | } 54 | } 55 | 56 | #[derive(Debug)] 57 | pub enum ExtensionRegistryError { 58 | /// To be used by custom implementations of `twig::api::ext::Extension::init()` 59 | ExtensionInitFailure { 60 | reason: String, 61 | }, 62 | DuplicateExtension { 63 | name: String, 64 | }, 65 | DuplicateFilter { 66 | prev: Box 67 | }, 68 | DuplicateFunction { 69 | prev: Box 70 | }, 71 | DuplicateOperatorUnary { 72 | prev: ext::UnaryOperator 73 | }, 74 | DuplicateOperatorBinary { 75 | prev: ext::BinaryOperator 76 | }, 77 | DuplicateTest { 78 | prev: Box 79 | }, 80 | DuplicateTagHandler { 81 | prev: Box 82 | }, 83 | DuplicateTokenParser { 84 | prev: Box 85 | }, 86 | } 87 | 88 | impl Error for ExtensionRegistryError { 89 | fn description(&self) -> &str { 90 | match *self { 91 | ExtensionRegistryError::ExtensionInitFailure{..} => "Engine extension failed to initialize.", 92 | ExtensionRegistryError::DuplicateExtension{..} => "Duplicate extension.", 93 | ExtensionRegistryError::DuplicateFilter{..} => "Duplicate filter.", 94 | ExtensionRegistryError::DuplicateFunction{..} => "Duplicate function.", 95 | ExtensionRegistryError::DuplicateOperatorBinary{..} => "Duplicate binary operator.", 96 | ExtensionRegistryError::DuplicateOperatorUnary{..} => "Duplicate unary operator.", 97 | ExtensionRegistryError::DuplicateTest{..} => "Duplicate test.", 98 | ExtensionRegistryError::DuplicateTagHandler{..} => "Duplicate tag handler.", 99 | ExtensionRegistryError::DuplicateTokenParser{..} => "Duplicate token parser.", 100 | } 101 | } 102 | } 103 | 104 | impl Display for ExtensionRegistryError { 105 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 106 | try!(write!(f, "{}", self.description())); 107 | 108 | match *self { 109 | ExtensionRegistryError::ExtensionInitFailure { 110 | ref reason 111 | } => { 112 | write!(f, " {}", reason) 113 | }, 114 | ExtensionRegistryError::DuplicateExtension { 115 | ref name 116 | } => { 117 | write!(f, " {prev:?} has already been registered.", 118 | prev = name) 119 | }, 120 | ExtensionRegistryError::DuplicateFilter { 121 | prev: ref p 122 | } => { 123 | write!(f, " {prev:?} has already been registered.", 124 | prev = p) 125 | }, 126 | ExtensionRegistryError::DuplicateFunction { 127 | prev: ref p 128 | } => { 129 | write!(f, " {prev:?} has already been registered.", 130 | prev = p) 131 | }, 132 | ExtensionRegistryError::DuplicateOperatorBinary { 133 | prev: ref p 134 | } => { 135 | write!(f, " {prev:?} has already been registered.", 136 | prev = p) 137 | }, 138 | ExtensionRegistryError::DuplicateOperatorUnary { 139 | prev: ref p 140 | } => { 141 | write!(f, " {prev:?} has already been registered.", 142 | prev = p) 143 | }, 144 | ExtensionRegistryError::DuplicateTest { 145 | prev: ref p 146 | } => { 147 | write!(f, " {prev:?} has already been registered.", 148 | prev = p) 149 | }, 150 | ExtensionRegistryError::DuplicateTagHandler { 151 | prev: ref p 152 | } => { 153 | write!(f, " {prev:?} has already been registered.", 154 | prev = p) 155 | }, 156 | ExtensionRegistryError::DuplicateTokenParser { 157 | prev: ref p 158 | } => { 159 | write!(f, " {prev:?} has already been registered.", 160 | prev = p) 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/engine/extension_registry.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Extension registry. 7 | //! 8 | //! Stores 9 | 10 | use std::collections::{HashSet, HashMap}; 11 | use api::ext::{self, Extension}; 12 | use api::error::Traced; 13 | use engine::Options; 14 | use engine::error::ExtensionRegistryError; 15 | 16 | pub type Iter<'a> = ::std::collections::hash_map::Values<'a, String, Box>; 17 | 18 | #[derive(Debug, Default)] 19 | pub struct ExtensionRegistry { 20 | ext_names: HashSet, 21 | filters: HashMap>, 22 | functions: HashMap>, 23 | tests: HashMap>, 24 | token_parsers: HashMap>, 25 | node_visitors: Vec>, 26 | operators_unary: HashMap, 27 | operators_binary: HashMap, 28 | _globals: Vec>, 29 | } 30 | 31 | impl ExtensionRegistry { 32 | /// Initialize new extension registry instance. 33 | pub fn new(iterable: I, options: &Options) -> Result> where 34 | I: IntoIterator> 35 | { 36 | let mut builder = Builder::default(); 37 | 38 | for mut ext in iterable { 39 | if !builder.staged.ext_names.insert(ext.name().to_string()) { 40 | return traced_err!(ExtensionRegistryError::DuplicateExtension { 41 | name: ext.name().to_string() 42 | }) 43 | }; 44 | 45 | try!(ext.init(&mut builder, options)); 46 | } 47 | 48 | Ok(builder.into()) 49 | } 50 | 51 | /// Returns true if the given extension is registered 52 | pub fn has(&self, name: &str) -> bool { 53 | self.ext_names.contains(name) 54 | } 55 | 56 | /// Get all registered extension names. 57 | pub fn ext_names(&self) -> &HashSet { 58 | &self.ext_names 59 | } 60 | 61 | /// Get the token parser instances defined by engine extensions. 62 | pub fn token_parsers(&self) -> &HashMap> { 63 | &self.token_parsers 64 | } 65 | 66 | // /// Get token parsers by registered tag 67 | // pub fn _token_parser_by_tags(&self) -> &HashMap> { 68 | // &self._token_parser_by_tags 69 | // } 70 | 71 | /// Get the node visitor instances defined by engine extensions. 72 | pub fn node_visitors(&self) -> &Vec> { 73 | &self.node_visitors 74 | } 75 | 76 | /// Get the filters defined by engine extensions. 77 | pub fn filters(&self) -> &HashMap> { 78 | &self.filters 79 | } 80 | 81 | /// Get the tests defined by engine extensions. 82 | pub fn tests(&self) -> &HashMap> { 83 | &self.tests 84 | } 85 | 86 | /// Get the functions defined by engine extensions. 87 | pub fn functions(&self) -> &HashMap> { 88 | &self.functions 89 | } 90 | 91 | /// Get the unary operators defined by engine extensions. 92 | pub fn operators_unary(&self) -> &HashMap { 93 | &self.operators_unary 94 | } 95 | 96 | /// Get the binary operators defined by engine extensions. 97 | pub fn operators_binary(&self) -> &HashMap { 98 | &self.operators_binary 99 | } 100 | 101 | /// Get the global variables defined by engine extensions. 102 | pub fn _globals(&self) -> &Vec> { 103 | &self._globals 104 | } 105 | } 106 | 107 | #[derive(Debug, Default)] 108 | pub struct Builder { 109 | staged: ExtensionRegistry, 110 | } 111 | 112 | impl Builder { 113 | #[allow(dead_code)] 114 | /// Register token parser instances with the engine. 115 | fn push_token_parsers(&mut self, iterable: I) -> Result<(), Traced> where 116 | I: IntoIterator)> 117 | { 118 | for (k, v) in iterable { 119 | // #NOTE:60 can't have a reference to something owned within the same struct 120 | // and don't want to clone! 121 | // 122 | // if let Some(prev) = self._token_parser_by_tags.insert(v.tag().to_string(), &v) { 123 | // return traced_err!(ExtensionRegistryError::DuplicateTagHandler { 124 | // prev: prev, 125 | // ext_name: ext.name() 126 | // }) 127 | // } 128 | 129 | if let Some(prev) = self.staged.token_parsers.insert(k, v) { 130 | return traced_err!(ExtensionRegistryError::DuplicateTokenParser { 131 | prev: prev 132 | }) 133 | } 134 | } 135 | 136 | Ok(()) 137 | } 138 | 139 | #[allow(dead_code)] 140 | /// Register node visitor instances with the engine. 141 | fn push_node_visitors(&mut self, iterable: I) -> Result<(), Traced> where 142 | I: IntoIterator> 143 | { 144 | for v in iterable { 145 | self.staged.node_visitors.push(v) 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | #[allow(dead_code)] 152 | /// Register filters with the engine. 153 | fn push_filters(&mut self, iterable: I) -> Result<(), Traced> where 154 | I: IntoIterator)> 155 | { 156 | for (k, v) in iterable { 157 | if let Some(prev) = self.staged.filters.insert(k, v) { 158 | return traced_err!(ExtensionRegistryError::DuplicateFilter { 159 | prev: prev 160 | }) 161 | } 162 | } 163 | 164 | Ok(()) 165 | } 166 | 167 | #[allow(dead_code)] 168 | /// Register tests with the engine. 169 | fn push_tests(&mut self, iterable: I) -> Result<(), Traced> where 170 | I: IntoIterator)> 171 | { 172 | for (k, v) in iterable { 173 | if let Some(prev) = self.staged.tests.insert(k, v) { 174 | return traced_err!(ExtensionRegistryError::DuplicateTest { 175 | prev: prev 176 | }) 177 | } 178 | } 179 | 180 | Ok(()) 181 | } 182 | 183 | #[allow(dead_code)] 184 | /// Register functions with the engine. 185 | fn push_functions(&mut self, iterable: I) -> Result<(), Traced> where 186 | I: IntoIterator)> 187 | { 188 | for (k, v) in iterable { 189 | if let Some(prev) = self.staged.functions.insert(k, v) { 190 | return traced_err!(ExtensionRegistryError::DuplicateFunction { 191 | prev: prev 192 | }) 193 | } 194 | } 195 | 196 | Ok(()) 197 | } 198 | 199 | #[allow(dead_code)] 200 | /// Register unary operators with the engine. 201 | fn push_operators_unary(&mut self, iterable: I) -> Result<(), Traced> where 202 | I: IntoIterator 203 | { 204 | for v in iterable { 205 | if let Some(prev) = self.staged.operators_unary.insert(v.repr.clone(), v) { 206 | return traced_err!(ExtensionRegistryError::DuplicateOperatorUnary { 207 | prev: prev 208 | }) 209 | } 210 | } 211 | 212 | Ok(()) 213 | } 214 | 215 | #[allow(dead_code)] 216 | /// Register binary operators with the engine. 217 | fn push_operators_binary(&mut self, iterable: I) -> Result<(), Traced> where 218 | I: IntoIterator 219 | { 220 | for v in iterable { 221 | if let Some(prev) = self.staged.operators_binary.insert(v.repr.clone(), v) { 222 | return traced_err!(ExtensionRegistryError::DuplicateOperatorBinary { 223 | prev: prev 224 | }) 225 | } 226 | } 227 | 228 | Ok(()) 229 | } 230 | 231 | #[allow(dead_code)] 232 | /// Register global variables with the engine. 233 | fn push_globals(&mut self, _iterable: I) -> Result<(), Traced> where 234 | I: IntoIterator> 235 | { 236 | unimplemented!() 237 | } 238 | } 239 | 240 | impl Into for Builder { 241 | fn into(self) -> ExtensionRegistry { 242 | self.staged 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! The Twig Engine. 7 | 8 | mod template_cache; 9 | use loader::Loader; 10 | use std::rc::Rc; 11 | use template; 12 | use setup::Setup; 13 | use api::{parser, Parser, lexer, Lexer}; 14 | use api::error::Traced; 15 | 16 | pub mod error; 17 | pub mod options; 18 | pub mod extension_registry; 19 | pub use self::error::{TwigError, ExtensionRegistryError}; 20 | pub use self::options::Options; 21 | pub use self::extension_registry::ExtensionRegistry; 22 | 23 | #[derive(Debug)] 24 | pub struct Engine { 25 | options: Options, 26 | ext: Rc, 27 | loader: Option>, 28 | lexer: Option, 29 | parser: Option, 30 | } 31 | 32 | 33 | impl Engine { 34 | /// Create a new Twig `Engine`. 35 | /// 36 | /// Note: You may want to 37 | /// 38 | /// # Examples 39 | /// 40 | /// ``` 41 | /// use twig::engine::{Engine, Options, ExtensionRegistry}; 42 | /// 43 | /// let twig = Engine::new(ExtensionRegistry::default(), Options::default()); 44 | /// ``` 45 | /// 46 | /// # Altnernative 47 | /// 48 | /// ``` 49 | /// use twig::Setup; 50 | /// 51 | /// let twig = Setup::default().init_engine().unwrap(); 52 | /// ``` 53 | pub fn new(ext: ExtensionRegistry, options: Options) -> Self { 54 | Engine { 55 | options: options, 56 | ext: Rc::new(ext), // TODO STREAMLINING: - get rid of this! 57 | loader: None, 58 | lexer: None, 59 | parser: None, 60 | } 61 | } 62 | 63 | /// Renders a template. 64 | /// 65 | /// # Failures 66 | /// * When the template cannot be found 67 | /// * When an error occurred during compilation 68 | /// * When an error occurred during rendering 69 | pub fn render(&mut self, _path: &str, _data: ()) -> Result> { 70 | unimplemented!() 71 | } 72 | 73 | /// Displays a template. 74 | /// 75 | /// # Failures 76 | /// * When the template cannot be found 77 | /// * When an error occurred during compilation 78 | /// * When an error occurred during rendering 79 | pub fn display(&mut self, _path: &str, _data: ()) -> Result<(), Traced> { 80 | unimplemented!() 81 | } 82 | 83 | /// Loads and compiles a template. 84 | /// 85 | /// # Failures 86 | /// * When the template cannot be found 87 | /// * When an error occurred during compilation 88 | pub fn load_template(&mut self, path: &str, _index: Option) -> Result> { 89 | // TODO: Cache compiled templates 90 | // * cache lookup 91 | // * check if cache is fresh 92 | // * store in cache 93 | 94 | let template_raw = try!(self.load_template_raw(path)); 95 | return self.compile_template(&template_raw); 96 | } 97 | 98 | /// Loads raw template. 99 | /// 100 | /// # Failures 101 | /// * When the template cannot be found 102 | fn load_template_raw(&mut self, path: &str) -> Result> { 103 | let loader = try!(self.loader()); 104 | let source = try_traced!(loader.source(path)); 105 | Ok(template::Raw::new(source, path)) 106 | } 107 | 108 | /// Compiles a template. 109 | /// 110 | /// # Failures 111 | /// * When an error occurred during lexing or parsing. 112 | fn compile_template(&mut self, template: &template::Raw) -> Result> { 113 | let tokenstream = { 114 | let lexer = try!(self.lexer()); 115 | try_traced!(lexer.tokenize(template)) 116 | }; 117 | 118 | let compiled = { 119 | let parser = try!(self.parser()); 120 | try_traced!(parser.parse(&tokenstream)) 121 | }; 122 | 123 | Ok(compiled) 124 | } 125 | 126 | /// Get the engine extensions. 127 | pub fn extensions(&self) -> &ExtensionRegistry { 128 | &self.ext 129 | } 130 | 131 | /// Sets the loader instance. 132 | pub fn set_loader(&mut self, loader: Box) -> &mut Engine { 133 | self.loader = Some(loader); // TODO: switch to callback pattern to provide arguments 134 | 135 | self 136 | } 137 | 138 | /// Get the loader instance. 139 | pub fn loader(&mut self) -> Result<&mut Loader, Traced> { 140 | match self.loader { 141 | Some(ref mut loader) => return Ok(&mut **loader), 142 | None => { 143 | return traced_err!(TwigError::LoaderNotInitialized) 144 | } 145 | } 146 | } 147 | 148 | /// Get the lexer instance. 149 | pub fn lexer(&mut self) -> Result<&Lexer, Traced> { 150 | match self.lexer { 151 | Some(ref lexer) => return Ok(lexer), 152 | None => { 153 | let _options: lexer::Options = unimplemented!(); 154 | 155 | self.lexer = Some(try_traced!(Lexer::new(_options))); 156 | return self.lexer(); 157 | } 158 | } 159 | } 160 | 161 | /// Get the parser instance. 162 | pub fn parser(&mut self) -> Result<&Parser, Traced> { 163 | match self.parser { 164 | Some(ref parser) => return Ok(parser), 165 | None => { 166 | let _options: parser::Options = unimplemented!(); 167 | 168 | self.parser = Some(try_traced!(Parser::new(_options))); 169 | return self.parser(); 170 | } 171 | } 172 | } 173 | } 174 | 175 | // NOTE: `derive(Default)` would not initialize any extensions 176 | impl Default for Engine { 177 | fn default() -> Engine { 178 | Setup::default().init_engine().unwrap() 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/engine/options.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig configuration options. 7 | 8 | use std::path::{Path, PathBuf}; 9 | use extension::escaper; 10 | use extension::optimizer; 11 | 12 | pub type AutoEscape = escaper::Mode; 13 | pub type Optimizations = optimizer::Mode; 14 | 15 | 16 | #[derive(Debug)] 17 | pub struct Options { 18 | debug: bool, 19 | strict_variables: bool, 20 | auto_escape: AutoEscape, 21 | cache: Option, 22 | auto_reload: Option, // defaults to `self.debug` if unset 23 | optimizations: Optimizations, 24 | } 25 | 26 | impl Default for Options { 27 | fn default() -> Options { 28 | Options { 29 | debug: false, 30 | strict_variables: false, 31 | auto_escape: escaper::Mode::default(), 32 | cache: None, 33 | auto_reload: None, 34 | optimizations: optimizer::Mode::default(), 35 | } 36 | } 37 | } 38 | 39 | impl Options { 40 | pub fn debug(&self) -> bool { 41 | self.debug 42 | } 43 | 44 | pub fn set_debug(&mut self, debug: bool) { 45 | self.debug = debug; 46 | } 47 | 48 | pub fn strict_variables(&self) -> bool { 49 | self.strict_variables 50 | } 51 | 52 | pub fn set_strict_variables(&mut self, strict_variables: bool) { 53 | self.strict_variables = strict_variables; 54 | } 55 | 56 | pub fn auto_escape(&self) -> AutoEscape { 57 | self.auto_escape 58 | } 59 | 60 | pub fn set_auto_escape(&mut self, auto_escape: AutoEscape) { 61 | self.auto_escape = auto_escape; 62 | } 63 | 64 | pub fn cache(&self) -> Option<&Path> { 65 | // TODO: why doesn't this work? -> self.cache.map(|ref buf| buf.as_ref()) 66 | match self.cache { 67 | Some(ref buf) => Some(buf.as_ref()), 68 | None => None 69 | } 70 | } 71 | 72 | pub fn set_cache(&mut self, cache: Option) { 73 | self.cache = cache; 74 | } 75 | 76 | /// if unset it defaults to `self.debug()` 77 | pub fn auto_reload(&self) -> bool { 78 | self.auto_reload.unwrap_or(self.debug) 79 | } 80 | 81 | pub fn set_auto_reload(&mut self, auto_reload: bool) { 82 | self.auto_reload = Some(auto_reload); 83 | } 84 | 85 | pub fn optimizations(&self) -> Optimizations { 86 | self.optimizations 87 | } 88 | 89 | pub fn set_optimizations(&mut self, optimizations: Optimizations) { 90 | self.optimizations = optimizations; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/engine/template_cache.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig filecache for compiled templates. 7 | 8 | use std::collections::HashMap; 9 | use std::path::{Path, PathBuf}; 10 | 11 | #[allow(dead_code)] 12 | pub struct Cache { 13 | loaded_templates: HashMap, 14 | filecache: Option, 15 | _auto_reload: bool, 16 | } 17 | 18 | #[allow(dead_code)] 19 | impl Cache { 20 | /// Clears the internal template cache. 21 | pub fn clear(&mut self) { 22 | self.loaded_templates.clear() 23 | } 24 | 25 | /// Loads a template by name. 26 | pub fn load(_name: &str) -> &str { 27 | unimplemented!() 28 | } 29 | 30 | /// Sets the cache directory or None if filecache is disabled. 31 | pub fn set_filecache(&mut self, filecache: Option<&Path>) { 32 | match filecache { 33 | None => self.filecache = None, 34 | Some(path) => { 35 | if path.file_name().is_some() { 36 | panic!("path must be a directory") 37 | } 38 | 39 | self.filecache = Some(path.to_path_buf()); 40 | } 41 | } 42 | } 43 | 44 | /// Gets the cache directory or None if filecache is disabled. 45 | pub fn filecache(&self) -> Option<&Path> { 46 | match self.filecache { 47 | None => None, 48 | Some(ref pathbuf) => Some(pathbuf.as_path()) 49 | } 50 | } 51 | 52 | /// Clears the template cache files on the filesystem. 53 | pub fn clear_filecache(&self) { 54 | if let Some(ref _path) = self.filecache { 55 | unimplemented!() 56 | } 57 | } 58 | 59 | /// Gets the cache filename for a given template. 60 | pub fn get_cache_filename(&self, _name: &str) -> String { 61 | unimplemented!() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Twig (ported to Rust). 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig error 7 | -------------------------------------------------------------------------------- /src/extension/core/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Core Extension. 7 | 8 | use api::Extension; 9 | use api::error::Traced; 10 | use engine; 11 | 12 | #[allow(dead_code)] // dummy 13 | #[derive(Default, Debug, PartialEq)] 14 | pub struct Core; 15 | 16 | impl Extension for Core { 17 | fn name(&self) -> &'static str { "core" } 18 | 19 | fn init(&mut self, _registry: &mut engine::extension_registry::Builder, _options: &engine::Options) 20 | -> Result<(), Traced> { 21 | // unimplemented!() 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl Core { 27 | pub fn new() -> Box { 28 | Box::new(Core::default()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/extension/debug/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Debug Extension. 7 | 8 | use api::Extension; 9 | use api::error::Traced; 10 | use engine; 11 | 12 | #[allow(dead_code)] // dummy 13 | #[derive(Default, Debug, PartialEq)] 14 | pub struct Debug; 15 | 16 | impl Extension for Debug { 17 | fn name(&self) -> &'static str { "debug" } 18 | 19 | fn init(&mut self, _registry: &mut engine::extension_registry::Builder, _options: &engine::Options) 20 | -> Result<(), Traced> { 21 | // unimplemented!() 22 | 23 | Ok(()) 24 | } 25 | } 26 | 27 | impl Debug { 28 | pub fn new() -> Box { 29 | Box::new(Debug) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/extension/escaper/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Escaper Extension 7 | 8 | use api::Extension; 9 | use api::error::Traced; 10 | use engine; 11 | 12 | #[derive(Default, Debug, PartialEq)] 13 | pub struct Escaper { 14 | mode: Mode, 15 | } 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq)] 18 | pub enum Mode { 19 | Html, 20 | _Enabled, 21 | _Disabled, 22 | _Filename, 23 | _Callback, 24 | } 25 | 26 | impl Default for Mode { 27 | fn default() -> Mode { 28 | Mode::Html 29 | } 30 | } 31 | 32 | impl Extension for Escaper { 33 | fn name(&self) -> &'static str { "escaper" } 34 | 35 | fn init(&mut self, _registry: &mut engine::extension_registry::Builder, _options: &engine::Options) 36 | -> Result<(), Traced> { 37 | // unimplemented!() 38 | 39 | Ok(()) 40 | } 41 | } 42 | 43 | impl Escaper { 44 | pub fn new(mode: Mode) -> Box { 45 | Box::new(Escaper { 46 | mode: mode 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/extension/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig Extensions 7 | //! 8 | //! Define new behavior during the compilation process. 9 | //! 10 | //! # API 11 | //! 12 | //! See `twig::api::ext` for details on implementing new extensions. 13 | 14 | pub mod core; 15 | pub mod debug; 16 | pub mod escaper; 17 | pub mod optimizer; 18 | pub use self::core::Core; 19 | pub use self::debug::Debug; 20 | pub use self::escaper::Escaper; 21 | pub use self::optimizer::Optimizer; 22 | -------------------------------------------------------------------------------- /src/extension/optimizer/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Optimizer Extension 7 | 8 | use api::Extension; 9 | use api::error::Traced; 10 | use engine; 11 | 12 | #[derive(Default, Debug, PartialEq)] 13 | pub struct Optimizer { 14 | mode: Mode 15 | } 16 | 17 | #[derive(Clone, Copy, Debug, PartialEq)] 18 | pub enum Mode { 19 | Enabled, 20 | _Disabled, 21 | } 22 | 23 | impl Default for Mode { 24 | fn default() -> Mode { 25 | Mode::Enabled 26 | } 27 | } 28 | 29 | impl Extension for Optimizer { 30 | fn name(&self) -> &'static str { "optimizer" } 31 | 32 | fn init(&mut self, _registry: &mut engine::extension_registry::Builder, _options: &engine::Options) 33 | -> Result<(), Traced> { 34 | // unimplemented!() 35 | 36 | Ok(()) 37 | } 38 | } 39 | 40 | impl Optimizer { 41 | pub fn new(mode: Mode) -> Box { 42 | Box::new(Optimizer { 43 | mode: mode 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // This file is part of Twig (ported to Rust). 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! # Twig Templating for Rust 7 | //! 8 | //! **Work in progress** - This library is still in development and not yet ready for use. 9 | //! Take a look at the [CHANGELOG][changelog] for more details. 10 | //! 11 | //! [Twig Rust][github] is a template engine for everyone writing web applications with Rust. 12 | //! It is derived from [Twig (for PHP)][twigphp] and intended to become a _fully compatible_ port - as far as it makes sense. 13 | //! 14 | //! By design Twig is 15 | //! 16 | //! * flexible 17 | //! * fast 18 | //! * and secure 19 | //! 20 | //! ## Getting Started 21 | //! 22 | //! ``` 23 | //! use twig::{Setup, Engine}; 24 | //! 25 | //! let setup = Setup::default(); 26 | //! let twig = setup.init_engine().unwrap(); 27 | //! // .. 28 | //! ``` 29 | //! 30 | //! ## Syntax and Semantics 31 | //! 32 | //! Twig uses a syntax similar to the Django and Jinja template languages which inspired the Twig runtime environment. 33 | //! 34 | //! ```html 35 | //! 36 | //! 37 | //! 38 | //! Display a thread of posts 39 | //! 40 | //! 41 | //!

{{ thread.title }}

42 | //!
    43 | //! {% for post in thread.posts %} 44 | //!
  • {{ post }}
  • 45 | //! {% endfor %} 46 | //!
47 | //! {# note: this comment will be ignored #} 48 | //! 49 | //! 50 | //! ``` 51 | //! 52 | //! Take a look at this introduction: [Twig for template designers](http://twig.sensiolabs.org/doc/templates.html). 53 | //! 54 | //! ## Flexible Architecture 55 | //! 56 | //! Twig is designed to be highly extensible: 57 | //! 58 | //! * the Twig compiler only defines *general semantics* and a very flexible *extension mechanism*. 59 | //! * extensions define specific behavior and data transformations (like if-statement, for-loop, escape-filter, multiplication-operator, call-expression, etc.) 60 | //! * extensions are chosen at runtime. 61 | //! * if you don't like the default behavior (like if-statement, or call-expression) or if you are missing some functionality (like helpers for a new target like excel-files), all you need to do is replace, add or change extensions. 62 | //! 63 | //! ## License 64 | //! 65 | //! Twig-Rust is released under the [new BSD license][license] (code and documentation) - as is the original Twig for PHP. 66 | //! 67 | //! [github]: https://github.com/rust-web/twig 68 | //! [license]: https://github.com/rust-web/twig/blob/master/LICENSE 69 | //! [changelog]: https://github.com/rust-web/twig/blob/master/CHANGELOG.md 70 | //! [twigphp]: http://twig.sensiolabs.org/documentation 71 | 72 | #[macro_use] extern crate quick_error; 73 | extern crate regex; 74 | 75 | #[macro_use] pub mod api; 76 | pub mod engine; 77 | pub mod extension; 78 | pub mod loader; 79 | pub mod setup; 80 | pub mod template; 81 | 82 | pub use engine::Engine; 83 | pub use setup::Setup; 84 | -------------------------------------------------------------------------------- /src/loader/error.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Typisation of loader errors. 7 | 8 | use std::fmt::{self, Display}; 9 | use std::error::Error; 10 | use std::path::PathBuf; 11 | 12 | #[derive(Debug)] 13 | pub enum LoaderError { 14 | ArrayTemplateNotFound { 15 | name: String 16 | }, 17 | FileSystemTemplateNotFound { 18 | raw_path: PathBuf, 19 | namespace: String, 20 | dirs: Vec 21 | }, 22 | FileSystemNamespaceNotInitialized { 23 | namespace: String 24 | }, 25 | FileSystemMalformedNamespacedPath { 26 | template_name: String 27 | }, 28 | FileSystemInvalidPath { 29 | path: PathBuf 30 | }, 31 | FileSystemTemplateNotReadable { 32 | name: String, 33 | path: PathBuf 34 | } 35 | } 36 | 37 | impl Error for LoaderError { 38 | fn description(&self) -> &str { 39 | match *self { 40 | LoaderError::ArrayTemplateNotFound{..} 41 | | LoaderError::FileSystemTemplateNotFound{..} => "Template not found.", 42 | LoaderError::FileSystemNamespaceNotInitialized{..} => "Loader is not initialized.", 43 | LoaderError::FileSystemMalformedNamespacedPath{..} 44 | | LoaderError::FileSystemInvalidPath{..} => "Invalid template path.", 45 | LoaderError::FileSystemTemplateNotReadable{..} => "Could not read template file.", 46 | } 47 | } 48 | } 49 | 50 | impl Display for LoaderError { 51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 52 | try!(write!(f, "{}", self.description())); 53 | 54 | match *self { 55 | LoaderError::ArrayTemplateNotFound { 56 | ref name 57 | } => { 58 | write!(f, " Template {:?} is not present in array template loader.", name) 59 | }, 60 | LoaderError::FileSystemTemplateNotFound { 61 | ref raw_path, ref namespace, ref dirs 62 | } => { 63 | write!(f, " Unable to find template path {path:?} in namespace {id:?} (looked into: {dirs:?})", 64 | path = raw_path, 65 | id = namespace, 66 | dirs = dirs) 67 | }, 68 | LoaderError::FileSystemNamespaceNotInitialized { 69 | ref namespace 70 | } => { 71 | write!(f, " There are no registered directories for template namespace {}", namespace) 72 | }, 73 | LoaderError::FileSystemMalformedNamespacedPath { 74 | ref template_name 75 | } => { 76 | write!(f, " {template} is malformed (expecting a namespaced template path like '@namespace/path_to_file').", 77 | template = template_name) 78 | }, 79 | LoaderError::FileSystemInvalidPath{ 80 | ref path 81 | } => { 82 | write!(f, " Looks like you try to load a template outside configured directories ({path:?}).", 83 | path = path) 84 | }, 85 | LoaderError::FileSystemTemplateNotReadable{ 86 | ref name, ref path 87 | } => { 88 | write!(f, " Missing read-permission for {path:?} while loading template {name:?}.", 89 | path = path, name = name) 90 | }, 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/loader/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig template loader. 7 | 8 | use api::error::Traced; 9 | use std::fmt::Debug; 10 | use std::borrow::Cow; 11 | 12 | pub mod error; 13 | pub use self::error::LoaderError; 14 | 15 | 16 | pub trait Loader : Debug { 17 | /// Gets the source code of a template, given its name 18 | /// 19 | /// Returns a Cow to allow for efficient caching mechanisms. 20 | /// 21 | /// # Failures 22 | /// * When `name` is not found 23 | fn source<'a>(&'a mut self, name: &str) -> Result, Traced>; 24 | 25 | /// Gets the cache key to use for the cache for a given template 26 | /// 27 | /// # Failures 28 | /// * When `name` is not found 29 | fn cache_key<'a>(&'a mut self, name: &str) -> Result, LoaderError>; 30 | 31 | /// returns true if the template is still fresh 32 | fn is_fresh(&mut self, name: &str, time: i64) -> bool; 33 | } 34 | -------------------------------------------------------------------------------- /src/setup/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Stores the Twig configuration. 7 | 8 | use std::path::Path; 9 | use extension; 10 | use api::Extension; 11 | use api::error::Traced; 12 | use engine::{Engine, options, Options, ExtensionRegistry}; 13 | use engine::error::{TwigError}; 14 | 15 | #[allow(dead_code)] 16 | pub const VERSION : &'static str = "1.18.1"; 17 | 18 | #[derive(Debug)] 19 | pub struct Setup { 20 | opt: Options, 21 | ext: Vec>, 22 | } 23 | 24 | impl Default for Setup { 25 | fn default() -> Setup { 26 | // prepend default extensions 27 | let mut ext: Vec> = vec![]; 28 | ext.push(extension::Core::new()); 29 | 30 | Setup { 31 | opt: Options::default(), 32 | ext: ext, 33 | } 34 | } 35 | } 36 | 37 | /// Builds an instance of the Twig Engine, according to supplied options and engine extensions. 38 | /// 39 | /// The following extensions will be registered by default: 40 | /// * core 41 | /// * escaper 42 | /// * optimizer 43 | /// 44 | /// # Examples 45 | /// 46 | /// ``` 47 | /// use twig::Setup; 48 | /// use twig::extension::Debug; 49 | /// 50 | /// let twig = Setup::default() 51 | /// .set_strict_variables(true) 52 | /// .add_extension(Debug::new()) 53 | /// .init_engine() 54 | /// .unwrap(); 55 | /// ``` 56 | #[allow(dead_code)] 57 | impl Setup { 58 | /// Create engine from setup. 59 | /// 60 | /// # Examples 61 | /// 62 | /// ``` 63 | /// use twig::Setup; 64 | /// 65 | /// let twig = Setup::default().init_engine().unwrap(); 66 | /// ``` 67 | pub fn init_engine(self) -> Result> { 68 | let Setup { opt, mut ext } = self; 69 | 70 | // append default extensions 71 | ext.push(extension::Escaper::new(opt.auto_escape())); 72 | ext.push(extension::Optimizer::new(opt.optimizations())); 73 | 74 | // init extensions 75 | let extension_registry = try_traced!(ExtensionRegistry::new(ext, &opt)); 76 | 77 | Ok(Engine::new(extension_registry, opt)) 78 | } 79 | 80 | /// Registers an extension 81 | pub fn add_extension(mut self, extension: Box) -> Self { 82 | self.ext.push(extension); 83 | 84 | self 85 | } 86 | 87 | /// When set to true, it automatically set "auto_reload" to true as well 88 | /// (default to false) 89 | pub fn set_debug(mut self, debug: bool) -> Self { 90 | self.opt.set_debug(debug); 91 | 92 | self 93 | } 94 | 95 | /// Whether to ignore invalid variables in templates 96 | /// (default to false). 97 | pub fn set_strict_variables(mut self, strict_variables: bool) -> Self { 98 | self.opt.set_strict_variables(strict_variables); 99 | 100 | self 101 | } 102 | 103 | /// Whether to enable auto-escaping (default to html): 104 | /// * false: disable auto-escaping 105 | /// * true: equivalent to html 106 | /// * html, js: set the autoescaping to one of the supported strategies 107 | /// * filename: set the autoescaping strategy based on the template filename extension 108 | /// * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename" 109 | pub fn set_auto_escape(mut self, auto_escape: options::AutoEscape) -> Self { 110 | self.opt.set_auto_escape(auto_escape); 111 | 112 | self 113 | } 114 | 115 | /// An absolute path where to store the compiled templates (optional) 116 | pub fn set_cache(mut self, cache: Option<&Path>) -> Self { 117 | self.opt.set_cache(cache.map(|reference| reference.to_owned())); 118 | 119 | self 120 | } 121 | 122 | /// Whether to reload the template if the original source changed (optional). 123 | /// If you don't provide the auto_reload option, it will be 124 | /// determined automatically based on the debug value. 125 | pub fn set_auto_reload(mut self, auto_reload: bool) -> Self { 126 | self.opt.set_auto_reload(auto_reload); 127 | 128 | self 129 | } 130 | 131 | /// A flag that indicates whether optimizations are applied 132 | pub fn set_optimizations(mut self, optimizations: options::Optimizations) -> Self { 133 | self.opt.set_optimizations(optimizations); 134 | 135 | self 136 | } 137 | 138 | /// Get all options 139 | pub fn options(&self) -> &Options { 140 | &self.opt 141 | } 142 | 143 | /// Get all extensions 144 | pub fn extensions(&self) -> &Vec> { 145 | &self.ext 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/template/compiled/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Default base class for compiled templates. 7 | 8 | 9 | pub struct Compiled; 10 | -------------------------------------------------------------------------------- /src/template/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig Templates. 7 | 8 | pub mod raw; 9 | pub mod compiled; 10 | pub use self::raw::Raw; 11 | pub use self::compiled::Compiled; 12 | -------------------------------------------------------------------------------- /src/template/raw/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is part of rust-web/twig 2 | // 3 | // For the copyright and license information, please view the LICENSE 4 | // file that was distributed with this source code. 5 | 6 | //! Twig raw template. 7 | 8 | use std::fmt; 9 | 10 | #[derive(Default)] 11 | #[derive(Debug, Clone)] 12 | pub struct Raw { 13 | name: String, // twig template name, e.g. "@namespace/path/to/template" 14 | pub code: String, 15 | } 16 | 17 | #[allow(unused_variables)] 18 | impl Raw { 19 | pub fn new(code: C, name: N) -> Raw where 20 | C: ToString, 21 | N: ToString, 22 | // NOTE: Into would be more efficient 23 | // but Cow<'_, str> does not implement Into 24 | // -> suggest this as new std lib feature? 25 | { 26 | let mut x = Raw { 27 | name: name.to_string(), 28 | code: code.to_string(), 29 | }; 30 | x.fix_linebreaks(); 31 | 32 | return x; 33 | } 34 | 35 | fn fix_linebreaks(&mut self) { 36 | self.code = self.code.replace("\r\n","\n").replace("\r","\n"); 37 | } 38 | 39 | pub fn name(&self) -> &str { 40 | &self.name 41 | } 42 | } 43 | 44 | impl fmt::Display for Raw { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 46 | write!(f, "template ({name:?}): {code:?}", 47 | name = self.name(), 48 | code = self.code) 49 | } 50 | } 51 | 52 | 53 | #[cfg(test)] 54 | mod test { 55 | use super::*; 56 | 57 | #[test] 58 | pub fn new() { 59 | let t = Raw::new("A", "B"); 60 | 61 | assert_eq!(t.code, "A"); 62 | assert_eq!(t.name, "B"); 63 | } 64 | 65 | #[test] 66 | pub fn fix_linebreaks() { 67 | let mut t = Raw { 68 | code: "1\r\n2\n3\r".to_string(), 69 | ..Default::default() 70 | }; 71 | t.fix_linebreaks(); 72 | assert_eq!(t.code, "1\n2\n3\n"); 73 | } 74 | } 75 | --------------------------------------------------------------------------------