render_tree/
render.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use super::{Document, Node};

/// The Render trait defines a type that can be added to a Document.
/// It is defined for `Node`, `String`, `&str`, and `Document`.alloc
///
/// It is also defined for `Option<T>` where `T` is `Render`, as well
/// as `&T` where `T` is both `Render` and `Clone`.
///
/// Generally speaking, if you need to make a type `Render`, and it's
/// not one of your types, you can ergonomically make a newtype wrapper
/// for it.
///
/// For example, if you want to render `std::time::Duration`:
///
/// ```
/// #[macro_use]
/// extern crate render_tree;
/// extern crate termcolor;
/// use render_tree::{Render, Document, Line, RenderComponent};
/// use std::time::Duration;
/// use termcolor::StandardStream;
///
/// struct RenderDuration(Duration);
///
/// impl Render for RenderDuration {
///     fn render(self, into: Document) -> Document {
///         into.add(format!("{} seconds and {} nanos", self.0.as_secs(), self.0.subsec_nanos()))
///     }
/// }
///
/// struct MessageContents {
///     code: usize,
///     header: String,
///     body: String,
///     duration: Duration,
/// }
///
/// fn message(args: MessageContents, into: Document) -> Document {
///     into.render(tree! {
///         <Line as {
///             {args.code} ":" {args.header} "for" {RenderDuration(args.duration)}
///         }>
///
///         <Line as {
///             {args.body}
///         }>
///     })
/// }
///
/// fn main() -> std::io::Result<()> {
///     let contents = MessageContents {
///         code: 200,
///         header: "Hello world".to_string(),
///         body: "This is the body of the message".to_string(),
///         duration: Duration::new(100, 1_000_000)
///     };
///
///     let document = tree! { <message args={contents}> };
///
///     document.write()
/// }
/// ```
pub trait Render: Sized {
    /// Produce a new Document with `self` added to the `into` Document.
    fn render(self, into: Document) -> Document;

    fn into_fragment(self) -> Document {
        self.render(Document::empty())
    }

    fn add<Right: Render>(self, other: Right) -> Combine<Self, Right> {
        Combine {
            left: self,
            right: other,
        }
    }
}

pub struct Combine<Left: Render, Right: Render> {
    pub(crate) left: Left,
    pub(crate) right: Right,
}

impl<Left: Render, Right: Render> Render for Combine<Left, Right> {
    fn render(self, into: Document) -> Document {
        into.add(self.left).add(self.right)
    }
}

/// A node is rendered by adding itself to the document
impl Render for Node {
    fn render(self, document: Document) -> Document {
        document.add_node(self)
    }
}

/// A Document is rendered by extending its nodes onto the original
/// document.
impl Render for Document {
    fn render(self, into: Document) -> Document {
        into.extend(self)
    }
}

// /// An Option<impl Render> is rendered by doing nothing if None or
// /// rendering the inner value if Some.
// impl<T> Render for Option<T>
// where
//     T: Render,
// {
//     fn render(self, document: Document) -> Document {
//         match self {
//             None => document,
//             Some(item) => item.render(document),
//         }
//     }
// }

struct IfSome<'item, T: 'item, R: Render, F: Fn(&T) -> R + 'item> {
    option: &'item Option<T>,
    callback: F,
}

impl<'item, T, R, F> Render for IfSome<'item, T, R, F>
where
    T: 'item,
    R: Render,
    F: Fn(&T) -> R,
{
    fn render(self, mut into: Document) -> Document {
        if let Some(inner) = self.option {
            into = into.add((self.callback)(inner));
        }

        into
    }
}

#[allow(non_snake_case)]
pub fn IfSome<'item, T: 'item, R: Render + 'item>(
    option: &'item Option<T>,
    callback: impl Fn(&T) -> R + 'item,
) -> impl Render + 'item {
    IfSome { option, callback }
}

struct SomeValue<'item, T: 'item> {
    option: &'item Option<T>,
}

impl<'item, T> Render for SomeValue<'item, T>
where
    T: Render + Clone + 'item,
{
    fn render(self, mut into: Document) -> Document {
        if let Some(inner) = self.option {
            into = inner.clone().render(into)
        }

        into
    }
}

#[allow(non_snake_case)]
pub fn SomeValue<'item, R: Render + Clone>(option: &'item Option<R>) -> impl Render + 'item {
    SomeValue { option }
}

pub struct Empty;

impl Render for Empty {
    fn render(self, document: Document) -> Document {
        document
    }
}

impl<T: ::std::fmt::Display> Render for T {
    fn render(self, document: Document) -> Document {
        document.add(Node::Text(self.to_string()))
    }
}