├── assets ├── tpl │ ├── hello.tpl │ └── simple │ │ ├── 1.tpl │ │ ├── 2a.tpl │ │ └── 2b.tpl └── html │ └── simple │ ├── 1-a.html │ └── 2a.html ├── src ├── abc.rs ├── container │ ├── mod.rs │ ├── cycle.rs │ ├── args.rs │ ├── stack.rs │ ├── expression.rs │ ├── parsed.rs │ ├── template.rs │ └── incrust.rs ├── types │ ├── char.rs │ ├── mod.rs │ ├── bool.rs │ ├── real.rs │ ├── function.rs │ ├── int.rs │ ├── map.rs │ ├── string.rs │ ├── list.rs │ ├── abc.rs │ └── defaults.rs ├── renderer │ ├── writer.rs │ ├── mod.rs │ ├── abc.rs │ ├── renderer.rs │ ├── expression.rs │ └── evaluator.rs ├── loader │ ├── mod.rs │ ├── abc.rs │ ├── group.rs │ ├── dict.rs │ ├── namespace.rs │ └── filesystem.rs ├── parser │ ├── mod.rs │ ├── abc.rs │ ├── macros.rs │ ├── block_level.rs │ ├── literals.rs │ ├── statements.rs │ └── expressions.rs ├── filter │ ├── mod.rs │ ├── README.md │ ├── html_escape.rs │ ├── html_unescape.rs │ ├── url_escape.rs │ ├── url_unescape.rs │ └── newline2space.rs └── lib.rs ├── COPYRIGHT ├── .gitignore ├── Cargo.toml ├── LICENSE-MIT ├── .travis.yml ├── tests ├── loader.rs └── compos.rs ├── README.md └── LICENSE-APACHE /assets/tpl/hello.tpl: -------------------------------------------------------------------------------- 1 | Hello, {{ name | e }}! 2 | -------------------------------------------------------------------------------- /src/abc.rs: -------------------------------------------------------------------------------- 1 | pub use loader::abc::*; 2 | pub use parser::abc::*; 3 | pub use renderer::abc::*; 4 | pub use types::abc::*; 5 | -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Incrust is dual-licensed under Apache 2.0 and MIT terms. 2 | 3 | Incrust is copyright 2016, Incrust Developers. 4 | -------------------------------------------------------------------------------- /src/container/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod args; 2 | pub mod stack; 3 | pub mod cycle; 4 | pub mod expression; 5 | pub mod incrust; 6 | pub mod parsed; 7 | pub mod template; 8 | -------------------------------------------------------------------------------- /assets/html/simple/1-a.html: -------------------------------------------------------------------------------- 1 |

fruits

