Skip to content

Commit

Permalink
Implement pretty printed HTML output. Fixes #8.
Browse files Browse the repository at this point in the history
  • Loading branch information
s-ludwig committed Sep 27, 2016
1 parent f8d167d commit 182499d
Showing 1 changed file with 95 additions and 18 deletions.
113 changes: 95 additions & 18 deletions source/diet/html.d
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import diet.traits;
attribute.
dst = The output range to write the generated HTML to.
Traits:
In addition to the default Diet traits, adding an enum field
`htmlOutputStyle` of type `HTMLOutputStyle` to a traits
struct can be used to control the style of the generated
HTML.
See_Also: `compileHTMLDietString`, `compileHTMLDietStrings`
*/
template compileHTMLDietFile(string filename, ALIASES...)
Expand Down Expand Up @@ -60,7 +66,7 @@ template compileHTMLDietFile(string filename, ALIASES...)
alias TRAITS = DietTraits!ALIASES;
pragma(msg, "Compiling Diet HTML template "~filename~"...");
private Document _diet_nodes() { return applyTraits!TRAITS(parseDiet!(translate!TRAITS)(_diet_files)); }
enum _dietParser = getHTMLMixin(_diet_nodes());
enum _dietParser = getHTMLMixin(_diet_nodes(), dietOutputRangeName, getHTMLOutputStyle!TRAITS);

