1 /// Wrappers allow defining custom HTML/XML structures that take children as content.
2 ///
3 /// This module builds upon [elemi.generator](elemi.generator.html).
4 ///
5 /// $(B [#examples|See examples] for usage instructions!)
6 ///
7 /// History: $(LIST
8 ///     * Introduced in Elemi 1.4.0
9 /// )
10 module elemi.wrapper;
11 
12 /// Wrappers make it possible to place children inside chunks of premade HTML or XML code.
13 /// A wrapper is given a function — `content` in this example — which writes user-given code
14 /// to the output stream.
15 @safe unittest {
16     import elemi;
17 
18     auto text = buildHTML() ~ (html) {
19 
20         // Wrapper: <span></span><div>CONTENT</div>
21         // Prepends an empty span node, and wraps the content in a div.
22         auto divWrapper = buildWrapper() ~ (content) {
23             html.span ~ { },
24             html.div ~ {
25                 content();
26             };
27         };
28 
29         // Place text content inside the wrapper
30         divWrapper ~ {
31             html ~ "First wrapper";
32         };
33         divWrapper ~ {
34             html ~ "Second wrapper";
35         };
36     };
37     assert(text == "<span></span><div>First wrapper</div><span></span><div>Second wrapper</div>");
38 
39 }
40 
41 /// Wrappers are especially useful when created in a function.
42 @safe unittest {
43     import elemi;
44 
45     Wrapper section(HTML html, string title) {
46         return buildWrapper() ~ (content) {
47             html.div ~ {
48                 html.h1 ~ title;
49                 content();
50             };
51         };
52     }
53 
54     auto text = buildHTML() ~ (html) {
55         section(html, "Title") ~ {
56             html.p ~ "Content";
57         };
58     };
59 
60     assert(text == "<div><h1>Title</h1><p>Content</p></div>");
61 
62 }
63 
64 import elemi.generator;
65 
66 static if (__traits(compiles, { import core.attribute : mustuse; })) {
67     import core.attribute : mustuse;
68 }
69 else {
70     import std.meta : AliasSeq;
71     alias mustuse = AliasSeq!();
72 }
73 
74 /// Prepare a wrapper. Connect with a one parameter function: `buildWrapper ~ (content) { }`.
75 /// The argument given, `content`, is a function, which can be called to write user-provided
76 /// content.
77 ///
78 WrapperBuilder buildWrapper() @safe {
79     return WrapperBuilder();
80 }
81 
82 /// A wrapper builder. Create using `buildWrapper`.
83 ///
84 /// See [module documentation for examples](elemi.wrapper.html#examples).
85 @mustuse
86 struct WrapperBuilder {
87 
88     Wrapper opBinary(string op : "~")(void delegate(Wrapper.Generator) @safe build) @safe {
89         return Wrapper(build);
90     }
91 
92     SystemWrapper opBinary(string op : "~")(void delegate(Wrapper.Generator) build) @safe {
93         return SystemWrapper(build);
94     }
95 
96 }
97 
98 /// A wrapper element that can be used in `@safe` code.
99 @mustuse
100 struct Wrapper {
101 
102     alias Generator = void delegate() @safe;
103 
104     /// Function generating wrapper code.
105     void delegate(Generator) @safe generator;
106 
107     /// Place XML/HTML code in this wrapper, and write it to the output stream.
108     /// Params:
109     ///     build = Function to write content inside the wrapper.
110     void opBinary(string op : "~")(void delegate() @safe build) @safe {
111         generator(build);
112     }
113 
114     /// ditto
115     void opBinary(string op : "~")(void delegate() @system build) @system {
116         Generator trustedBuild = () @trusted => build();
117         generator(trustedBuild);
118     }
119 
120 }
121 
122 /// A wrapper element that can only be used in `@system` code.
123 @mustuse
124 struct SystemWrapper {
125 
126     alias Generator = Wrapper.Generator;
127 
128     /// Function generating wrapper code.
129     void delegate(Generator) generator;
130 
131     /// Place XML/HTML code in this wrapper, and write it to the output stream.
132     /// Params:
133     ///     build = Function to write content inside the wrapper.
134     void opBinary(string op : "~")(void delegate() @system build) @system {
135         Generator trustedBuild = () @trusted => build();
136         generator(trustedBuild);
137     }
138 
139 }
140 
141 @("Wrappers can be used with @system")
142 @system unittest {
143     import elemi.html;
144 
145     auto safeWrapper(HTML html) {
146         return buildWrapper() ~ (content) @safe {
147             html.h1 ~ { };
148             content();
149         };
150     }
151 
152     auto content = buildHTML() ~ (html) @system {
153         safeWrapper(html) ~ {
154             html.h2 ~ { };
155         };
156     };
157 
158     assert(content == "<h1></h1><h2></h2>");
159 }
160 
161 @("Wrappers themselves can be system")
162 @system unittest {
163     import elemi.html;
164 
165     auto systemWrapper(HTML html) {
166         return buildWrapper() ~ (content) @system {
167             html.h1 ~ { };
168             content();
169         };
170     }
171 
172     auto content = buildHTML() ~ (html) {
173         systemWrapper(html) ~ () @safe {
174             html.h2 ~ { };
175         };
176     };
177 
178     assert(content == "<h1></h1><h2></h2>");
179 }