Skip to content

Commit

Permalink
Add support for rendering to io::Write (#111)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko committed Sep 5, 2022
1 parent 1b45695 commit 6ae3a5c
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 16 deletions.
11 changes: 8 additions & 3 deletions minijinja/src/environment.rs
Expand Up @@ -210,9 +210,14 @@ impl<'source> Environment<'source> {
let name = "<string>";
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.
Expand Down
39 changes: 36 additions & 3 deletions minijinja/src/output.rs
@@ -1,4 +1,4 @@
use std::fmt;
use std::{fmt, io};

use crate::utils::AutoEscape;
use crate::value::Value;
Expand All @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -116,3 +124,28 @@ impl fmt::Write for NullWriter {
Ok(())
}
}

pub struct WriteWrapper<W> {
pub w: W,
pub err: Option<io::Error>,
}

impl<W: io::Write> fmt::Write for WriteWrapper<W> {
#[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
})
}
}
60 changes: 50 additions & 10 deletions minijinja/src/template.rs
@@ -1,14 +1,14 @@
use std::collections::BTreeMap;
use std::fmt;
use std::{fmt, io};

use serde::Serialize;

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;
Expand Down Expand Up @@ -86,14 +86,54 @@ impl<'env> Template<'env> {

fn _render(&self, root: Value) -> Result<String, Error> {
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<S: Serialize, W: io::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.
Expand Down

0 comments on commit 6ae3a5c

Please sign in to comment.