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}