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;
|
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 copy () {
|
||||||
Delims d = new Delims();
|
Delims d = new Delims();
|
||||||
d.start1 = start1;
|
d.start1 = start1;
|
||||||
@@ -708,6 +717,9 @@ public class Mustache
|
|||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {
|
||||||
write(out, _text);
|
write(out, _text);
|
||||||
}
|
}
|
||||||
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
|
into.append(_text);
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
return "Text(" + _text.replace("\r", "\\r").replace("\n", "\\n") + ")" +
|
||||||
_leadBlank + "/" + _trailBlank;
|
_leadBlank + "/" + _trailBlank;
|
||||||
@@ -761,6 +773,9 @@ public class Mustache
|
|||||||
// would happen if we just called execute() with ctx.data
|
// would happen if we just called execute() with ctx.data
|
||||||
_template.executeSegs(ctx, out);
|
_template.executeSegs(ctx, out);
|
||||||
}
|
}
|
||||||
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
|
delims.addTag('>', _name, into);
|
||||||
|
}
|
||||||
protected final Compiler _comp;
|
protected final Compiler _comp;
|
||||||
protected final String _name;
|
protected final String _name;
|
||||||
protected Template _template;
|
protected Template _template;
|
||||||
@@ -791,6 +806,9 @@ public class Mustache
|
|||||||
}
|
}
|
||||||
write(out, _escaper.escape(_formatter.format(value)));
|
write(out, _escaper.escape(_formatter.format(value)));
|
||||||
}
|
}
|
||||||
|
@Override public void decompile (Delims delims, StringBuilder into) {
|
||||||
|
delims.addTag(' ', _name, into);
|
||||||
|
}
|
||||||
@Override public String toString () {
|
@Override public String toString () {
|
||||||
return "Var(" + _name + ":" + _line + ")";
|
return "Var(" + _name + ":" + _line + ")";
|
||||||
}
|
}
|
||||||
@@ -863,6 +881,11 @@ public class Mustache
|
|||||||
executeSegs(tmpl, ctx.nest(value, 0, false, false), out);
|
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 () {
|
@Override public String toString () {
|
||||||
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Section(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
}
|
}
|
||||||
@@ -896,6 +919,11 @@ public class Mustache
|
|||||||
executeSegs(tmpl, ctx, out);
|
executeSegs(tmpl, ctx, out);
|
||||||
} // TODO: fail?
|
} // 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 () {
|
@Override public String toString () {
|
||||||
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
return "Inverted(" + _name + ":" + _line + "): " + Arrays.toString(_segs);
|
||||||
}
|
}
|
||||||
@@ -904,6 +932,7 @@ public class Mustache
|
|||||||
|
|
||||||
protected static class FauxSegment extends Template.Segment {
|
protected static class FauxSegment extends Template.Segment {
|
||||||
@Override public void execute (Template tmpl, Template.Context ctx, Writer out) {} // nada
|
@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"; }
|
@Override public String toString () { return "Faux"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,35 @@ public class Template
|
|||||||
execute(context, out);
|
execute(context, out);
|
||||||
return out.toString();
|
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
|
/** 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) {
|
@Override public void execute (Object context, Writer out) {
|
||||||
execute(currentCtx.nest(context, 0, false, false), 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) {
|
private void execute (Context ctx, Writer out) {
|
||||||
for (Segment seg : segs) {
|
for (Segment seg : segs) {
|
||||||
seg.execute(Template.this, ctx, out);
|
seg.execute(Template.this, ctx, out);
|
||||||
@@ -302,6 +335,8 @@ public class Template
|
|||||||
protected static abstract class Segment {
|
protected static abstract class Segment {
|
||||||
abstract void execute (Template tmpl, Context ctx, Writer out);
|
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) {
|
protected static void write (Writer out, String data) {
|
||||||
try {
|
try {
|
||||||
out.write(data);
|
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 public void testNonStandardDefaultDelims () {
|
||||||
test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", context("foo", "bar"));
|
test(Mustache.compiler().withDelims("<% %>"), "bar", "<%foo%>", context("foo", "bar"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user