2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /src/types/char.rs: -------------------------------------------------------------------------------- 1 | use types::abc::*; 2 | use Arg; 3 | 4 | 5 | impl <'t> Type<'t> for char { 6 | fn clone_type(&self) -> Arg<'static> { 7 | Arg::Owned(box *self) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /assets/html/simple/2a.html: -------------------------------------------------------------------------------- 1 |

fruits

2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod abc; 2 | pub mod defaults; 3 | 4 | pub mod char; 5 | pub mod string; 6 | pub mod bool; 7 | pub mod real; 8 | pub mod int; 9 | 10 | pub mod list; 11 | //pub mod map; 12 | 13 | pub mod function; 14 | -------------------------------------------------------------------------------- /src/renderer/writer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | pub struct Writer<'w> ( 4 | pub &'w mut fmt::Write 5 | ); 6 | 7 | impl <'w> fmt::Write for Writer<'w> { 8 | fn write_str(&mut self, s: &str) -> fmt::Result { 9 | self.0.write_str(s) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/tpl/simple/1.tpl: -------------------------------------------------------------------------------- 1 |

{{ title | e }}

2 | 3 | 4 | {%- if fruits %} 5 | 10 | {%- endif %} 11 | 12 | -------------------------------------------------------------------------------- /src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod abc; 2 | pub mod evaluator; 3 | pub mod expression; 4 | #[allow(clippy::module_inception)] 5 | pub mod renderer; 6 | pub mod writer; 7 | 8 | pub use self::evaluator::eval_expr; 9 | pub use self::renderer::text; 10 | pub use self::writer::Writer; 11 | -------------------------------------------------------------------------------- /src/loader/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod abc; 2 | pub mod dict; 3 | pub mod filesystem; 4 | pub mod group; 5 | pub mod namespace; 6 | 7 | pub use self::dict::DictLoader; 8 | pub use self::filesystem::FilesystemLoader; 9 | pub use self::group::GroupLoader; 10 | pub use self::namespace::NamespaceLoader; 11 | -------------------------------------------------------------------------------- /assets/tpl/simple/2a.tpl: -------------------------------------------------------------------------------- 1 |

{{ title | e }}

2 | 3 | 4 | {%- if fruits %} 5 | 10 | {%- endif %} 11 | 12 | -------------------------------------------------------------------------------- /assets/tpl/simple/2b.tpl: -------------------------------------------------------------------------------- 1 |

{{ title() | e }}

2 | 3 | 4 | {%- if fruits %} 5 | 10 | {%- endif %} 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .idea 3 | *.iml 4 | .project 5 | out/ 6 | 7 | # Compiled files 8 | *.o 9 | *.so 10 | *.rlib 11 | *.dll 12 | 13 | # Executables 14 | *.exe 15 | 16 | # Generated by Cargo 17 | target/ 18 | **/*.rs.bk 19 | tmp/ 20 | Cargo.lock 21 | 22 | # Autogenerated 23 | #src/bin/release.rs 24 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod macros; 3 | 4 | pub mod abc; 5 | pub mod block_level; 6 | pub mod expressions; 7 | pub mod literals; 8 | pub mod statements; 9 | 10 | pub use self::block_level::*; 11 | pub use self::expressions::*; 12 | pub use self::literals::*; 13 | pub use self::statements::*; 14 | -------------------------------------------------------------------------------- /src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod html_escape; 2 | pub mod html_unescape; 3 | pub mod newline2space; 4 | pub mod url_escape; 5 | pub mod url_unescape; 6 | 7 | pub use self::html_escape::*; 8 | pub use self::html_unescape::*; 9 | pub use self::newline2space::*; 10 | pub use self::url_escape::*; 11 | pub use self::url_unescape::*; 12 | -------------------------------------------------------------------------------- /src/filter/README.md: -------------------------------------------------------------------------------- 1 | # Escapers / unescapers 2 | 3 | ## html_escape, escape, e 4 | 5 | 6 | ## html_unescape, unescape 7 | 8 | 9 | ## url_escape, u 10 | 11 | 12 | ## url_unescape 13 | 14 | 15 | # Beautifiers 16 | 17 | ## newline_to_space, nl2spc 18 | 19 | ## nl2br 20 | 21 | ## TODO strip_html 22 | ## TODO strip_newline 23 | 24 | -------------------------------------------------------------------------------- /src/loader/abc.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | pub type LoadResult = Result, LoadError>; 5 | 6 | #[derive(Debug, PartialEq)] 7 | pub enum LoadError { 8 | BadName(Cow<'static, str>), 9 | IoError(Cow<'static, str>), 10 | NotFound, 11 | } 12 | 13 | 14 | pub trait Loader: fmt::Debug + Send + Sync { 15 | fn load(&self, name: &str) -> LoadResult; 16 | } 17 | -------------------------------------------------------------------------------- /src/parser/abc.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | 4 | pub type TemplateParseResult = Result; 5 | 6 | #[derive(Debug)] 7 | pub enum TemplateParseError { 8 | Syntax(Cow<'static, str>), 9 | } 10 | 11 | //quick_error! { 12 | // #[derive(Debug)] 13 | // pub enum ParseError { 14 | // Syntax(err: String) { 15 | // from() 16 | // }, 17 | // } 18 | //} 19 | -------------------------------------------------------------------------------- /src/loader/group.rs: -------------------------------------------------------------------------------- 1 | use ::abc::{Loader, LoadResult, LoadError}; 2 | 3 | pub type GroupLoader = Vec>; 4 | 5 | impl Loader for Vec> { 6 | fn load(&self, name: &str) -> LoadResult { 7 | for loader in self { 8 | return match loader.load(name) { 9 | Err(LoadError::NotFound) => continue, 10 | Err(err) => Err(err), 11 | Ok(res) => Ok(res), 12 | } 13 | } 14 | Err(LoadError::NotFound) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/loader/dict.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::hash_map::{HashMap}; 3 | use std::hash::BuildHasher; 4 | 5 | use ::abc::{Loader, LoadResult, LoadError}; 6 | 7 | pub type DictLoader = HashMap, Cow<'static, str>>; 8 | 9 | impl Loader for HashMap, Cow<'static, str>, S> 10 | where 11 | S: BuildHasher + Sync + Send, 12 | { 13 | fn load(&self, name: &str) -> LoadResult { 14 | match self.get(name) { 15 | Some(entry) => Ok(entry.to_owned()), 16 | None => Err(LoadError::NotFound), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/loader/namespace.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use ::abc::{Loader, LoadResult, LoadError}; 4 | 5 | 6 | #[derive(Debug)] 7 | pub struct NamespaceLoader { 8 | namespace: Cow<'static, str>, 9 | loader: Box, 10 | } 11 | 12 | impl NamespaceLoader { 13 | pub fn new(namespace: &str, loader: Box) -> Box { 14 | let namespace = namespace.to_owned().into(); 15 | Box::new(NamespaceLoader { namespace, loader }) 16 | } 17 | } 18 | 19 | impl Loader for NamespaceLoader { 20 | fn load(&self, name: &str) -> LoadResult { 21 | match name.starts_with(self.namespace.as_ref()) { 22 | true => self.loader.load(&name[self.namespace.len()..]), 23 | false => Err(LoadError::NotFound), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "incrust" 3 | version = "0.3.0-pre" 4 | authors = ["Alexander Irbis "] 5 | license = "MIT/Apache-2.0" 6 | description = "Template engine inspired by Jinja2" 7 | keywords = ["template", "jinja2", "django", "twig", "swig"] 8 | categories = ["parser-implementations", "template-engine", "web-programming"] 9 | readme = "README.md" 10 | homepage = "https://github.com/alexander-irbis/incrust" 11 | repository = "https://github.com/alexander-irbis/incrust" 12 | documentation = "https://docs.rs/incrust/" 13 | exclude = [ 14 | ".gitignore", 15 | ".travis.yml", 16 | "appveyor.yml", 17 | ] 18 | #build = "build.rs" 19 | publish = true 20 | 21 | [badges] 22 | travis-ci = { repository = "alexander-irbis/incrust" } 23 | 24 | 25 | [features] 26 | default = [] 27 | verbose = [] 28 | quiet = [] 29 | 30 | 31 | [dependencies] 32 | marksman_escape = "0.1.2" 33 | log = "0.4" 34 | maplit = "1.0" 35 | nom = { version = "^3.0", features = ["verbose-errors"] } 36 | url = "1.4" 37 | -------------------------------------------------------------------------------- /src/types/bool.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use types::abc::*; 4 | use Arg; 5 | 6 | 7 | impl <'t> Type<'t> for bool { 8 | fn clone_type(&self) -> Arg<'static> { 9 | Arg::Owned(box *self) 10 | } 11 | } 12 | 13 | impl AsBool for bool { 14 | fn is_bool(&self) -> bool { 15 | true 16 | } 17 | 18 | fn to_bool(&self) -> bool { 19 | *self 20 | } 21 | } 22 | 23 | impl AsReal for bool { 24 | fn try_as_real(&self) -> Option { 25 | Some(if *self { 1_f64 } else { 0_f64 }) 26 | } 27 | } 28 | 29 | impl AsInt for bool { 30 | fn try_as_int(&self) -> Option { 31 | Some(if *self { 1_i64 } else { 0_i64 }) 32 | } 33 | } 34 | 35 | impl IPartialEq for bool { 36 | fn eq<'o>(&self, other: &'o Arg<'o>) -> bool { 37 | other.is_bool() && *self == other.to_bool() 38 | } 39 | } 40 | 41 | impl IPartialOrd for bool { 42 | fn partial_cmp<'o>(&self, other: &'o Arg<'o>) -> Option { 43 | if other.is_bool() { 44 | (self as &PartialOrd).partial_cmp(&other.to_bool()) 45 | } else { 46 | None 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Incrust Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/filter/html_escape.rs: -------------------------------------------------------------------------------- 1 | use abc::{Filter, FilterResult, FilterError}; 2 | use renderer::Writer; 3 | use {VarContext, ex, Arg}; 4 | 5 | 6 | #[derive(Debug)] 7 | pub struct Escape; 8 | 9 | impl Filter for Escape { 10 | #[allow(unused_variables)] 11 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext, value: Option>) -> FilterResult> { 12 | use marksman_escape::Escape as F; 13 | 14 | match value { 15 | None => Ok(None), 16 | Some(value) => { 17 | match value.try_as_string() { 18 | None => { 19 | let mut tmp = String::new(); 20 | value.render(&mut Writer(&mut tmp))?; 21 | String::from_utf8(F::new(tmp.bytes()).collect()) 22 | }, 23 | Some(string) => { 24 | String::from_utf8(F::new(string.as_ref().bytes()).collect()) 25 | } 26 | } 27 | .map(ex).map(Some) 28 | .map_err(|err| FilterError::Process(format!("{:?}", err).into())) 29 | }, 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/container/cycle.rs: -------------------------------------------------------------------------------- 1 | use types::abc::*; 2 | 3 | use {Arg, ex}; 4 | 5 | 6 | #[derive(Debug, Clone, Copy)] 7 | pub struct LoopState { 8 | index: usize, 9 | is_first: bool, 10 | is_last: bool, 11 | } 12 | 13 | impl LoopState { 14 | pub fn new(is_last: bool) -> Self { 15 | LoopState { 16 | index: 0, 17 | is_first: true, 18 | is_last, 19 | } 20 | } 21 | 22 | pub fn next(self, is_last: bool) -> Self { 23 | LoopState { 24 | index: self.index + 1, 25 | is_first: false, 26 | is_last, 27 | } 28 | } 29 | } 30 | 31 | impl <'t> Type<'t> for LoopState { 32 | fn clone_type(&self) -> Arg<'static> { 33 | Arg::Owned(box *self) 34 | } 35 | } 36 | 37 | 38 | impl IComposable for LoopState { 39 | fn get_attr(&self, id: &str) -> Option { 40 | match id { 41 | "index0" => Some(ex(self.index as i64)), 42 | "index" => Some(ex((self.index + 1) as i64)), 43 | "first" => Some(ex(self.is_first)), 44 | "last" => Some(ex(self.is_last)), 45 | _ => None 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/filter/html_unescape.rs: -------------------------------------------------------------------------------- 1 | use abc::{Filter, FilterResult, FilterError}; 2 | use renderer::Writer; 3 | use {VarContext, ex, Arg}; 4 | 5 | 6 | #[derive(Debug)] 7 | pub struct Unescape; 8 | 9 | impl Filter for Unescape { 10 | #[allow(unused_variables)] 11 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext, value: Option>) -> FilterResult> { 12 | use marksman_escape::Unescape as F; 13 | 14 | match value { 15 | None => Ok(None), 16 | Some(value) => { 17 | match value.try_as_string() { 18 | None => { 19 | let mut tmp = String::new(); 20 | value.render(&mut Writer(&mut tmp))?; 21 | String::from_utf8(F::new(tmp.bytes()).collect()) 22 | }, 23 | Some(string) => { 24 | String::from_utf8(F::new(string.as_ref().bytes()).collect()) 25 | } 26 | } 27 | .map(ex).map(Some) 28 | .map_err(|err| FilterError::Process(format!("{:?}", err).into())) 29 | }, 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/filter/url_escape.rs: -------------------------------------------------------------------------------- 1 | use abc::{Filter, FilterResult}; 2 | use renderer::Writer; 3 | use {VarContext, ex, Arg}; 4 | 5 | 6 | #[derive(Debug)] 7 | pub struct UrlEscape; 8 | 9 | impl Filter for UrlEscape { 10 | #[allow(unused_variables)] 11 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext, value: Option>) -> FilterResult> { 12 | use url::percent_encoding::utf8_percent_encode; 13 | use url::percent_encoding::PATH_SEGMENT_ENCODE_SET; 14 | 15 | match value { 16 | None => Ok(None), 17 | Some(value) => { 18 | let value = match value.try_as_string() { 19 | None => { 20 | let mut tmp = String::new(); 21 | value.render(&mut Writer(&mut tmp))?; 22 | utf8_percent_encode(&tmp, PATH_SEGMENT_ENCODE_SET) 23 | .collect::() 24 | }, 25 | Some(string) => { 26 | utf8_percent_encode(&string, PATH_SEGMENT_ENCODE_SET) 27 | .collect::() 28 | } 29 | }; 30 | Ok(Some(ex(value))) 31 | }, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/loader/filesystem.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | use std::path::PathBuf; 4 | 5 | use ::abc::{Loader, LoadResult, LoadError}; 6 | 7 | #[derive(Debug)] 8 | pub struct FilesystemLoader { 9 | path: PathBuf, 10 | } 11 | 12 | impl FilesystemLoader { 13 | pub fn new

(path: P) -> Box where P: Into { 14 | Box::new(FilesystemLoader { path: path.into() }) 15 | } 16 | } 17 | 18 | impl Loader for FilesystemLoader { 19 | fn load(&self, name: &str) -> LoadResult { 20 | // TODO Real Security 21 | if name.contains("..") { 22 | return Err(LoadError::BadName(r#"".." is not supported in a name"#.into())) 23 | } 24 | let mut path = self.path.join(name); 25 | if path.extension().is_none() { 26 | path.set_extension("tpl"); 27 | } 28 | debug!("Load template: {}, path: {:?}", name, path); 29 | if !path.exists() || !path.is_file() { 30 | return Err(LoadError::NotFound); 31 | } 32 | let mut buf = String::new(); 33 | File::open(&path) 34 | .and_then(|mut f| f.read_to_string(&mut buf).map(|_| () )) 35 | .map_err(|err| LoadError::IoError(format!("{:?}; {:?}", err, path).into()))?; 36 | Ok(buf.into()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/filter/url_unescape.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use abc::{Filter, FilterResult, FilterError}; 4 | use renderer::Writer; 5 | use {VarContext, ex, Arg}; 6 | 7 | 8 | #[derive(Debug)] 9 | pub struct UrlUnescape; 10 | 11 | impl Filter for UrlUnescape { 12 | #[allow(unused_variables)] 13 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext, value: Option>) -> FilterResult> { 14 | use url::percent_encoding::percent_decode; 15 | 16 | match value { 17 | None => Ok(None), 18 | Some(value) => { 19 | match value.try_as_string() { 20 | None => { 21 | let mut tmp = String::new(); 22 | value.render(&mut Writer(&mut tmp))?; 23 | percent_decode(tmp.as_bytes()) 24 | .decode_utf8() 25 | .map(Cow::into_owned) 26 | }, 27 | Some(string) => { 28 | percent_decode(string.as_bytes()) 29 | .decode_utf8() 30 | .map(Cow::into_owned) 31 | } 32 | } 33 | .map(ex).map(Some) 34 | .map_err(|err| FilterError::Process(format!("{:?}", err).into())) 35 | }, 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Incrust developers 2 | // 3 | // Licensed under the Apache License, Version 2.0 4 | // or the MIT 5 | // license , at your 6 | // option. All files in the project carrying such notice may not be copied, 7 | // modified, or distributed except according to those terms. 8 | 9 | #![feature(associated_type_defaults)] 10 | #![feature(box_syntax)] 11 | #![feature(specialization)] 12 | 13 | #![allow(clippy::items_after_statements)] 14 | #![allow(clippy::match_bool)] 15 | 16 | #![cfg_attr(feature = "quiet", allow(warnings))] 17 | 18 | 19 | extern crate marksman_escape; 20 | #[macro_use] 21 | extern crate log; 22 | #[macro_use] 23 | extern crate maplit; 24 | #[macro_use] 25 | extern crate nom; 26 | extern crate url; 27 | 28 | 29 | pub mod abc; 30 | pub mod container; 31 | pub mod loader; 32 | pub mod filter; 33 | pub mod parser; 34 | pub mod renderer; 35 | pub mod types; 36 | 37 | pub use self::abc::Loader; 38 | pub use self::container::args::{Arg, Args, EntityId, ex}; 39 | pub use self::container::stack::{Stack, VarContext}; 40 | pub use self::container::incrust::Incrust; 41 | pub use self::container::template::Template; 42 | pub use self::loader::{DictLoader, FilesystemLoader, GroupLoader, NamespaceLoader}; 43 | pub use self::types::abc::Type; 44 | pub use self::types::function::Function; 45 | -------------------------------------------------------------------------------- /src/filter/newline2space.rs: -------------------------------------------------------------------------------- 1 | use abc::{Filter, FilterResult}; 2 | use {VarContext, ex, Arg}; 3 | 4 | 5 | #[derive(Debug)] 6 | pub struct NewlineToSpace; 7 | 8 | impl Filter for NewlineToSpace { 9 | #[allow(unused_variables)] 10 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext, value: Option>) -> FilterResult> { 11 | match value { 12 | None => Ok(None), 13 | Some(value) => { 14 | let value = match value.try_as_string() { 15 | None => String::new(), 16 | Some(string) => string.replace(is_nl, " "), 17 | }; 18 | Ok(Some(ex(value))) 19 | }, 20 | } 21 | } 22 | } 23 | 24 | fn is_nl(c: char) -> bool { 25 | match c { 26 | '\n' | '\r' => true, 27 | _ => false, 28 | } 29 | } 30 | 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | #![allow(clippy::used_underscore_binding)] 35 | 36 | use {Incrust, ex, Args}; 37 | 38 | #[test] 39 | fn it_works() { 40 | let incrust = Incrust::default(); 41 | let test = |expected, sample| { 42 | let args: Args = hashmap!{ "sample".into() => ex(sample) }; 43 | assert_eq!(expected, incrust.render_text("{{ sample | newline_to_space }}", &args).unwrap()); 44 | }; 45 | 46 | test("", ""); 47 | test(" ", " "); 48 | test(" ", "\n"); 49 | test(" ", "\r"); 50 | test(" ", "\r\n"); 51 | test(" ", " \n"); 52 | test(" ", "\n "); 53 | test(" ", " \n "); 54 | test("1 2", "1\n2"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | - staging 5 | - trying 6 | 7 | dist: trusty 8 | sudo: false 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - gcc 14 | - g++ 15 | - libssl-dev 16 | - libelf-dev 17 | - libdw-dev 18 | - binutils-dev 19 | - libiberty-dev 20 | - pkg-config 21 | 22 | language: rust 23 | rust: 24 | - nightly-2018-11-18 25 | 26 | matrix: 27 | allow_failures: 28 | - rust: nightly 29 | 30 | cache: 31 | directories: 32 | - "$HOME/.cargo" 33 | - "$HOME/.local" 34 | - "$TRAVIS_BUILD_DIR/target" 35 | 36 | env: 37 | global: 38 | - DEADLINKS_VERS=0.3.0 39 | - RUSTFLAGS="-C link-dead-code" 40 | - RUST_LOG=off 41 | 42 | install: 43 | - cargo install --list 44 | - cd $TRAVIS_BUILD_DIR 45 | - cargo update 46 | 47 | script: skip 48 | 49 | jobs: 50 | include: 51 | - stage: test 52 | env: 53 | - FEATURE=test 54 | rust: nightly-2018-11-18 55 | script: 56 | - cargo test --verbose --all 57 | 58 | - stage: test 59 | env: 60 | - FEATURE=test 61 | rust: nightly 62 | script: 63 | - cargo test --verbose --all 64 | 65 | - stage: quality 66 | env: 67 | - FEATURE=non-fatal-checks 68 | install: 69 | - cargo-deadlinks -V | grep $DEADLINKS_VERS || cargo install cargo-deadlinks --vers $DEADLINKS_VERS --force 70 | script: 71 | - cargo doc --no-deps && cargo deadlinks --dir target/doc || true 72 | 73 | - stage: quality 74 | env: 75 | - FEATURE=clippy 76 | rust: nightly-2018-11-18 77 | install: 78 | - rustup component add clippy-preview 79 | script: 80 | - cargo clippy -- -D warnings 81 | -------------------------------------------------------------------------------- /src/types/real.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use types::abc::*; 4 | use Arg; 5 | 6 | 7 | impl <'t> Type<'t> for f64 { 8 | fn clone_type(&self) -> Arg<'static> { 9 | Arg::Owned(box *self) 10 | } 11 | } 12 | 13 | 14 | impl AsBool for f64 { 15 | fn to_bool(&self) -> bool { 16 | *self != 0.0 17 | } 18 | } 19 | 20 | 21 | impl AsReal for f64 { 22 | fn try_as_real(&self) -> Option { 23 | Some(*self) 24 | } 25 | 26 | fn is_real(&self) -> bool { 27 | true 28 | } 29 | } 30 | 31 | 32 | impl AsInt for f64 { 33 | fn try_as_int(&self) -> Option { 34 | Some(*self as i64) 35 | } 36 | } 37 | 38 | 39 | impl IPartialEq for f64 { 40 | fn eq<'o>(&self, other: &'o Arg<'o>) -> bool { 41 | other.try_as_real().map(|s| s == *self).unwrap_or(false) 42 | } 43 | } 44 | 45 | 46 | impl IPartialOrd for f64 { 47 | fn partial_cmp<'o>(&self, other: &'o Arg<'o>) -> Option { 48 | if other.is_real() || other.is_int() { 49 | other.try_as_real().and_then(|s| (self as &PartialOrd).partial_cmp(&s)) 50 | } else { 51 | None 52 | } 53 | } 54 | } 55 | 56 | 57 | impl IArithm for f64 { 58 | // todo Cow::Borrowed for Zero and One cases 59 | fn try_add<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_real().map(|s| { Arg::Owned(box (*self + s)) }) } 60 | fn try_sub<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_real().map(|s| { Arg::Owned(box (*self - s)) }) } 61 | fn try_mul<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_real().map(|s| { Arg::Owned(box (*self * s)) }) } 62 | fn try_div<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_real().map(|s| { Arg::Owned(box (*self / s)) }) } 63 | } 64 | -------------------------------------------------------------------------------- /src/types/function.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter, Error}; 2 | 3 | use abc::EvalResult; 4 | use VarContext; 5 | 6 | use types::abc::*; 7 | use Arg; 8 | 9 | 10 | pub struct Function { 11 | pub f: for <'res, 'ctx> fn(&[Arg<'res>], &'ctx VarContext<'res>) -> EvalResult>, 12 | } 13 | 14 | impl Function { 15 | // TODO update API to satisfy convention 16 | #[allow(clippy::new_ret_no_self)] 17 | pub fn new(f: for <'res, 'ctx> fn(&[Arg<'res>], &'ctx VarContext<'res>) -> EvalResult>) -> Arg<'static> { 18 | Arg::Owned(box Function { f }) 19 | } 20 | } 21 | 22 | impl Clone for Function { 23 | fn clone(&self) -> Self { 24 | Function { f: self.f } 25 | } 26 | } 27 | 28 | impl Debug for Function { 29 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 30 | fmt.write_str("AnonymousFunction") 31 | } 32 | } 33 | 34 | impl <'t> Type<'t> for Function { 35 | fn clone_type(&self) -> Arg<'static> { 36 | Arg::Owned(box self.clone()) 37 | } 38 | } 39 | 40 | impl IInvokable for Function { 41 | fn invoke<'r: 'a + 'c, 'a, 'c>(&self, args: &'a [Arg<'r>], context: &'c VarContext<'r>) -> EvalResult> { 42 | (self.f)(args, context) 43 | } 44 | } 45 | 46 | 47 | 48 | // -------------------------------------------------------------------------------------------------------------------- 49 | 50 | 51 | //impl Type for (fn(&[BType], &Context) -> EvalResult) { 52 | // fn iclone<'a>(&self) -> BType<'a> { Box::new(self.clone()) } 53 | // fn to_bool(&self) -> bool { true } 54 | //} 55 | // 56 | //impl <'a, 'b: 'a> IInvokable<'a> for (fn(&[BType], &Context) -> EvalResult) { 57 | // fn invoke(&self, args: &[BType], context: &Context) -> EvalResult { 58 | // self(args, context, env) 59 | // } 60 | //} 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/types/int.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | use types::abc::*; 4 | use Arg; 5 | 6 | 7 | impl <'t> Type<'t> for i64 { 8 | fn clone_type(&self) -> Arg<'static> { 9 | Arg::Owned(box *self) 10 | } 11 | } 12 | 13 | impl AsBool for i64 { 14 | fn to_bool(&self) -> bool { 15 | *self != 0 16 | } 17 | } 18 | 19 | impl AsReal for i64 { 20 | fn try_as_real(&self) -> Option { 21 | Some(*self as f64) 22 | } 23 | } 24 | 25 | impl AsInt for i64 { 26 | fn try_as_int(&self) -> Option { 27 | Some(*self) 28 | } 29 | 30 | fn is_int(&self) -> bool { 31 | true 32 | } 33 | } 34 | 35 | 36 | impl IPartialEq for i64 { 37 | fn eq<'o>(&self, other: &'o Arg<'o>) -> bool { 38 | other.try_as_int().map(|s| s == *self).unwrap_or(false) 39 | } 40 | } 41 | 42 | 43 | impl IPartialOrd for i64 { 44 | fn partial_cmp<'o>(&self, other: &'o Arg<'o>) -> Option { 45 | if other.is_int() { 46 | other.try_as_int().and_then(|s| (self as &PartialOrd).partial_cmp(&s)) 47 | } else if other.is_real() { 48 | let val = *self as f64; 49 | other.try_as_real().and_then(|s| (&val as &PartialOrd).partial_cmp(&s)) 50 | } else { 51 | None 52 | } 53 | } 54 | } 55 | 56 | 57 | impl IArithm for i64 { 58 | // todo Cow::Borrowed for Zero and One cases 59 | fn try_add<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_int().map(|s| { Arg::Owned(box (*self + s)) }) } 60 | fn try_sub<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_int().map(|s| { Arg::Owned(box (*self - s)) }) } 61 | fn try_mul<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_int().map(|s| { Arg::Owned(box (*self * s)) }) } 62 | fn try_div<'o>(&self, other: Arg<'o>) -> Option> { other.try_as_int().map(|s| { Arg::Owned(box (*self / s)) }) } 63 | } 64 | -------------------------------------------------------------------------------- /src/types/map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::{HashMap, Keys, Values, Iter}; 2 | use std::iter::Iterator; 3 | 4 | use super::abc::*; 5 | 6 | 7 | impl <'a> Type for HashMap> { 8 | fn to_bool(&self) -> bool { !self.is_empty() } 9 | } 10 | 11 | impl <'a> IClone for HashMap> { 12 | fn iclone<'b>(&self) -> Result, CloneError> { 13 | let cloned: HashMap> = HashMap::new(); 14 | for (k, v) in self.iter() { 15 | cloned.insert(k, v.iclone()?); 16 | } 17 | Ok( Box::new(cloned) ) 18 | } 19 | } 20 | 21 | 22 | impl <'a, 'b: 'a> IIterable<'a> for HashMap> { 23 | fn is_empty(&self) -> bool { 24 | HashMap::is_empty(self) 25 | } 26 | 27 | fn ivalues(&self) -> VIterator { 28 | VIterator { me: self.values() } 29 | } 30 | } 31 | 32 | 33 | 34 | //impl <'a> IMap for HashMap> { 35 | // fn ivalues(&self) -> Option>> { 36 | // Some(Box::new(VIterator { me: self.values() })) 37 | // } 38 | //// fn ikeys(&self) -> Option { 39 | //// Some(KIterator { me: self.keys() }) 40 | //// } 41 | //// fn ikeyvalues(&self) -> Option { 42 | //// Some(KVIterator { me: self.iter() }) 43 | //// } 44 | //} 45 | 46 | 47 | 48 | //impl <'a> Iterator for KIterator<'a> { 49 | // type Item = &'a EntityId; 50 | // 51 | // fn next(&mut self) -> Option<&'a EntityId> { 52 | // match self.me.next() { 53 | // Some(next) => Some(next), 54 | // None => None 55 | // } 56 | // } 57 | //} 58 | // 59 | //impl <'a> Iterator for KVIterator<'a> { 60 | // type Item = (&'a EntityId, &'a Var); 61 | // 62 | // fn next(&mut self) -> Option<(&'a EntityId, &'a Var)> { 63 | // match self.me.next() { 64 | // Some(next) => Some(next), 65 | // None => None 66 | // } 67 | // } 68 | //} 69 | -------------------------------------------------------------------------------- /src/container/args.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | use std::convert::AsRef; 4 | use std::ops::Deref; 5 | 6 | use types::abc::Type; 7 | 8 | 9 | pub type EntityId<'a> = Cow<'a, str>; 10 | pub type Args<'a> = HashMap, Arg<'a>>; 11 | 12 | 13 | #[inline] 14 | pub fn ex<'r, A: Into>>(v: A) -> Arg<'r> { 15 | v.into() 16 | } 17 | 18 | 19 | #[derive(Debug)] 20 | pub enum Arg<'r> { 21 | Owned(Box Type<'t> + 'r>), 22 | Ref(&'r (for <'t> Type<'t> + 'r)), 23 | } 24 | 25 | 26 | impl <'r> Clone for Arg<'r> { 27 | fn clone(&self) -> Arg<'r> { 28 | match *self { 29 | Arg::Ref(b) => Arg::Ref(b), 30 | Arg::Owned(ref o) => o.clone_type(), 31 | } 32 | } 33 | } 34 | 35 | 36 | impl <'r> Arg<'r> { 37 | pub fn to_ref(&'r self) -> Arg<'r> { 38 | Arg::Ref(self.as_ref()) 39 | } 40 | 41 | pub fn to_owned(&self) -> Arg<'static> { 42 | self.clone_type() 43 | } 44 | 45 | pub fn into_owned(self) -> Arg<'r> { 46 | match self { 47 | Arg::Owned(_) => self, 48 | Arg::Ref(_) => self.clone_type(), 49 | } 50 | } 51 | } 52 | 53 | 54 | impl <'r> Deref for Arg<'r> { 55 | type Target = for <'t> Type<'t> + 'r; 56 | 57 | fn deref(&self) -> &Self::Target { 58 | match *self { 59 | Arg::Owned(ref b) => b.deref(), 60 | Arg::Ref(r) => r, 61 | } 62 | } 63 | } 64 | 65 | 66 | impl <'r> AsRef Type<'t> + 'r> for Arg<'r> { 67 | fn as_ref(&self) -> &(for <'t> Type<'t> + 'r) { 68 | &**self 69 | } 70 | } 71 | 72 | 73 | impl <'r> From<&'r Arg<'r>> for Arg<'r> { 74 | fn from(v: &'r Arg<'r>) -> Arg<'r> { 75 | Arg::Ref(v.as_ref()) 76 | } 77 | } 78 | 79 | impl <'r, T: for <'t> Type<'t> + 'r> From for Arg<'r> { 80 | fn from(v: T) -> Arg<'r> { 81 | Arg::Owned(box v) 82 | } 83 | } 84 | 85 | impl <'r> From<&'r (for <'t> Type<'t> + 'r)> for Arg<'r> { 86 | fn from(v: &'r (for <'t> Type<'t> + 'r)) -> Arg<'r> { 87 | Arg::Ref(v) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/container/stack.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use abc::*; 4 | use {Args, Arg, Incrust, Template}; 5 | 6 | 7 | pub type TemplateStack<'a> = Vec<&'a Template>; 8 | 9 | 10 | pub struct Stack<'a> { 11 | env: &'a Incrust, 12 | template_stack: Vec>, 13 | args: &'a Args<'a>, 14 | } 15 | 16 | 17 | pub struct VarContext<'a> { 18 | global: &'a Stack<'a>, 19 | parent: Option<&'a VarContext<'a>>, 20 | args: &'a Args<'a>, 21 | } 22 | 23 | 24 | impl <'a> Stack<'a> { 25 | pub fn new(env: &'a Incrust, template: Cow<'a, Template>, args: &'a Args<'a>) -> RenderResult { 26 | let mut stack = Stack { env, template_stack: vec![], args }; 27 | 28 | let mut parent = Some(template); 29 | while let Some(template) = parent { 30 | parent = template.get_parent(&stack.top_scope())?.map(Cow::Owned); 31 | stack.template_stack.push(template); 32 | } 33 | 34 | Ok(stack) 35 | } 36 | 37 | pub fn top_scope(&'a self) -> VarContext<'a> { 38 | VarContext::new(self, self.args) 39 | } 40 | 41 | pub fn template(&'a self) -> &'a Template { 42 | self.template_stack.last().unwrap() 43 | } 44 | 45 | pub fn stack(&'a self) -> &'a [Cow<'a, Template>] { 46 | &self.template_stack 47 | } 48 | 49 | pub fn env(&self) -> &'a Incrust { 50 | self.env 51 | } 52 | } 53 | 54 | 55 | impl <'a> VarContext<'a> { 56 | pub fn new(global: &'a Stack<'a>, args: &'a Args<'a>) -> Self { 57 | VarContext { global, parent: None, args } 58 | } 59 | 60 | pub fn nested_scope(&'a self, args: &'a Args<'a>) -> Self { 61 | VarContext { global: self.global, parent: Some(self), args } 62 | } 63 | 64 | pub fn template(&self) -> &'a Template { 65 | self.global.template() 66 | } 67 | 68 | pub fn global(&self) -> &'a Stack<'a> { 69 | self.global 70 | } 71 | 72 | pub fn env(&self) -> &'a Incrust { 73 | self.global.env() 74 | } 75 | 76 | pub fn get(&self, id: &str) -> Option> { 77 | self.args.get(id).map(Arg::from) 78 | .or_else(|| self.parent 79 | .and_then(|p| p.get(id)) 80 | .or_else(|| self.global.env().top_context().get(id).map(Arg::from)) 81 | ) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/loader.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate maplit; 3 | extern crate incrust; 4 | 5 | use std::path::Path; 6 | use incrust::{Incrust, ex, Loader, FilesystemLoader, NamespaceLoader}; 7 | 8 | 9 | #[test] 10 | fn dict() { 11 | let mut incrust = Incrust::new(); 12 | incrust.loaders.push(Box::new(hashmap!{ 13 | "1".into() => r#"

{{ title | e }}

14 | 15 | 16 | {%- if fruits %} 17 |
    18 | {%- for fruit in fruits %} 19 |
  • {{ loop.index }}. {{ fruit | e }}
  • 20 | {%- endfor %} 21 |
22 | {%- endif %} 23 |
24 | "#.into(), 25 | })); 26 | let sample_loader = FilesystemLoader::new(&Path::new("./assets/html/simple")); 27 | 28 | let sample_a = sample_loader.load("1-a.html").unwrap(); 29 | let args = || hashmap!{ 30 | "title".into() => ex("fruits"), 31 | "fruits".into() => ex(vec![ex("Orange"), ex("Apple"), ex("Banana")]) 32 | }; 33 | 34 | assert_eq!(sample_a, incrust.render("1", &args()).unwrap()); 35 | } 36 | 37 | #[test] 38 | fn filesystem() { 39 | let mut incrust = Incrust::new(); 40 | incrust.loaders.push(FilesystemLoader::new(&Path::new("./assets/tpl/simple"))); 41 | let sample_loader = FilesystemLoader::new(&Path::new("./assets/html/simple")); 42 | 43 | let sample_a = sample_loader.load("1-a.html").unwrap(); 44 | let args = hashmap!{ 45 | "title".into() => ex("fruits"), 46 | "fruits".into() => ex(vec![ex("Orange"), ex("Apple"), ex("Banana")]) 47 | }; 48 | 49 | assert_eq!(sample_a, incrust.render("1", &args).unwrap()); 50 | assert_eq!(sample_a, incrust.render("1.tpl", &args).unwrap()); 51 | } 52 | 53 | #[test] 54 | fn namespace() { 55 | let mut incrust = Incrust::new(); 56 | incrust.loaders.push(NamespaceLoader::new("simple:", FilesystemLoader::new(&Path::new("./assets/tpl/simple")))); 57 | let sample_loader = FilesystemLoader::new(&Path::new("./assets/html/simple")); 58 | 59 | let sample_a = sample_loader.load("1-a.html").unwrap(); 60 | let args = hashmap!{ 61 | "title".into() => ex("fruits"), 62 | "fruits".into() => ex(vec![ex("Orange"), ex("Apple"), ex("Banana")]) 63 | }; 64 | 65 | assert_eq!(sample_a, incrust.render("simple:1", &args).unwrap()); 66 | assert_eq!(sample_a, incrust.render("simple:1.tpl", &args).unwrap()); 67 | } 68 | -------------------------------------------------------------------------------- /tests/compos.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_syntax)] 2 | #![feature(specialization)] 3 | 4 | #[macro_use] 5 | extern crate maplit; 6 | extern crate incrust; 7 | 8 | use std::path::Path; 9 | 10 | use incrust::abc::EvalResult; 11 | use incrust::{Incrust, ex, Loader, FilesystemLoader, Type, Arg, Function, VarContext}; 12 | use incrust::types::abc::{AsComposable, IComposable}; 13 | 14 | #[derive(Debug, Clone)] 15 | struct Fruit { 16 | title: String, 17 | price: f64, 18 | } 19 | 20 | impl Fruit { 21 | pub fn new(title: &str, price: f64) -> Fruit { Fruit { title: title.to_owned(), price } } 22 | } 23 | 24 | impl <'t> Type<'t> for Fruit { 25 | fn clone_type(&self) -> Arg<'static> { 26 | Arg::Owned(box self.clone()) 27 | } 28 | } 29 | 30 | 31 | impl AsComposable for Fruit { 32 | fn try_as_composable(&self) -> Option<&IComposable> { Some(self) } 33 | } 34 | 35 | 36 | impl IComposable for Fruit { 37 | fn get_attr(&self, id: &str) -> Option { 38 | match id { 39 | "title" => Some(ex(self.title.as_str())), 40 | "price" => Some(ex(self.price)), 41 | _ => None 42 | } 43 | } 44 | } 45 | 46 | 47 | #[test] 48 | fn attributes() { 49 | let mut incrust = Incrust::new(); 50 | incrust.loaders.push(FilesystemLoader::new(&Path::new("./assets/tpl/simple"))); 51 | let sample_loader = FilesystemLoader::new(&Path::new("./assets/html/simple")); 52 | 53 | let sample_a = sample_loader.load("2a.html").unwrap(); 54 | let args = hashmap!{ 55 | "title".into() => ex("fruits"), 56 | "fruits".into() => ex(vec![ 57 | ex(Fruit::new("Orange", 4.0)), 58 | ex(Fruit::new("Apple", 2.5)), 59 | ex(Fruit::new("Banana", 2.25)), 60 | ]) 61 | }; 62 | 63 | assert_eq!(sample_a, incrust.render("2a", &args).unwrap()); 64 | } 65 | 66 | 67 | #[test] 68 | fn invokables() { 69 | let mut incrust = Incrust::new(); 70 | incrust.loaders.push(FilesystemLoader::new(&Path::new("./assets/tpl/simple"))); 71 | let sample_loader = FilesystemLoader::new(&Path::new("./assets/html/simple")); 72 | 73 | fn title<'res, 'ctx>(_: &[Arg<'res>], _: &'ctx VarContext<'res>) -> EvalResult> { 74 | Ok(Some(ex("fruits"))) 75 | } 76 | 77 | let sample_a = sample_loader.load("2a.html").unwrap(); 78 | let args = hashmap!{ 79 | "title".into() => Function::new(title), 80 | "fruits".into() => ex(vec![ 81 | ex(Fruit::new("Orange", 4.0)), 82 | ex(Fruit::new("Apple", 2.5)), 83 | ex(Fruit::new("Banana", 2.25)), 84 | ]) 85 | }; 86 | 87 | assert_eq!(sample_a, incrust.render("2b", &args).unwrap()); 88 | } 89 | -------------------------------------------------------------------------------- /src/container/expression.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Clone)] 2 | pub struct Mustache { 3 | pub expr: FullExpression, 4 | } 5 | 6 | #[derive(Debug, PartialEq, Clone)] 7 | pub struct FullExpression { 8 | pub expr: DisjExpr, 9 | pub filters: Vec 10 | } 11 | 12 | 13 | #[derive(Debug, PartialEq, Clone)] 14 | pub enum ConjOp { 15 | And, 16 | } 17 | 18 | #[derive(Debug, PartialEq, Clone)] 19 | pub enum DisjOp { 20 | Or, 21 | } 22 | 23 | #[derive(Debug, PartialEq, Clone)] 24 | pub enum CmpOp { 25 | Lt, 26 | Lte, 27 | Eq, 28 | Neq, 29 | In, 30 | Nin, 31 | Gte, 32 | Gt, 33 | } 34 | 35 | #[derive(Debug, PartialEq, Clone)] 36 | pub enum SumOp { 37 | Add, 38 | Sub, 39 | } 40 | 41 | #[derive(Debug, PartialEq, Clone)] 42 | pub enum MulOp { 43 | Mul, 44 | Div, 45 | } 46 | 47 | #[derive(Debug, PartialEq, Clone)] 48 | pub struct DisjExpr { 49 | pub list: Vec, 50 | } 51 | 52 | #[derive(Debug, PartialEq, Clone)] 53 | pub struct ConjExpr { 54 | pub list: Vec, 55 | } 56 | 57 | #[derive(Debug, PartialEq, Clone)] 58 | pub struct CmpExpr { 59 | pub list: Vec, 60 | } 61 | 62 | #[derive(Debug, PartialEq, Clone)] 63 | pub struct CmpItem(pub CmpOp, pub Expr); 64 | 65 | 66 | #[derive(Debug, PartialEq, Clone)] 67 | pub struct Expr { 68 | pub sum: Vec, 69 | } 70 | 71 | #[derive(Debug, PartialEq, Clone)] 72 | pub struct ExprItem(pub SumOp, pub Term); 73 | 74 | 75 | #[derive(Debug, PartialEq, Clone)] 76 | pub struct Term { 77 | pub mul: Vec, 78 | } 79 | 80 | #[derive(Debug, PartialEq, Clone)] 81 | pub struct TermItem(pub MulOp, pub Factor); 82 | 83 | 84 | #[derive(Debug, PartialEq, Clone)] 85 | pub enum Factor { 86 | Invocation(Invocation), 87 | Index(Index), 88 | Attribute(Attribute), 89 | Variable(String), 90 | Literal(Literal), 91 | Subexpression(DisjExpr), 92 | } 93 | 94 | #[derive(Debug, PartialEq, Clone)] 95 | pub struct Attribute { 96 | pub id: String, 97 | pub on: Box, 98 | } 99 | 100 | #[derive(Debug, PartialEq, Clone)] 101 | pub struct Invocation { 102 | pub args: Vec, 103 | pub on: Box, 104 | } 105 | 106 | #[derive(Debug, PartialEq, Clone)] 107 | pub struct Index { 108 | pub index: Box, 109 | pub on: Box, 110 | } 111 | 112 | #[derive(Debug, PartialEq, Clone)] 113 | pub enum Literal { 114 | Char(char), 115 | Str(String), 116 | Int(i64), 117 | Real(f64), 118 | } 119 | 120 | #[derive(Debug, PartialEq, Clone)] 121 | pub enum FilterItem { 122 | Simple(String), 123 | } 124 | 125 | -------------------------------------------------------------------------------- /src/parser/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! stmt_simple { 3 | ( $i:expr, $cmd: expr ) => ({ 4 | use nom::multispace; 5 | 6 | do_parse!($i, 7 | tag!("{%") >> 8 | l: opt!(tag!("-")) >> 9 | many0!(multispace) >> 10 | tag!($cmd) >> 11 | many0!(multispace) >> 12 | r: opt!(tag!("-")) >> 13 | tag!("%}") >> 14 | ( SimpleStatement { strip_left: l.is_some(), strip_right: r.is_some() } ) 15 | ) 16 | }); 17 | } 18 | 19 | 20 | #[macro_export] 21 | macro_rules! stmt_named { 22 | ( $i:expr, $cmd: expr ) => ({ 23 | use nom::multispace; 24 | use parser::expressions::identifier; 25 | 26 | do_parse!($i, 27 | tag!("{%") >> 28 | l: opt!(tag!("-")) >> 29 | many0!(multispace) >> 30 | tag!($cmd) >> 31 | many0!(multispace) >> 32 | e: identifier >> 33 | many0!(multispace) >> 34 | r: opt!(tag!("-")) >> 35 | tag!("%}") >> 36 | ( NamedStatement { strip_left: l.is_some(), strip_right: r.is_some(), name: e } ) 37 | ) 38 | }); 39 | } 40 | 41 | 42 | #[macro_export] 43 | macro_rules! stmt_expr { 44 | ( $i:expr, $cmd: expr ) => ({ 45 | use nom::multispace; 46 | use parser::expressions::full_expression; 47 | 48 | do_parse!($i, 49 | tag!("{%") >> 50 | l: opt!(tag!("-")) >> 51 | many0!(multispace) >> 52 | tag!($cmd) >> 53 | many0!(multispace) >> 54 | e: full_expression >> 55 | many0!(multispace) >> 56 | r: opt!(tag!("-")) >> 57 | tag!("%}") >> 58 | ( ExprStatement { strip_left: l.is_some(), strip_right: r.is_some(), expression: e } ) 59 | ) 60 | }); 61 | } 62 | 63 | 64 | /// `take_till!(T -> bool) => &[T] -> IResult<&[T], &[T]>` 65 | /// returns the longest list of bytes until the provided function succeeds 66 | /// 67 | /// The argument is either a function `&[T] -> bool` or a macro returning a `bool 68 | #[macro_export] 69 | macro_rules! take_till_slc ( 70 | ($input:expr, $submac:ident!( $($args:tt)* )) => ( 71 | { 72 | use nom::InputLength; 73 | match $input.iter().enumerate().position(|(i, _)| $submac!(&$input[i..], $($args)*)) { 74 | Some(n) => IResult::Done(&$input[n..], &$input[..n]), 75 | None => IResult::Done(&$input[($input).input_len()..], $input) 76 | } 77 | } 78 | ); 79 | ($input:expr, $f:expr) => ( 80 | take_till_slc!($input, call!($f)); 81 | ); 82 | ); 83 | -------------------------------------------------------------------------------- /src/parser/block_level.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | #[allow(unused_imports)] 3 | use nom::{IResult, Err as NomErr, ErrorKind, alpha, alphanumeric, space, multispace}; 4 | 5 | use container::expression::Mustache; 6 | use container::parsed::ParsedNode; 7 | 8 | use parser::statements::statement; 9 | use parser::statements::stmt_edge; 10 | use parser::expressions::full_expression; 11 | 12 | 13 | named!(pub text<&[u8], Vec >, terminated!( nodes, eof!() ) ); 14 | 15 | 16 | pub fn nodes(input: &[u8]) -> IResult<&[u8], Vec > { 17 | let (i, list) = try_parse!(input, many0!(node) ); 18 | IResult::Done(i, list) 19 | } 20 | 21 | 22 | 23 | named!(node<&[u8], ParsedNode>, 24 | alt!( 25 | plain_text | 26 | comment | 27 | mustache | 28 | statement 29 | ) 30 | ); 31 | 32 | 33 | fn comment(input: &[u8]) -> IResult<&[u8], ParsedNode> { 34 | let (i, (_, comment, _)) = try_parse!(input, 35 | tuple!( 36 | tag!("{#"), 37 | map_res!( 38 | take_until!("#}"), 39 | str::from_utf8 40 | ), 41 | tag!("#}") 42 | ) 43 | ); 44 | IResult::Done(i, ParsedNode::Comment(comment.to_owned())) 45 | } 46 | 47 | 48 | fn mustache(input: &[u8]) -> IResult<&[u8], ParsedNode> { 49 | do_parse!(input, 50 | tag!("{{") >> 51 | many0!(multispace) >> 52 | fe: full_expression >> 53 | many0!(multispace) >> 54 | tag!("}}") >> 55 | ( Mustache::new(fe).into() ) 56 | ) 57 | } 58 | 59 | 60 | fn plain_text(input: &[u8]) -> IResult<&[u8], ParsedNode> { 61 | fn try_brace<'a>(input: &'a [u8]) -> IResult<&[u8], &str> { 62 | let b = || -> IResult<&'a [u8], ()> { 63 | let _ = try_parse!(input, alt!( mustache | comment | stmt_edge ) ); 64 | IResult::Done(input, ()) 65 | }; 66 | match b().is_done() { 67 | false => IResult::Done(&input[1..], "{"), 68 | true => IResult::Error(NomErr::Code(ErrorKind::Custom(0))), 69 | } 70 | } 71 | 72 | let (i, text) = try_parse!(input, 73 | do_parse!( 74 | v: many1!( 75 | alt!( map_res!( is_not!("{"), str::from_utf8 ) | try_brace ) 76 | ) >> 77 | ( v.join("") ) 78 | ) 79 | ); 80 | IResult::Done(i, ParsedNode::Text(text)) 81 | } 82 | 83 | 84 | 85 | // --------------------------------------------------------------------------- 86 | 87 | 88 | //#[cfg(test)] 89 | //mod tests { 90 | // #![cfg_attr(feature = "cargo-clippy", allow(used_underscore_binding))] 91 | // 92 | // use nom::IResult::Done; 93 | // 94 | //} 95 | -------------------------------------------------------------------------------- /src/types/string.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::cmp::Ordering; 3 | 4 | use types::abc::*; 5 | //use types::function::Function; 6 | use {Arg, ex}; 7 | 8 | 9 | impl <'t> Type<'t> for String { 10 | fn clone_type(&self) -> Arg<'static> { 11 | Arg::Owned(box self.clone()) 12 | } 13 | } 14 | 15 | 16 | impl AsBool for String { 17 | fn to_bool(&self) -> bool { 18 | !self.is_empty() 19 | } 20 | } 21 | 22 | 23 | impl AsString for String { 24 | fn is_string(&self) -> bool { 25 | true 26 | } 27 | 28 | fn try_as_string(&self) -> Option> { 29 | Some(Cow::Borrowed(self)) 30 | } 31 | } 32 | 33 | 34 | impl IPartialEq for String { 35 | fn eq<'o>(&self, other: &'o Arg<'o>) -> bool { 36 | other.is_string() && other.try_as_string().map(|s| s.as_ref() == self).unwrap_or(false) 37 | } 38 | } 39 | 40 | 41 | impl IPartialOrd for String { 42 | fn partial_cmp<'o>(&self, other: &'o Arg<'o>) -> Option { 43 | if other.is_string() { 44 | other.try_as_string().and_then(|s| self.as_str().partial_cmp(s.as_ref())) 45 | } else { 46 | None 47 | } 48 | } 49 | } 50 | 51 | 52 | impl IArithm for String { 53 | fn try_add<'o> (&self, other: Arg<'o>) -> Option> { 54 | if self == "" { 55 | match other.is_string() { 56 | true => Some(other), 57 | false => other.try_as_string() 58 | .map(|s| ex(s.into_owned())), 59 | } 60 | } else { 61 | other.try_as_string() 62 | .map(move |s| ex(self.to_string() + s.as_ref())) 63 | } 64 | } 65 | } 66 | 67 | 68 | impl <'r> Into> for &'r str { 69 | fn into(self) -> Arg<'r> { 70 | ex(self.to_owned()) 71 | // FIXME Arg::Ref(self) 72 | } 73 | } 74 | 75 | 76 | impl AsComposable for String { 77 | fn try_as_composable(&self) -> Option<&IComposable> { 78 | Some(self) 79 | } 80 | } 81 | 82 | 83 | impl IComposable for String { 84 | fn get_attr(&self, id: &str) -> Option { 85 | match id { 86 | "length" => Some(ex(self.len() as i64)), 87 | // "len" => Some(ex(Len(self))), 88 | // "len" => Some(Function::new(move |_args, _context| Ok(Some(box (self.len() as i64)))).into() )), 89 | _ => None 90 | } 91 | } 92 | } 93 | 94 | 95 | //use incrust::Context; 96 | //use abc::EvalResult; 97 | // 98 | //#[derive(Clone, Debug)] 99 | //struct Len<'a>(&'a str); 100 | // 101 | //impl <'aa> Type for Len<'aa> { 102 | // fn iclone<'a>(&self) -> BType<'a> { 103 | // box self.clone() 104 | // } 105 | //} 106 | // 107 | //impl <'a> Into> for Len<'a> { 108 | // fn into(self) -> BType<'a> { 109 | // box self 110 | // } 111 | //} 112 | // 113 | //impl <'a> IInvokable for Len<'a> { 114 | // fn invoke(&self, _args: &[BType], _context: &Context) -> EvalResult { 115 | // Ok(Some((self.0.len() as i64).into())) 116 | // } 117 | //} 118 | 119 | -------------------------------------------------------------------------------- /src/renderer/abc.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | 4 | use abc::*; 5 | use {Arg, VarContext}; 6 | 7 | 8 | pub type EvalResult = Result, EvalError>; 9 | 10 | #[derive(Debug)] 11 | pub enum EvalError { 12 | Invoke(InvokeError), 13 | NoneArg, 14 | NotComposable, 15 | NotIndexable, 16 | NotMappable, 17 | UnexpectedIndexType, 18 | AttributeNotExists(Cow<'static, str>), 19 | IndexNotExists(usize), 20 | KeyNotExists(Cow<'static, str>), 21 | Input(Cow<'static, str>), 22 | Process(Cow<'static, str>), 23 | } 24 | 25 | 26 | #[derive(Debug)] 27 | pub enum InvokeError { 28 | NotInvokable, 29 | WrongArgsNumber(usize, usize), 30 | WrongArgType(usize, ExpectedArgType), 31 | } 32 | 33 | #[derive(Debug)] 34 | pub enum ExpectedArgType { 35 | String, 36 | Int, 37 | Real, 38 | Bool 39 | } 40 | 41 | impl From for EvalError { fn from(err: InvokeError) -> Self { EvalError::Invoke(err) } } 42 | 43 | 44 | pub type FilterResult = Result, FilterError>; 45 | 46 | #[derive(Debug)] 47 | pub enum FilterError { 48 | UnknownFormatter(Cow<'static, str>), 49 | Input(Cow<'static, str>), 50 | Process(Cow<'static, str>), 51 | Format(fmt::Error), 52 | } 53 | 54 | impl From for FilterError { fn from(err: fmt::Error) -> Self { FilterError::Format(err) } } 55 | 56 | 57 | pub trait Filter: fmt::Debug + Send + Sync { 58 | fn filter<'s: 'a, 'a>(&'s self, context: &'a VarContext<'a>, value: Option>) -> FilterResult>; 59 | } 60 | 61 | 62 | pub type RenderResult = Result; 63 | 64 | #[derive(Debug)] 65 | pub enum RenderError { 66 | LoadTemplate(LoadError), 67 | ParseTemplate(TemplateParseError), 68 | VariableNotExists(Cow<'static, str>), 69 | EvalExpression(EvalError), 70 | Filter(FilterError), 71 | FunctionCallException(Cow<'static, str>), 72 | Format(fmt::Error), 73 | } 74 | 75 | impl From for RenderError { fn from(err: LoadError) -> Self { RenderError::LoadTemplate(err) } } 76 | impl From for RenderError { fn from(err: EvalError) -> Self { RenderError::EvalExpression(err) } } 77 | impl From for RenderError { fn from(err: TemplateParseError) -> Self { RenderError::ParseTemplate(err) } } 78 | impl From for RenderError { fn from(err: FilterError) -> Self { RenderError::Filter(err) } } 79 | impl From for RenderError { fn from(err: fmt::Error) -> Self { RenderError::Format(err) } } 80 | 81 | 82 | //quick_error! { 83 | // #[derive(Debug)] 84 | // pub enum RenderError { 85 | // LoadTemplate(err: LoadError) { 86 | // from() 87 | // }, 88 | // EvalExpression(err: EvalError) { 89 | // from() 90 | // }, 91 | // ParseTemplate(err: ParseError) { 92 | // from() 93 | // }, 94 | // Filter(err: FilterError) { 95 | // from() 96 | // }, 97 | // Format(err: fmt::Error) { 98 | // from() 99 | // }, 100 | // VariableNotExists(err: String) { 101 | // 102 | // }, 103 | // FunctionCallException(err: String) { 104 | // 105 | // }, 106 | // } 107 | //} 108 | -------------------------------------------------------------------------------- /src/container/parsed.rs: -------------------------------------------------------------------------------- 1 | use container::expression::*; 2 | 3 | 4 | pub type ParsedNodes = Vec; 5 | 6 | 7 | #[derive(Debug, PartialEq, Clone)] 8 | pub enum ParsedNode { 9 | Raw(ParsedRawStatement), 10 | Text(String), 11 | Comment(String), 12 | Mustache(Mustache), 13 | For(ParsedForStatement), 14 | If(ParsedIfStatement), 15 | Block(ParsedBlockStatement), 16 | Extends(ExprStatement), 17 | Include(ExprStatement), 18 | } 19 | 20 | #[derive(Debug, PartialEq, Clone)] 21 | pub struct SimpleStatement { 22 | pub strip_left: bool, 23 | pub strip_right: bool, 24 | } 25 | 26 | #[derive(Debug, PartialEq, Clone)] 27 | pub struct NamedStatement { 28 | pub strip_left: bool, 29 | pub strip_right: bool, 30 | pub name: String, 31 | } 32 | 33 | #[derive(Debug, PartialEq, Clone)] 34 | pub struct ExprStatement { 35 | pub strip_left: bool, 36 | pub strip_right: bool, 37 | pub expression: FullExpression, 38 | } 39 | 40 | #[derive(Debug, PartialEq, Clone)] 41 | pub struct ParsedRawStatement { 42 | pub begin: SimpleStatement, 43 | pub text: String, 44 | pub end: SimpleStatement, 45 | } 46 | 47 | #[derive(Debug, PartialEq, Clone)] 48 | pub struct ParsedIfBranch { 49 | pub begin: ExprStatement, 50 | pub block: ParsedNodes, 51 | } 52 | 53 | #[derive(Debug, PartialEq, Clone)] 54 | pub struct ParsedElseBranch { 55 | pub begin: SimpleStatement, 56 | pub block: ParsedNodes, 57 | } 58 | 59 | #[derive(Debug, PartialEq, Clone)] 60 | pub struct ParsedIfStatement { 61 | pub if_branches: Vec, 62 | pub else_branch: Option, 63 | pub end: SimpleStatement, 64 | } 65 | 66 | #[derive(Debug, PartialEq, Clone)] 67 | pub struct ParsedForStatement { 68 | pub begin: ExprStatement, 69 | pub block: ParsedNodes, 70 | pub key_var: Option, 71 | pub value_var: String, 72 | pub end: SimpleStatement, 73 | } 74 | 75 | #[derive(Debug, PartialEq, Clone)] 76 | pub struct ParsedBlockStatement { 77 | pub begin: NamedStatement, 78 | pub block: ParsedNodes, 79 | pub end: SimpleStatement, 80 | } 81 | 82 | // --------------------------------------------------------------------------- 83 | 84 | impl Default for SimpleStatement { 85 | fn default() -> Self { 86 | SimpleStatement { 87 | strip_left: false, 88 | strip_right: false, 89 | } 90 | } 91 | } 92 | 93 | impl Default for NamedStatement { 94 | fn default() -> Self { 95 | NamedStatement { 96 | strip_left: false, 97 | strip_right: false, 98 | name: Default::default(), 99 | } 100 | } 101 | } 102 | 103 | //impl Default for ExprStatement { 104 | // fn default() -> Self { 105 | // ExprStatement { 106 | // strip_left: false, 107 | // strip_right: false, 108 | // expression: None, 109 | // } 110 | // } 111 | //} 112 | 113 | impl Mustache { pub fn new(expr: FullExpression) -> Self { Mustache { expr } } } 114 | impl From for ParsedNode { fn from(v: Mustache) -> Self { ParsedNode::Mustache(v) } } 115 | 116 | 117 | impl FullExpression { 118 | pub fn new(expr: DisjExpr, filters: Vec) -> Self { 119 | FullExpression { expr, filters } 120 | } 121 | } 122 | 123 | 124 | impl From for Factor { fn from(v: Literal) -> Self { Factor::Literal(v) } } 125 | impl From for Factor { fn from(v: String) -> Self { Factor::Variable(v) } } 126 | impl From for Factor { fn from(v: DisjExpr) -> Self { Factor::Subexpression(v) } } 127 | impl From for Factor { fn from(v: Attribute) -> Self { Factor::Attribute(v) } } 128 | impl From for Factor { fn from(v: Invocation) -> Self { Factor::Invocation(v) } } 129 | 130 | impl From for ParsedNode { fn from(v: ParsedIfStatement) -> Self { ParsedNode::If(v) } } 131 | impl From for ParsedNode { fn from(v: ParsedForStatement) -> Self { ParsedNode::For(v) } } 132 | impl From for ParsedNode { fn from(v: ParsedBlockStatement) -> Self { ParsedNode::Block(v) } } 133 | impl From for ParsedNode { fn from(v: ParsedRawStatement) -> Self { ParsedNode::Raw(v) } } 134 | -------------------------------------------------------------------------------- /src/renderer/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use abc::*; 4 | use container::expression::*; 5 | use container::template::*; 6 | use renderer::Writer; 7 | use {Arg, Args, VarContext}; 8 | 9 | use super::eval_expr; 10 | 11 | 12 | pub fn text(context: &VarContext) -> RenderResult { 13 | let mut buffer = String::new(); 14 | render_text(&mut buffer, context, context.template().root.as_slice())?; 15 | Ok(buffer) 16 | } 17 | 18 | 19 | pub fn render_text(writer: &mut W, context: &VarContext, nodes: &[Node]) -> RenderResult<()> { 20 | for x in nodes { 21 | match *x { 22 | Node::Text(ref txt) => write!(writer, "{}", txt)?, 23 | Node::Mustache(ref mus) => render_mustache(writer, context, mus)?, 24 | Node::For(ref stmt) => render_for(writer, context, stmt)?, 25 | Node::If(ref stmt) => render_if(writer, context, stmt)?, 26 | Node::Block(ref stmt) => render_block(writer, context, stmt)?, 27 | Node::Include(ref expr) => render_include(writer, context, expr)?, 28 | } 29 | } 30 | Ok(()) 31 | } 32 | 33 | 34 | pub fn render_mustache(writer: &mut W, context: &VarContext, mus: &Mustache) -> RenderResult<()> { 35 | render_expression(writer, context, &mus.expr) 36 | } 37 | 38 | 39 | pub fn render_expression(writer: &mut W, context: &VarContext, expr: &FullExpression) -> RenderResult<()> { 40 | let mut acc = eval_expr(context, &expr.expr)?; 41 | for filter in &expr.filters { 42 | acc = match *filter { 43 | FilterItem::Simple(ref id) => context.env().filter(id, context, acc)?, 44 | }; 45 | } 46 | match acc { 47 | None => write!(writer, "#None")?, 48 | Some(acc) => acc.render(&mut Writer(writer))?, 49 | } 50 | Ok(()) 51 | } 52 | 53 | 54 | pub fn render_for(writer: &mut W, context: &VarContext, stmt: &ForStatement) -> RenderResult<()> { 55 | // FIXME implement instead: expression(&stmt.begin.expression, context) 56 | if let Some(value) = eval_expr(context, &stmt.expression.expr)? { 57 | if let Some(iterable) = value.try_as_iterable() { 58 | use container::cycle::LoopState; 59 | // TODO the "last" marker in a loop 60 | let mut state = LoopState::new(false); 61 | for v in iterable.ivalues() { 62 | { 63 | let local_scope: Args = hashmap! { 64 | stmt.value_var.as_str().into() => v.to_ref(), 65 | "loop".into() => Arg::Ref(&state), 66 | }; 67 | render_text(writer, &context.nested_scope(&local_scope), &stmt.block)?; 68 | } 69 | state = state.next(false); 70 | } 71 | } 72 | }; 73 | Ok(()) 74 | } 75 | 76 | 77 | pub fn render_if(writer: &mut W, context: &VarContext, stmt: &IfStatement) -> RenderResult<()> { 78 | for branch in &stmt.if_branches { 79 | if let Some(res) = eval_expr(context, &branch.expr.expr)? { 80 | if res.to_bool() { 81 | render_text(writer, context, &branch.block)?; 82 | return Ok(()); 83 | } 84 | } 85 | } 86 | if let Some(ref branch) = stmt.else_branch { 87 | render_text(writer, context, branch)?; 88 | } 89 | Ok(()) 90 | } 91 | 92 | 93 | pub fn render_block(writer: &mut W, context: &VarContext, name: &str) -> RenderResult<()> { 94 | for template in context.global().stack() { 95 | if let Some(block) = template.blocks.get(name) { 96 | render_text(writer, context, block)?; 97 | break; 98 | }; 99 | } 100 | Ok(()) 101 | } 102 | 103 | 104 | pub fn render_include(writer: &mut W, context: &VarContext, expr: &FullExpression) -> RenderResult<()> { 105 | let name = eval_expr(context, &expr.expr)? 106 | .ok_or_else(|| LoadError::BadName("Can't evaluate name (None result)".into()))?; 107 | let name = name.try_as_string() 108 | .ok_or_else(|| LoadError::BadName("Name is not string".into()))?; 109 | let template = context.global().env().get_template(&name)?; 110 | // FIXME Base context 111 | // render_text(writer, &context.global().top_scope(), template.root.as_slice()) 112 | // Current scope context 113 | render_text(writer, context, template.root.as_slice()) 114 | } 115 | -------------------------------------------------------------------------------- /src/types/list.rs: -------------------------------------------------------------------------------- 1 | use types::abc::*; 2 | use {Arg, ex}; 3 | 4 | 5 | impl <'r, 't> Type<'t> for Vec> { 6 | // fn clone_type(&self) -> Arg<'static> { 7 | // Arg::Owned(box self.clone()) 8 | // } 9 | fn clone_type(&self) -> Arg<'static> { 10 | Arg::Owned( 11 | box self.iter() 12 | .map(|v| (*v).clone_type()) 13 | .collect::>>() 14 | ) 15 | } 16 | } 17 | 18 | // todo resolve specialization conflict 19 | //impl IRender for Vec { 20 | // default fn render<'w>(&self, writer: &mut Writer<'w>) -> fmt::Result { 21 | // debug!("Default render for List {:?}", self); 22 | // write!(writer, "#List") 23 | // } 24 | //} 25 | 26 | impl <'r> AsBool for Vec> { 27 | fn to_bool(&self) -> bool { 28 | !self.is_empty() 29 | } 30 | } 31 | 32 | impl <'r> AsIterable for Vec> { 33 | fn try_as_iterable(&self) -> Option<&IIterable> { 34 | Some(self) 35 | } 36 | } 37 | 38 | impl <'r> AsComposable for Vec> { 39 | fn try_as_composable(&self) -> Option<&IComposable> { 40 | Some(self) 41 | } 42 | } 43 | 44 | 45 | impl <'r> IIterable for Vec> { 46 | fn is_empty(&self) -> bool { 47 | Vec::is_empty(self) 48 | } 49 | 50 | fn ivalues<'s: 'i, 'i>(&'s self) -> Box + 'i> { 51 | box self.iter().map(Arg::to_ref) 52 | } 53 | } 54 | 55 | 56 | impl <'r> IIndexable for Vec> { 57 | fn get_index(&self, index: usize) -> Option { 58 | self.get(index).map(Arg::to_ref) 59 | } 60 | 61 | fn len(&self) -> usize { 62 | Vec::len(self) 63 | } 64 | 65 | fn is_empty(&self) -> bool { 66 | Vec::is_empty(self) 67 | } 68 | } 69 | 70 | 71 | impl <'r> IComposable for Vec> { 72 | fn get_attr(&self, id: &str) -> Option { 73 | match id { 74 | "length" => Some(ex(self.len() as i64)), 75 | _ => None 76 | } 77 | } 78 | } 79 | 80 | 81 | //impl <'a> IIterable for Vec> { 82 | // fn ivalues<'b>(&self) -> Iterator> { 83 | // Some(Box::new(VIterator { me: self.iter() })) 84 | // } 85 | //} 86 | // 87 | 88 | 89 | //impl <'a> Iterator for KIterator<'a> { 90 | // type Item = &'a EntityId; 91 | // 92 | // fn next(&mut self) -> Option<&'a EntityId> { 93 | // match self.me.next() { 94 | // Some(next) => Some(next), 95 | // None => None 96 | // } 97 | // } 98 | //} 99 | // 100 | //impl <'a> Iterator for KVIterator<'a> { 101 | // type Item = (&'a EntityId, &'a Var); 102 | // 103 | // fn next(&mut self) -> Option<(&'a EntityId, &'a Var)> { 104 | // match self.me.next() { 105 | // Some(next) => Some(next), 106 | // None => None 107 | // } 108 | // } 109 | //} 110 | 111 | 112 | // -------------------------------------------------------------------------------------------------------------------- 113 | 114 | 115 | impl <'r, 't> Type<'t> for &'r [Arg<'r>] { 116 | // fn clone_type(&self) -> Arg<'static> { 117 | // Arg::Owned(box self.clone()) 118 | // } 119 | fn clone_type(&self) -> Arg<'static> { 120 | Arg::Owned( 121 | box self.iter() 122 | .map(|v| (*v).clone_type()) 123 | .collect::>>() 124 | ) 125 | } 126 | } 127 | 128 | // todo resolve specialization conflict 129 | //impl IRender for Vec { 130 | // default fn render<'w>(&self, writer: &mut Writer<'w>) -> fmt::Result { 131 | // debug!("Default render for List {:?}", self); 132 | // write!(writer, "#List") 133 | // } 134 | //} 135 | 136 | impl <'r> AsBool for &'r [Arg<'r>] { 137 | fn to_bool(&self) -> bool { 138 | !(*self).is_empty() 139 | } 140 | } 141 | 142 | impl <'r> AsIterable for &'r [Arg<'r>] { 143 | fn try_as_iterable(&self) -> Option<&IIterable> { 144 | Some(self) 145 | } 146 | } 147 | 148 | impl <'r> AsComposable for &'r [Arg<'r>] { 149 | fn try_as_composable(&self) -> Option<&IComposable> { 150 | Some(self) 151 | } 152 | } 153 | 154 | 155 | impl <'r> IIterable for &'r [Arg<'r>] { 156 | fn is_empty(&self) -> bool { 157 | (*self).is_empty() 158 | } 159 | 160 | fn ivalues<'s: 'i, 'i>(&'s self) -> Box + 'i> { 161 | box self.iter().map(Arg::to_ref) 162 | } 163 | } 164 | 165 | 166 | impl <'r> IIndexable for &'r [Arg<'r>] { 167 | fn get_index(&self, index: usize) -> Option { 168 | self.get(index).map(Arg::to_ref) 169 | } 170 | 171 | fn len(&self) -> usize { 172 | (*self).len() 173 | } 174 | 175 | fn is_empty(&self) -> bool { 176 | (*self).is_empty() 177 | } 178 | } 179 | 180 | 181 | impl <'r> IComposable for &'r [Arg<'r>] { 182 | fn get_attr(&self, id: &str) -> Option { 183 | match id { 184 | "length" => Some(ex(self.len() as i64)), 185 | _ => None 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/types/abc.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::borrow::Cow; 3 | use std::cmp::Ordering; 4 | use std::fmt; 5 | use std::iter::Iterator; 6 | use std::slice::Iter; 7 | 8 | use abc::EvalResult; 9 | use renderer::Writer; 10 | use Arg; 11 | use VarContext; 12 | 13 | 14 | // -------------------------------------------------------------------------------------------------------------------- 15 | 16 | pub trait Type<'r>: 17 | AsString + AsBool + AsReal + AsInt + AsIterable + 18 | AsIndexable + AsMappable + AsComposable + AsInvokable + 19 | AsPartialEq + AsPartialOrd + 20 | IArithm + IRender + fmt::Debug + Send + Sync 21 | { 22 | fn clone_type(&self) -> Arg<'static>; 23 | } 24 | 25 | // --- [ try interfaces ] --------------------------------------------------------------------------------------------- 26 | 27 | pub trait IRender { 28 | fn render<'w>(&self, writer: &mut Writer<'w>) -> fmt::Result; 29 | } 30 | 31 | pub trait AsAny { 32 | fn try_as_any(&self) -> Option<&Any>; 33 | } 34 | 35 | pub trait AsString { 36 | fn is_string(&self) -> bool; 37 | fn try_as_string(&self) -> Option>; 38 | } 39 | 40 | pub trait AsBool { 41 | fn is_bool(&self) -> bool; 42 | fn to_bool(&self) -> bool; 43 | } 44 | 45 | pub trait AsReal { 46 | fn is_real(&self) -> bool; 47 | fn try_as_real(&self) -> Option; 48 | } 49 | 50 | pub trait AsInt { 51 | fn is_int(&self) -> bool; 52 | fn try_as_int(&self) -> Option; 53 | } 54 | 55 | pub trait AsInvokable { 56 | fn is_invokable(&self) -> bool; 57 | fn try_as_invokable(&self) -> Option<&IInvokable>; 58 | } 59 | 60 | pub trait AsIterable { 61 | fn is_iterable(&self) -> bool; 62 | fn try_as_iterable(&self) -> Option<&IIterable>; 63 | } 64 | 65 | pub trait AsComposable { 66 | fn is_composable(&self) -> bool; 67 | fn try_as_composable(&self) -> Option<&IComposable>; 68 | } 69 | 70 | pub trait AsIndexable { 71 | fn is_indexable(&self) -> bool; 72 | fn try_as_indexable(&self) -> Option<&IIndexable>; 73 | } 74 | 75 | pub trait AsMappable { 76 | fn is_mappable(&self) -> bool; 77 | fn try_as_mappable(&self) -> Option<&IMappable>; 78 | } 79 | 80 | pub trait AsPartialEq { 81 | fn is_partial_eq(&self) -> bool; 82 | fn try_as_partial_eq(&self) -> Option<&IPartialEq>; 83 | } 84 | 85 | pub trait AsPartialOrd { 86 | fn is_partial_ord(&self) -> bool; 87 | fn try_as_partial_ord(&self) -> Option<&IPartialOrd>; 88 | } 89 | 90 | 91 | // --- [ impl interfaces ] -------------------------------------------------------------------------------------------- 92 | 93 | pub trait IArithm { 94 | fn try_add<'o>(&self, other: Arg<'o>) -> Option>; 95 | fn try_sub<'o>(&self, other: Arg<'o>) -> Option>; 96 | fn try_mul<'o>(&self, other: Arg<'o>) -> Option>; 97 | fn try_div<'o>(&self, other: Arg<'o>) -> Option>; 98 | } 99 | 100 | pub trait IInvokable { 101 | fn invoke<'r: 'a + 'c, 'a, 'c>(&self, args: &'a [Arg<'r>], context: &'c VarContext<'r>) -> EvalResult>; 102 | } 103 | 104 | pub trait IIterable { 105 | fn is_empty(&self) -> bool; 106 | fn ivalues<'s: 'i, 'i>(&'s self) -> Box + 'i>; 107 | } 108 | 109 | pub trait IIndexable { 110 | // fn has_index(&self, index: usize) -> bool; 111 | fn get_index(&self, index: usize) -> Option; 112 | fn is_empty(&self) -> bool; 113 | fn len(&self) -> usize; 114 | // fn as_slice(&self) -> &[Arg]; 115 | } 116 | 117 | pub trait IComposable { 118 | fn get_attr(&self, id: &str) -> Option; 119 | // fn attrs(&self) -> &[BType]; 120 | } 121 | 122 | pub trait IMappable { 123 | // fn has_key(&self, key: &str) -> bool; 124 | fn get_by_key(&self, key: &str) -> Option; 125 | fn is_empty(&self) -> bool; 126 | fn len(&self) -> usize; 127 | } 128 | 129 | pub trait IPartialEq { 130 | fn eq<'o>(&self, other: &'o Arg<'o>) -> bool; 131 | fn ne<'o>(&self, other: &'o Arg<'o>) -> bool { !self.eq(other) } 132 | } 133 | 134 | pub trait IPartialOrd { 135 | fn partial_cmp<'o>(&self, other: &'o Arg<'o>) -> Option; 136 | fn lt<'o>(&self, other: &'o Arg<'o>) -> Option { 137 | self.partial_cmp(other).map(|res| res == Ordering::Less) 138 | } 139 | fn le<'o>(&self, other: &'o Arg<'o>) -> Option { 140 | self.partial_cmp(other).map(|res| res != Ordering::Greater) 141 | } 142 | fn gt<'o>(&self, other: &'o Arg<'o>) -> Option { 143 | self.partial_cmp(other).map(|res| res == Ordering::Greater) 144 | } 145 | fn ge<'o>(&self, other: &'o Arg<'o>) -> Option { 146 | self.partial_cmp(other).map(|res| res != Ordering::Less) 147 | } 148 | } 149 | 150 | 151 | // --- [ feature interfaces ] ----------------------------------------------------------------------------------------- 152 | 153 | pub struct VIterator<'r> { 154 | pub me: Iter<'r, Arg<'r>>, 155 | } 156 | 157 | impl <'r> Iterator for VIterator<'r> { 158 | type Item = &'r Arg<'r>; 159 | 160 | fn next(&mut self) -> Option { 161 | self.me.next() 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/types/defaults.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | use std::fmt::Display; 4 | use std::fmt::Write; 5 | 6 | use types::abc::*; 7 | use renderer::Writer; 8 | use Arg; 9 | 10 | 11 | // CHECK whether it is necessary 12 | //impl <'a, T> Type for &'a T where T: Type { 13 | //// fn iclone(&self) -> Arg { 14 | //// (*self as &Type).iclone() 15 | //// } 16 | //} 17 | 18 | 19 | // --- [ default implementations ] ------------------------------------------------------------------------------------ 20 | 21 | impl <'r, T> IRender for T where T: for <'t> Type<'t> + 'r { 22 | default fn render<'w>(&self, writer: &mut Writer<'w>) -> fmt::Result { 23 | debug!("Default render for Type {:?}", self); 24 | write!(writer, "#Type") 25 | } 26 | } 27 | 28 | impl <'r, T> IRender for T where T: for <'t> Type<'t> + 'r + Display { 29 | default fn render<'w>(&self, writer: &mut Writer<'w>) -> fmt::Result { 30 | write!(writer, "{}", self) 31 | } 32 | } 33 | 34 | impl <'r, T> AsString for T where T: for <'t> Type<'t> + 'r { 35 | default fn is_string(&self) -> bool { 36 | false 37 | } 38 | 39 | default fn try_as_string(&self) -> Option> { 40 | None 41 | } 42 | } 43 | 44 | impl <'r, T> AsString for T where T: for <'t> Type<'t> + 'r + Display { 45 | default fn try_as_string(&self) -> Option> { 46 | Some(Cow::Owned(ToString::to_string(self))) 47 | } 48 | } 49 | 50 | impl <'r, T> AsBool for T where T: for <'t> Type<'t> + 'r { 51 | default fn is_bool(&self) -> bool { 52 | false 53 | } 54 | default fn to_bool(&self) -> bool { 55 | true 56 | } 57 | } 58 | 59 | impl <'r, T> AsReal for T where T: for <'t> Type<'t> + 'r { 60 | default fn is_real(&self) -> bool { 61 | false 62 | } 63 | default fn try_as_real(&self) -> Option { 64 | None 65 | } 66 | } 67 | 68 | impl <'r, T> AsInt for T where T: for <'t> Type<'t> + 'r { 69 | default fn is_int(&self) -> bool { 70 | false 71 | } 72 | 73 | default fn try_as_int(&self) -> Option { 74 | None 75 | } 76 | } 77 | 78 | impl <'r, T> AsIterable for T where T: for <'t> Type<'t> + 'r { 79 | default fn is_iterable(&self) -> bool { 80 | false 81 | } 82 | 83 | default fn try_as_iterable(&self) -> Option<&IIterable> { 84 | None 85 | } 86 | } 87 | 88 | impl <'r, T> AsIterable for T where T: for <'t> Type<'t> + 'r + IIterable { 89 | fn is_iterable(&self) -> bool { 90 | true 91 | } 92 | 93 | default fn try_as_iterable(&self) -> Option<&IIterable> { 94 | Some(self) 95 | } 96 | } 97 | 98 | impl <'r, T> AsIndexable for T where T: for <'t> Type<'t> + 'r { 99 | default fn is_indexable(&self) -> bool { 100 | false 101 | } 102 | 103 | default fn try_as_indexable(&self) -> Option<&IIndexable> { 104 | None 105 | } 106 | } 107 | 108 | impl <'r, T> AsIndexable for T where T: for <'t> Type<'t> + 'r + IIndexable { 109 | fn is_indexable(&self) -> bool { 110 | true 111 | } 112 | 113 | default fn try_as_indexable(&self) -> Option<&IIndexable> { 114 | Some(self) 115 | } 116 | } 117 | 118 | impl <'r, T> AsMappable for T where T: for <'t> Type<'t> + 'r { 119 | default fn is_mappable(&self) -> bool { 120 | false 121 | } 122 | 123 | default fn try_as_mappable(&self) -> Option<&IMappable> { 124 | None 125 | } 126 | } 127 | 128 | impl <'r, T> AsMappable for T where T: for <'t> Type<'t> + 'r + IMappable { 129 | fn is_mappable(&self) -> bool { 130 | true 131 | } 132 | 133 | default fn try_as_mappable(&self) -> Option<&IMappable> { 134 | Some(self) 135 | } 136 | } 137 | 138 | impl <'r, T> AsComposable for T where T: for <'t> Type<'t> + 'r { 139 | default fn is_composable(&self) -> bool { 140 | false 141 | } 142 | 143 | default fn try_as_composable(&self) -> Option<&IComposable> { 144 | None 145 | } 146 | } 147 | 148 | impl <'r, T> AsComposable for T where T: for <'t> Type<'t> + 'r + IComposable { 149 | fn is_composable(&self) -> bool { 150 | true 151 | } 152 | 153 | default fn try_as_composable(&self) -> Option<&IComposable> { 154 | Some(self) 155 | } 156 | } 157 | 158 | impl <'r, T> AsInvokable for T where T: for <'t> Type<'t> + 'r { 159 | default fn is_invokable(&self) -> bool { 160 | false 161 | } 162 | 163 | default fn try_as_invokable(&self) -> Option<&IInvokable> { 164 | None 165 | } 166 | } 167 | 168 | impl <'r, T> AsInvokable for T where T: for <'t> Type<'t> + 'r + IInvokable { 169 | fn is_invokable(&self) -> bool { 170 | true 171 | } 172 | 173 | default fn try_as_invokable(&self) -> Option<&IInvokable> { 174 | Some(self) 175 | } 176 | } 177 | 178 | 179 | impl <'r, T> AsPartialEq for T where T: for <'t> Type<'t> + 'r { 180 | default fn is_partial_eq(&self) -> bool { 181 | false 182 | } 183 | 184 | default fn try_as_partial_eq(&self) -> Option<&IPartialEq> { 185 | None 186 | } 187 | } 188 | 189 | impl <'r, T> AsPartialEq for T where T: for <'t> Type<'t> + 'r + IPartialEq { 190 | default fn is_partial_eq(&self) -> bool { 191 | true 192 | } 193 | 194 | default fn try_as_partial_eq(&self) -> Option<&IPartialEq> { 195 | Some(self) 196 | } 197 | } 198 | 199 | impl <'r, T> AsPartialOrd for T where T: for <'t> Type<'t> + 'r { 200 | default fn is_partial_ord(&self) -> bool { 201 | false 202 | } 203 | 204 | default fn try_as_partial_ord(&self) -> Option<&IPartialOrd> { 205 | None 206 | } 207 | } 208 | 209 | impl <'r, T> AsPartialOrd for T where T: for <'t> Type<'t> + 'r + IPartialOrd { 210 | default fn is_partial_ord(&self) -> bool { 211 | true 212 | } 213 | 214 | default fn try_as_partial_ord(&self) -> Option<&IPartialOrd> { 215 | Some(self) 216 | } 217 | } 218 | 219 | 220 | // ------------------------------------------------------------------------------------------------- 221 | 222 | 223 | impl <'r, S> IArithm for S where S: for <'t> Type<'t> + 'r { 224 | default fn try_add<'o>(&self, _other: Arg<'o>) -> Option> { None } 225 | default fn try_sub<'o>(&self, _other: Arg<'o>) -> Option> { None } 226 | default fn try_mul<'o>(&self, _other: Arg<'o>) -> Option> { None } 227 | default fn try_div<'o>(&self, _other: Arg<'o>) -> Option> { None } 228 | } 229 | -------------------------------------------------------------------------------- /src/parser/literals.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | #[allow(unused_imports)] 3 | use nom::{IResult, Err as NomErr, ErrorKind, alpha, alphanumeric, space, multispace}; 4 | 5 | use container::expression::Factor; 6 | use container::expression::Literal; 7 | 8 | 9 | pub fn literal(input: &[u8]) -> IResult<&[u8], Factor> { 10 | let (i, l) = try_parse!(input, alt!(lit_str | lit_char | lit_num) ); 11 | IResult::Done(i, Factor::Literal(l)) 12 | } 13 | 14 | // -------------------------------------------------------------------------------------------------------------------- 15 | 16 | pub fn lit_num(input: &[u8]) -> IResult<&[u8], Literal> { 17 | let (i, s) = try_parse!(input, map_res!( is_a!("0123456789."), str::from_utf8 ) ); 18 | match parse_int(s).or_else(|_| parse_float(s)) { 19 | Ok(res) => IResult::Done(i, res), 20 | Err(err) => IResult::Error(NomErr::Code(ErrorKind::Custom(err))), 21 | } 22 | } 23 | 24 | fn parse_float(s: &str) -> Result { 25 | let n: f64 = s.parse().map_err(|_| 1301_u32)?; 26 | Ok(Literal::Real(n)) 27 | } 28 | 29 | fn parse_int(s: &str) -> Result { 30 | let n: i64 = s.parse().map_err(|_| 1302_u32)?; 31 | Ok(Literal::Int(n)) 32 | } 33 | 34 | // --------------------------------------------------------------------------- 35 | 36 | fn lit_char(input: &[u8]) -> IResult<&[u8], Literal> { 37 | let (i, (_, s, _)) = try_parse!(input, 38 | tuple!( 39 | char!('\''), 40 | char_char_agg, 41 | char!('\'') 42 | ) 43 | ); 44 | trace!("lit_char {:?}", s); 45 | match parse_char(s.as_str()) { 46 | Ok(chr) => IResult::Done(i, Literal::Char(chr)), 47 | Err(err) => IResult::Error(NomErr::Code(ErrorKind::Custom(err))), 48 | } 49 | } 50 | 51 | named!(pub char_char_agg<&[u8], String>, do_parse!( 52 | c: many0!(alt!( char_escaped | char_char )) >> 53 | (c.join("")) 54 | )); 55 | 56 | named!(pub char_char<&[u8], &str>, map_res!( is_not!(r#"\'"#), str::from_utf8 ) ); 57 | 58 | named!(pub char_escaped<&[u8], &str>, do_parse!( 59 | char!('\\') >> 60 | c: alt!( 61 | char!('\\') | 62 | char!('\'') | 63 | char!('"') | 64 | char!('t') | 65 | char!('r') | 66 | char!('n') 67 | ) >> 68 | (match c { 69 | '\\' => "\\", 70 | '\'' => "\'", 71 | '"' => "\"", 72 | 't' => "\t", 73 | 'r' => "\r", 74 | 'n' => "\n", 75 | _ => unreachable!() 76 | }) 77 | )); 78 | 79 | fn parse_char(s: &str) -> Result { 80 | let mut i = s.chars(); 81 | match (i.next(), i.next()) { 82 | (None, _) => Err(1303u32), 83 | (Some(c), None) => Ok(c), 84 | _ => Err(1304u32), 85 | } 86 | } 87 | 88 | // -------------------------------------------------------------------------------------------------------------------- 89 | 90 | named!(pub lit_str<&[u8], Literal>, do_parse!( 91 | char!('"') >> s: str_char_agg >> char!('"') >> 92 | (Literal::Str(s)) 93 | )); 94 | 95 | named!(pub str_char_agg<&[u8], String>, do_parse!( 96 | c: many0!(alt!( char_escaped | str_char )) >> 97 | (c.join("")) 98 | )); 99 | 100 | named!(pub str_char<&[u8], &str>, map_res!( is_not!(r#"\""#), str::from_utf8 ) ); 101 | 102 | // -------------------------------------------------------------------------------------------------------------------- 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use nom::IResult::Done; 107 | 108 | #[test] 109 | fn literal_str_char() { 110 | assert_eq!(Done(&b""[..], r#"\"#), super::char_escaped(br#"\\"#)); 111 | assert_eq!(Done(&b""[..], r#"""#), super::char_escaped(br#"\""#)); 112 | assert_eq!(Done(&b""[..], "\t"), super::char_escaped(br#"\t"#)); 113 | assert_eq!(Done(&b""[..], "\r"), super::char_escaped(br#"\r"#)); 114 | assert_eq!(Done(&b""[..], "\n"), super::char_escaped(br#"\n"#)); 115 | } 116 | 117 | #[test] 118 | fn literal_str() { 119 | use container::expression::Literal::Str; 120 | assert_eq!(Done(&b""[..], Str(r#" {{ "#.into())), super::lit_str(br#"" {{ ""#)); 121 | assert_eq!(Done(&b""[..], Str(r#" {{ "#.into()).into()), super::literal(br#"" {{ ""#)); 122 | assert_eq!(Done(&b""[..], Str(r#"{{"#.into()).into()), super::literal(br#""{{""#)); 123 | assert_eq!(Done(&b""[..], Str("New\nline".into()).into()), super::literal(br#""New\nline""#)); 124 | assert_eq!(Done(&b""[..], Str(r#""Quotes""#.into()).into()), super::literal(br#""\"Quotes\"""#)); 125 | } 126 | 127 | #[test] 128 | fn literal_char() { 129 | use container::expression::Literal::Char; 130 | assert_eq!("\\\\", r#"\\"#); 131 | 132 | assert_eq!(Done(&b""[..], r#"\"#), super::char_escaped(br#"\\"#)); 133 | 134 | assert_eq!(Ok('\\'), super::parse_char("\\")); 135 | assert_eq!(Ok('\n'), super::parse_char("\n")); 136 | 137 | assert_eq!(Done(&b""[..], Char('\\')), super::lit_char(br#"'\\'"#)); 138 | assert_eq!(Done(&b""[..], Char('\'')), super::lit_char(br#"'\''"#)); 139 | assert_eq!(Done(&b""[..], Char('"') ), super::lit_char(br#"'\"'"#)); 140 | assert_eq!(Done(&b""[..], Char('"') ), super::lit_char(br#"'"'"#)); 141 | assert_eq!(Done(&b""[..], Char('\t')), super::lit_char(br#"'\t'"#)); 142 | assert_eq!(Done(&b""[..], Char('\r')), super::lit_char(br#"'\r'"#)); 143 | assert_eq!(Done(&b""[..], Char('\n')), super::lit_char(br#"'\n'"#)); 144 | assert_eq!(Done(&b""[..], Char(' ') ), super::lit_char(br#"' '"#)); 145 | 146 | assert!(super::lit_char(br#"''"#).is_err()); 147 | assert!(super::lit_char(br#"' '"#).is_err()); 148 | } 149 | 150 | #[test] 151 | fn literal_int() { 152 | use container::expression::Literal::Int; 153 | assert_eq!(Ok(Int(42)), super::parse_int("42")); 154 | assert_eq!(Done(&b""[..], Int(42)), super::lit_num(b"42")); 155 | assert_eq!(Done(&b""[..], Int(42).into()), super::literal(b"42")); 156 | } 157 | 158 | #[test] 159 | fn literal_real() { 160 | #![allow(clippy::approx_constant)] 161 | use container::expression::Literal::Real; 162 | assert_eq!(Ok(Real(3.141_592_6)), super::parse_float("3.1415926")); 163 | assert_eq!(Ok(Real(0.1)), super::parse_float(".1")); 164 | assert_eq!(Ok(Real(1.0)), super::parse_float("1.")); 165 | 166 | assert_eq!(Done(&b""[..], Real(3.141_592_6)), super::lit_num(b"3.1415926")); 167 | assert_eq!(Done(&b""[..], Real(0.1)), super::lit_num(b".1")); 168 | assert_eq!(Done(&b""[..], Real(1.0)), super::lit_num(b"1.")); 169 | 170 | assert_eq!(Done(&b""[..], Real(3.141_592_6).into()), super::literal(b"3.1415926")); 171 | assert_eq!(Done(&b""[..], Real(0.1).into()), super::literal(b".1")); 172 | assert_eq!(Done(&b""[..], Real(1.0).into()), super::literal(b"1.")); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/renderer/expression.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use abc::RenderResult; 4 | use VarContext; 5 | use container::expression::*; 6 | 7 | 8 | pub fn render_expr(writer: &mut W, context: &VarContext, expr: &DisjExpr) -> RenderResult<()> { 9 | let mut itr = expr.list.iter(); 10 | match itr.next() { 11 | None => unreachable!(), 12 | Some(conj) => { 13 | render_conj(writer, context, conj)?; 14 | for conj in itr { 15 | write!(writer, " or ")?; 16 | render_conj(writer, context, conj)?; 17 | } 18 | Ok(()) 19 | } } } 20 | 21 | 22 | pub fn render_conj(writer: &mut W, context: &VarContext, expr: &ConjExpr) -> RenderResult<()> { 23 | let mut itr = expr.list.iter(); 24 | match itr.next() { 25 | None => unreachable!(), 26 | Some(cmp) => { 27 | render_cmp(writer, context, cmp)?; 28 | for cmp in itr { 29 | write!(writer, " and ")?; 30 | render_cmp(writer, context, cmp)?; 31 | } 32 | Ok(()) 33 | } } } 34 | 35 | 36 | pub fn render_cmp(writer: &mut W, context: &VarContext, expr: &CmpExpr) -> RenderResult<()> { 37 | let mut itr = expr.list.iter(); 38 | match itr.next() { 39 | None => unreachable!(), 40 | Some(&CmpItem(ref _op, ref sum)) => { 41 | render_sum(writer, context, sum)?; 42 | for &CmpItem(ref op, ref sum) in itr { 43 | let op = match *op { 44 | CmpOp::Lt => "<", 45 | CmpOp::Lte => "<=", 46 | CmpOp::Eq => "==", 47 | CmpOp::Neq => "!=", 48 | CmpOp::In => "in", 49 | CmpOp::Nin => "not in", 50 | CmpOp::Gte => ">=", 51 | CmpOp::Gt => ">", 52 | }; 53 | write!(writer, " {} ", op)?; 54 | render_sum(writer, context, sum)?; 55 | } 56 | Ok(()) 57 | } } } 58 | 59 | 60 | pub fn render_sum(writer: &mut W, context: &VarContext, expr: &Expr) -> RenderResult<()> { 61 | let mut itr = expr.sum.iter(); 62 | match itr.next() { 63 | None => unreachable!(), 64 | Some(&ExprItem(ref _op, ref term)) => { 65 | render_prod(writer, context, term)?; 66 | for &ExprItem(ref op, ref term) in itr { 67 | let op = match *op { 68 | SumOp::Add => "+", 69 | SumOp::Sub => "-", 70 | }; 71 | write!(writer, " {} ", op)?; 72 | render_prod(writer, context, term)?; 73 | } 74 | Ok(()) 75 | } } } 76 | 77 | 78 | pub fn render_prod(writer: &mut W, context: &VarContext, term: &Term) -> RenderResult<()> { 79 | let mut itr = term.mul.iter(); 80 | match itr.next() { 81 | None => unreachable!(), 82 | Some(&TermItem(ref _op, ref factor)) => { 83 | render_factor(writer, context, factor)?; 84 | for &TermItem(ref op, ref factor) in itr { 85 | let op = match *op { 86 | MulOp::Mul => "*", 87 | MulOp::Div => "/", 88 | }; 89 | write!(writer, " {} ", op)?; 90 | render_factor(writer, context, factor)?; 91 | } 92 | Ok(()) 93 | } } } 94 | 95 | 96 | pub fn render_factor(writer: &mut W, context: &VarContext, fctr: &Factor) -> RenderResult<()> { 97 | match *fctr { 98 | Factor::Literal(ref lit) => { 99 | render_literal(writer, lit)?; 100 | }, 101 | Factor::Variable(ref id) => { 102 | write!(writer, "{}", id)?; 103 | }, 104 | Factor::Subexpression(ref expr) => { 105 | write!(writer, "(")?; 106 | render_expr(writer, context, expr)?; 107 | write!(writer, ")")?; 108 | }, 109 | Factor::Attribute(ref attr) => { 110 | render_factor(writer, context, &*attr.on)?; 111 | write!(writer, ".{}", attr.id)?; 112 | }, 113 | Factor::Index(ref index) => { 114 | render_factor(writer, context, &*index.on)?; 115 | write!(writer, "[")?; 116 | render_expr(writer, context, &index.index)?; 117 | write!(writer, "]")?; 118 | }, 119 | Factor::Invocation(ref inv) => { 120 | render_factor(writer, context, &*inv.on)?; 121 | write!(writer, "(")?; 122 | for (i, expr) in inv.args.iter().enumerate() { 123 | if i != 0 { write!(writer, ", ")?; } 124 | render_expr(writer, context, expr)?; 125 | } 126 | write!(writer, ")")?; 127 | }, 128 | }; 129 | Ok(()) 130 | } 131 | 132 | 133 | pub fn render_literal(writer: &mut W, l: &Literal) -> RenderResult<()> { 134 | match *l { 135 | Literal::Str(ref string) => write!(writer, "{:?}", string)?, 136 | Literal::Char(ref chr) => write!(writer, "{:?}", chr)?, 137 | Literal::Int(ref int) => write!(writer, "{}", int)?, 138 | Literal::Real(ref real) => write!(writer, "{}", real)?, 139 | } 140 | Ok(()) 141 | } 142 | 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | #![allow(clippy::used_underscore_binding)] 147 | use std::fmt::Debug; 148 | 149 | use nom::IResult; 150 | 151 | use {Incrust, Args, ex, Template}; 152 | use parser::expressions::expression as parse_expr; 153 | 154 | fn unwrap_iresult(result: IResult) -> T { 155 | match result { 156 | IResult::Done(_, v) => v, 157 | IResult::Error(e) => panic!("{:?}", e), 158 | IResult::Incomplete(i) => panic!("{:?}", i), 159 | } 160 | } 161 | 162 | #[test] 163 | fn eval_expr() { 164 | use super::render_expr; 165 | 166 | let args: Args = hashmap!{ 167 | "the_one".into() => ex("World"), 168 | "one".into() => ex(1_i64), 169 | "two".into() => ex(2_i64), 170 | }; 171 | let incrust = Incrust::default(); 172 | let template = Template::default(); 173 | let context = incrust.create_global_context(&template, &args).unwrap(); 174 | let context = context.top_scope(); 175 | 176 | let parse = |s| unwrap_iresult(parse_expr(s)); 177 | let test = |s, b| { 178 | let mut buf = String::new(); 179 | render_expr(&mut buf, &context, &parse(b)).unwrap(); 180 | assert_eq!(s, buf) 181 | }; 182 | 183 | test("1", b"1"); 184 | test("1 + 1", b"1+1"); 185 | test("1 + 1", b"1 + 1"); 186 | test("1 - 1", b"1 \n -\t1"); 187 | 188 | test("(1 / 1)", b"(1 / 1)"); 189 | test("1 * 1", b"1 * 1"); 190 | test("1 + 1 * 1", b"1 + 1 * 1"); 191 | test("(1 + 1) * 1", b"(1 + 1) * 1"); 192 | test("(1 + (1 / 1)) * 1", b"(1+(1/1))*1"); 193 | 194 | test("True and False", b"True and False"); 195 | test("0 or 1 and 2", b"0 or 1 and 2"); 196 | 197 | test("1 == 1", b"1 == 1"); 198 | test("1 != 1", b"1 != 1"); 199 | test("1 <= 1", b"1 <= 1"); 200 | test("1 >= 1", b"1 >= 1"); 201 | test("1 < 1", b"1 < 1"); 202 | test("1 > 1", b"1 > 1"); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![](https://img.shields.io/crates/l/incrust.svg) 3 | [![crates.io](https://img.shields.io/crates/v/incrust.svg)](https://crates.io/crates/incrust) 4 | 5 | [![Build Status](https://travis-ci.org/alexander-irbis/incrust.svg)](https://travis-ci.org/alexander-irbis/incrust) 6 | ![Minimal rust version -](https://img.shields.io/badge/stable-_--_-red.svg) 7 | ![Nightly rust version from April 08, 2018](https://img.shields.io/badge/nightly-2018--04--08-yellow.svg) 8 | 9 | ## {% Incrust %} 10 | 11 | > Incrust is a template engine inspired by Jinja2 and written in Rust. 12 | 13 | In fact it is a [Jinja2](http://jinja.pocoo.org/), [Django](https://docs.djangoproject.com/en/1.10/topics/templates/), 14 | [Twig](http://twig.sensiolabs.org/), [Swig](http://paularmstrong.github.io/swig/), [Liquid](https://shopify.github.io/liquid/) 15 | (and probably others) template engines constellation, which uses similar methodologies. 16 | 17 | ### Unstable 18 | 19 | The implementation is at a very early stage and the API is a subject of changes. 20 | 21 | __Note that Incrust currently requires the nightly version of the Rust compiler.__ 22 | 23 | 24 | ## Installation 25 | 26 | Incrust is [available on crates.io](https://crates.io/crates/incrust) and can be included in your Cargo enabled project like this: 27 | 28 | ```toml 29 | [dependencies] 30 | incrust = "=0.2.15" 31 | ``` 32 | 33 | For ease of use hashmaps you may use the [maplit](https://crates.io/crates/maplit) 34 | 35 | Then you need to setup your environment: 36 | 37 | ```rust 38 | #[macro_use] 39 | extern crate maplit; 40 | extern crate incrust; 41 | 42 | use incrust::ex; 43 | 44 | fn create_env() -> Incrust { 45 | use incrust::{Incrust, FilesystemLoader}; 46 | 47 | let mut instance = Incrust::default(); 48 | instance.loaders.push(FilesystemLoader::new("./assets/tpl")); 49 | instance 50 | } 51 | 52 | fn main() { 53 | let incrust = create_env(); 54 | let args = hashmap!{ "name".into() => ex("World") }; 55 | incrust.render("hello", &args).unwrap(); 56 | } 57 | ``` 58 | 59 | Though Incrust has smart loaders, it may be used just as advanced formatter to render directly from string template 60 | 61 | ```rust 62 | let args = hashmap!{ "name".into() => ex("World") }; 63 | incrust.render_text("Hello, {{ name | e }}!", &args).unwrap(); 64 | // or with prepared template 65 | let hello = incrust.parse("Hello, {{ name | e }}!"); 66 | incrust.render_parsed(&hello, &args).unwrap(); 67 | ``` 68 | 69 | 70 | ## Syntax examples 71 | 72 | ### Comments 73 | 74 | ```twig 75 |

Visible {# partially #} paragraph

76 | ``` 77 | ```html 78 |

Visible paragraph

79 | ``` 80 | 81 | ### Escaping 82 | 83 | ```twig 84 | Example: {% raw %}{{ mustaches }}{% endraw %} 85 | ``` 86 | ```html 87 | Example: {{ mustaches }} 88 | ``` 89 | 90 | ### Literals 91 | 92 | ```twig 93 | Braces: {{ "{{" }} 94 | Pi: {{ 3.1415926 }} 95 | ``` 96 | ```html 97 | Braces: {{ 98 | Pi: 3.1415926 99 | ``` 100 | 101 | ### Filters 102 | 103 | ```rust 104 | let args = hashmap!{ "title".into() => ex("") }; 105 | ``` 106 | ```twig 107 |

{{ title | escape }}

108 | ``` 109 | ```html 110 |

<Cats & Dogs>

111 | ``` 112 | 113 | ### Expressions 114 | 115 | ```rust 116 | let args = hashmap!{ 117 | "what".into() => ex("Hello"), 118 | "who".into() => ex("World") 119 | }; 120 | ``` 121 | ```twig 122 | Say: "{{ what + ", " + who }}!" 123 | ``` 124 | ```html 125 | Say: "Hello, World!" 126 | ``` 127 | 128 | ```rust 129 | let args = hashmap!{ 130 | "alpha".into() => ex(6isize), 131 | "omega".into() => ex(7f64) 132 | }; 133 | ``` 134 | ```twig 135 | The answer is {{ alpha * omega }} 136 | ``` 137 | ```html 138 | The answer is 42 139 | ``` 140 | 141 | ### Lazy boolean evaluation 142 | 143 | ```twig 144 | Amount: {{ amount and ("" + amount + " pcs") or "-" }} 145 | ``` 146 | ```rust 147 | assert_eq!("Amount: 6 pcs", incrust.render("tpl", &hashmap!{ "amount".into() => ex(6isize) }).unwrap()); 148 | assert_eq!("Amount: -", incrust.render("tpl", &hashmap!{ "amount".into() => ex(0isize) }).unwrap()); 149 | ``` 150 | 151 | ### Conditional statements 152 | 153 | ```twig 154 | String {% if "" %}has chars{% else %}is empty{% endif %} 155 | It's {% if False %}false{% elif True %}true{% endif %} 156 | ``` 157 | ```html 158 | String is empty 159 | It's true 160 | ``` 161 | 162 | ### For-Loop statements 163 | 164 | ```rust 165 | let args = hashmap!{ "fruits".into() => ex(vec![ex("Orange"), ex("Apple"), ex("Banana")]) }; 166 | ``` 167 | ```twig 168 |
    169 | {%- for fruit in fruits %} 170 |
  • {{ loop.index }}. {{ fruit | e }}
  • 171 | {%- endfor %} 172 |
173 | ``` 174 | ```html 175 |
    176 |
  • 1. Orange
  • 177 |
  • 2. Apple
  • 178 |
  • 3. Banana
  • 179 |
180 | ``` 181 | 182 | 183 | ### Template inheritance 184 | 185 | ```rust 186 | let args = hashmap!{ "parent_layout".into() => ex("default") }; 187 | incrust.render("template", &args).unwrap() 188 | ``` 189 | default.tpl 190 | ```twig 191 | 192 |

{% block title %}Default title{% endblock %}

193 |
194 | {%- block body %} 195 |

Default body

196 | {%- endblock %} 197 |

198 | 199 | ``` 200 | template.tpl 201 | ```html 202 | {% extends parent_layout %} 203 | {% block title -%} 204 | New title 205 | {%- endblock %} 206 | ``` 207 | Output 208 | ```html 209 | 210 |

New title

211 |
212 |

Default body

213 |

214 | 215 | ``` 216 | 217 | 218 | ### Include 219 | 220 | ```rust 221 | let args = hashmap!{ "menu".into() => ex("default_menu") }; 222 | assert_eq!(expected, incrust.render("tpl", &args).unwrap()); 223 | ``` 224 | default_menu.tpl 225 | ```twig 226 | 230 | ``` 231 | template.tpl 232 | ```html 233 | 236 | 237 |

Body

238 | ``` 239 | Output 240 | ```html 241 | 247 | 248 |

Body

249 | ``` 250 | 251 | 252 | ## Alternatives 253 | 254 | If you are looking for a template engine for your project, you may also look at these projects. 255 | 256 | ### With a similar syntax 257 | 258 | * [cobalt-org/liquid-rust](https://github.com/cobalt-org/liquid-rust) Liquid templating for Rust 259 | * [colin-kiegel/twig-rust](https://github.com/colin-kiegel/twig-rust) Rust port of the twig-php template library 260 | * [Nercury/twig-rs](https://github.com/Nercury/twig-rs) The Twig templating engine for Rust (work in progress) 261 | * [Keats/tera](https://github.com/Keats/tera) A template engine for Rust 262 | * [djc/askama](https://github.com/djc/askama) Type-safe, compiled Jinja-like templates for Rust 263 | 264 | 265 | ### Others 266 | 267 | * [sunng87/handlebars-rust](https://github.com/sunng87/handlebars-rust) Rust templating with Handlebars 268 | * [nickel-org/rust-mustache](https://github.com/nickel-org/rust-mustache) Mustache template library for rust 269 | 270 | 271 | ## License 272 | 273 | This project is licensed under either of 274 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 275 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 276 | at your option. 277 | 278 | 279 | ### Contribution 280 | 281 | Unless you explicitly state otherwise, any contribution intentionally submitted 282 | for inclusion in the work by you, as defined in the Apache-2.0 license, 283 | shall be dual licensed as above, without any additional terms or conditions. 284 | -------------------------------------------------------------------------------- /src/parser/statements.rs: -------------------------------------------------------------------------------- 1 | use std::str; 2 | #[allow(unused_imports)] 3 | use nom::{IResult, Err as NomErr, ErrorKind, alpha, alphanumeric, space, multispace}; 4 | 5 | use container::expression::*; 6 | use container::parsed::*; 7 | 8 | use super::block_level::nodes; 9 | 10 | 11 | named!(pub statement<&[u8], ParsedNode>, 12 | alt!( if_block | for_block | raw_block | block_block | extends_block | include_block ) 13 | ); 14 | 15 | named!(pub stmt_edge<&[u8], ParsedNode>, do_parse!( 16 | tag!("{%") >> (ParsedNode::Text("".into())) 17 | )); 18 | 19 | // -------------------------------------------------------------------------------------------------------------------- 20 | 21 | named!(pub stmt_raw<&[u8], SimpleStatement>, stmt_simple!("raw")); 22 | named!(pub stmt_endraw<&[u8], SimpleStatement>, stmt_simple!("endraw")); 23 | 24 | fn raw_block(input: &[u8]) -> IResult<&[u8], ParsedNode> { 25 | fn is_end(input: &[u8]) -> bool { 26 | stmt_endraw(input).is_done() 27 | } 28 | do_parse!( 29 | input, 30 | b: stmt_raw >> 31 | txt: map_res!( 32 | take_till_slc!(is_end), 33 | str::from_utf8 34 | ) >> 35 | e: stmt_endraw >> 36 | (ParsedRawStatement { begin: b, text: txt.into(), end: e }.into()) 37 | ) 38 | } 39 | 40 | // -------------------------------------------------------------------------------------------------------------------- 41 | 42 | named!(pub stmt_for<&[u8], ExprStatement>, stmt_expr!("for")); 43 | named!(pub stmt_endfor<&[u8], SimpleStatement>, stmt_simple!("endfor")); 44 | 45 | named!(pub for_block<&[u8], ParsedNode>, do_parse!( s: for_statement >> (s.into()) )); 46 | 47 | pub fn for_statement(input: &[u8]) -> IResult<&[u8], ParsedForStatement> { 48 | fn finish(begin: ExprStatement, block: ParsedNodes, end: SimpleStatement) -> Option<(ParsedForStatement)> { 49 | let mut expr: DisjExpr = begin.expression.expr; 50 | if expr.list.len() != 1 {return None} 51 | 52 | let mut expr: ConjExpr = expr.list.remove(0); 53 | if expr.list.len() != 1 {return None} 54 | 55 | let mut expr: CmpExpr = expr.list.remove(0); 56 | if expr.list.len() != 2 {return None} 57 | 58 | let left = expr.list.remove(0); 59 | let right = expr.list.remove(0); 60 | 61 | match right.0 { 62 | CmpOp::In => {}, 63 | _ => return None, 64 | } 65 | 66 | let mut expr: Expr = left.1; 67 | if expr.sum.len() != 1 {return None} 68 | 69 | let mut expr: Term = expr.sum.remove(0).1; 70 | if expr.mul.len() != 1 {return None} 71 | 72 | let left = expr.mul.remove(0).1; 73 | 74 | match left { 75 | Factor::Variable(value_var) => { 76 | let expression = FullExpression { 77 | filters: begin.expression.filters, 78 | expr: DisjExpr { list: vec![ 79 | ConjExpr { list: vec![ 80 | CmpExpr { list: vec![ right ] } 81 | ]} 82 | ]}, 83 | }; 84 | let begin = ExprStatement { expression, ..begin }; 85 | Some(ParsedForStatement { begin, block, key_var: None, value_var, end }) 86 | }, 87 | _ => None 88 | } 89 | } 90 | 91 | let (i, (b, t, e)) = try_parse!(input, tuple!( stmt_for, nodes, stmt_endfor ) ); 92 | 93 | match finish(b, t, e) { 94 | Some(result) => IResult::Done(i, result), 95 | None => IResult::Error(NomErr::Code(ErrorKind::Custom(1331))), 96 | } 97 | } 98 | 99 | // -------------------------------------------------------------------------------------------------------------------- 100 | 101 | named!(pub stmt_if<&[u8], ExprStatement>, stmt_expr!("if")); 102 | named!(pub stmt_elif<&[u8], ExprStatement>, stmt_expr!("elif")); 103 | named!(pub stmt_else<&[u8], SimpleStatement>, stmt_simple!("else")); 104 | named!(pub stmt_endif<&[u8], SimpleStatement>, stmt_simple!("endif")); 105 | 106 | named!(pub if_block<&[u8], ParsedNode>, do_parse!( s: if_statement >> ( s.into() ) )); 107 | 108 | pub fn if_statement(input: &[u8]) -> IResult<&[u8], ParsedIfStatement> { 109 | 110 | named!(if_<&[u8], ParsedIfBranch>, do_parse!( 111 | begin: stmt_if >> block: nodes >> (ParsedIfBranch { begin, block }) 112 | )); 113 | named!(elif<&[u8], ParsedIfBranch>, do_parse!( 114 | begin: stmt_elif >> block: nodes >> (ParsedIfBranch { begin, block }) 115 | )); 116 | named!(else_<&[u8], ParsedElseBranch>, do_parse!( 117 | begin: stmt_else >> block: nodes >> (ParsedElseBranch { begin, block }) 118 | )); 119 | 120 | do_parse!( input, 121 | if_branches: do_parse!( 122 | ifs: if_ >> 123 | elifs: many0!(elif) >> 124 | ({ 125 | let mut list = elifs; 126 | list.insert(0, ifs); 127 | list 128 | }) 129 | ) >> 130 | else_branch: opt!(else_) >> 131 | end: stmt_endif >> 132 | (ParsedIfStatement { if_branches, else_branch, end }) 133 | ) 134 | } 135 | 136 | 137 | // -------------------------------------------------------------------------------------------------------------------- 138 | 139 | named!(pub stmt_block<&[u8], NamedStatement>, stmt_named!("block")); 140 | named!(pub stmt_endblock<&[u8], SimpleStatement>, stmt_simple!("endblock")); 141 | 142 | named!(pub block_block<&[u8], ParsedNode>, do_parse!( s: block_statement >> (s.into()) )); 143 | 144 | named!(pub block_statement<&[u8], ParsedBlockStatement>, do_parse!( 145 | begin: stmt_block >> 146 | block: nodes >> 147 | end: stmt_endblock >> 148 | (ParsedBlockStatement { begin, block, end }) 149 | )); 150 | 151 | 152 | // -------------------------------------------------------------------------------------------------------------------- 153 | 154 | named!(pub stmt_extends<&[u8], ExprStatement>, stmt_expr!("extends")); 155 | 156 | named!(pub extends_block<&[u8], ParsedNode>, do_parse!( 157 | s: stmt_extends >> (ParsedNode::Extends(s)) 158 | )); 159 | 160 | 161 | // --------------------------------------------------------------------------- 162 | 163 | named!(pub stmt_include<&[u8], ExprStatement>, stmt_expr!("include")); 164 | 165 | named!(pub include_block<&[u8], ParsedNode>, do_parse!( 166 | s: stmt_include >> (ParsedNode::Include(s)) 167 | )); 168 | 169 | 170 | // --------------------------------------------------------------------------- 171 | 172 | #[cfg(test)] 173 | mod tests { 174 | #![allow(clippy::used_underscore_binding)] 175 | 176 | use nom::IResult::Done; 177 | use container::expression::*; 178 | use container::parsed::*; 179 | use container::parsed::ParsedNode::*; 180 | 181 | #[test] 182 | fn statement() { 183 | use super::stmt_raw; 184 | 185 | let empty_statement = SimpleStatement::default(); 186 | let strip_statement = SimpleStatement { strip_left: true, strip_right: true }; 187 | 188 | let expected = |statement: &SimpleStatement| Done(&b""[..], statement.clone()); 189 | 190 | 191 | assert_eq!(expected(&empty_statement), stmt_raw(b"{%raw%}")); 192 | assert_eq!(expected(&empty_statement), stmt_raw(b"{% raw %}")); 193 | assert_eq!(expected(&empty_statement), stmt_raw(b"{% raw %}")); 194 | assert_eq!(expected(&empty_statement), stmt_raw(b"{%\traw\t%}")); 195 | assert_eq!(expected(&empty_statement), stmt_raw(b"{%\nraw\n%}")); 196 | assert_eq!(expected(&empty_statement), stmt_raw(b"{%\rraw\r%}")); 197 | 198 | assert_eq!(expected(&strip_statement), stmt_raw(b"{%-raw-%}")); 199 | assert_eq!(expected(&strip_statement), stmt_raw(b"{%- raw -%}")); 200 | } 201 | 202 | #[test] 203 | fn raw() { 204 | use super::raw_block; 205 | 206 | let expected = |txt| { 207 | Done(&b""[..], Raw( 208 | ParsedRawStatement { 209 | begin: SimpleStatement { strip_left: false, strip_right: false }, 210 | text: String::from(txt), 211 | end: SimpleStatement { strip_left: false, strip_right: false }, 212 | } 213 | )) 214 | }; 215 | 216 | assert_eq!(expected("{{ raw }}"), raw_block(b"{% raw %}{{ raw }}{% endraw %}")); 217 | assert_eq!(expected("{% if %}"), raw_block(b"{% raw %}{% if %}{% endraw %}")); 218 | assert_eq!(expected("{% if %}"), raw_block(b"{% raw %}{% if %}{% endraw %}")); 219 | } 220 | 221 | #[test] 222 | fn if_() { 223 | let sample = |strip_right| ParsedIfStatement { 224 | if_branches: vec![ 225 | ParsedIfBranch { 226 | begin: ExprStatement { 227 | strip_left: false, 228 | strip_right: strip_right, 229 | expression: FullExpression { 230 | expr: DisjExpr { 231 | list: vec![ConjExpr { 232 | list: vec![CmpExpr { 233 | list: vec![CmpItem(CmpOp::Eq, Expr { 234 | sum: vec![ExprItem(SumOp::Add, Term { 235 | mul: vec![TermItem(MulOp::Mul, Factor::Variable("True".into()) )], 236 | })], 237 | })], 238 | }], 239 | }], 240 | }, 241 | filters: Vec::new(), 242 | } 243 | }, 244 | block: vec![Text("_".into())] 245 | } 246 | ], 247 | else_branch: None, 248 | end: SimpleStatement::default(), 249 | }; 250 | 251 | assert_eq!(Done(&b""[..], sample(false)), super::if_statement(b"{% if True %}_{% endif %}")); 252 | assert_eq!(Done(&b""[..], sample(true)), super::if_statement(b"{% if True-%}_{% endif %}")); 253 | assert_eq!(Done(&b""[..], sample(true)), super::if_statement(b"{% if True -%}_{% endif %}")); 254 | } 255 | 256 | #[test] 257 | fn for_() { 258 | // TODO test for statement 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/container/template.rs: -------------------------------------------------------------------------------- 1 | use std::convert::AsRef; 2 | use std::borrow::Cow; 3 | use std::collections::HashMap; 4 | 5 | use abc::*; 6 | use container::expression::*; 7 | use container::parsed::{ParsedNodes, ParsedNode}; 8 | use container::stack::VarContext; 9 | 10 | 11 | pub type Nodes = Vec; 12 | 13 | 14 | #[derive(Debug, Default, PartialEq, Clone)] 15 | pub struct Template { 16 | pub root: Nodes, 17 | pub blocks: HashMap, Nodes>, 18 | pub extends: Option, 19 | } 20 | 21 | impl AsRef