Simple support for decompiling fragments.
There are limitations! See the javadoc for Template.Fragment.decompile. Closes #75.
This commit is contained in:
@@ -557,6 +557,15 @@ public class Mustache
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addTag (char prefix, String name, StringBuilder into) {
|
||||
into.append(start1);
|
||||
into.append(start2);
|
||||
if (prefix != ' ') into.append(prefix);
|
||||
into.append(name);
|
||||
into.append(end1);
|
||||
into.append(end2);
|
||||
}
|
||||
|
||||
Delims copy () {
|
||||
Delims d = new Delims();
|
||||
d.start1 = start1;
|
||||
@@ -708,6 +717,9 @@ public class Mustache
|
||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||
write(out, _text);
|
||||
}
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||
into.append(_text);
|
||||
}
|
||||
@Override public String toString () {
|
||||
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
||||
_leadBlank + "/" + _trailBlank;
|
||||
@@ -761,6 +773,9 @@ public class Mustache
|
||||
// would happen if we just called execute() with ctx.data
|
||||
_template.executeSegs(ctx, out);
|
||||
}
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||
delims.addTag('>', _name, into);
|
||||
}
|
||||
protected final Compiler _comp;
|
||||
protected final String _name;
|
||||
protected Template _template;
|
||||
@@ -791,6 +806,9 @@ public class Mustache
|
||||
}
|
||||
write(out, _escaper.escape(_formatter.format(value)));
|
||||
}
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||
delims.addTag(' ', _name, into);
|
||||
}
|
||||
@Override public String toString () {
|
||||
return "Var(" + _name + ":" + _line + ")";
|
||||
}
|
||||
@@ -863,6 +881,11 @@ public class Mustache
|
||||
executeSegs(tmpl, ctx.nest(value, 0, false, false), out);
|
||||
}
|
||||
}
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||
delims.addTag('#', _name, into);
|
||||
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
||||
delims.addTag('/', _name, into);
|
||||
}
|
||||
@Override public String toString () {
|
||||
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||
}
|
||||
@@ -896,6 +919,11 @@ public class Mustache
|
||||
executeSegs(tmpl, ctx, out);
|
||||
} // TODO: fail?
|
||||
}
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||
delims.addTag('^', _name, into);
|
||||
for (Template.Segment seg : _segs) seg.decompile(delims, into);
|
||||
delims.addTag('/', _name, into);
|
||||
}
|
||||
@Override public String toString () {
|
||||
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||
}
|
||||
@@ -904,6 +932,7 @@ public class Mustache
|
||||
|
||||
protected static class FauxSegment extends Template.Segment {
|
||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
||||
@Override public void decompile (Delims delims, StringBuilder into) {} // nada
|
||||
@Override public String toString () { return "Faux"; }
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,35 @@ public class Template
|
||||
execute(context, out);
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
/** Decompiles the template inside this lamdba and returns <em>an approximation</em> of
|
||||
* the original template from which it was parsed. This is not the exact character for
|
||||
* character representation because the original text is not preserved because that would
|
||||
* incur a huge memory penalty for all users of the library when the vast majority of
|
||||
* them do not call decompile.
|
||||
*
|
||||
* <p>Limitations:
|
||||
* <ul><li> Whitespace inside tags is not preserved: i.e. {@code {{ foo.bar }}} becomes
|
||||
* {@code {{foo.bar}}}.
|
||||
* <li> If the delimiters are changed by the template, those are not preserved.
|
||||
* The delimiters configured on the {@link Compiler} are used for all decompilation.
|
||||
* </ul>
|
||||
*
|
||||
* <p>This feature is meant to enable use of lambdas for i18n such that you can recover
|
||||
* the contents of a lambda (so long as they're simple) to use as the lookup key for a
|
||||
* translation string. For example: {@code {{#i18n}}Hello {{user.name}}!{{/i18n}}} can be
|
||||
* sent to an {@code i18n} lambda which can use {@code decompile} to recover the text
|
||||
* {@code Hello {{user.name}}!} to be looked up in a translation dictionary. The
|
||||
* translated fragment could then be compiled and cached and then executed in lieu of the
|
||||
* original fragment using {@link Template.Fragment#context}.
|
||||
*/
|
||||
public String decompile () {
|
||||
return decompile(new StringBuilder()).toString();
|
||||
}
|
||||
|
||||
/** Decompiles this fragment into {@code into}. See {@link #decompile()}.
|
||||
* @return {@code into} for call chaining. */
|
||||
public abstract StringBuilder decompile (StringBuilder into);
|
||||
}
|
||||
|
||||
/** A sentinel object that can be returned by a {@link Mustache.Collector} to indicate that a
|
||||
@@ -126,6 +155,10 @@ public class Template
|
||||
@Override public void execute (Object context, Writer out) {
|
||||
execute(currentCtx.nest(context, 0, false, false), out);
|
||||
}
|
||||
@Override public StringBuilder decompile (StringBuilder into) {
|
||||
for (Segment seg : segs) seg.decompile(_compiler.delims, into);
|
||||
return into;
|
||||
}
|
||||
private void execute (Context ctx, Writer out) {
|
||||
for (Segment seg : segs) {
|
||||
seg.execute(Template.this, ctx, out);
|
||||
@@ -302,6 +335,8 @@ public class Template
|
||||
protected static abstract class Segment {
|
||||
abstract void execute (Template tmpl, Context ctx, Writer out);
|
||||
|
||||
abstract void decompile (Mustache.Delims delims, StringBuilder into);
|
||||
|
||||
protected static void write (Writer out, String data) {
|
||||
try {
|
||||
out.write(data);
|
||||
|
||||
@@ -594,6 +594,42 @@ public abstract class SharedTests extends GWTTestCase
|
||||
}));
|
||||
}
|
||||
|
||||
@Test public void testLambdaDecompile () {
|
||||
test("Foo {{a}}, Bar {{a}}", "{{#lam}}Foo {{a}}{{/lam}}, {{#lam}}Bar {{a}}{{/lam}}",
|
||||
context("lam", new Mustache.Lambda() {
|
||||
public void execute (Template.Fragment frag, Writer out) throws IOException {
|
||||
out.write(frag.decompile());
|
||||
}
|
||||
}));
|
||||
// whitespace inside tags is dropped!
|
||||
test("Foo {{a}}, Bar {{a}}", "{{#lam}}Foo {{a}}{{/lam}}, {{#lam}}Bar {{ a }}{{/lam}}",
|
||||
context("lam", new Mustache.Lambda() {
|
||||
public void execute (Template.Fragment frag, Writer out) throws IOException {
|
||||
out.write(frag.decompile());
|
||||
}
|
||||
}));
|
||||
// custom delimiters are ignored!
|
||||
test("Foo {{a}}, Bar {{a}}",
|
||||
"{{#lam}}Foo {{a}}{{/lam}}, {{=(( ))=}}((#lam))Bar ((a))((/lam))",
|
||||
context("lam", new Mustache.Lambda() {
|
||||
public void execute (Template.Fragment frag, Writer out) throws IOException {
|
||||
out.write(frag.decompile());
|
||||
}
|
||||
}));
|
||||
// coalesced whitespace around section tags is preserved
|
||||
test("{{#section}}\n" +
|
||||
"{{a}}\n" +
|
||||
"{{/section}}",
|
||||
"{{#lam}}{{#section}}\n" +
|
||||
"{{a}}\n" +
|
||||
"{{/section}}{{/lam}}",
|
||||
context("lam", new Mustache.Lambda() {
|
||||
public void execute (Template.Fragment frag, Writer out) throws IOException {
|
||||
out.write(frag.decompile());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Test public void testNonStandardDefaultDelims () {
|
||||
test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", context("foo", "bar"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user