Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
890f1c6
Initial release: Partial block helper
thekid Sep 17, 2017
5d9d96b
Replace template loader with implementation supporting `@partial-block`
thekid Sep 17, 2017
db97d3d
Add partial block helper
thekid Sep 17, 2017
413ca52
Add possibility to test with templates
thekid Sep 17, 2017
bf1726b
Add test for parsing partial blocks
thekid Sep 17, 2017
46df23d
Initial support for inline partials
thekid Sep 17, 2017
9cff7f3
Fix return
thekid Sep 17, 2017
0c4c560
QA: Add missing apidocs
thekid Sep 17, 2017
034b6af
Add tests for Templates class
thekid Sep 17, 2017
d41ff92
Upgrade to xp-forge/mustache v5.1.0 and make use of new template load…
thekid Sep 17, 2017
9f6a63d
Test node handling
thekid Sep 17, 2017
377aba7
Fix listing()
thekid Sep 17, 2017
f11dd79
Add link to handlebars spec
thekid Sep 17, 2017
b662f32
Fix register() not working with Source instances
thekid Sep 17, 2017
6e88762
Eagerly evaluate partial blocks, so nested inline partials get evaluated
thekid Sep 17, 2017
7e7f438
Extend com.handlebarsjs.unittest.PartialBlockHelperTest::layout() test
thekid Sep 17, 2017
1ef5167
Only evaluate `@partial-block` once
thekid Sep 17, 2017
8d23ec6
Add tests for nested partial blocks
thekid Sep 17, 2017
29517dd
Fix partial blocks within each
thekid Sep 17, 2017
37ea0db
Add tests for contexts
thekid Sep 17, 2017
5310886
Refactor and support decorators
thekid Sep 17, 2017
3136259
Add Decoration class
thekid Sep 17, 2017
27a7539
QA: Shorten invocation code
thekid Sep 17, 2017
5e43601
Rename decorators() -> enter()
thekid Sep 17, 2017
16a0ed6
Document Decorators
thekid Sep 17, 2017
da39668
Speed up decorator name lookup
thekid Sep 17, 2017
e292e24
Rename evaluate() -> enter()
thekid Sep 17, 2017
fd5efef
Do not run decorators twice
thekid Sep 18, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"keywords": ["module", "xp"],
"require" : {
"xp-framework/core": "^9.0 | ^8.0 | ^7.0 | ^6.5",
"xp-forge/mustache": "^5.0.1",
"xp-forge/mustache": "^5.1",
"xp-framework/logging": "^8.0 | ^7.0 | ^6.5",
"php" : ">=5.6.0"
},
Expand Down
2 changes: 2 additions & 0 deletions src/main/php/com/handlebarsjs/BlockHelpers.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ static function __static() {
self::$byName['unless']= XPClass::forName('com.handlebarsjs.UnlessBlockHelper');
self::$byName['with']= XPClass::forName('com.handlebarsjs.WithBlockHelper');
self::$byName['each']= XPClass::forName('com.handlebarsjs.EachBlockHelper');
self::$byName['>']= XPClass::forName('com.handlebarsjs.PartialBlockHelper');
self::$byName['*inline']= XPClass::forName('com.handlebarsjs.InlinePartialHelper');

@thekid thekid Sep 17, 2017

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not support generic decorators as e.g.

{{#grid data}}
  {{#*column "Column 1"}}{{foo}}{{/column}}
  {{#*column "Column 2"}}{{bar}}{{/column}}
{{/grid}}

...just *inline as a special case! This is the same as the original implementation in handlebars-java

}

/**
Expand Down
11 changes: 6 additions & 5 deletions src/main/php/com/handlebarsjs/HandlebarsEngine.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@
* @see http://handlebarsjs.com/
*/
class HandlebarsEngine {
protected $mustache;
protected $mustache, $templates;
protected $builtin= [];

/**
* Constructor. Initializes builtin helpers.
*/
public function __construct() {
$this->mustache= (new MustacheEngine())->withParser(new HandlebarsParser());
$this->templates= new Templates();
$this->mustache= (new MustacheEngine())->withTemplates($this->templates)->withParser(new HandlebarsParser());

// This: Access the current value in the context
$this->setBuiltin('this', function($node, $context, $options) {
Expand Down Expand Up @@ -115,11 +116,11 @@ public function withLogger($logger) {
/**
* Sets template loader to be used
*
* @param com.github.mustache.TemplateLoader $l
* @param com.github.mustache.templates.Templates|com.github.mustache.TemplateLoader $l
* @return self this
*/
public function withTemplates(TemplateLoader $l) {
$this->mustache->withTemplates($l);
public function withTemplates($l) {
$this->templates->delegate($l);
return $this;
}

Expand Down
39 changes: 39 additions & 0 deletions src/main/php/com/handlebarsjs/InlinePartialHelper.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php namespace com\handlebarsjs;

use com\github\mustache\NodeList;

/**
* Partial blocks
*
* @see http://handlebarsjs.com/partials.html
* @test xp://com.handlebarsjs.unittest.InlinePartialHelperTest
*/
class InlinePartialHelper extends BlockNode {

/**
* Creates a new with block helper
*
* @param string[] $options
* @param com.github.mustache.NodeList $fn
* @param com.github.mustache.NodeList $inverse
* @param string $start
* @param string $end
*/
public function __construct($options= [], NodeList $fn= null, NodeList $inverse= null, $start= '{{', $end= '}}') {
parent::__construct('inline', $options, $fn, $inverse, $start, $end);
}

/**
* Evaluates this node
*
* @param com.github.mustache.Context $context the rendering context
* @return string
*/
public function evaluate($context) {
$f= $this->options[0];
$target= $f($this, $context, []);

$context->engine->getTemplates()->register($target, $this->fn);
return '';
}
}
48 changes: 48 additions & 0 deletions src/main/php/com/handlebarsjs/PartialBlockHelper.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php namespace com\handlebarsjs;

use com\github\mustache\NodeList;

/**
* Partial blocks
*
* @see http://handlebarsjs.com/partials.html
* @test xp://com.handlebarsjs.unittest.PartialBlockHelperTest
*/
class PartialBlockHelper extends BlockNode {

/**
* Creates a new with block helper
*
* @param string[] $options
* @param com.github.mustache.NodeList $fn
* @param com.github.mustache.NodeList $inverse
* @param string $start
* @param string $end
*/
public function __construct($options= [], NodeList $fn= null, NodeList $inverse= null, $start= '{{', $end= '}}') {
$template= (string)array_shift($options);
parent::__construct($template, $options, $fn, $inverse, $start, $end);
}

/**
* Evaluates this node
*
* @param com.github.mustache.Context $context the rendering context
* @return string
*/
public function evaluate($context) {
$templates= $context->engine->getTemplates();

$source= $templates->source($this->name);
if ($source->exists()) {
$previous= $templates->register('@partial-block', $this->fn);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will re-run the decorators unnecessarily...

try {
return $context->engine->render($source, $context, $this->start, $this->end, '');
} finally {
$templates->register('@partial-block', $previous);
}
} else {
return $this->fn->evaluate($context);
}
}
}
66 changes: 66 additions & 0 deletions src/main/php/com/handlebarsjs/Templates.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php namespace com\handlebarsjs;

use text\StringTokenizer;
use com\github\mustache\Node;
use com\github\mustache\Template;
use com\github\mustache\templates\Tokens;
use com\github\mustache\templates\Compiled;
use com\github\mustache\templates\NotFound;

/**
* Template loading implementation
*
* @test xp://com.handlebarsjs.unittest.TemplatesTest
*/
class Templates extends \com\github\mustache\templates\Templates {
private $templates= [];
private $delegate;

/**
* Sets delegate loader
*
* @param com.github.mustache.templates.Templates $delegate
* @return void
*/
public function delegate($delegate) {
$this->delegate= $delegate;
}

/**
* Adds template
*
* @param string $name
* @param string|com.github.mustache.Node $content
* @return string
*/
public function register($name, $content) {
$previous= isset($this->templates[$name]) ? $this->templates[$name] : null;
if ($content instanceof Node) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch is not tested

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Done!

$this->templates[$name]= new Compiled(new Template($name, $content));
} else {
$this->templates[$name]= new Tokens($name, new StringTokenizer($content));
}
return $previous;
}

/**
* Load a template by a given name
*
* @param string $name The template name without file extension
* @return com.github.mustache.templates.Source
*/
public function source($name) {
if (isset($this->templates[$name])) {
return $this->templates[$name];
} else if ($this->delegate) {
return $this->delegate->source($name);
} else {
return new NotFound('Cannot find template '.$name);
}
}

/** @return com.github.mustache.TemplateListing */
public function listing() {
return $this->delegate->listing();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct, the listing is missing the templates registered in $this->templates; also, it fails if $this->delegate is null.

}
}
9 changes: 8 additions & 1 deletion src/test/php/com/handlebarsjs/unittest/HelperTest.class.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
<?php namespace com\handlebarsjs\unittest;

use com\handlebarsjs\HandlebarsEngine;
use com\github\mustache\InMemory;

abstract class HelperTest extends \unittest\TestCase {
protected $templates;

/** @return void */
public function setUp() {
$this->templates= new InMemory();
}

/**
* Evaluate a string template against given variables and return the output.
Expand All @@ -12,6 +19,6 @@ abstract class HelperTest extends \unittest\TestCase {
* @return string
*/
protected function evaluate($template, $variables) {
return (new HandlebarsEngine())->render($template, $variables);
return (new HandlebarsEngine())->withTemplates($this->templates)->render($template, $variables);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php namespace com\handlebarsjs\unittest;

class InlinePartialHelperTest extends HelperTest {

#[@test]
public function basic_usage() {
$template= '{{#*inline "myPartial"}}My Content{{/inline}}{{#each children}}{{> myPartial}} {{/each}}';
$this->assertEquals('My Content My Content My Content ', $this->evaluate($template, ['children' => [1, 2, 3]]));
}
}
23 changes: 23 additions & 0 deletions src/test/php/com/handlebarsjs/unittest/ParsingTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
use com\handlebarsjs\Quoted;
use com\handlebarsjs\Constant;
use com\handlebarsjs\Expression;
use com\handlebarsjs\PartialBlockHelper;
use com\handlebarsjs\InlinePartialHelper;
use com\github\mustache\NodeList;
use com\github\mustache\TextNode;
use com\github\mustache\VariableNode;
use com\github\mustache\TemplateFormatException;
use text\StringTokenizer;
Expand Down Expand Up @@ -100,6 +103,26 @@ public function dynamic_partial_with_context() {
);
}

#[@test]
public function partial_block() {
$this->assertEquals(
new NodeList([new PartialBlockHelper(['layout'], new NodeList([
new TextNode('Content')
]))]),
$this->parse('{{#> layout}}Content{{/layout}}')
);
}

#[@test]
public function inline_partial() {
$this->assertEquals(
new NodeList([new InlinePartialHelper([new Quoted('myPartial')], new NodeList([
new TextNode('Content')
]))]),
$this->parse('{{#*inline "myPartial"}}Content{{/inline}}')
);
}

#[@test, @expect(class= TemplateFormatException::class, withMessage= '/Illegal nesting/')]
public function incorrect_ending_tag() {
$this->parse('{{#each users}}...{{/each users}}');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php namespace com\handlebarsjs\unittest;

class PartialBlockHelperTest extends HelperTest {

#[@test]
public function existing_partial() {
$this->templates->add('layout', 'My layout');
$this->assertEquals('My layout', $this->evaluate('{{#> layout}}My content{{/layout}}', []));
}

#[@test]
public function existing_partial_with_partial_block() {
$this->templates->add('layout', 'My layout {{> @partial-block }}');
$this->assertEquals('My layout My content', $this->evaluate('{{#> layout}}My content{{/layout}}', []));
}

#[@test]
public function non_existant_partial_renders_default() {
$this->assertEquals('My content', $this->evaluate('{{#> layout}}My content{{/layout}}', []));
}
}
42 changes: 42 additions & 0 deletions src/test/php/com/handlebarsjs/unittest/TemplatesTest.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php namespace com\handlebarsjs\unittest;

use com\handlebarsjs\Templates;

class TemplatesTest extends \unittest\TestCase {

#[@test]
public function can_create() {
new Templates();
}

#[@test]
public function source() {
$fixture= new Templates();
$fixture->register('test', 'My content');
$this->assertEquals('My content', $fixture->source('test')->code());
}

#[@test]
public function non_existant_source() {
$fixture= new Templates();
$this->assertFalse($fixture->source('non-existant')->exists());
}

#[@test]
public function existant_source() {
$fixture= new Templates();
$fixture->register('test', 'My content');
$this->assertTrue($fixture->source('test')->exists());
}

#[@test]
public function register_returns_previous() {
$fixture= new Templates();

$prev= [];
$prev[]= $fixture->register('@partial-block', 'A');
$prev[]= $fixture->register('@partial-block', 'B')->code();

$this->assertEquals([null, 'A'], $prev);
}
}