Skip to main content

tabnas_alchemy/shared/
text.rs

1//! Text output: the boundary every renderer writes its fragments to.
2//!
3//! A renderer produces many small fragments (a quote, a field, a comma) and
4//! must never hold a whole document. [`TextOut`] is the boundary: a
5//! fragment in, a failure out. [`JoinOut`] is a text output that also
6//! knows logical items, the shape a join answers. The writers and the text
7//! combinators that implement them (render's `WriteOut`, `Join`,
8//! `ReplaceText`) are render's, and alchemy reaches them through
9//! [`Renderers`](crate::shared::Renderers).
10
11use crate::shared::error::Fail;
12
13/// A consumer of text fragments.
14///
15/// Fragments arrive in order and are concatenated; where the boundaries
16/// fall carries no meaning. `flush` pushes everything held so far to the
17/// final destination, and a renderer calls it exactly once, at the end of
18/// the protocol it renders, so that a document that failed half way is not
19/// flushed as if it were whole.
20pub trait TextOut {
21    fn write_str(&mut self, s: &str) -> Result<(), Fail>;
22    fn flush(&mut self) -> Result<(), Fail>;
23
24    /// Whether any text has reached the final destination, so that a
25    /// failure found now leaves partial output behind. A renderer asks this
26    /// when it fails and reports `committed_output` from the answer, which
27    /// is how a host knows to print `output: "partial"` rather than
28    /// `"none"`. The default is the conservative answer for an output that
29    /// cannot tell: whatever the renderer handed over may be out. render's
30    /// `WriteOut` answers exactly, from the bytes its writer received; a
31    /// fragment that is still buffered is not committed, and `into_inner`
32    /// drops it rather than sending it after the fact.
33    fn has_committed(&self) -> bool {
34        true
35    }
36}
37
38impl<O: TextOut + ?Sized> TextOut for &mut O {
39    fn write_str(&mut self, s: &str) -> Result<(), Fail> {
40        (**self).write_str(s)
41    }
42
43    fn flush(&mut self) -> Result<(), Fail> {
44        (**self).flush()
45    }
46
47    fn has_committed(&self) -> bool {
48        (**self).has_committed()
49    }
50}
51
52impl<O: TextOut + ?Sized> TextOut for Box<O> {
53    fn write_str(&mut self, s: &str) -> Result<(), Fail> {
54        (**self).write_str(s)
55    }
56
57    fn flush(&mut self) -> Result<(), Fail> {
58        (**self).flush()
59    }
60
61    fn has_committed(&self) -> bool {
62        (**self).has_committed()
63    }
64}
65
66/// A [`TextOut`] that writes a separator between logical items: what
67/// [`Renderers::join`](crate::shared::Renderers::join) answers.
68///
69/// An item is what lies between [`JoinOut::item_start`] and
70/// [`JoinOut::item_end`]; it may be written in any number of fragments, or
71/// in none, and an empty item is still an item. A fragment written outside
72/// an item is an item of its own. The separator goes before every item but
73/// the first, never between the fragments of one item.
74pub trait JoinOut: TextOut {
75    /// Begin an item: the separator is written now if an item came before.
76    /// Starting an item inside an item is `PROTOCOL_ORDER_ERROR`.
77    fn item_start(&mut self) -> Result<(), Fail>;
78
79    /// End the current item. Ending when no item is open is
80    /// `PROTOCOL_ORDER_ERROR`.
81    fn item_end(&mut self) -> Result<(), Fail>;
82}