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}