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}