minijinja/
output.rs

1use std::ptr::addr_of_mut;
2use std::{fmt, io};
3
4use crate::error::{Error, ErrorKind};
5use crate::utils::AutoEscape;
6use crate::value::Value;
7
8/// How should output be captured?
9#[derive(Debug, Clone, Copy, Eq, PartialEq)]
10#[cfg_attr(feature = "unstable_machinery_serde", derive(serde::Serialize))]
11pub enum CaptureMode {
12    Capture,
13    #[allow(unused)]
14    Discard,
15}
16
17/// An abstraction over [`fmt::Write`](std::fmt::Write) for the rendering.
18///
19/// This is a utility type used in the engine which can be written into like one
20/// can write into an [`std::fmt::Write`] value.  It's primarily used internally
21/// in the engine but it's also passed to the custom formatter function.
22pub struct Output<'a> {
23    w: *mut (dyn fmt::Write + 'a),
24    target: *mut (dyn fmt::Write + 'a),
25    capture_stack: Vec<Option<String>>,
26}
27
28impl<'a> Output<'a> {
29    /// Creates a new output.
30    pub(crate) fn new(w: &'a mut (dyn fmt::Write + 'a)) -> Self {
31        Self {
32            w,
33            target: w,
34            capture_stack: Vec::new(),
35        }
36    }
37
38    /// Creates a null output that writes nowhere.
39    pub(crate) fn null() -> Self {
40        // The null writer also has a single entry on the discarding capture
41        // stack.  In fact, `w` is more or less useless here as we always
42        // shadow it.  This is done so that `is_discarding` returns true.
43        Self {
44            w: NullWriter::get_mut(),
45            target: NullWriter::get_mut(),
46            capture_stack: vec![None],
47        }
48    }
49
50    /// Begins capturing into a string or discard.
51    pub(crate) fn begin_capture(&mut self, mode: CaptureMode) {
52        self.capture_stack.push(match mode {
53            CaptureMode::Capture => Some(String::new()),
54            CaptureMode::Discard => None,
55        });
56        self.retarget();
57    }
58
59    /// Ends capturing and returns the captured string as value.
60    pub(crate) fn end_capture(&mut self, auto_escape: AutoEscape) -> Value {
61        let rv = if let Some(captured) = self.capture_stack.pop().unwrap() {
62            if !matches!(auto_escape, AutoEscape::None) {
63                Value::from_safe_string(captured)
64            } else {
65                Value::from(captured)
66            }
67        } else {
68            Value::UNDEFINED
69        };
70        self.retarget();
71        rv
72    }
73
74    fn retarget(&mut self) {
75        self.target = match self.capture_stack.last_mut() {
76            Some(Some(stream)) => stream,
77            Some(None) => NullWriter::get_mut(),
78            None => self.w,
79        };
80    }
81
82    #[inline(always)]
83    fn target(&mut self) -> &mut dyn fmt::Write {
84        // SAFETY: this is safe because we carefully maintain the capture stack
85        // to update self.target whenever it's modified
86        unsafe { &mut *self.target }
87    }
88
89    /// Returns `true` if the output is discarding.
90    #[inline(always)]
91    #[allow(unused)]
92    pub(crate) fn is_discarding(&self) -> bool {
93        matches!(self.capture_stack.last(), Some(None))
94    }
95
96    /// Writes some data to the underlying buffer contained within this output.
97    #[inline]
98    pub fn write_str(&mut self, s: &str) -> fmt::Result {
99        self.target().write_str(s)
100    }
101
102    /// Writes some formatted information into this instance.
103    #[inline]
104    pub fn write_fmt(&mut self, a: fmt::Arguments<'_>) -> fmt::Result {
105        self.target().write_fmt(a)
106    }
107}
108
109impl fmt::Write for Output<'_> {
110    #[inline]
111    fn write_str(&mut self, s: &str) -> fmt::Result {
112        fmt::Write::write_str(self.target(), s)
113    }
114
115    #[inline]
116    fn write_char(&mut self, c: char) -> fmt::Result {
117        fmt::Write::write_char(self.target(), c)
118    }
119
120    #[inline]
121    fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
122        fmt::Write::write_fmt(self.target(), args)
123    }
124}
125
126pub struct NullWriter;
127
128impl NullWriter {
129    /// Returns a reference to the null writer.
130    pub fn get_mut() -> &'static mut NullWriter {
131        static mut NULL_WRITER: NullWriter = NullWriter;
132        // SAFETY: this is safe as the null writer is a ZST
133        unsafe { &mut *addr_of_mut!(NULL_WRITER) }
134    }
135}
136
137impl fmt::Write for NullWriter {
138    #[inline]
139    fn write_str(&mut self, _s: &str) -> fmt::Result {
140        Ok(())
141    }
142
143    #[inline]
144    fn write_char(&mut self, _c: char) -> fmt::Result {
145        Ok(())
146    }
147}
148
149pub struct WriteWrapper<W> {
150    pub w: W,
151    pub err: Option<io::Error>,
152}
153
154impl<W> WriteWrapper<W> {
155    /// Replaces the given error with the held error if available.
156    pub fn take_err(&mut self, original: Error) -> Error {
157        self.err
158            .take()
159            .map(|io_err| {
160                Error::new(ErrorKind::WriteFailure, "I/O error during rendering")
161                    .with_source(io_err)
162            })
163            .unwrap_or(original)
164    }
165}
166
167impl<W: io::Write> fmt::Write for WriteWrapper<W> {
168    #[inline]
169    fn write_str(&mut self, s: &str) -> fmt::Result {
170        self.w.write_all(s.as_bytes()).map_err(|e| {
171            self.err = Some(e);
172            fmt::Error
173        })
174    }
175
176    #[inline]
177    fn write_char(&mut self, c: char) -> fmt::Result {
178        self.w
179            .write_all(c.encode_utf8(&mut [0; 4]).as_bytes())
180            .map_err(|e| {
181                self.err = Some(e);
182                fmt::Error
183            })
184    }
185}