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 }