hiccup/
lib.rs

1/// # `hiccup!`: 
2/// * The main objective of this lib is to prevent unclosed html tags.
3/// This macro is inspired by Clojures [hiccup](https://github.com/weavejester/hiccup)
4/// 
5/// ## Basic usage: 
6/// 
7/// The macro `hiccup! receives a mutable string as the first argument and mutates the string to emit the HTML.
8/// The order of the elements is: 
9/// 1. `tag` as the first element.
10/// 2. Optional attribute inside the tag should follow the tag name as `{attribute1=>"value1 value2 ... valuen", attr=>"value"}`. Also, the attributes should be inside `{...}` and separate each key value pair by `,`.
11/// The element should be written as `key=>"value"`, where key is a symbol, followed by an arrow (`=>`), and then the value as a string `"value"`.
12/// 3. After (Optional) the tag name or the attributes `{...}` you could include `[...]` that can have other tags, such as `p["text"]` or regular string values.
13/// 4. Inside the `[...]` you also can substitute your string for some simple rust code inside a `(...)`. This can bem something like `p[format!("{:?}", 3 + 4)]` or `div[(x)]` where x was defined in the outside. 
14/// 
15/// ### Differences between Clojure and Rust Hiccup: 
16/// * [Clojure](https://github.com/weavejester/hiccup/wiki/Syntax): `[:a {:href "http://github.com"} "GitHub"]`
17/// * Rust: `a{href=>"http://github.com"}["GitHub"]`
18/// 
19/// ### Syntax with code inside: 
20/// * `a{href=>"http://github.com"}[(format!("{:?}", 3 + 5))]`
21/// * `p[(x)]`, where `x` can be another `html` or simply a value;
22/// 
23/// ## Example
24/// 
25/// ### Basic syntax
26/// ```rust
27/// use hiccup::hiccup;
28///
29/// fn main() {
30///     let mut html = String::new();
31///
32///     let _ = hiccup!(&mut html,
33///         html[
34///             head[meta{name=>"author", content=>"Julia Naomi"}
35///                 title["Hiccup guide"]]
36///             body{class=>"amazing hiccup guide"}[
37///                 h1{font=>"bold", color=>"red"}["Hiccup is the best!"]
38///                 p["please lookup clojure's hiccup for better ideas on this macro"]]
39///         ]);
40///
41///     assert_eq!(html,"<html><head><meta name=\"author\" content=\"Julia Naomi\"/>\
42///     <title>Hiccup guide</title></head><body class=\"amazing hiccup guide\">\
43///     <h1 font=\"bold\" color=\"red\">Hiccup is the best!</h1>\
44///     <p>please lookup clojure\'s hiccup for better ideas on this macro</p></body></html>");
45/// }
46/// ```
47/// 
48/// ### With remote code execution
49/// ```rust
50/// use hiccup::hiccup;
51///
52/// fn main() {
53///     let mut html_inner = String::new();
54///     let mut html_outer = String::new();
55///     let x = "inner my str";
56///     let y = "my str2";
57///     let z = "my str3";
58///
59///     let _ = hiccup!(&mut html_inner,
60///         div[
61///             div{hello=>"inner world"}[(x)]
62///         ]
63///     );
64///
65///     let _ = hiccup!(&mut html_outer,
66///         html[
67///             body{class=>"amazing hiccup guide"}[
68///                 p["please lookup clojure's hiccup for better ideas on this macro"]
69///                 div[
70///                     div{hello=>"world"}[(html_inner)]
71///                     div[(y.to_owned() + " " + z)]
72///                     p["bye"]
73///                 ]
74///             ]
75///         ]);
76///
77///     assert_eq!(html_outer,"<html><body class=\"amazing hiccup guide\">\
78///     <p>please lookup clojure\'s hiccup for better ideas on this macro</p>\
79///     <div><div hello=\"world\"><div><div hello=\"inner world\">inner my str</div></div></div>\
80///     <div>my str2 my str3</div><p>bye</p></div></body></html>");
81/// }
82/// ```
83/// ## FAQs
84/// 1. Is it possible tu use this lib as an XML templating?
85/// > Yes, I added a more generic XML case to the tests recently
86/// 
87/// ```rust
88/// use hiccup::hiccup;
89///
90/// fn main() {
91///     let mut out = String::new();
92///
93///     let _ = hiccup!(&mut out,
94///         xml{metas=>"I forgot them all", version=>"any version"}[
95///             family[name{name=>"Rubiechiov", origin=>"Kazakhstan"}]
96///             members{class=>"close family"}[
97///                 member{age=>"oldest", color=>"yellow"}["some name"]
98///                 member{age=>"mid-age", color=>"yellow"}["some other name"]
99///                 member{age=>"yougest", color=>"brown"}["Julia"]]
100///         ]);
101///
102///     assert_eq!(out,"<xml metas=\"I forgot them all\" version=\"any version\"><family>\
103///     <name name=\"Rubiechiov\" origin=\"Kazakhstan\"/></family><members class=\"close family\">\
104///     <member age=\"oldest\" color=\"yellow\">some name</member>\
105///     <member age=\"mid-age\" color=\"yellow\">some other name</member>\
106///     <member age=\"yougest\" color=\"brown\">Julia</member></members></xml>");
107/// }
108/// ```
109/// 
110#[macro_export]
111macro_rules! hiccup {
112    ($w:expr, ) => (());
113
114    ($w:expr, $e:tt) => {{
115        use std::fmt::Write;
116        let _ = write!($w, "{}", $e);
117    }};
118
119    ($w:expr, $tag:ident {$($key:expr => $value:expr),*}[($($code:block)*) $($inner:tt)*] $($rest:tt)*) => {{
120        use std::fmt::Write;
121        
122        let _ = write!($w, "<{}", stringify!($tag));
123        $(
124            let _ = write!($w, " {}=", stringify!($key));
125            let _ = write!($w, "{}", stringify!($value));
126        )*
127        let _ = write!($w, ">");
128        $($code)*
129        hiccup!($w, $($inner)*);
130        let _ = write!($w, "</{}>", stringify!($tag));
131        hiccup!($w, $($rest)*);
132    }};
133
134    ($w:expr, $tag:ident {$($key:expr => $value:expr),*}[$($inner:tt)*] $($rest:tt)*) => {{
135        use std::fmt::Write;
136        
137        let _ = write!($w, "<{}", stringify!($tag));
138        $(
139            let _ = write!($w, " {}=", stringify!($key));
140            let _ = write!($w, "{}", stringify!($value));
141        )*
142        let _ = write!($w, ">");
143
144        hiccup!($w, $($inner)*);
145        let _ = write!($w, "</{}>", stringify!($tag));
146        hiccup!($w, $($rest)*);
147    }};
148
149    ($w:expr, $tag:ident [($($code:block)*) $($inner:tt)*] $($rest:tt)*) => {{
150        use std::fmt::Write;
151        
152        let _ = write!($w, "<{}>", stringify!($tag));
153        $($code)*
154        hiccup!($w, $($inner)*);
155        let _ = write!($w, "</{}>", stringify!($tag));
156        hiccup!($w, $($rest)*);
157    }};
158
159    ($w:expr, $tag:ident {$($key:expr => $value:expr),*} $($rest:tt)*) => {{
160        use std::fmt::Write;
161        
162        let _ = write!($w, "<{}", stringify!($tag));
163        $(
164            let _ = write!($w, " {}=", stringify!($key));
165            let _ = write!($w, "{}", stringify!($value));
166        )*
167        let _ = write!($w, "/>");
168        hiccup!($w, $($rest)*);
169    }};
170
171    ($w:expr, $tag:ident [$($inner:tt)*] $($rest:tt)*) => {{
172        use std::fmt::Write;
173        
174        let _ = write!($w, "<{}>", stringify!($tag));
175        hiccup!($w, $($inner)*);
176        let _ = write!($w, "</{}>", stringify!($tag));
177        hiccup!($w, $($rest)*);
178    }};
179
180    ($w:expr, $tag:ident [($($code:block)*)] $($rest:tt)*) => {{
181        use std::fmt::Write;
182        
183        let _ = write!($w, "<{}>", stringify!($tag));
184        $($code)*
185        let _ = write!($w, "</{}>", stringify!($tag));
186        hiccup!($w, $($rest)*);
187    }};
188}
189
190#[cfg(test)]
191mod tests {
192    #[test]
193    fn basic_html() {
194        let mut out = String::new();
195
196        let _ = hiccup!(&mut out,
197            html[
198                head[title["Hiccup guide"]]
199                body[h1["Hiccup is the best!"]]
200            ]);
201
202            assert_eq!(out, "<html><head><title>Hiccup guide</title></head>\
203            <body><h1>Hiccup is the best!</h1></body></html>");
204    }
205
206    #[test]
207    fn attr_block() {
208        let mut out = String::new();
209
210        let _ = hiccup!(&mut out,
211            html[
212                head[title["Hiccup guide"]]
213                body[h1{class=>"value", c=>"v"}["Hiccup is the best!"]]
214            ]);
215
216        assert_eq!(out, "<html><head><title>Hiccup guide</title></head><body>\
217        <h1 class=\"value\" c=\"v\">Hiccup is the best!</h1></body></html>");
218    }
219}