public
Created

string templating in D

  • Download Gist
stringtemplate.d
D
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
// first version - code demonstrating the basic idea.
// see below for a fuller-featured implementation
 
import std.string;
import std.range;
import std.stdio;
 
/* A very simple string templating system. Placeholders of the form
* %{variable} are replaced by the corresponding variable in the current
* scope.
*
* Since this works via simple macro expansion you could use an expression
* in place of a variable, but it will be evaluated multiple times.
*/
string formatTemplate(string tmpl)
{
string[] vars;
string ret = "";
 
while (!tmpl.empty)
{
auto i = tmpl.indexOf("%{");
if (i == -1)
break;
 
ret ~= tmpl[0 .. i] ~ "%s";
tmpl = tmpl[i + 2 .. $];
auto j = tmpl.indexOf("}");
vars ~= tmpl[0 .. j];
tmpl = tmpl[j + 1 .. $];
}
 
string retval = `format(q"|` ~ ret ~ tmpl ~ `|", `;
retval ~= join(vars, ", ");
retval ~= ")";
return retval;
}
 
void main()
{
// variables
string foo = "answer";
int bar = 42;
 
/* $ cat template.txt
The %{foo} is %{bar}
*/
enum tmpl = import("template.txt");
string a = mixin(formatTemplate(tmpl));
writeln(a);
}
stringtemplate_cond.d
D
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
// line-oriented version, with conditionals
 
import std.algorithm;
import std.string : join, chomp;
import std.range;
import std.exception;
import std.stdio;
 
/* A very simple line-oriented string templating system.
* Placeholders of the form %{variable} are replaced by the corresponding
* variable in the current scope.
*
* Conditional sections can be marked off by
* <| if condition |>
* text
* <| fi condition |>
*
* note that the if/fi block has to have matching conditions. sections
* can be nested; proper nesting is checked at compile time.
*
* There is no <| else |> directive; you can use
* <| if condition |>
* text
* <| fi condition |>
* <| if !condition |>
* text
* <| fi !condition |>
*
* Since this works via simple macro expansion you could use an expression
* in place of a variable, but it will be evaluated multiple times. Conditions
* will be surrounded by parentheses, so you can use expressions there too.
*
* Also, since we are expanding the template into the current lexical scope,
* we have to explicitly pass in the name of a string[] variable to which to append
* the result (to sidestep any variable capture issues). [TODO: add gensym support]
*
* Sample usage:
*
* enum tmpl = import("template.txt");
* string foo = "hello";
* string bar = "world";
* bool enableFoo = true;
* bool enableBar = false;
* string result = [];
* mixin(formatTemplate("result", tmpl);
*/
 
class Section
{
string condition;
string[] text;
bool close = false;
 
this(string c)
{
condition = c;
text = [];
}
}
 
bool isStartSection(string line)
{
return line.startsWith("<| if") &&
line.endsWith(" |>");
}
 
bool isEndSection(string line)
{
return line.startsWith("<| fi") &&
line.endsWith(" |>");
}
 
Section[] getSections(string[] lines)
{
Section[] sections = [new Section(null)];
Section[] open = [];
 
foreach( line; lines )
{
if ( line.isStartSection() )
{
if (!sections.empty)
{
sections.back.close = false;
}
string cond = line[6 .. $ - 3];
sections ~= new Section(cond);
open ~= sections.back;
}
else if ( line.isEndSection() )
{
string cond = line[6 .. $ - 3];
// balanced sections
enforce (!open.empty, "fi before if");
Section s = open.back;
enforce(s.condition == cond, "unbalanced fi: " ~ cond);
sections.back.close = true;
sections ~= new Section(null);
open = open[0 .. $ - 1];
}
else
{
sections.back.text ~= line;
}
}
 
enforce(open.empty, "open if: " ~ open.back.condition);
 
return sections;
}
 
string quote(string line)
{
return `q"|` ~ line ~ `|"`;
}
 
string formatLine(string text)
{
string[] vars;
string ret = "";
 
while (!text.empty)
{
auto i = text.indexOf("%{");
if (i == -1)
break;
 
ret ~= text[0 .. i] ~ "%s";
text = text[i + 2 .. $];
auto j = text.indexOf("}");
vars ~= text[0 .. j];
text = text[j + 1 .. $];
}
 
string retval = quote(ret ~ text);
if (!vars.empty)
{
retval = "format(" ~ retval ~ ", ";
retval ~= join(vars, ", ");
retval ~= ")";
}
return retval;
}
 
string[] formatSection(string acc, Section section)
{
string[] ret = [];
if ( section.condition !is null )
{
ret ~= "if (" ~ section.condition ~ ") {";
}
 
foreach( line; section.text )
{
ret ~= acc ~ "~=" ~ formatLine(line) ~ ";";
}
 
if ( section.close )
{
ret ~= "}";
}
 
return ret;
}
 
string formatTemplate(string acc, string tmpl)
{
string[] ret = [];
auto sections = getSections(tmpl.chomp.split("\n"));
foreach(section; sections)
{
ret ~= formatSection(acc, section);
}
 
debug(stringTemplate) { ret = "writeln(q\"$" ~ join(ret, "\n") ~ "$\");" ~ ret; }
 
return ret.join("\n");
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.