Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 102 additions & 3 deletions tera/src/tera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -997,7 +997,7 @@ impl Tera {
/// assert_eq!(output, "My age is 18.");
/// ```
///
/// To render a template with an empty context, simply pass an empty [`Context`] object.
/// To render a template with no context, simply pass a [`Context::new()`] object.
///
/// ```
/// # use tera::{Tera, Context};
Expand Down Expand Up @@ -1047,7 +1047,7 @@ impl Tera {
) -> TeraResult<()> {
let template = self.must_get_template(template_name)?;
let mut vm = VirtualMachine::new(self, template);
vm.render_to(context, &self.global_context, write)
vm.render_to(None, context, &self.global_context, write)
}

/// Returns the global context, allowing modifications to it
Expand Down Expand Up @@ -1141,7 +1141,7 @@ impl Tera {
}

let mut vm = VirtualMachine::new(self, &template);
vm.render_to(context, &self.global_context, write)
vm.render_to(None, context, &self.global_context, write)
}

/// Renders a one off template (for example a template coming from a user input) given a `Context`
Expand Down Expand Up @@ -1269,6 +1269,66 @@ impl Tera {

Ok(())
}

/// Renders a block by name with the given context.
///
/// # Examples
///
/// ```
/// # use tera::{Tera, Context};
/// // Create new tera instance with demo template
/// let mut tera = Tera::default();
/// tera.add_raw_template("hello.html", "<h1>Hello</h1>{% block content %}in block{% endblock %}");
///
/// // Render a template with an empty context
/// let output = tera.render_block("hello.html", "content", &Context::new()).unwrap();
/// assert_eq!(output, "in block");
/// ```
pub fn render_block(
&self,
template_name: &str,
block_name: &str,
context: &Context,
) -> TeraResult<String> {
let template = self.must_get_template(template_name)?;
if !template.block_lineage.contains_key(block_name) {
return Err(Error::message(format!(
"Block `{block_name}` not found in template `{template_name}`",
)));
}
let mut vm = VirtualMachine::new(self, template);
vm.render_block(block_name, context, &self.global_context)
}

/// Renders a block by name with the given context to something that implements [`Write`].
///
/// # Examples
///
/// ```
/// # use tera::{Tera, Context};
/// let mut tera = Tera::default();
/// tera.add_raw_template("hello.html", "<h1>Hello</h1>{% block content %}in block{% endblock %}");
///
/// let mut buffer = Vec::new();
/// tera.render_block_to("hello.html", "content", &Context::new(), &mut buffer).unwrap();
/// assert_eq!(buffer, b"in block");
/// ```
pub fn render_block_to(
&self,
template_name: &str,
block_name: &str,
context: &Context,
write: impl Write,
) -> TeraResult<()> {
let template = self.must_get_template(template_name)?;
if !template.block_lineage.contains_key(block_name) {
return Err(Error::message(format!(
"Block `{block_name}` not found in template `{template_name}`",
)));
}
let mut vm = VirtualMachine::new(self, template);
vm.render_to(Some(block_name), context, &self.global_context, write)
}
}

impl Default for Tera {
Expand Down Expand Up @@ -1680,4 +1740,43 @@ mod tests {
.unwrap();
insta::assert_snapshot!(result, @"<script>");
}

#[test]
fn render_block_works() {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
(
"base.html",
"{% block nav %}nav{% endblock %}{% block content %}default{% endblock %}",
),
(
"child.html",
"{% extends \"base.html\" %}{% block content %}child-{{super()}}{% endblock %}",
),
(
"nested.html",
"{% block outer %}<o>{% block inner %}inner{% endblock %}</o>{% endblock %}",
),
])
.unwrap();

// unknown blocks error
assert!(
tera.render_block("child.html", "unknown", &Context::new())
.is_err()
);
let result = tera
.render_block("child.html", "content", &Context::new())
.unwrap();
assert_eq!(result, "child-default");

let inner = tera
.render_block("nested.html", "inner", &Context::new())
.unwrap();
assert_eq!(inner, "inner");
let outer = tera
.render_block("nested.html", "outer", &Context::new())
.unwrap();
assert_eq!(outer, "<o>inner</o>");
}
}
36 changes: 32 additions & 4 deletions tera/src/vm/interpreter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io::Write;
use std::io::{self, Write};
use std::sync::Arc;

use crate::errors::{Error, ErrorKind, ReportError, TeraResult};
Expand Down Expand Up @@ -564,7 +564,14 @@ impl<'tera> VirtualMachine<'tera> {
let old_chunk = state.chunk.replace(block_chunk);
state.blocks.insert(block_name, (block_lineage, 0));
let old_block_name = state.current_block_name.replace(block_name);
let res = self.interpret(state, output);
let res = if state.capture_block == Some(block_name.as_str()) {
let mut buf = Vec::with_capacity(256);
let r = self.interpret(state, &mut buf);
state.block_buffer = buf;
r
} else {
self.interpret(state, output)
};
state.chunk = old_chunk;
state.current_block_name = old_block_name;
res?;
Expand Down Expand Up @@ -944,12 +951,24 @@ impl<'tera> VirtualMachine<'tera> {
global_context: &Context,
) -> TeraResult<String> {
let mut output = Vec::with_capacity(self.template.size_hint());
self.render_to(context, global_context, &mut output)?;
self.render_to(None, context, global_context, &mut output)?;
Ok(String::from_utf8(output)?)
}

pub(crate) fn render_block(
&mut self,
block_name: &str,
context: &Context,
global_context: &Context,
) -> TeraResult<String> {
let mut output = Vec::with_capacity(self.template.size_hint());
self.render_to(Some(block_name), context, global_context, &mut output)?;
Ok(String::from_utf8(output)?)
}

pub(crate) fn render_to(
&mut self,
block_name: Option<&str>,
context: &Context,
global_context: &Context,
mut output: impl Write,
Expand All @@ -964,6 +983,15 @@ impl<'tera> VirtualMachine<'tera> {
let mut state = State::new_with_chunk(context, chunk);
state.global_context = Some(global_context);
state.filters = Some(&self.tera.filters);
self.interpret(&mut state, &mut output)

if let Some(block) = block_name {
state.capture_block = Some(block);
// we don't care about keeping the full rendered template
self.interpret(&mut state, &mut io::sink())?;
output.write_all(&state.block_buffer)?;
} else {
self.interpret(&mut state, &mut output)?;
}
Ok(())
}
}
6 changes: 6 additions & 0 deletions tera/src/vm/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub struct State<'tera> {
pub(crate) escape_buffer: Vec<u8>,
/// Used in includes only
pub(crate) include_parent: Option<&'tera State<'tera>>,
/// Which block we are asked to render
pub(crate) capture_block: Option<&'tera str>,
/// The buffer just for the one block we want to return
pub(crate) block_buffer: Vec<u8>,

/// (block name, (all_chunks, level))
pub(crate) blocks: BTreeMap<&'tera str, (Vec<&'tera Chunk>, usize)>,
Expand Down Expand Up @@ -61,6 +65,8 @@ impl<'t> State<'t> {
capture_buffers: Vec::with_capacity(4),
escape_buffer: Vec::with_capacity(128),
include_parent: None,
capture_block: None,
block_buffer: Vec::new(),
blocks: BTreeMap::new(),
current_block_name: None,
filters: None,
Expand Down
Loading