render_tree/
component.rs

1use crate::{Document, Render};
2
3pub trait BlockComponent: Sized {
4    fn with<F: FnOnce(Document) -> Document>(
5        component: Self,
6        block: F,
7    ) -> CurriedBlockComponent<Self, F> {
8        CurriedBlockComponent { component, block }
9    }
10
11    fn append(self, block: impl FnOnce(Document) -> Document, document: Document) -> Document;
12}
13
14pub struct CurriedBlockComponent<B: BlockComponent, Block: FnOnce(Document) -> Document> {
15    component: B,
16    block: Block,
17}
18
19impl<B: BlockComponent, Block: FnOnce(Document) -> Document> Render
20    for CurriedBlockComponent<B, Block>
21{
22    fn render(self, document: Document) -> Document {
23        (self.component).append(self.block, document)
24    }
25}
26
27// IterBlockComponent //
28
29pub trait IterBlockComponent: Sized {
30    type Item;
31
32    fn with<F: FnMut(Self::Item, Document) -> Document>(
33        component: Self,
34        block: F,
35    ) -> CurriedIterBlockComponent<Self, F> {
36        CurriedIterBlockComponent { component, block }
37    }
38
39    fn append(
40        self,
41        block: impl FnMut(Self::Item, Document) -> Document,
42        document: Document,
43    ) -> Document;
44}
45
46pub struct CurriedIterBlockComponent<
47    B: IterBlockComponent,
48    Block: FnMut(B::Item, Document) -> Document,
49> {
50    component: B,
51    block: Block,
52}
53
54impl<B: IterBlockComponent, Block: FnMut(B::Item, Document) -> Document> Render
55    for CurriedIterBlockComponent<B, Block>
56{
57    fn render(self, document: Document) -> Document {
58        (self.component).append(self.block, document)
59    }
60}
61
62// OnceBlockComponent //
63
64pub trait OnceBlockComponent: Sized {
65    type Item;
66
67    fn with<F: FnOnce(Self::Item, Document) -> Document>(
68        component: Self,
69        block: F,
70    ) -> CurriedOnceBlockComponent<Self, F> {
71        CurriedOnceBlockComponent { component, block }
72    }
73
74    fn append(
75        self,
76        block: impl FnOnce(Self::Item, Document) -> Document,
77        document: Document,
78    ) -> Document;
79}
80
81pub struct CurriedOnceBlockComponent<
82    B: OnceBlockComponent,
83    Block: FnOnce(B::Item, Document) -> Document,
84> {
85    component: B,
86    block: Block,
87}
88
89impl<B: OnceBlockComponent, Block: FnOnce(B::Item, Document) -> Document> Render
90    for CurriedOnceBlockComponent<B, Block>
91{
92    fn render(self, document: Document) -> Document {
93        (self.component).append(self.block, document)
94    }
95}
96
97// InlineComponent //
98
99struct CurriedInlineComponent<T> {
100    function: fn(T, Document) -> Document,
101    data: T,
102}
103
104impl<T> Render for CurriedInlineComponent<T> {
105    fn render(self, document: Document) -> Document {
106        (self.function)(self.data, document)
107    }
108}
109
110#[allow(non_snake_case)]
111pub fn Component<T>(function: fn(T, Document) -> Document, data: T) -> impl Render {
112    CurriedInlineComponent { function, data }
113}
114
115/// This trait defines a renderable entity with arguments. Types that implement
116/// `RenderComponent` can be packaged up together with their arguments in a
117/// `Component`, and the `Component` is renderable.
118///
119/// # Example
120///
121/// ```
122/// #[macro_use]
123/// extern crate render_tree;
124/// extern crate termcolor;
125/// use render_tree::{Document, Line, Render, RenderComponent};
126/// use termcolor::StandardStream;
127///
128/// struct MessageContents {
129///     code: usize,
130///     header: String,
131///     body: String,
132/// }
133///
134/// fn Message(args: MessageContents, into: Document) -> Document {
135///     into.add(tree! {
136///         <Line as {
137///             {args.code} ":" {args.header}
138///         }>
139///
140///         <Line as {
141///             {args.body}
142///         }>
143///     })
144/// }
145///
146/// fn main() -> std::io::Result<()> {
147///     let message = MessageContents {
148///         code: 200,
149///         header: "Hello world".to_string(),
150///         body: "This is the body of the message".to_string()
151///     };
152///
153///     let document = tree! { <Message args={message}> };
154///
155///     document.write()
156/// }
157/// ```
158pub trait RenderComponent<'args> {
159    type Args;
160
161    fn render(&self, args: Self::Args, into: Document) -> Document;
162}
163
164pub struct OnceBlock<F: FnOnce(Document) -> Document>(pub F);
165
166impl<F> Render for OnceBlock<F>
167where
168    F: FnOnce(Document) -> Document,
169{
170    fn render(self, into: Document) -> Document {
171        (self.0)(into)
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use crate::component::*;
178
179    #[test]
180    fn test_inline_component() -> ::std::io::Result<()> {
181        struct Header {
182            code: usize,
183            message: &'static str,
184        }
185
186        impl Render for Header {
187            fn render(self, document: Document) -> Document {
188                document.add(tree! {
189                    {self.code} {": "} {self.message}
190                })
191            }
192        }
193
194        let code = 1;
195        let message = "Something went wrong";
196
197        let document = tree! {
198            <Header code={code} message={message}>
199        };
200
201        assert_eq!(document.to_string()?, "1: Something went wrong");
202
203        Ok(())
204    }
205
206    #[test]
207    fn test_block_component() -> ::std::io::Result<()> {
208        struct Message {
209            code: usize,
210            message: &'static str,
211            trailing: &'static str,
212        }
213
214        impl BlockComponent for Message {
215            fn append(
216                self,
217                block: impl FnOnce(Document) -> Document,
218                mut document: Document,
219            ) -> Document {
220                document = document.add(tree! {
221                    {self.code} {": "} {self.message} {" "}
222                });
223
224                document = block(document);
225
226                document = document.add(tree! {
227                    {self.trailing}
228                });
229
230                document
231            }
232        }
233
234        let code = 1;
235        let message = "Something went wrong";
236
237        let document = tree! {
238            <Message code={code} message={message} trailing={" -- yikes!"} as {
239                {"!!! It's really quite bad !!!"}
240            }>
241        };
242
243        assert_eq!(
244            document.to_string()?,
245            "1: Something went wrong !!! It's really quite bad !!! -- yikes!"
246        );
247
248        Ok(())
249    }
250
251    #[test]
252    fn test_once_block_component() -> ::std::io::Result<()> {
253        struct Message {
254            code: usize,
255            message: Option<&'static str>,
256            trailing: &'static str,
257        }
258
259        impl OnceBlockComponent for Message {
260            type Item = String;
261
262            fn append(
263                self,
264                block: impl FnOnce(String, crate::Document) -> crate::Document,
265                mut document: crate::Document,
266            ) -> crate::Document {
267                document = document.add(tree! {
268                    {self.code} {": "}
269                });
270
271                if let Some(message) = self.message {
272                    document = block(message.to_string(), document);
273                }
274
275                document = document.add(tree! {
276                    {" "} {self.trailing}
277                });
278
279                document
280            }
281        }
282
283        let code = 1;
284        let message = Some("Something went wrong");
285
286        let document = tree! {
287            <Message code={code} message={message} trailing={"-- yikes!"} as |message| {
288                {message} {" "} {"!!! It's really quite bad !!!"}
289            }>
290        };
291
292        assert_eq!(
293            document.to_string()?,
294            "1: Something went wrong !!! It's really quite bad !!! -- yikes!"
295        );
296
297        Ok(())
298    }
299}