diff --git a/minijinja/src/environment.rs b/minijinja/src/environment.rs index 23105547..e37fd5d3 100644 --- a/minijinja/src/environment.rs +++ b/minijinja/src/environment.rs @@ -210,9 +210,14 @@ impl<'source> Environment<'source> { let name = ""; let compiled = CompiledTemplate::from_name_and_source(name, source)?; let mut rv = String::new(); - let mut out = Output::with_string(&mut rv, self.get_initial_auto_escape(name)); - Vm::new(self).eval(&compiled.instructions, root, &compiled.blocks, &mut out)?; - Ok(rv) + Vm::new(self) + .eval( + &compiled.instructions, + root, + &compiled.blocks, + &mut Output::with_string(&mut rv, self.get_initial_auto_escape(name)), + ) + .map(|_| rv) } /// Sets a new function to select the default auto escaping. diff --git a/minijinja/src/output.rs b/minijinja/src/output.rs index e822602d..3b3b3be1 100644 --- a/minijinja/src/output.rs +++ b/minijinja/src/output.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, io}; use crate::utils::AutoEscape; use crate::value::Value; @@ -24,8 +24,6 @@ pub struct Output<'a> { pub(crate) auto_escape: AutoEscape, } -pub struct NullWriter; - impl<'a> Output<'a> { /// Creates an output writing to a string. pub(crate) fn with_string(buf: &'a mut String, auto_escape: AutoEscape) -> Self { @@ -36,6 +34,14 @@ impl<'a> Output<'a> { } } + pub(crate) fn with_write(w: &'a mut (dyn fmt::Write + 'a), auto_escape: AutoEscape) -> Self { + Self { + w, + capture_stack: Vec::new(), + auto_escape, + } + } + /// Creates a null output that writes nowhere. pub(crate) fn null() -> Self { static mut NULL_WRITER: NullWriter = NullWriter; @@ -105,6 +111,8 @@ impl fmt::Write for Output<'_> { } } +pub struct NullWriter; + impl fmt::Write for NullWriter { #[inline] fn write_str(&mut self, _s: &str) -> fmt::Result { @@ -116,3 +124,28 @@ impl fmt::Write for NullWriter { Ok(()) } } + +pub struct WriteWrapper { + pub w: W, + pub err: Option, +} + +impl fmt::Write for WriteWrapper { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.w.write_all(s.as_bytes()).map_err(|e| { + self.err = Some(e); + fmt::Error + }) + } + + #[inline] + fn write_char(&mut self, c: char) -> fmt::Result { + self.w + .write_all(c.encode_utf8(&mut [0; 4]).as_bytes()) + .map_err(|e| { + self.err = Some(e); + fmt::Error + }) + } +} diff --git a/minijinja/src/template.rs b/minijinja/src/template.rs index dabd26f4..c9a48510 100644 --- a/minijinja/src/template.rs +++ b/minijinja/src/template.rs @@ -1,5 +1,5 @@ use std::collections::BTreeMap; -use std::fmt; +use std::{fmt, io}; use serde::Serialize; @@ -7,8 +7,8 @@ use crate::compiler::codegen::CodeGenerator; use crate::compiler::instructions::Instructions; use crate::compiler::parser::parse; use crate::environment::Environment; -use crate::error::{attach_basic_debug_info, Error}; -use crate::output::Output; +use crate::error::{attach_basic_debug_info, Error, ErrorKind}; +use crate::output::{Output, WriteWrapper}; use crate::utils::AutoEscape; use crate::value::Value; use crate::vm::Vm; @@ -86,14 +86,54 @@ impl<'env> Template<'env> { fn _render(&self, root: Value) -> Result { let mut rv = String::new(); - let mut out = Output::with_string(&mut rv, self.initial_auto_escape); - Vm::new(self.env).eval( - &self.compiled.instructions, + self._eval( root, - &self.compiled.blocks, - &mut out, - )?; - Ok(rv) + &mut Output::with_string(&mut rv, self.initial_auto_escape), + ) + .map(|_| rv) + } + + /// Renders the template into a [`io::Write`]. + /// + /// This works exactly like [`render`](Self::render) but instead writes the template + /// as it's evaluating into a [`io::Write`]. + /// + /// ``` + /// # use minijinja::{Environment, context}; + /// # let mut env = Environment::new(); + /// # env.add_template("hello", "Hello {{ name }}!").unwrap(); + /// use std::io::stdout; + /// + /// let tmpl = env.get_template("hello").unwrap(); + /// tmpl.render_to_write(context!(name => "John"), &mut stdout()).unwrap(); + /// ``` + pub fn render_to_write(&self, ctx: S, w: W) -> Result<(), Error> { + let mut wrapper = WriteWrapper { w, err: None }; + self._eval( + Value::from_serializable(&ctx), + &mut Output::with_write(&mut wrapper, self.initial_auto_escape), + ) + .map_err(|err| { + wrapper + .err + .take() + .map(|io_err| { + Error::new(ErrorKind::WriteFailure, "I/O error during rendering") + .with_source(io_err) + }) + .unwrap_or(err) + }) + } + + fn _eval(&self, root: Value, out: &mut Output) -> Result<(), Error> { + Vm::new(self.env) + .eval( + &self.compiled.instructions, + root, + &self.compiled.blocks, + out, + ) + .map(|_| ()) } /// Returns the root instructions.