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}