1 // Written in the D Programming Language 2 3 // embd 4 // Copyright (C) 2013 Nathan M. Swan 5 // Available under the MIT (Expat) License 6 7 /++ 8 + Low-level API for embedding D code into text. 9 + 10 + Copyright: Copyright © 2013 Nathan M. Swan 11 + License: MIT (Expat) License 12 + Authors: Nathan M. Swan 13 +/ 14 module embd; 15 16 import std.algorithm; 17 import std.array; 18 import std.conv; 19 import std.exception; 20 import std.string; 21 import std.utf; 22 23 public: 24 /// The object which manages the rendering of an embd template. 25 /// 26 /// To use, create a subclass, manually implement write, and 27 /// automatically implement render by mixin(renderer). 28 /// 29 /// This allows you to access fields and other methods of your subclass 30 /// from the template (which is generated to be within the body of render). 31 interface Context { 32 33 /// Write the content to whatever you are writing to. 34 /// 35 /// Params: 36 /// content = the text to write 37 /// evalCode = what evaluation character occured after the start delimiter, 38 /// dchar.init if static content. 39 /// 40 /// Example: 41 /// --- 42 /// content -> write("content", dchar.init); 43 /// <%= expr() %> -> write(expr(), '='); 44 /// --- 45 void write(string content, dchar evalCode); 46 47 /// Renders the template. Don't implement this manually, instead 48 /// mixin(renderer) in your subclass. 49 /// 50 /// Params: 51 /// embd_code = the embd template 52 /// embd_evalCodes = the allowed evaluation codes, passed to write 53 /// to signal how to postprocess the dynamic content 54 /// (e.g. whether to html escape or not) 55 /// embd_start = the delimeter signalling the start of embedded code 56 /// embd_end = the delimeter signalling the end of embedded code 57 void render(string embd_code, 58 const(dchar)[] embd_evalCodes, 59 string embd_start, string embd_end)(); 60 61 /// The render implementation to mixin to your subclass. 62 enum renderer = q{ 63 public void render(string embd_code, 64 const(dchar)[] embd_evalCodes=`=`, 65 string embd_start=`<%`, string embd_end=`%>` 66 )() { 67 mixin(embd__createRenderingCode(embd_code, embd_start, embd_end, embd_evalCodes)); 68 } 69 }; 70 } 71 72 73 version (Have_vibe_d) 74 { 75 import vibe.core.stream; 76 import vibe.templ.utils; 77 import vibe.textfilter.html; 78 79 /** Renders an EMBD template to an output stream. 80 81 These functions provice means to render EMPD templates in a way similar 82 to the render!() function of vibe.d for rendering Diet templates. 83 84 Note that these functions are only available if "vibe-d" is available 85 as a dependency or if a "Have_vibe_d" version identifier is specified 86 manually. 87 88 Examples: 89 90 --- 91 string caption = "Hello, World!"; 92 //res.renderEmbd!("test.embd", caption)(); 93 res.bodyWriter.renderEmbdCompat!("test.embd", string, "caption")(caption); 94 --- 95 */ 96 void renderEmbd(string FILE, ALIASES...)(OutputStream dst) 97 { 98 99 mixin(vibe.templ.utils.localAliases!(0, ALIASES)); 100 101 class LocalContext : Context { 102 OutputStream output__; 103 104 mixin(renderer); 105 106 void write(string content, dchar eval_code) 107 { 108 if (eval_code == '=') 109 filterHtmlEscape(output__, content); 110 else 111 output__.write(content, false); 112 } 113 } 114 115 scope ctx = new LocalContext; 116 ctx.output__ = dst; 117 ctx.render!(import(FILE), `!=`, `<%`, `%>`)(); 118 } 119 120 /// ditto 121 void renderEmbd(string FILE, ALIASES...)(HTTPServerResponse res, string content_type = "text/html; charset=UTF-8") 122 { 123 res.contentType = content_type; 124 renderEmbd!(FILE, ALIASES)(res.bodyWriter); 125 } 126 127 /// ditto 128 void renderEmbdCompat(string FILE, TYPES_AND_NAMES...)(OutputStream dst, ...) 129 { 130 import core.vararg; 131 import std.variant; 132 mixin(localAliasesCompat!(0, TYPES_AND_NAMES)); 133 134 class LocalContext : Context { 135 OutputStream output__; 136 137 mixin(renderer); 138 139 void write(string content, dchar eval_code) 140 { 141 if (eval_code == '=') 142 filterHtmlEscape(output__, content); 143 else 144 output__.write(content, false); 145 } 146 } 147 148 scope ctx = new LocalContext; 149 ctx.output__ = dst; 150 ctx.render!(import(FILE), `!=`, `<%`, `%>`)(); 151 } 152 } 153 154 155 private: 156 unittest { 157 static class MyContext : embd.Context { 158 uint until = 20; 159 160 void write(string content, dchar evalCode) { 161 import std.stdio; 162 write(content); 163 } 164 165 mixin(renderer); 166 } 167 168 auto ctx = new MyContext(); 169 ctx.render!(import("test.embd.html"), `=`)(); 170 } 171 172 // public so it can be accessed from the mixin 173 public string embd__createRenderingCode(string embd_code, 174 string embd_start, string embd_end, 175 const(dchar)[] embd_evalCodes) { 176 // convert to dstring for slicing 177 dstring inCode = embd_code.to!dstring(); 178 dstring startDelim = embd_start.to!dstring(); 179 dstring endDelim = embd_end.to!dstring(); 180 string outCode = ""; 181 182 // two states: static content and dynamic content 183 dstring staticBuffer = ""; 184 while (!inCode.empty) { 185 if (inCode.startsWith(startDelim)) { 186 outCode ~= `write(`~generateQuotesFor(staticBuffer)~`, dchar.init);`; 187 staticBuffer = ""; 188 outCode ~= getDynamicContent(inCode, startDelim, endDelim, embd_evalCodes); 189 } else { 190 staticBuffer ~= inCode.front; 191 inCode.popFront(); 192 } 193 } 194 if (staticBuffer.length) { 195 outCode ~= `write(`~generateQuotesFor(staticBuffer)~`, dchar.init);`; 196 } 197 198 return outCode.to!string(); 199 } 200 201 string getDynamicContent(ref dstring inCode, 202 dstring startDelim, dstring endDelim, 203 const(dchar)[] evalCodes) { 204 // TODO allow endDelim to appear in strings 205 void notEmpty() { 206 enforce(!inCode.empty, 207 format("Starting '%s' not matched by closing '%s'.", startDelim, endDelim)); 208 } 209 210 inCode = inCode[startDelim.length .. $]; 211 notEmpty(); 212 dchar evalCode = dchar.init; 213 if (evalCodes.canFind(inCode.front)) { 214 evalCode = inCode.front; 215 inCode.popFront(); 216 } 217 string outCode = ""; 218 while (true) { 219 notEmpty(); 220 if (inCode.startsWith(endDelim)) { 221 inCode = inCode[endDelim.length .. $]; 222 break; 223 } else { 224 outCode ~= inCode.front.to!string(); 225 inCode.popFront(); 226 } 227 } 228 229 if (evalCode == dchar.init) { 230 return outCode; 231 } else { 232 return format(`write(%s, '\u%.4x');`, outCode, evalCode); 233 } 234 } 235 236 unittest { 237 assert(generateQuotesFor(` `) == `x"20"c`); 238 assert(generateQuotesFor(`ẍ`) == `x"e1ba8d"c`); 239 } 240 241 string generateQuotesFor(dstring buffer) { 242 // convert to ubyte so it doesn't convert back into a range of dchars 243 return format(`x"%-(%.2x%)"c`, cast(immutable(ubyte)[])buffer.to!string()); 244 }