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(createRenderingCode(embd_code, embd_start, embd_end, embd_evalCodes)); 68 } 69 }; 70 } 71 72 private: 73 unittest { 74 static class MyContext : embd.Context { 75 uint until = 20; 76 77 void write(string content, dchar evalCode) { 78 import std.stdio; 79 write(content); 80 } 81 82 mixin(renderer); 83 } 84 85 auto ctx = new MyContext(); 86 ctx.render!(import("test.embd.html"), `=`)(); 87 } 88 89 string createRenderingCode(string embd_code, 90 string embd_start, string embd_end, 91 const(dchar)[] embd_evalCodes) { 92 // convert to dstring for slicing 93 dstring inCode = embd_code.to!dstring(); 94 dstring startDelim = embd_start.to!dstring(); 95 dstring endDelim = embd_end.to!dstring(); 96 string outCode = ""; 97 98 // two states: static content and dynamic content 99 dstring staticBuffer = ""; 100 while (!inCode.empty) { 101 if (inCode.startsWith(startDelim)) { 102 outCode ~= `write(`~generateQuotesFor(staticBuffer)~`, dchar.init);`; 103 staticBuffer = ""; 104 outCode ~= getDynamicContent(inCode, startDelim, endDelim, embd_evalCodes); 105 } else { 106 staticBuffer ~= inCode.front; 107 inCode.popFront(); 108 } 109 } 110 if (staticBuffer.length) { 111 outCode ~= `write(`~generateQuotesFor(staticBuffer)~`, dchar.init);`; 112 } 113 114 return outCode.to!string(); 115 } 116 117 string getDynamicContent(ref dstring inCode, 118 dstring startDelim, dstring endDelim, 119 const(dchar)[] evalCodes) { 120 // TODO allow endDelim to appear in strings 121 void notEmpty() { 122 enforce(!inCode.empty, 123 xformat("Starting '%s' not matched by closing '%s'.", startDelim, endDelim)); 124 } 125 126 inCode = inCode[startDelim.length .. $]; 127 notEmpty(); 128 dchar evalCode = dchar.init; 129 if (evalCodes.canFind(inCode.front)) { 130 evalCode = inCode.front; 131 inCode.popFront(); 132 } 133 string outCode = ""; 134 while (true) { 135 notEmpty(); 136 if (inCode.startsWith(endDelim)) { 137 inCode = inCode[endDelim.length .. $]; 138 break; 139 } else { 140 outCode ~= inCode.front.to!string(); 141 inCode.popFront(); 142 } 143 } 144 145 if (evalCode == dchar.init) { 146 return outCode; 147 } else { 148 return xformat(`write(%s, '\u%.4x');`, outCode, evalCode); 149 } 150 } 151 152 unittest { 153 assert(generateQuotesFor(` `) == `x"20"c`); 154 assert(generateQuotesFor(`ẍ`) == `x"e1ba8d"c`); 155 } 156 157 string generateQuotesFor(dstring buffer) { 158 // convert to ubyte so it doesn't convert back into a range of dchars 159 return xformat(`x"%-(%.2x%)"c`, cast(immutable(ubyte)[])buffer.to!string()); 160 }