dynamic partials?

292 views
Skip to first unread message

Mark Anderson

unread,
Mar 26, 2016, 5:13:15 PM3/26/16
to mustache.java
We have a situation where a file system directory has personal blurbs of text per individual identified by id.
So I'd like to be able to do something like:
   {{> content/blurbs/{{id}}.txt}}
But I'm pretty sure that isn't going to work.
I know I could bind a special lambda, but I'd rather not special-case this situation.

I see that there is this endless discussion:
but it is another one of those "let's beat up on each other endlessly and then do nothing" issues.

One of the suggestions in that discussions is to use a special dereferencing character like:
   {{> content/blurbs/*id.txt}}
which would get through the mustache parser and leave it to my resolver to interpret the "*".
This syntax is probably what I'll do unless someone has a better idea, but a weakness is that
it has no natural terminator for the dereferenced symbol (unlike the nested curlies syntax above).

Any thoughts? Or sample code?

Thanks.

-mda

Mark Anderson

unread,
Mar 26, 2016, 5:37:06 PM3/26/16
to mustache.java
I see there was this commit last year:
with no documentation that I can find. But it seems like what it will support (from speed-reading the code)
is something like:
   {{+ content/blurbs/{{id}}.txt}}
Is that what it does? Or maybe it is solving some other problem?

-mda

Sam Pullara

unread,
Mar 26, 2016, 6:13:17 PM3/26/16
to mustac...@googlegroups.com
That is in a pull request and never passed muster. I would probably add it if I got a great contribution — however, it would probably work differently than you are suggesting. The essential idea would that you would say "{{+blurb}}", then at runtime I would evaluate "blurb" in the current scope and use that for the name of the partial to load, e.g. the blurb() method returns "content/blurbs/" + id + ".txt" or the like. Does that make sense and meet your requirements? It is somewhat tricky to implement as partials are normally compiled along with everything else at compile time. This would launch a compile step during execution of the template. With caching the performance should be fine and compilation is still very fast.


--
You received this message because you are subscribed to the Google Groups "mustache.java" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mustachejava...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark D. Anderson

unread,
Mar 26, 2016, 6:52:26 PM3/26/16
to mustac...@googlegroups.com
Well, I don't see how {{+blurb}} would be any different/better than just having a lambda like {{#blurb}}{{id}}{{/blurb}}.
 
What I was hoping for was something that would interpolate in arbitrary paths, not requiring a special function for
every use case. There might even be multiple references in the path. Some possible syntaxes:
1. {{> a/*foo/*bar.txt}}
2. {{> a/[foo]/[bar].txt}}
3. {{* a/[[foo]]/[[bar]].txt}}
4. {{* a/*foo*/*bar*.txt}}
5. {{@ a/[foo]/[bar].txt}}
 
The benefit of something like 1 or 2 is that I can implement them with my own custom resolver with no changes in mustachejava.
The benefit of something like 3 or 4 or 5 is that the can be added to core mustachejava for the benefit of posterity.
 
I would imagine that adding general support for nesting in the parser, like {{X {{a}}/{{b}}}} would be way too disruptive,
which is why some other nesting syntax is needed.
 
-mda
You received this message because you are subscribed to a topic in the Google Groups "mustache.java" group.
To unsubscribe from this group and all its topics, send an email to mustachejava...@googlegroups.com.

Sam Pullara

unread,
Mar 26, 2016, 8:32:16 PM3/26/16
to mustac...@googlegroups.com
Ok, now that I understand what you want I can definitively say I won't be implementing it in the core. It is too much like a mini programming language like filters / data formatters and the like. I have a proposal for you though.

You can't actually implement it using a MustacheResolver as partials are statically evaluated at compile time — so 1&2 won't work. A Function won't work either as you won't have the scope context to pass along. I've left open all the hooks though so you could implement it by overriding the creation of PartialCode() in the MustacheVisitor. I've checked in a test case that implements a version of this:


The syntax is {{>+a/[foo]/[bar].txt}} — it will do text replacement using mustache in the name with the alternate delimiters. It then compiles the newly resolved partial and caches it. This leaks memory if you created an unlimited number of dynamic partials and it also doesn't have any depth protection for recursion, but it seems to do what you were looking to do.

Sam

Mark D. Anderson

unread,
Mar 26, 2016, 8:59:44 PM3/26/16
to mustac...@googlegroups.com
On Sat, Mar 26, 2016, at 05:32 PM, Sam Pullara wrote:
> Ok, now that I understand what you want I can definitively say I won't be implementing it in the core. It is too much like a mini programming language like filters / data formatters and the like. I have a proposal for you though.
>
> You can't actually implement it using a MustacheResolver as partials are statically evaluated at compile time — so 1&2 won't work.

