render_tree/
render.rs

1use super::{Document, Node};
2
3/// The Render trait defines a type that can be added to a Document.
4/// It is defined for `Node`, `String`, `&str`, and `Document`.alloc
5///
6/// It is also defined for `Option<T>` where `T` is `Render`, as well
7/// as `&T` where `T` is both `Render` and `Clone`.
8///
9/// Generally speaking, if you need to make a type `Render`, and it's
10/// not one of your types, you can ergonomically make a newtype wrapper
11/// for it.
12///
13/// For example, if you want to render `std::time::Duration`:
14///
15/// ```
16/// #[macro_use]
17/// extern crate render_tree;
18/// extern crate termcolor;
19/// use render_tree::{Render, Document, Line, RenderComponent};
20/// use std::time::Duration;
21/// use termcolor::StandardStream;
22///
23/// struct RenderDuration(Duration);
24///
25/// impl Render for RenderDuration {
26///     fn render(self, into: Document) -> Document {
27///         into.add(format!("{} seconds and {} nanos", self.0.as_secs(), self.0.subsec_nanos()))
28///     }
29/// }
30///
31/// struct MessageContents {
32///     code: usize,
33///     header: String,
34///     body: String,
35///     duration: Duration,
36/// }
37///
38/// fn message(args: MessageContents, into: Document) -> Document {
39///     into.render(tree! {
40///         <Line as {
41///             {args.code} ":" {args.header} "for" {RenderDuration(args.duration)}
42///         }>
43///
44///         <Line as {
45///             {args.body}
46///         }>
47///     })
48/// }
49///
50/// fn main() -> std::io::Result<()> {
51///     let contents = MessageContents {
52///         code: 200,
53///         header: "Hello world".to_string(),
54///         body: "This is the body of the message".to_string(),
55///         duration: Duration::new(100, 1_000_000)
56///     };
57///
58///     let document = tree! { <message args={contents}> };
59///
60///     document.write()
61/// }
62/// ```
63pub trait Render: Sized {
64    /// Produce a new Document with `self` added to the `into` Document.
65    fn render(self, into: Document) -> Document;
66
67    fn into_fragment(self) -> Document {
68        self.render(Document::empty())
69    }
70
71    fn add<Right: Render>(self, other: Right) -> Combine<Self, Right> {
72        Combine {
73            left: self,
74            right: other,
75        }
76    }
77}
78
79pub struct Combine<Left: Render, Right: Render> {
80    pub(crate) left: Left,
81    pub(crate) right: Right,
82}
83
84impl<Left: Render, Right: Render> Render for Combine<Left, Right> {
85    fn render(self, into: Document) -> Document {
86        into.add(self.left).add(self.right)
87    }
88}
89
90/// A node is rendered by adding itself to the document
91impl Render for Node {
92    fn render(self, document: Document) -> Document {
93        document.add_node(self)
94    }
95}
96
97/// A Document is rendered by extending its nodes onto the original
98/// document.
99impl Render for Document {
100    fn render(self, into: Document) -> Document {
101        into.extend(self)
102    }
103}
104
105// /// An Option<impl Render> is rendered by doing nothing if None or
106// /// rendering the inner value if Some.
107// impl<T> Render for Option<T>
108// where
109//     T: Render,
110// {
111//     fn render(self, document: Document) -> Document {
112//         match self {
113//             None => document,
114//             Some(item) => item.render(document),
115//         }
116//     }
117// }
118
119struct IfSome<'item, T: 'item, R: Render, F: Fn(&T) -> R + 'item> {
120    option: &'item Option<T>,
121    callback: F,
122}
123
124impl<'item, T, R, F> Render for IfSome<'item, T, R, F>
125where
126    T: 'item,
127    R: Render,
128    F: Fn(&T) -> R,
129{
130    fn render(self, mut into: Document) -> Document {
131        if let Some(inner) = self.option {
132            into = into.add((self.callback)(inner));
133        }
134
135        into
136    }
137}
138
139#[allow(non_snake_case)]
140pub fn IfSome<'item, T: 'item, R: Render + 'item>(
141    option: &'item Option<T>,
142    callback: impl Fn(&T) -> R + 'item,
143) -> impl Render + 'item {
144    IfSome { option, callback }
145}
146
147struct SomeValue<'item, T: 'item> {
148    option: &'item Option<T>,
149}
150
151impl<'item, T> Render for SomeValue<'item, T>
152where
153    T: Render + Clone + 'item,
154{
155    fn render(self, mut into: Document) -> Document {
156        if let Some(inner) = self.option {
157            into = inner.clone().render(into)
158        }
159
160        into
161    }
162}
163
164#[allow(non_snake_case)]
165pub fn SomeValue<'item, R: Render + Clone>(option: &'item Option<R>) -> impl Render + 'item {
166    SomeValue { option }
167}
168
169pub struct Empty;
170
171impl Render for Empty {
172    fn render(self, document: Document) -> Document {
173        document
174    }
175}
176
177impl<T: ::std::fmt::Display> Render for T {
178    fn render(self, document: Document) -> Document {
179        document.add(Node::Text(self.to_string()))
180    }
181}