static if (_diet_use_cache) {
shared static this()
Expand Down Expand Up @@ -131,7 +137,7 @@ template compileHTMLDietStrings(alias FILES_GROUP, ALIASES...)
{
mixin(localAliasesMixin!(0, ALIASES));
//pragma(msg, getHTMLMixin(_diet_nodes()));
mixin(getHTMLMixin(_diet_nodes()));
mixin(getHTMLMixin(_diet_nodes(), dietOutputRangeName, getHTMLOutputStyle!TRAITS));
}

void compileHTMLDietStrings(R)(ref R dst)
Expand All @@ -150,9 +156,10 @@ template compileHTMLDietStrings(alias FILES_GROUP, ALIASES...)
Returns:
A string of D statements suitable to be mixed in inside of a function.
*/
string getHTMLMixin(in Document doc, string range_name = dietOutputRangeName)
string getHTMLMixin(in Document doc, string range_name = dietOutputRangeName, HTMLOutputStyle style = HTMLOutputStyle.compact)
{
CTX ctx;
ctx.pretty = style == HTMLOutputStyle.pretty;
ctx.rangeName = range_name;
string ret = "import diet.internal.html : htmlEscape, htmlAttribEscape;\n";
ret ~= "import std.format : formattedWrite;\n";
Expand Down Expand Up @@ -188,6 +195,42 @@ unittest {
test!"#foo"("<div id=\"foo\"></div>");
}


/** Determines how the generated HTML gets styled.
To use this, put an enum field named `htmlOutputStyle` into a diet traits
struct and pass that to the render function.
The default output style is `compact`.
*/
enum HTMLOutputStyle {
compact, /// Outputs no extraneous whitespace (including line breaks) around HTML tags
pretty, /// Inserts line breaks and indents lines according to their nesting level in the HTML structure
}

///
unittest {
@dietTraits
struct Traits {
enum htmlOutputStyle = HTMLOutputStyle.pretty;
}

import std.array : appender;
auto dst = appender!string();
dst.compileHTMLDietString!("html\n\tbody\n\t\tp Hello", Traits);
import std.conv : to;
assert(dst.data == "<html>\n\t<body>\n\t\t<p>Hello</p>\n\t</body>\n</html>", [dst.data].to!string);
}

private @property template getHTMLOutputStyle(TRAITS...)
{
static if (TRAITS.length) {
static if (is(typeof(TRAITS[0].htmlOutputStyle)))
enum getHTMLOutputStyle = TRAITS[0].htmlOutputStyle;
else enum getHTMLOutputStyle = getHTMLOutputStyle!(TRAITS[1 .. $]);
} else enum getHTMLOutputStyle = HTMLOutputStyle.compact;
}

private string getHTMLMixin(ref CTX ctx, in Node node, bool first)
{
switch (node.name) {
Expand All @@ -210,9 +253,13 @@ private string getElementMixin(ref CTX ctx, in Node node)
{
import std.algorithm : countUntil;

bool need_newline = ctx.needPrettyNewline(node.contents);

// write tag name
string tagname = node.name.length ? node.name : "div";
string ret = ctx.rawText(node.loc, "<"~tagname);
string ret;
if (need_newline) ctx.prettyNewLine();
ret ~= ctx.rawText(node.loc, "<"~tagname);

bool had_class = false;

Expand Down Expand Up @@ -288,32 +335,38 @@ private string getElementMixin(ref CTX ctx, in Node node)
ret ~= ctx.rawText(node.loc, ">");

// write contents
ctx.depth++;
if (need_newline) {
ctx.depth++;
ctx.prettyNewLine();
}

foreach (i, c; node.contents)
ret ~= ctx.getNodeContentsMixin(c, i == 0);
ctx.depth--;

if (need_newline) {
ctx.depth--;
ctx.prettyNewLine();
}

// write end tag
ret ~= ctx.rawText(node.loc, "</"~tagname~">");

if (need_newline) ctx.prettyNewLine();

return ret;
}

private string getNodeContentsMixin(ref CTX ctx, in NodeContent c, bool first)
{
final switch (c.kind) with (NodeContent.Kind) {
case node:
string ret;
ret ~= ctx.prettyNewLine(c.loc);
ret ~= getHTMLMixin(ctx, c.node, first);
ret ~= ctx.prettyNewLine(c.loc);
return ret;
return getHTMLMixin(ctx, c.node, first);
case text:
return ctx.rawText(c.loc, c.value);
case interpolation:
return ctx.statement(c.loc, q{%s.htmlEscape(%s);}, ctx.rangeName, c.value);
return ctx.textStatement(c.loc, q{%s.htmlEscape(%s);}, ctx.rangeName, c.value);
case rawInterpolation:
return ctx.statement(c.loc, q{() @trusted { return (&%s); } ().formattedWrite("%%s", %s);}, ctx.rangeName, c.value);
return ctx.textStatement(c.loc, q{() @trusted { return (&%s); } ().formattedWrite("%%s", %s);}, ctx.rangeName, c.value);
}
}

Expand Down Expand Up @@ -397,18 +450,21 @@ private string getCodeMixin(ref CTX ctx, in ref Node node)
private string getCommentMixin(ref CTX ctx, in ref Node node)
{
string ret = ctx.rawText(node.loc, "<!--");
ctx.depth++;
foreach (i, c; node.contents)
ret ~= ctx.getNodeContentsMixin(c, i == 0);
ctx.depth--;
ret ~= ctx.rawText(node.loc, "-->");
return ret;
}

private struct CTX {
bool isHTML5;
bool pretty = false;
bool pretty;
int depth = 0;
string rangeName;
bool inRawText;
bool newlinePending, anyText;

pure string statement(ARGS...)(Location loc, string fmt, ARGS args)
{
Expand All @@ -418,14 +474,24 @@ private struct CTX {
return ret;
}

pure string textStatement(ARGS...)(Location loc, string fmt, ARGS args)
{
string ret;
if (pretty && newlinePending) ret ~= rawText(loc, null);
ret ~= statement(loc, fmt, args);
return ret;
}

pure string rawText(ARGS...)(Location loc, string text)
{
string ret;
if (!this.inRawText) {
ret = this.rangeName ~ ".put(\"";
this.inRawText = true;
}
ret ~= outputPendingNewline();
ret ~= dstringEscape(text);
anyText = true;
return ret;
}

Expand All @@ -438,10 +504,21 @@ private struct CTX {
return null;
}

string prettyNewLine(in ref Location loc) {
import std.array : replicate;
if (pretty) return rawText(loc, "\n"~"\t".replicate(depth));
else return null;
void prettyNewLine() { newlinePending = true; }

bool needPrettyNewline(in NodeContent[] contents) {
import std.algorithm.searching : any;
return pretty && contents.any!(c => c.kind == NodeContent.Kind.node);
}

private pure string outputPendingNewline()
{
if (pretty && newlinePending) {
import std.array : replicate;
newlinePending = false;
return anyText ? "\n"~"\t".replicate(depth) : null;
}
return null;
}
}

Expand Down

0 comments on commit 182499d

Please sign in to comment.