Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rendering to io::Write #111

Merged
merged 1 commit into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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