()
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 |
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