-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding dynamic partials, discussed in #54 #134
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More specific context for who needs it: the discussion that led to this PR started exactly here.
Review @anomal00us:
In principle, I'm in favor of this spec, but there are a couple of things that I believe should be done before merging it:
- The spec should be made optional. That's just a matter of prepending a tilde
~
to the name of the spec, i.e.,~dynamic.yml
. Actually, make that~dynamic-partials.yml
, for consistency with the other specs. (You could leave off the tilde if you feel that the spec should be mandatory, but in that case it can't be part of the spec until we bump the major version. That would seem a bit draconic for an extension that isn't very disruptive.) - As currently proposed, the spec covers only half of the behavior of the
{{*}}
tag, i.e., the partial-like behavior. I think the other half should be specified as well, i.e., the part where it resolves a name in the context in the same way that an interpolation tag would. That should include things like dotted names, implicit iterators and function calls (note that function calls are part of the standard resolution algorithm even when the lambdas extension isn't implemented). - I'd like to
implement the feature myself(edit: done) and run it against the proposed spec in order to check for any hidden surprises. Only after the previous point is addressed, of course. - I'd like to see at least one other implementation that passes the proposed spec (at least mostly, with a motivation for any omissions). If I'm right, you already implemented this feature yourself, so this point might be easy to address.
I made a few more minor comments below. Otherwise it looks good to me.
specs/dynamic.yml
Outdated
nodes: [ { content: 'Y', nodes: [] } ] | ||
} | ||
template: '{{*template}}' | ||
partials: { node: '{{content}}<{{#nodes}}{{>node}}{{/nodes}}>' } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To emphasize the point, I think you could even do {{content}}<{{#nodes}}{{*template}}{{/nodes}}>
. Although the current content of the partial is arguably more plausible, so I don't mind if you keep it this way, either.
specs/dynamic.yml
Outdated
# Whitespace Sensitivity | ||
|
||
- name: Surrounding Whitespace | ||
desc: The asterisk operator should not alter surrounding whitespace. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe nuance this sentence a bit by explicitly stating under which circumstances it applies.
specs/dynamic.yml
Outdated
expected: "| \t|\t |" | ||
|
||
- name: Inline Indentation | ||
desc: Whitespace should be left untouched. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise.
Update after my previous comment: I just implemented the feature, so that won't be a bottleneck in the remainder of the review process. |
Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com>
Yes, that's a good point.
Okay so I just need to describe the name resolution in the context. |
Is there a strong reason for using |
The main reason was to keep the language simple and not introducing a general deference operator. Here I wrote a brief list of both options' pros and cons. New general dereference operatorPros:
Cons:
Dynamic partialsPros:
Cons:
What do you think? |
Note: I am not suggesting that this specification should add support for |
My reasons for not wanting to have a general purpose dereference operator can be found here and here. Whether you find them strong is up to you. :-) I think there doesn't need to be a dynamic parent because dynamic partials and parents are mostly alternatives; the former enable polymorphism through a tagged union like construct while the latter enable polymorphism through an inheritance like construct. As for preserving the (This post crossed the previous two.) |
@anomal00us
I don't think it's less consistent, just consistent in a different (and more limiting) way. It's perfectly consistent with the current practice of having a tag consisting of an opening delimiter, a single-character sigil, a content without much internal structure and finally a closing delimiter. |
On the other hand, if you think of "parent templates" as "partials with parameters" (instead of an inheritance-like construct), then having those selected at runtime based on the data makes just as much sense as for partials. Again: I'm not proposing this (or any other extension of the feature) for inclusion in the currently-proposed specification. But I see value in making a syntactic choice that leaves our options open in the future, if we found out that there are in fact valid use-cases (at acceptable complexity costs) for other forms of dynamic resolution. |
@gasche I see your point, but I feel that As far as I'm concerned, there are only three flavors possible:
Number 3. would be a severely breaking change requiring a major version upgrade. Also, I would not want to use or implement such a Mustache 2.0 myself. In my opinion, number 2. is not a very strange thing to do. Partials are a powerful and versatile language feature, so it makes sense to dedicate a relatively large share of the sigils to them. Consider also #63 (comment) and my immediate follow-up comment, which would introduce Number 1. would be a bit disappointing in my opinion, but not as disappointing as number 3. |
I think that we have to decide this now, once for all. |
@gasche As for parents as "partials with parameters": I subscribe to that way of thinking, but even when you think of it that way, dynamic partials are also an alternative way of passing parameters. You can replace parents and blocks entirely by dynamically resolved non-parametric partials, if you are happy with the inversion of control. Which you must be in order to want to use dynamic partials in the first place. @anomal00us I think you make a valid point that if we do a dedicated |
At the moment my implementation isn't really compliant to mustache standards, regarding whitespaces and names (dotted names and objects) resolution, but for the rest it isn't that bad. |
I'm not sure I see what you have in mind, can you show a sketch of the translation?
Personally I think that the situation would not be any worse than with the |
Isn't it better if we plan things well now in order to have less breaking changes? |
IMO, neither I believe the main concern is whether
I haven't tried it yet, but I imagine it wouldn't be a two-character sigil; I'd implement it as a general purpose dereference operator, restricted to only work inside partial tags. It'd require another couple of transitions in the tokenizer state machine, but no more weird than, e.g., supporting the triple-mustache alt for |
To limit the slipperiness of this slope, I'd be perfectly happy limiting it to a single dereference, which applies to the (resolved) dotted tag name in its entirety. So:
|
Makes more sense yeah. |
This is my opinion but I think we're far from consensus still 😛 |
@anomal00us
Thanks. To guide my reading, could you mention the line numbers that specifically address dynamic partials? |
Inheritance: {{!parent.mustache}}
Hello, {{$block}}colleague{{/block}}.
{{!child.mustache}}
{{<parent}}{{$block}}friend{{/block}}{{/parent}} Dynamic partials, same result: {
"relation": "job",
"greet": "parent",
"informal": {
"relation": "social"
}
} {{!parent.mustache}}
Hello, {{*relation}}.
{{!child.mustache}}
{{#informal}}{{*greeting}}{{/informal}}
{{!job.mustache}}
colleague
{{!social.mustache}}
friend |
Sure! So it's pretty simple since it doesn't really handle complex objects. For being even more clear here's a more detailed example: // These are the arguments we render the partial with.
$args = array("dynamic" => "mainpage");
// This is the abstract syntax tree generated by the parser.
// I don't resolve constants for being more clear
$AST = array(
0 => array(
self::AST_TYPE = > self::TOKEN_TYPE_DYNAMIC_PARTIAL,
self::AST_VALUE => "dynamic"
));
// When the compiler translates the AST into code,
// we enter a the for loop on line 371 and at some point we will have
// $value = $AST[$i][self::AST_VALUE]; // = "dynamic"
// Now it should be more clear how this line resolves the reference
$value = $args[$value]; // = $args["dynamic"] = "mainpage" |
Good point, agreed.
It's ironic how you suggest restricting the dereference operator to a single level in order to limit the slipperiness of the slope, and immediately follow up by illustrating exactly what I'm worried about, i.e., more complexity inside the tag content in the form of a pipe operator. It reminds me of filters in Jinja and several other templating languages. I presume your implementation already supports that notation. Even if we're only adding a single-level dereference operator now, it will set a precedent for people to want to add other notational refinements to the tag content. Your pipe operator might be a likely candidate. I could also think of direct arguments like in Handlebars helpers, or perhaps case modifiers. I'm not opposed to people adding such extensions to their own implementations, but if we start adding them to the spec, I feel that Mustache is giving up its logic-less soul. I feel that the extremely minimal syntax is what distinguishes Mustache from other template languages in the first place. That doesn't mean that I want to limit the functionality of the language; I just want to uphold the clean, logic-less syntax. Filters, case modifiers, and even dereference operations could all also be achieved by making lambdas just slightly more powerful. All it requires is to pass a second argument to lambdas that accept it, containing the full context stack. This is an extension I've actually been meaning to suggest for a while; I'd call it "power lambdas". We don't need to discuss that now, though; I'm just pointing out that complicating tag contents is not the only way to make the language (even) more powerful. Long story short: I don't mind if people add support for expression inside tag contents to their own implementations, but please let's keep them out of the spec. If that means that we don't specifiy any way to do dynamic partials, so be it. |
Right, sorry, I wasn't suggesting that this proposal include that feature, I was illustrating how that same principle would work in a feature (filters / pipe operators) which is an extension already present in some mustache implementations. Happy to remove that example if it makes things clearer 😛 |
Dereferencing would be a really nice thing to have. I rewrote part of my script to see how it would look with a general dereference "operator".
I thought it would have been worse. What do you think? |
Please don't. :-) I understand that you didn't mean to suggest filter syntax, so you don't need to change your post. In fact, it perfectly illustrates how one operator can bring other in-tag expression syntax to mind. I'm just talking about associations here, and what they might do to future spec developments - if it crosses your mind now, it will cross other people's minds later as well. |
Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com>
…authored) Currently, an implementation treating comments as undefined variables successfully passes all tests, because both undefined variables and comments render to empty string. This clarifies the behavior: A comment MUST NOT render into anything, under any circumstances. This includes the case where a variable with the same name as the comment content is defined. The test data is designed in a way to trigger any possible name collision, including white spaces, a leading exclamation mark (!). Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com>
Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com> Update specs/~dynamic-names.yml Co-authored-by: Julian Gonggrijp <dev@juliangonggrijp.com>
Looks better now! |
I'm in holidays in August with poor connectivity, don't wait for me :-) |
@anomal00us I'm always happy to keep details in history, so no more squashing required as far as I'm concerned. If you feel like cleaning up some more, please let me know, otherwise I'll merge this. Happy holiday, @gasche! |
I'm fine with the current commits @jgonggrijp Happy holidays @gasche ! |
Congratulations with your first contribution @anomal00us! |
|
||
- name: Dynamic Names - Composed Dereferencing | ||
desc: Dotted Names are resolved entirely before dereferencing begins. | ||
data: { foo: 'fizz', bar: 'buzz', fizz: { buzz: { 'content' } } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't know if it's a more recent addition to the spec, but the YAML parser I use for my spec test cases doesn't like implicit null values in JSON-like flow maps:
Malformed inline YAML string { foo: 'fizz', bar: 'buzz', fizz: { buzz: { 'content' } } }
can we change this to the equivalent:
data: { foo: 'fizz', bar: 'buzz', fizz: { buzz: { content: null } } }
(sorry i didn't catch this before 🙁)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No problem, fortunately I had not tagged a release yet. Should be fixed now on master.
Addressing a comment by @bobthecow in #134 (review) ref #54, #134
Okay I opened a pull request on your fork: jgonggrijp/mustache#1 |
@jgonggrijp it turns out this feature is incredibly useful to JStachio and might become one of its most powerful features except with difference of using sealed class instances and enums. It basically is dispatching like you do in languages that support pattern matching. |
@agentgt Thanks for notifying me. JStachio is outside my realm of daily experience but that makes it interesting. Happy to hear that you found a good way to integrate dynamic partials with it! |
The idea is to allow to dynamically load partials.
Brief example:
Data:
Templates:
Result: