Skip to content

Commit

Permalink
Merge d7e7642 into 80b496a
Browse files Browse the repository at this point in the history
  • Loading branch information
morenol committed Aug 21, 2020
2 parents 80b496a + d7e7642 commit 6ee1b96
Show file tree
Hide file tree
Showing 20 changed files with 291 additions and 63 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ A jinja2-like template engine in [rust] inspired by *[Jinja2Cpp]*.
## Current Jinja2 support

* expressions. You can use almost every expression style: simple, filtered, conditional, and so on.
* filters that can be used via '|' operator (default, first, last, length, max, min, abs, float, int, string, sum, capitalize, title, upper, wordcount, truncate and center).
* filters that can be used via '|' operator (default, first, last, length, max, min, abs, float, int, string, sum, round, capitalize, title, upper, wordcount, truncate and center).
* 'if' statement (with 'elif' and 'else' branches)
* 'for' statement (without 'else' branch and 'if' part support)
* 'with' statement
* 'include' statement
* space control and 'raw'/'endraw' blocks

[Jinja2Cpp]: https://github.com/jinja2cpp/jinja2cpp
Expand All @@ -34,7 +35,7 @@ TODO:
- [ ] set
- [ ] filter
- [ ] extends
- [ ] include
- [x] include
- [ ] macro
- [ ] line statements
- [ ] expressions
Expand All @@ -59,7 +60,6 @@ TODO:
- [ ] rejectattr
- [ ] replace
- [ ] reverse
- [ ] round
- [ ] safe
- [ ] select
- [ ] selectattr
Expand Down
14 changes: 10 additions & 4 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
use crate::value::{Value, ValuesMap};
use crate::TemplateEnv;
use std::sync::{Arc, RwLock};

#[derive(Clone, Debug, Default)]
pub struct Context {
#[derive(Clone)]
pub struct Context<'a> {
global_scope: Arc<RwLock<ValuesMap>>,
external_scope: ValuesMap,
scopes: Vec<Arc<RwLock<ValuesMap>>>,
callback_renderer: Arc<&'a TemplateEnv<'a>>,
}