I don't understand how you could stop me :)
I just have to implement something like this:

MyResolver implements MustacheResolver {

Map<String,String> bindings; //injected by me

// replace any [foo] with bound value first
public Reader getReader(String resourceName) {
Matcher m = Pattern.compile("\\[.*?)\\]").matcher(resourceName);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String sym = m.group(1);
String v = bindings.get(sym);
if (v == null || v.isEmpty()) throw new IllegalArgumentException("no value for '" + sym + "'");
m.appendReplacement(sb, v);
}
m.appendTail(sb);
resourceName = sb.toString();
// now resolve normally
...
}

> A Function won't work either as you won't have the scope context to pass along.

Well, I gave you the scopes so i know what they are :).
Why couldn't I do a function to support:

{{#asPartial}}all/my/blurbs/{{id}}.txt{{/asPartial}}

like

static Function<String,String> asPartial = new Function<String,String>() {
public String apply(String v) {
Reader r = my_resolve(v);
return reader_to_string(r);
}
};

>I've left open all the hooks though so you could implement it by overriding the creation of PartialCode() in the MustacheVisitor. I've checked in a test case that implements a version of this:
>
> https://github.com/spullara/mustache.java/commit/eaee8ce2640413b20ddb405b8ee8018d73da913c#diff-f5e8866b19228187afb247b0ed8629f4R647
>
> The syntax is {{>+a/[foo]/[bar].txt}} — it will do text replacement using mustache in the name with the alternate delimiters. It then compiles the newly resolved partial and caches it. This leaks memory if you created an unlimited number of dynamic partials and it also doesn't have any depth protection for recursion, but it seems to do what you were looking to do.

There must be something fundamental about the mustache.java compile-time versus run-time that I'm not understanding,
in addition to my not knowing about any caching.
Are you saying that builtin to mustache.java is a cache layer and it will never call my resolver more than once
for the same string?

-mda

Sam

unread,
Mar 26, 2016, 9:25:30 PM3/26/16
to mustache.java
The resolver is called during compilation only. First there is a compile step that creates the tree of Code nodes, then later, when you call execute, those nodes are executed using the provided scope — generally there is no reason to compile more than once unless the templates are changing. Since the compile step which called once doesn't know about your scope it wouldn't pick up the right values. If you are suggesting that you will recompile your templates on every request, you could use this method, but that is a couple of orders of magnitude slower — though that might not bother you in your application, it is still very fast. As for the Function, it is possible to implement many of them but not just a single one which is what was thinking.

It is possible in your application that you always recompile the templates on a per request basis. If that is the case and can then run them in the same scope, Then your Reader version will work and a Function version could work as well. However, it is much less efficient than the one that I propose which will generically work for anyone that wants this functionality and will perform very well with only an additional compile per unique partial included and then only compiles that single partial.

One of the reasons I have so many hooks is that I do consistently refuse to add anything that looks like an additional language into the core. However, anyone that wants something bespoke for their application or wants to use one of the examples I have in the tests can easily make something that does almost whatever they want while remaining very efficient / high performance. One of the biggest extensions I have built is a complete I18N/L10N system for Twitter that uses a DSL in the variable names — works great, but very much specific to them and their process.

Sam

Mark D. Anderson

unread,
Mar 26, 2016, 10:16:14 PM3/26/16
to mustac...@googlegroups.com
Thanks Sam, that helps. What about caching in the core? It won't actually affect me for this particular application,
but I'm curious if the core will skip calling the resolver on the same string twice.
I assume that it will always call any lambda/function. That is, it assumes that functions aren't functional :)
 
-mda

Sam

unread,
Mar 27, 2016, 12:25:10 AM3/27/16
to mustache.java
During the compilation of a top level template the partials referenced are cached by name to make non-infinite recursion possible. Other than that, nothing is cached.

Sam
To unsubscribe from this group and all its topics, send an email to mustachejava+unsubscribe@googlegroups.com.

Glenn Boysko

unread,
Jan 24, 2019, 9:59:20 PM1/24/19
to mustache.java
Hello Sam:

FWIW, I would love to have this basic dynamic partial in Mustache. This post is from two years ago. I'm using 0.9.5. Could this already be in there? The documentation doesn't seem to be very complete.

Thanks,
Glenn
Reply all
Reply to author
Forward
0 new messages