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
27pub 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
62pub 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
97struct 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
115pub 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}