Created
August 18, 2019 13:15
-
-
Save WebFreak001/b07136abe6d977de3ccda76f8d1e606b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module diet2spasm; | |
import diet.defs; | |
import diet.dom; | |
import diet.internal.string; | |
import diet.input; | |
import diet.parser; | |
import diet.traits; | |
/// Compiles diet code to spasm compatible createElement/textContent/setAttribute D code usable for mixin. | |
/// Elements with an ID set are accessible via their ID in the resulting code. | |
/// You have to manually append all root nodes into some container if you pass container=null. | |
template compileSpasmDietFile(string filename, string container = null, TRAITS...) | |
{ | |
import diet.internal.string : stripUTF8BOM; | |
private static immutable contents = stripUTF8BOM(import(filename)); | |
enum compileSpasmDietFile = compileSpasmDietFileString!(filename, contents, container, TRAITS); | |
} | |
/// ditto | |
template compileSpasmDietFileString(string filename, alias contents, | |
string container = null, TRAITS...) | |
{ | |
enum _diet_files = collectFiles!(filename, contents); | |
pragma(msg, "Compiling Diet HTML template " ~ filename ~ "..."); | |
private Document _diet_nodes() | |
{ | |
return applyTraits!TRAITS(parseDiet!(translate!TRAITS)(_diet_files)); | |
} | |
enum compileSpasmDietFileString = getSpasmMixin(_diet_nodes(), container); | |
} | |
/// ditto | |
template compileSpasmDietString(string contents, string container = null, TRAITS...) | |
{ | |
enum compileSpasmDietString = compileSpasmDietStrings!(Group!(contents, | |
"diet-string"), container, TRAITS); | |
} | |
/// ditto | |
template compileSpasmDietStrings(alias FILES_GROUP, string container = null, TRAITS...) | |
{ | |
private static Document _diet_nodes() | |
{ | |
return applyTraits!TRAITS(parseDiet!(translate!TRAITS)(filesFromGroup!FILES_GROUP)); | |
} | |
enum compileSpasmDietStrings = getSpasmMixin(_diet_nodes(), container); | |
} | |
@safe: | |
/** Returns a mixin string that generates spasm code for the given DOM tree. | |
Params: | |
nodes = The root nodes of the DOM tree | |
range_name = Optional custom name to use for the output range, defaults | |
to `_diet_output`. | |
Returns: | |
A string of D statements suitable to be mixed in inside of a function. | |
*/ | |
string getSpasmMixin(in Document doc, string parent = null) | |
{ | |
import std.conv : text; | |
string ret; | |
foreach (i, n; doc.nodes) | |
ret ~= getSpasmMixin(doc.nodes[0], text("_node", i), parent); | |
return ret; | |
} | |
private string getSpasmMixin(in Node node, string id, string parent) | |
{ | |
import std.conv : text; | |
switch (node.name) | |
{ | |
default: | |
return getElementMixin(node, id, parent); | |
case Node.SpecialName.text: | |
string ret; | |
foreach (i, c; node.contents) | |
ret ~= getNodeContentsMixin(c, text(id, "_c", i), parent); | |
return ret; | |
case Node.SpecialName.code: | |
return getCodeMixin(node, id, parent); | |
case "doctype": | |
case Node.SpecialName.comment: | |
case Node.SpecialName.hidden: | |
return null; | |
} | |
} | |
private string getNodeContentsMixin(in NodeContent c, string id, string parent) | |
{ | |
final switch (c.kind) with (NodeContent.Kind) | |
{ | |
case node: | |
return getSpasmMixin(c.node, id, parent); | |
case text: | |
return makeStatement(c.loc, `%s.appendChild(document.createTextNode("%s"));`, | |
parent, c.value.dstringEscape); | |
case interpolation: | |
return makeStatement(c.loc, `%s.appendChild(document.createTextNode(%s));`, parent, c.value); | |
case rawInterpolation: | |
return makeStatement(c.loc, `%s.insertAdjacentHTML("beforeend", %s);`, parent, c.value); | |
} | |
} | |
private string getElementMixin(in Node node, string id, string parent) | |
{ | |
import std.conv : text; | |
if (node.id.contents.length == 1 && node.id.contents[0].kind == AttributeContent.Kind.text) | |
id = node.id.contents[0].value; | |
string tagname = node.name.length ? node.name : "div"; | |
string ret; | |
ret ~= makeStatement(node.loc, "auto %s = document.createElement(`%s`);", id, tagname); | |
bool had_class = false; | |
// write attributes | |
foreach (ai, att_; node.attributes) | |
{ | |
auto att = att_.dup; // this sucks... | |
// merge multiple class attributes into one | |
if (att.name == "class") | |
{ | |
if (had_class) | |
continue; | |
had_class = true; | |
foreach (ca; node.attributes[ai + 1 .. $]) | |
{ | |
if (ca.name != "class") | |
continue; | |
if (!ca.contents.length || (()@trusted => (ca.isText && !ca.expectText.length))()) | |
continue; | |
att.addText(" "); | |
att.addContents(ca.contents); | |
} | |
} | |
const is_expr = att.contents.length == 1 | |
&& att.contents[0].kind == AttributeContent.Kind.interpolation; | |
if (is_expr) | |
{ | |
auto expr = att.contents[0].value; | |
if (expr == "true") | |
{ | |
ret ~= makeStatement(node.loc, "%s.setAttribute(`%s`, `%s`);", id, att.name, att.name); | |
continue; | |
} | |
ret ~= makeStatement(node.loc, q{ | |
static if (is(typeof(() { return %s; }()) == bool) ) | |
} ~ '{', expr); | |
ret ~= makeStatement(node.loc, q{if (%s) %s.setAttribute(`%s`, `%s`); }, | |
expr, id, att.name, att.name); | |
ret ~= makeStatement(node.loc, | |
"} else " ~ q{static if (is(typeof(%s) : const(char)[])) } ~ "{{", expr); | |
ret ~= makeStatement(node.loc, q{ auto _diet_val = %s;}, expr); | |
ret ~= makeStatement(node.loc, q{ if (_diet_val !is null) } ~ '{'); | |
ret ~= makeStatement(node.loc, q{ %s.setAttribute(`%s`, _diet_val);}, id, att.name); | |
ret ~= makeStatement(node.loc, " }"); | |
ret ~= makeStatement(node.loc, "}} else {"); | |
} | |
ret ~= makeStatement(node.loc, "%s.setAttribute(`%s`, text(", id, att.name); | |
foreach (i, v; att.contents) | |
{ | |
final switch (v.kind) with (AttributeContent.Kind) | |
{ | |
case text: | |
ret ~= makeStatement(node.loc, `"%s", `, dstringEscape(v.value)); | |
break; | |
case interpolation, rawInterpolation: | |
ret ~= makeStatement(node.loc, `%s, `, v.value); | |
break; | |
} | |
} | |
ret ~= "));\n"; | |
if (is_expr) | |
ret ~= makeStatement(node.loc, "}"); | |
} | |
foreach (i, c; node.contents) | |
ret ~= getNodeContentsMixin(c, text(id, "_c", i), id); | |
if (parent.length) | |
ret ~= makeStatement(node.loc, "%s.appendChild(%s);", parent, id); | |
return ret; | |
} | |
private string getCodeMixin(in ref Node node, string id, string parent) | |
{ | |
(() @trusted { | |
enforcep(node.attributes.length == 0, "Code lines may not have attributes.", node.loc); | |
enforcep(node.attribs == NodeAttribs.none, | |
"Code lines may not specify translation or text block suffixes.", node.loc); | |
})(); | |
if (node.contents.length == 0) | |
return null; | |
string ret; | |
foreach (i, c; node.contents) | |
{ | |
if (i == 0 && c.kind == NodeContent.Kind.text) | |
{ | |
ret ~= makeStatement(node.loc, "%s {", c.value); | |
} | |
else | |
{ | |
assert(c.kind == NodeContent.Kind.node); | |
ret ~= getSpasmMixin(c.node, id, parent); | |
} | |
} | |
ret ~= makeStatement(node.loc, "}"); | |
return ret; | |
} | |
private string makeStatement(Args...)(Location loc, string fmt, Args args) | |
{ | |
import std.string : format; | |
return ("#line %s \"%s\"\n" ~ fmt ~ "\n").format(loc.line + 1, loc.file, args); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment