-
Notifications
You must be signed in to change notification settings - Fork 421
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
hogan.js style template inheritance #198
Conversation
{ | ||
$block = ''; | ||
|
||
$real_children = array_filter($children, function($ch) { |
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.
This doesn't work on PHP 5.2.x, and Mustache supports 5.2.x until the next major release.
How feasible would it be to add a pragma for template inheritance, then treat I understand that this makes templates a bit ugly, but I also really value compatibility with the Mustache spec — and with existing templates of existing users. While template inheritance is awesome, it's not strictly compatible with Mustache. For example, To make it easier for people who just want to use all the awesome all the time, it might make sense to add |
$node[Mustache_Tokenizer::NAME], | ||
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '', | ||
$level, | ||
$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.
This should be $node[Mustache_Tokenizer::NODES]
public function loadInheritanceSpec() | ||
{ | ||
return $this->loadSpec('inheritance'); | ||
} |
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.
Is there a way to include these tests even without the modified version of mustache/spec
?
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.
Yeah, I accidentally committed this. It can be removed because I added all the tests to the functional test here.
… to be used from array_filter instead
@bobthecow, implementing template inheritance as a pragma doesn’t work for our use case. Our developers are writing front-end features in hogan.js and sharing templates between the client and the server. If I understand pragmas correct, then implementing template inheritance as a pragma would mean that some templates could no longer be shared between the client (hogan) and the server (mustache.php). Is it possible that, instead, this change could go out with the next major version? (Being not backwards-compatible) |
@jazzdan Yeah, I hear you about the annoyance of pragmas, but I definitely want to make non-spec features opt-in, at least for now. First, you should be able to use pragmas and any library that doesn't understand a given pragma will just skip over it. For example, if Mustache.php used {{% INHERITANCE }}
{{< foo }}
{{$ bar }}this is bar{{/ bar }}
{{/ foo }} In Mustache.php, that would toggle template inheritance and everything would work as expected. But Hogan.js, which doesn't know about the {{< foo }}
{{$ bar }}this is bar{{/ bar }}
{{/ foo }} Second, I would consider adding a constructor option to the $m = new Mustache_Engine([
'enabled_pragmas' => [Mustache_Engine::PRAGMA_INHERITANCE]
]); Would that make sense? |
… of variables. Renamed parent_* to block_*.
break; | ||
|
||
case Mustache_Tokenizer::T_BLOCK_VAR: | ||
if ($parent[Mustache_Tokenizer::TYPE] == Mustache_Tokenizer::T_PARENT) { |
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.
It seems like this would fail for blocks inside sections inside parent tags:
{{< foo }}
{{# bar }}
{{$ baz }}{{/ baz }}
{{/ bar }}
{{/ foo }}
… which probably is a perfectly valid failure case, since the contents of parent tags should really just be the blocks to be overridden. But if that's the case, do we want to throw an exception when parsing if a block arg is ever nested inside something besides a parent tag? Or is it better to let things fail and not tell people why?
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 haven't tested in java or hogan, so i don't know whether this works, is ignored, or throws an exception for either of those)
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.
It does throw an exception in hogan.js:
Hogan = require("hogan.js");
var template = "{{< foo }}{{# bar }}{{$ baz }}{{/ baz }}{{/ bar }}{{/ foo }}";
var foo = "{{$baz}}default content{{/baz}}";
var data = {
bar: "set by user"
}
var template = Hogan.compile(template);
var output = template.render(data, {'foo': header});
console.log(output);
// Returns
/*
* /home/dmiller/development/test.js/node_modules/hogan.js/lib/compiler.js:203
* throw new Error('Illegal content in < super tag.');
* ^
* Error: Illegal content in < super tag.
* at buildTree (/home/dmiller/development/test.js/node_modules/hogan.js/lib/compiler.js:203:15)
* at buildTree (/home/dmiller/development/test.js/node_modules/hogan.js/lib/compiler.js:208:23)
* at Object.Hogan.parse (/home/dmiller/development/test.js/node_modules/hogan.js/lib/compiler.js:394:12)
* at Object.Hogan.compile (/home/dmiller/development/test.js/node_modules/hogan.js/lib/compiler.js:416:35)
* at Object.<anonymous> (/home/dmiller/development/test.js/index.js:11:22)
* at Module._compile (module.js:456:26)
* at Object.Module._extensions..js (module.js:474:10)
* at Module.load (module.js:356:32)
* at Function.Module._load (module.js:312:12)
* at Function.Module.runMain (module.js:497:10)
*/
I'll add a throw here for that.
…untered inside of a parent tag. Other small cleanups
@@ -61,10 +61,13 @@ public function __invoke($context = array()) | |||
* @param mixed $context Array or object rendering context (default: array()) | |||
* | |||
* @return string Rendered template | |||
* TODO construct a parent context stack here and pass it in |
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.
So is this TODO a TODONE yet?
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.
It surely is!
From the GRMustache test cases linked to comments on the spec issue, we should probably make sure we handle cases like this correctly: - name: Shenanigans
desc: Templates can override parent blocks inside sections
data: {items: [1, 2, 3]}
template: '{{<parent}}{{$value}}<{{.}}>{{/value}}{{/parent}}'
partials:
parent: '{{#items}}{{$value}}ignored{{/value}}{{/items}}'
expected: '<1><2><3>' It currently renders as |
Hey @bobthecow, I spent some time looking at this test case. I think that in order to support this the template inheritance feature I wrote would have to be rewritten to be lazily evaluated, instead of strictly evaluated. =\ Unfortunately I have run out of time to work on this at work, but I'd welcome any contributions to implement that behavior. I've confirmed that this test does pass in hogan. |
@jazzdan I've got an idea or two, I'll take a poke at it when I get a minute. |
@bobthecow did you ever get a chance to look at the GRMustache test case? Is there anything we can do to help get this into a release? Thanks for all your help so far, happy to report this has been running in production at @etsy for a few months now with no issues. |
@jazzdan Thanks for the poke. Unfortunately I haven't had the time to take a shot at that last issue. I'm fine releasing this as-is, with the understanding that it'll most likely change to be lazily evaluated in the future. Since that's the case, I'd prefer to guard the feature with a pragma to make it extra-clear that this isn't part of the spec :) I've merged your branch and added a pragma in the pragma-blocks feature branch. I've also added the ability to globally enable pragmas in the Engine, since that seems to be a much more useful way to enable them most of the time... take a look at that and let me know what you think? If you think this works, I'd like to add some documentation around this specific pragma / feature (including the bit about lazy evaluation), then it should be ready to drop into a release. |
@bobthecow awesome, thanks! I'd be happy to write up a wiki page here on it some time this week. |
That'd be great, thanks :) |
First draft of documentation available here. |
@jazzdan Thanks :) |
Sorry about the delay. This has been merged into |
Modeled after hogan.js, with functional tests for template inheritance copied from the hogan.js test suite.
An additional test I performed was modifying the mustache/spec to have these same template inheritance rules. I then successfully ran the test suites of mustache.php with this patch and the most recent version of hogan.js after pointing them both at my modified mustache spec. It appears that they are both 100% compatible with one another.
This wasn't a solo effort by any means. Much thanks to my coworkers @dtb, @mdg and @benburry at @etsy.