Skip to content

Commit

Permalink
Include statement
Browse files Browse the repository at this point in the history
  • Loading branch information
Luis Moreno committed Aug 21, 2020
1 parent c5bd71d commit d7e7642
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 15 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ A jinja2-like template engine in [rust] inspired by *[Jinja2Cpp]*.
* '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 Down
9 changes: 6 additions & 3 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ use crate::value::{Value, ValuesMap};
use crate::TemplateEnv;
use std::sync::{Arc, RwLock};

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

impl<'a> Context<'a> {
Expand All @@ -16,7 +16,7 @@ impl<'a> Context<'a> {
global_scope: Arc::new(RwLock::new(ValuesMap::default())),
external_scope,
scopes: vec![],
callback_renderer: Some(callback_renderer),
callback_renderer,
}
}
pub fn enter_scope(&mut self) -> Arc<RwLock<ValuesMap>> {
Expand Down Expand Up @@ -45,4 +45,7 @@ impl<'a> Context<'a> {
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()
}
}
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(())
}
}
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
1 change: 1 addition & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod filters;
mod scoped_context;
mod statement_for;
mod statement_if;
mod statement_include;
mod statement_with;
mod utils;
mod whitespace_control;
114 changes: 114 additions & 0 deletions tests/statement_include.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::sync::Arc;
use temple::error::{Error, ErrorKind, Result};
use temple::value::{Value, ValuesMap};
use temple::{MemoryFileSystem, Template, TemplateEnv};

fn assert_render_template_with_includes_eq(
input: &str,
expected: &str,
params: Option<ValuesMap>,
) -> Result<()> {
let mut temp_env = TemplateEnv::default();

let mut handler = MemoryFileSystem::new();
handler.add_file("simple.j2".to_string(), "Hello world!".to_string());
handler.add_file("header.j2".to_string(), "[{{ foo }}|{{ bar}}]".to_string());
handler.add_file("o_printer.j2".to_string(), "({{ o }})".to_string());
temp_env.add_filesystem_handler(Box::new(handler))?;

temp_env.add_global("bar".to_string(), Value::Integer(23));
temp_env.add_global("o".to_string(), Value::Integer(0));
let template_env = Arc::new(&temp_env);
let mut template = Template::new(template_env)?;
template.load(input)?;
let default_context = ValuesMap::default();
let context = params.unwrap_or(default_context);
let result = template.render_as_string(context)?;
assert_eq!(result, expected.to_string());
Ok(())
}

#[test]
fn simple_include() -> Result<()> {
assert_render_template_with_includes_eq("{% include \"simple.j2\" %}", "Hello world!", None)
}

#[test]
fn include_with_context() -> Result<()> {
let mut context = ValuesMap::default();
context.insert("foo".to_string(), Value::Integer(42));
assert_render_template_with_includes_eq(
"{% include \"header.j2\" %}",
"[42|23]",
Some(context.clone()),
)?;
assert_render_template_with_includes_eq(
"{% include \"header.j2\" with context %}",
"[42|23]",
Some(context),
)
}

#[test]
fn include_without_context() -> Result<()> {
let mut context = ValuesMap::default();
context.insert("o".to_string(), Value::Integer(42));
assert_render_template_with_includes_eq(
"{% include \"o_printer.j2\" without context %}",
"(0)",
Some(context),
)
}

#[test]
fn include_ignore_missing() -> Result<()> {
assert_render_template_with_includes_eq(
"{% include \"missing_inner_header.j2\" ignore missing %}",
"",
None,
)
}

#[test]
fn error_include_missing() -> Result<()> {
let result = assert_render_template_with_includes_eq(
"{% include \"missing_inner_header.j2\" %}",
"",
None,
);
assert_matches!(result, Err(Error::ParseRender(ErrorKind::TemplateNotFound)));
Ok(())
}

#[test]
fn error_include_ignore_missing() -> Result<()> {
let result = assert_render_template_with_includes_eq(
"{% include \"missing_inner_header.j2\" ignore mising %}",
"",
None,
);
assert_matches!(result, Err(Error::ParseRender(ErrorKind::ExpectedToken(_))));
Ok(())
}

#[test]
fn error_include_without_context() -> Result<()> {
let result = assert_render_template_with_includes_eq(
"{% include \"simple.j2\" without \"context\" %}",
"",
None,
);
assert_matches!(result, Err(Error::ParseRender(ErrorKind::ExpectedToken(_))));
Ok(())
}

#[test]
fn error_include_with_context() -> Result<()> {
let result = assert_render_template_with_includes_eq(
"{% include \"simple.j2\" with \"context\" %}",
"",
None,
);
assert_matches!(result, Err(Error::ParseRender(ErrorKind::ExpectedToken(_))));
Ok(())
}

0 comments on commit d7e7642

Please sign in to comment.