impl Context {
pub fn new(external_scope: ValuesMap) -> Self {
impl<'a> Context<'a> {
pub fn new(external_scope: ValuesMap, callback_renderer: Arc<&'a TemplateEnv>) -> Self {
Self {
global_scope: Arc::new(RwLock::new(ValuesMap::default())),
external_scope,
scopes: vec![],
callback_renderer,
}
}
pub fn enter_scope(&mut self) -> Arc<RwLock<ValuesMap>> {
Expand Down Expand Up @@ -42,4 +45,7 @@ impl Context {
pub fn set_global(&mut self, global_scope: Arc<RwLock<ValuesMap>>) {
self.global_scope = global_scope;
}
pub fn get_renderer_callback(&self) -> Arc<&'a TemplateEnv<'a>> {
self.callback_renderer.clone()
}
}
5 changes: 4 additions & 1 deletion src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ impl Filter {
Filter::Min => base_value.min(), // TODO Accept params
Filter::Round => {
let parameters = if params.is_some() {
params.as_ref().unwrap().parse(vec!["method"], context)?
params
.as_ref()
.unwrap()
.parse(vec!["precision", "method"], context)?
} else {
HashMap::default()
};
Expand Down
47 changes: 47 additions & 0 deletions src/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,57 @@ impl<'a> Render for ForStatement<'a> {
Ok(())
}
}
pub struct IncludeStatement<'a> {
ignore_missing: bool,
with_context: bool,
expr_name: Box<dyn Evaluate + 'a>,
}

impl<'a> IncludeStatement<'a> {
pub fn new(
ignore_missing: bool,
with_context: bool,
expr_name: Box<dyn Evaluate + 'a>,
) -> Self {
Self {
ignore_missing,
with_context,
expr_name,
}
}
}
impl<'a> Render for IncludeStatement<'a> {
fn render(&self, out: &mut dyn Write, params: Context) -> Result<()> {
let template_env = params.get_renderer_callback();
let name = self.expr_name.evaluate(params.clone())?.to_string();
let template_result = template_env.load_template(&name);

let template = match template_result {
Ok(tmp) => tmp,
Err(err) => {
if self.ignore_missing {
return Ok(());
} else {
return Err(err);
}
}
};
if self.with_context {
template.render(out, params)
} else {
let mut context = Context::new(ValuesMap::default(), template_env.clone());
context.set_global(template_env.globals());
template.render(out, context)
}
}
}

pub enum Statement<'a> {
If(IfStatement<'a>),
Else(ElseStatement<'a>),
For(ForStatement<'a>),
With(WithStatement<'a>),
Include(IncludeStatement<'a>),
}
impl<'a> Statement<'a> {
pub fn set_main_body(&mut self, body: Arc<ComposedRenderer<'a>>) {
Expand All @@ -185,6 +230,7 @@ impl<'a> Statement<'a> {
Statement::Else(statement) => statement.set_main_body(body),
Statement::For(statement) => statement.set_main_body(body),
Statement::With(statement) => statement.set_main_body(body),
_ => unreachable!(),
}
}
pub fn add_else_branch(&mut self, branch: Statement<'a>) {
Expand All @@ -202,6 +248,7 @@ impl<'a> Render for Statement<'a> {
Statement::Else(statement) => statement.render(out, params),
Statement::For(statement) => statement.render(out, params),
Statement::With(statement) => statement.render(out, params),
Statement::Include(statement) => statement.render(out, params),
}
}
}
Expand Down
84 changes: 76 additions & 8 deletions src/statement/parser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
ElseStatement, ForStatement, IfStatement, Statement, StatementInfo, StatementInfoList,
StatementInfoType, WithStatement,
ElseStatement, ForStatement, IfStatement, IncludeStatement, Statement, StatementInfo,
StatementInfoList, StatementInfoType, WithStatement,
};
use crate::error::{Error, ErrorKind, Result, SourceLocation};
use crate::expression_parser::ExpressionParser;
Expand Down Expand Up @@ -31,8 +31,9 @@ impl StatementParser {
StatementParser::parse_endfor(&mut lexer, &mut statementinfo_list)
}
Some(Token::With) => StatementParser::parse_with(&mut lexer, &mut statementinfo_list),
Some(Token::EndWith) => {
StatementParser::parse_endwith(&mut lexer, &mut statementinfo_list)
Some(Token::EndWith) => StatementParser::parse_endwith(&mut statementinfo_list),
Some(Token::Include) => {
StatementParser::parse_include(&mut lexer, &mut statementinfo_list)
}
_ => todo!(),
}
Expand Down Expand Up @@ -226,10 +227,7 @@ impl StatementParser {
statementinfo_list.push(statement_info);
Ok(())
}
fn parse_endwith<'a>(
_lexer: &mut Peekable<Lexer<'a, Token<'a>>>,
statementinfo_list: &mut StatementInfoList<'a>,
) -> Result<()> {
fn parse_endwith<'a>(statementinfo_list: &mut StatementInfoList<'a>) -> Result<()> {
if statementinfo_list.len() <= 1 {
return Err(Error::from(ErrorKind::UnexpectedStatement(
SourceLocation::new(1, 2),
Expand All @@ -252,4 +250,74 @@ impl StatementParser {
)))
}
}
fn parse_include<'a>(
lexer: &mut Peekable<Lexer<'a, Token<'a>>>,
statementinfo_list: &mut StatementInfoList<'a>,
) -> Result<()> {
if statementinfo_list.is_empty() {
return Err(Error::from(ErrorKind::UnexpectedStatement(
SourceLocation::new(1, 2),
)));
}
let expr = ExpressionParser::full_expresion_parser(lexer)?;
let mut is_ignore_missing = false;
let mut is_with_context = true;

if let Some(Token::Ignore) = lexer.peek() {
lexer.next();
if let Some(Token::Missing) = lexer.peek() {
is_ignore_missing = true;
} else {
return Err(Error::from(ErrorKind::ExpectedToken(SourceLocation::new(
1, 2,
))));
}
lexer.next();
}

match lexer.next() {
Some(Token::With) => {
if let Some(Token::Context) = lexer.peek() {
lexer.next();
} else {
return Err(Error::from(ErrorKind::ExpectedToken(SourceLocation::new(
1, 2,
))));
}
}
Some(Token::Without) => {
is_with_context = false;
if let Some(Token::Context) = lexer.peek() {
lexer.next();
} else {
return Err(Error::from(ErrorKind::ExpectedToken(SourceLocation::new(
1, 2,
))));
}
}
None => {}
_ => {
return Err(Error::from(ErrorKind::UnexpectedToken(
SourceLocation::new(1, 2),
)));
}
}
if lexer.next().is_some() {
return Err(Error::from(ErrorKind::UnexpectedToken(
SourceLocation::new(1, 2),
)));
}
let renderer = Statement::Include(IncludeStatement::new(
is_ignore_missing,
is_with_context,
Box::new(expr),
));
statementinfo_list
.last_mut()
.unwrap()
.current_composition
.add_renderer(Box::new(renderer));

Ok(())
}
}
10 changes: 6 additions & 4 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::error::Result;
use crate::renderer::{ComposedRenderer, Render};
use crate::template_env::TemplateEnv;
use crate::template_parser::TemplateParser;
use crate::value::ValuesMap;
use std::borrow::Cow;
use std::io::Write;
use std::sync::Arc;
Expand Down Expand Up @@ -46,17 +47,18 @@ impl<'a> Template<'a> {
Ok(())
}

pub fn render_as_string(&self, params: Context) -> Result<String> {
pub fn render_as_string(&self, params: ValuesMap) -> Result<String> {
let mut b: Vec<u8> = Vec::new();
self.render(&mut b, params)?;
let mut context = Context::new(params, self.template_env.clone());
context.set_global(self.template_env.globals());
self.render(&mut b, context)?;
Ok(String::from_utf8(b).expect("Found invalid UTF-8"))
}
}

impl<'a> Render for Template<'a> {
fn render(&self, out: &mut dyn Write, mut params: Context) -> Result<()> {
fn render(&self, out: &mut dyn Write, params: Context) -> Result<()> {
if let Some(ref renderer) = self.renderer {
params.set_global(self.template_env.globals());
renderer.render(out, params)
} else {
todo!()
Expand Down
12 changes: 9 additions & 3 deletions src/template_env.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::Result;
use crate::error::{Error, ErrorKind, Result};
use crate::value::{Value, ValuesMap};
use crate::FileSystemHandler;
use crate::Template;
Expand Down Expand Up @@ -97,19 +97,25 @@ impl<'a> TemplateEnv<'a> {
self.filesystem_handlers.push(handler);
Ok(())
}
pub fn load_template(&mut self, filename: &str) -> Result<Template> {
pub fn load_template(&self, filename: &str) -> Result<Template> {
let mut template = Template::new(Arc::new(self))?;
let mut not_found = true;
for handler in &self.filesystem_handlers {
let stream = handler.open_stream(filename);
let mut content = String::default();

if let Some(mut reader) = stream {
reader.read_to_string(&mut content)?;
template.load(content)?;
not_found = false;
break;
}
}
Ok(template)
if not_found {
Err(Error::from(ErrorKind::TemplateNotFound))
} else {
Ok(template)
}
}
}

Expand Down
9 changes: 6 additions & 3 deletions src/value/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,15 @@ impl Value {
.remove("method")
.unwrap_or_else(|| Value::String("common".to_string()));

let precision = params.remove("precision").unwrap_or(Value::Double(0.0));

let pow10 = 10.0_f64.powf(precision.float(HashMap::default())?);
if let Value::Double(value) = self {
if let Value::String(method_str) = method {
match method_str.as_str() {
"common" => Ok(Value::Double(value.round())),
"ceil" => Ok(Value::Double(value.ceil())),
"floor" => Ok(Value::Double(value.floor())),
"common" => Ok(Value::Double((value * pow10).round() / pow10)),
"ceil" => Ok(Value::Double((value * pow10).ceil() / pow10)),
"floor" => Ok(Value::Double((value * pow10).floor() / pow10)),
_ => Err(Error::from(ErrorKind::InvalidValueType)),
}
} else {
Expand Down
5 changes: 3 additions & 2 deletions tests/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::sync::Arc;
use temple::error::{Error, ErrorKind, Result};
use temple::{Context, Template, TemplateEnv};
use temple::value::ValuesMap;
use temple::{Template, TemplateEnv};

#[test]
fn expected_endraw() -> Result<()> {
Expand Down Expand Up @@ -115,7 +116,7 @@ fn undefined_value() -> Result<()> {
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load("{{ undefinedValue }}")?;
let context = Context::default();
let context = ValuesMap::default();
let result = template.render_as_string(context);
assert_matches!(
result,
Expand Down
3 changes: 0 additions & 3 deletions tests/expressions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::utils::assert_render_template_eq;
use temple::error::Result;
use temple::value::{Value, ValuesMap};
use temple::Context;

#[test]
fn basic_math_expression() -> Result<()> {
Expand Down Expand Up @@ -85,7 +84,6 @@ fn render_with_context() -> Result<()> {
let mut context = ValuesMap::default();
context.insert("foo".to_string(), Value::Integer(42));
context.insert("bar".to_string(), Value::Double(3.5));
let context = Context::new(context);

assert_render_template_eq("{{ foo }}", "42", Some(context.clone()))?;
assert_render_template_eq("{{ foo + bar }}", "45.5", Some(context.clone()))
Expand All @@ -94,7 +92,6 @@ fn render_with_context() -> Result<()> {
fn accessors() -> Result<()> {
let mut context = ValuesMap::default();
context.insert("text".to_string(), Value::String("hello".to_string()));
let context = Context::new(context);
assert_render_template_eq("{{ text[2] }}", "l", Some(context))?;
assert_render_template_eq("{{ [0, 1, 2][2] }}", "2", None)?;
assert_render_template_eq("{{ (0, 1, 2)[2] }}", "2", None)?;
Expand Down
Loading

0 comments on commit 6ee1b96

Please sign in to comment.