render_tree/helpers.rs
1use crate::component::OnceBlock;
2use crate::{BlockComponent, Document, IterBlockComponent, Node, Render};
3use std::fmt;
4
5/// Creates a `Render` that, when appended into a [`Document`], repeats
6/// a given string a specified number of times.
7pub fn repeat(item: impl fmt::Display, size: usize) -> impl Render {
8 PadItem(item, size)
9}
10
11pub(crate) struct PadItem<T>(pub T, pub usize);
12
13impl<T: fmt::Display> fmt::Display for PadItem<T> {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 for _ in 0..(self.1) {
16 self.0.fmt(f)?;
17 }
18 Ok(())
19 }
20}
21
22/// A list of items that can be appended into a [`Document`]. For each item in
23/// `items`, the callback is invoked, and its return value is appended to
24/// the document.
25///
26/// # Example
27///
28/// ```
29/// # use render_tree::{Document, Each, Line, Render, RenderComponent};
30/// #
31/// # fn main() -> Result<(), ::std::io::Error> {
32/// struct Point(i32, i32);
33///
34/// let items = vec![Point(10, 20), Point(5, 10), Point(6, 42)];
35///
36/// let document = Document::with(Each(
37/// &items,
38/// |item, doc| doc.add(Line("Point(".add(item.0).add(",").add(item.1).add(")")))
39/// ));
40///
41/// assert_eq!(document.to_string()?, "Point(10,20)\nPoint(5,10)\nPoint(6,42)\n");
42/// #
43/// # Ok(())
44/// # }
45/// ```
46///
47/// And with the [`tree!`] macro:
48///
49/// ```
50/// # #[macro_use]
51/// # extern crate render_tree;
52/// # use render_tree::{Document, Each, Line, Render, RenderComponent};
53/// # use render_tree::prelude::*;
54/// #
55/// # fn main() -> Result<(), ::std::io::Error> {
56/// struct Point(i32, i32);
57///
58/// let items = vec![Point(10, 20), Point(5, 10), Point(6, 42)];
59///
60/// let document = tree! {
61/// <Each items={items} as |item| {
62/// <Line as {
63/// "Point(" {item.0} "," {item.1} ")"
64/// }>
65/// }>
66/// };
67///
68/// assert_eq!(document.to_string()?, "Point(10,20)\nPoint(5,10)\nPoint(6,42)\n");
69/// #
70/// # Ok(())
71/// # }
72/// ```
73
74pub struct Each<U, Iterator: IntoIterator<Item = U>> {
75 pub items: Iterator,
76}
77
78impl<U, Iterator: IntoIterator<Item = U>> IterBlockComponent for Each<U, Iterator> {
79 type Item = U;
80
81 fn append(
82 self,
83 mut block: impl FnMut(U, Document) -> Document,
84 mut document: Document,
85 ) -> Document {
86 for item in self.items {
87 document = block(item, document);
88 }
89
90 document
91 }
92}
93
94// impl<'item, U, Iterator> IterBlockHelper for Each<U, Iterator>
95// where
96// Iterator: IntoIterator<Item = U>,
97// {
98// type Args = Iterator;
99// type Item = U;
100
101// fn args(items: Iterator) -> Each<U, Iterator> {
102// Each { items }
103// }
104
105// fn render(
106// self,
107// callback: impl Fn(Self::Item, Document) -> Document,
108// mut into: Document,
109// ) -> Document {
110// for item in self.items {
111// into = callback(item, into);
112// }
113
114// into
115// }
116// }
117
118impl<U, I: IntoIterator<Item = U>> From<I> for Each<U, I> {
119 fn from(from: I) -> Each<U, I> {
120 Each { items: from }
121 }
122}
123
124#[allow(non_snake_case)]
125pub fn Each<U, I: IntoIterator<Item = U>>(
126 items: impl Into<Each<U, I>>,
127 callback: impl Fn(U, Document) -> Document,
128) -> impl Render {
129 IterBlockComponent::with(items.into(), callback)
130}
131
132///
133
134/// A section that can be appended into a document. Sections are invisible, but
135/// can be targeted in stylesheets with selectors using their name.
136pub struct Section {
137 pub name: &'static str,
138}
139
140impl BlockComponent for Section {
141 fn append(self, block: impl FnOnce(Document) -> Document, mut document: Document) -> Document {
142 document = document.add(Node::OpenSection(self.name));
143 document = block(document);
144 document = document.add(Node::CloseSection);
145 document
146 }
147}
148
149#[allow(non_snake_case)]
150pub fn Section(name: &'static str, block: impl FnOnce(Document) -> Document) -> Document {
151 let document = Document::empty();
152 Section { name }.append(block, document)
153}
154
155// impl OnceBlockHelper for Section {
156// type Args = Section;
157// type Item = ();
158
159// fn args(args: Section) -> Section {
160// args
161// }
162
163// fn render(
164// self,
165// callback: impl FnOnce((), Document) -> Document,
166// mut into: Document,
167// ) -> Document {
168// into = into.add_node(Node::OpenSection(self.name));
169// into = callback((), into);
170// into.add_node(Node::CloseSection)
171// }
172// }
173
174// impl From<&'static str> for Section {
175// fn from(from: &'static str) -> Section {
176// Section { name: from }
177// }
178// }
179
180// #[allow(non_snake_case)]
181// pub fn Section(
182// section: impl Into<Section>,
183// block: impl FnOnce(Document) -> Document,
184// mut document: Document,
185// ) -> Document {
186// let section = section.into();
187// document = document.add(Node::OpenSection(section.name));
188// document = block(document);
189// document = document.add(Node::CloseSection);
190// document
191// }
192
193///
194
195/// Equivalent to [`Each()`], but inserts a joiner between two adjacent elements.
196///
197/// # Example
198///
199/// ```
200/// # use render_tree::{Document, Join, Line, Render, RenderComponent};
201/// #
202/// # fn main() -> Result<(), ::std::io::Error> {
203/// struct Point(i32, i32);
204///
205/// let items = vec![Point(10, 20), Point(5, 10), Point(6, 42)];
206///
207/// let document = Document::with(Join(
208/// (&items, ", "),
209/// |item, doc| doc.add("Point(").add(item.0).add(",").add(item.1).add(")")
210/// ));
211///
212/// assert_eq!(document.to_string()?, "Point(10,20), Point(5,10), Point(6,42)");
213///
214/// # Ok(())
215/// # }
216/// ```
217pub struct Join<U, Iterator: IntoIterator<Item = U>> {
218 pub iterator: Iterator,
219 pub joiner: &'static str,
220}
221
222impl<U, I: IntoIterator<Item = U>> From<(I, &'static str)> for Join<U, I> {
223 fn from(from: (I, &'static str)) -> Join<U, I> {
224 Join {
225 iterator: from.0,
226 joiner: from.1,
227 }
228 }
229}
230
231#[allow(non_snake_case)]
232pub fn Join<U, F, Iterator>(join: impl Into<Join<U, Iterator>>, callback: F) -> impl Render
233where
234 F: Fn(U, Document) -> Document,
235 Iterator: IntoIterator<Item = U>,
236{
237 IterBlockComponent::with(join.into(), callback)
238}
239
240impl<'item, U, Iterator> IterBlockComponent for Join<U, Iterator>
241where
242 Iterator: IntoIterator<Item = U>,
243{
244 type Item = U;
245
246 fn append(
247 self,
248 mut block: impl FnMut(Self::Item, Document) -> Document,
249 mut into: Document,
250 ) -> Document {
251 let mut is_first = true;
252
253 for item in self.iterator {
254 if is_first {
255 is_first = false;
256 } else {
257 into = into.add(self.joiner);
258 }
259
260 into = block(item, into);
261 }
262
263 into
264 }
265}
266
267/// Inserts a line into a [`Document`]. The contents are inserted first, followed
268/// by a newline.
269#[allow(non_snake_case)]
270pub fn Line(item: impl Render) -> impl Render {
271 OnceBlock(|document| item.render(document).add_node(Node::Newline))
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::helpers::*;
277
278 #[test]
279 fn test_each() -> ::std::io::Result<()> {
280 struct Point(i32, i32);
281
282 let items = &vec![Point(10, 20), Point(5, 10), Point(6, 42)][..];
283
284 let document = tree! {
285 <Each items={items} as |item| {
286 <Line as {
287 "Point(" {item.0} "," {item.1} ")"
288 }>
289 }>
290 };
291
292 assert_eq!(
293 document.to_string()?,
294 "Point(10,20)\nPoint(5,10)\nPoint(6,42)\n"
295 );
296
297 Ok(())
298 }
299
300 #[test]
301 fn test_join() -> ::std::io::Result<()> {
302 struct Point(i32, i32);
303
304 let items = &vec![Point(10, 20), Point(5, 10), Point(6, 42)][..];
305
306 let document = tree! {
307 <Join iterator={items} joiner={"\n"} as |item| {
308 "Point(" {item.0} "," {item.1} ")"
309 }>
310 };
311
312 assert_eq!(
313 document.to_string()?,
314 "Point(10,20)\nPoint(5,10)\nPoint(6,42)"
315 );
316
317 Ok(())
318 }
319}