Merge branch 'dev' into feature/higher-order-sections
Conflicts: Mustache.php
This commit is contained in:
+74
-20
@@ -33,7 +33,45 @@ class Mustache {
|
|||||||
// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
|
// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
|
||||||
protected $_charset = 'UTF-8';
|
protected $_charset = 'UTF-8';
|
||||||
|
|
||||||
const PRAGMA_DOT_NOTATION = 'DOT-NOTATION';
|
/**
|
||||||
|
* Pragmas are macro-like directives that, when invoked, change the behavior or
|
||||||
|
* syntax of Mustache.
|
||||||
|
*
|
||||||
|
* They should be considered extremely experimental. Most likely their implementation
|
||||||
|
* will change in the future.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {{%DOT-NOTATION}} pragma allows context traversal via dots. Given the following context:
|
||||||
|
*
|
||||||
|
* $context = array('foo' => array('bar' => array('baz' => 'qux')));
|
||||||
|
*
|
||||||
|
* One could access nested properties using dot notation:
|
||||||
|
*
|
||||||
|
* {{%DOT-NOTATION}}{{foo.bar.baz}}
|
||||||
|
*
|
||||||
|
* Which would render as `qux`.
|
||||||
|
*/
|
||||||
|
const PRAGMA_DOT_NOTATION = 'DOT-NOTATION';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {{%IMPLICIT-ITERATOR}} pragma allows access to non-associative array data in an
|
||||||
|
* iterable section:
|
||||||
|
*
|
||||||
|
* $context = array('items' => array('foo', 'bar', 'baz'));
|
||||||
|
*
|
||||||
|
* With this template:
|
||||||
|
*
|
||||||
|
* {{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}}
|
||||||
|
*
|
||||||
|
* Would render as `foobarbaz`.
|
||||||
|
*
|
||||||
|
* {{%IMPLICIT-ITERATOR}} accepts an optional 'iterator' argument which allows implicit
|
||||||
|
* iterator tags other than {{.}} ...
|
||||||
|
*
|
||||||
|
* {{%IMPLICIT-ITERATOR iterator=i}}{{#items}}{{i}}{{/items}}
|
||||||
|
*/
|
||||||
|
const PRAGMA_IMPLICIT_ITERATOR = 'IMPLICIT-ITERATOR';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}}
|
* The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}}
|
||||||
@@ -56,6 +94,7 @@ class Mustache {
|
|||||||
|
|
||||||
protected $_pragmasImplemented = array(
|
protected $_pragmasImplemented = array(
|
||||||
self::PRAGMA_DOT_NOTATION,
|
self::PRAGMA_DOT_NOTATION,
|
||||||
|
self::PRAGMA_IMPLICIT_ITERATOR,
|
||||||
self::PRAGMA_UNESCAPED
|
self::PRAGMA_UNESCAPED
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -188,25 +227,40 @@ class Mustache {
|
|||||||
|
|
||||||
// regular section
|
// regular section
|
||||||
case '#':
|
case '#':
|
||||||
if ($val) {
|
|
||||||
// higher order sections
|
// higher order sections
|
||||||
if (is_callable($val)) {
|
if (is_callable($val)) {
|
||||||
$content = call_user_func($val, $content);
|
$content = call_user_func($val, $content);
|
||||||
$replace .= $this->_renderTemplate($content);
|
$replace .= $this->_renderTemplate($content);
|
||||||
} else if ($this->_varIsIterable($val)) {
|
} else if ($this->_varIsIterable($val)) {
|
||||||
foreach ($val as $local_context) {
|
if ($this->_hasPragma(self::PRAGMA_IMPLICIT_ITERATOR)) {
|
||||||
$this->_pushContext($local_context);
|
if ($opt = $this->_getPragmaOptions(self::PRAGMA_IMPLICIT_ITERATOR)) {
|
||||||
$replace .= $this->_renderTemplate($content);
|
$iterator = $opt['iterator'];
|
||||||
$this->_popContext();
|
} else {
|
||||||
|
$iterator = '.';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (is_array($val) || is_object($val)) {
|
$iterator = false;
|
||||||
$this->_pushContext($val);
|
}
|
||||||
$replace .= $this->_renderTemplate($content);
|
|
||||||
$this->_popContext();
|
foreach ($val as $local_context) {
|
||||||
|
if ($iterator) {
|
||||||
|
$iterator_context = array($iterator => $local_context);
|
||||||
|
$this->_pushContext($iterator_context);
|
||||||
} else {
|
} else {
|
||||||
$replace .= $content;
|
$this->_pushContext($local_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$replace .= $this->_renderTemplate($content);
|
||||||
|
$this->_popContext();
|
||||||
|
}
|
||||||
|
} else if ($val) {
|
||||||
|
if (is_array($val) || is_object($val)) {
|
||||||
|
$this->_pushContext($val);
|
||||||
|
$replace .= $this->_renderTemplate($content);
|
||||||
|
$this->_popContext();
|
||||||
|
} else {
|
||||||
|
$replace .= $content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -297,11 +351,11 @@ class Mustache {
|
|||||||
* @throws MustacheException Unknown pragma
|
* @throws MustacheException Unknown pragma
|
||||||
*/
|
*/
|
||||||
protected function _getPragmaOptions($pragma_name) {
|
protected function _getPragmaOptions($pragma_name) {
|
||||||
if (!$this->_hasPragma()) {
|
if (!$this->_hasPragma($pragma_name)) {
|
||||||
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->_localPragmas[$pragma_name];
|
return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -527,7 +581,7 @@ class Mustache {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function _getVariable($tag_name) {
|
protected function _getVariable($tag_name) {
|
||||||
if ($this->_hasPragma(self::PRAGMA_DOT_NOTATION)) {
|
if ($this->_hasPragma(self::PRAGMA_DOT_NOTATION) && $tag_name != '.') {
|
||||||
$chunks = explode('.', $tag_name);
|
$chunks = explode('.', $tag_name);
|
||||||
$first = array_shift($chunks);
|
$first = array_shift($chunks);
|
||||||
|
|
||||||
@@ -603,7 +657,7 @@ class Mustache {
|
|||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
protected function _varIsIterable($var) {
|
protected function _varIsIterable($var) {
|
||||||
return is_object($var) || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
|
return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ImplicitIterator extends Mustache {
|
||||||
|
protected $data = array('Donkey Kong', 'Luigi', 'Mario', 'Peach', 'Yoshi');
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{{%IMPLICIT-ITERATOR}}
|
||||||
|
{{#data}}
|
||||||
|
* {{.}}
|
||||||
|
{{/data}}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
* Donkey Kong
|
||||||
|
* Luigi
|
||||||
|
* Mario
|
||||||
|
* Peach
|
||||||
|
* Yoshi
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SectionIteratorObjects extends Mustache {
|
||||||
|
public $start = "It worked the first time.";
|
||||||
|
|
||||||
|
public function middle() {
|
||||||
|
return new IteratorObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public $final = "Then, surprisingly, it worked the final time.";
|
||||||
|
}
|
||||||
|
|
||||||
|
class IteratorObject implements Iterator {
|
||||||
|
protected $_position = 0;
|
||||||
|
|
||||||
|
protected $_data = array(
|
||||||
|
array('item' => 'And it worked the second time.'),
|
||||||
|
array('item' => 'As well as the third.'),
|
||||||
|
);
|
||||||
|
|
||||||
|
public function rewind() {
|
||||||
|
$this->_position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function current() {
|
||||||
|
return $this->_data[$this->_position];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function key() {
|
||||||
|
return $this->_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function next() {
|
||||||
|
++$this->_position;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function valid() {
|
||||||
|
return isset($this->_data[$this->_position]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
* {{ start }}
|
||||||
|
{{# middle }}
|
||||||
|
* {{ item }}
|
||||||
|
{{/ middle }}
|
||||||
|
* {{ final }}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* It worked the first time.
|
||||||
|
* And it worked the second time.
|
||||||
|
* As well as the third.
|
||||||
|
* Then, surprisingly, it worked the final time.
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SectionMagicObjects extends Mustache {
|
||||||
|
public $start = "It worked the first time.";
|
||||||
|
|
||||||
|
public function middle() {
|
||||||
|
return new MagicObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public $final = "Then, surprisingly, it worked the final time.";
|
||||||
|
}
|
||||||
|
|
||||||
|
class MagicObject {
|
||||||
|
protected $_data = array(
|
||||||
|
'foo' => 'And it worked the second time.',
|
||||||
|
'bar' => 'As well as the third.'
|
||||||
|
);
|
||||||
|
|
||||||
|
public function __get($key) {
|
||||||
|
return isset($this->_data[$key]) ? $this->_data[$key] : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __isset($key) {
|
||||||
|
return isset($this->_data[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
* {{ start }}
|
||||||
|
{{# middle }}
|
||||||
|
* {{ foo }}
|
||||||
|
* {{ bar }}
|
||||||
|
{{/ middle }}
|
||||||
|
* {{ final }}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* It worked the first time.
|
||||||
|
* And it worked the second time.
|
||||||
|
* As well as the third.
|
||||||
|
* Then, surprisingly, it worked the final time.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class SectionObjects extends Mustache {
|
||||||
|
public $start = "It worked the first time.";
|
||||||
|
|
||||||
|
public function middle() {
|
||||||
|
return new SectionObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public $final = "Then, surprisingly, it worked the final time.";
|
||||||
|
}
|
||||||
|
|
||||||
|
class SectionObject {
|
||||||
|
public $foo = 'And it worked the second time.';
|
||||||
|
public $bar = 'As well as the third.';
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
* {{ start }}
|
||||||
|
{{# middle }}
|
||||||
|
* {{ foo }}
|
||||||
|
* {{ bar }}
|
||||||
|
{{/ middle }}
|
||||||
|
* {{ final }}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* It worked the first time.
|
||||||
|
* And it worked the second time.
|
||||||
|
* As well as the third.
|
||||||
|
* Then, surprisingly, it worked the final time.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once '../Mustache.php';
|
||||||
|
|
||||||
|
class MustacheObjectSectionTest extends PHPUnit_Framework_TestCase {
|
||||||
|
public function testBasicObject() {
|
||||||
|
$alpha = new Alpha();
|
||||||
|
$this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testObjectWithGet() {
|
||||||
|
$beta = new Beta();
|
||||||
|
$this->assertEquals('Foo', $beta->render('{{#foo}}{{name}}{{/foo}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSectionObjectWithGet() {
|
||||||
|
$gamma = new Gamma();
|
||||||
|
$this->assertEquals('Foo', $gamma->render('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Alpha extends Mustache {
|
||||||
|
public $foo;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->foo = new StdClass();
|
||||||
|
$this->foo->name = 'Foo';
|
||||||
|
$this->foo->number = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Beta extends Mustache {
|
||||||
|
protected $_data = array();
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->_data['foo'] = new StdClass();
|
||||||
|
$this->_data['foo']->name = 'Foo';
|
||||||
|
$this->_data['foo']->number = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __isset($name) {
|
||||||
|
return array_key_exists($name, $this->_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($name) {
|
||||||
|
return $this->_data[$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Gamma extends Mustache {
|
||||||
|
public $bar;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->bar = new Beta();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once '../Mustache.php';
|
||||||
|
require_once 'PHPUnit/Framework.php';
|
||||||
|
|
||||||
|
class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
|
public function testEnablePragma() {
|
||||||
|
$m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}'));
|
||||||
|
$m->expects($this->exactly(1))
|
||||||
|
->method('_renderPragma')
|
||||||
|
->with(array('{{%IMPLICIT-ITERATOR}}', 'IMPLICIT-ITERATOR', null));
|
||||||
|
$m->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImplicitIterator() {
|
||||||
|
$m1 = new Mustache('{{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}}', array('items' => array('a', 'b', 'c')));
|
||||||
|
$this->assertEquals('abc', $m1->render());
|
||||||
|
|
||||||
|
$m2 = new Mustache('{{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}}', array('items' => array(1, 2, 3)));
|
||||||
|
$this->assertEquals('123', $m2->render());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDotNotationCollision() {
|
||||||
|
$m = new Mustache(null, array('items' => array('foo', 'bar', 'baz')));
|
||||||
|
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR}}{{%DOT-NOTATION}}{{#items}}{{.}}{{/items}}'));
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%DOT-NOTATION}}{{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCustomIterator() {
|
||||||
|
$m = new Mustache(null, array('items' => array('foo', 'bar', 'baz')));
|
||||||
|
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR}}{{#items}}{{.}}{{/items}}'));
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR iterator=i}}{{#items}}{{i}}{{/items}}'));
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR iterator=items}}{{#items}}{{items}}{{/items}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDotNotationContext() {
|
||||||
|
$m = new Mustache(null, array('items' => array(
|
||||||
|
array('index' => 1, 'name' => 'foo'),
|
||||||
|
array('index' => 2, 'name' => 'bar'),
|
||||||
|
array('index' => 3, 'name' => 'baz'),
|
||||||
|
)));
|
||||||
|
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR}}{{#items}}{{#.}}{{name}}{{/.}}{{/items}}'));
|
||||||
|
$this->assertEquals('123', $m->render('{{%IMPLICIT-ITERATOR iterator=i}}{{%DOT-NOTATION}}{{#items}}{{i.index}}{{/items}}'));
|
||||||
|
$this->assertEquals('foobarbaz', $m->render('{{%IMPLICIT-ITERATOR iterator=i}}{{%DOT-NOTATION}}{{#items}}{{i.name}}{{/items}}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+17
-13
@@ -262,26 +262,30 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
|
|||||||
foreach ($children as $file) {
|
foreach ($children as $file) {
|
||||||
if (!$file->isFile()) continue;
|
if (!$file->isFile()) continue;
|
||||||
|
|
||||||
$filename = $file->getPathInfo();
|
$filename = $file->getPathname();
|
||||||
$info = pathinfo($filename);
|
$info = pathinfo($filename);
|
||||||
|
|
||||||
switch($info['extension']) {
|
if (isset($info['extension'])) {
|
||||||
case 'php':
|
switch($info['extension']) {
|
||||||
$class = $info['filename'];
|
case 'php':
|
||||||
include_once($filename);
|
$class = $info['filename'];
|
||||||
break;
|
include_once($filename);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'mustache':
|
case 'mustache':
|
||||||
$template = file_get_contents($filename);
|
$template = file_get_contents($filename);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'txt':
|
case 'txt':
|
||||||
$output = file_get_contents($filename);
|
$output = file_get_contents($filename);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ret[$example] = array($class, $template, $output);
|
if (!empty($class)) {
|
||||||
|
$ret[$example] = array($class, $template, $output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$files->next();
|
$files->next();
|
||||||
|
|||||||
Reference in New Issue
Block a user