Merge branch 'feature/higher-order-sections' into dev

This commit is contained in:
Justin Hileman
2011-09-10 14:04:02 -07:00
3 changed files with 193 additions and 24 deletions
+30 -4
View File
@@ -239,7 +239,10 @@ class Mustache {
// regular section
case '#':
if ($this->_varIsIterable($val)) {
// higher order sections
if ($this->_varIsCallable($val)) {
$rendered_content = $this->_renderTemplate(call_user_func($val, $content));
} else if ($this->_varIsIterable($val)) {
foreach ($val as $local_context) {
$this->_pushContext($local_context);
$rendered_content .= $this->_renderTemplate($content);
@@ -616,7 +619,7 @@ class Mustache {
* @return string
*/
protected function _renderEscaped($tag_name, $leading, $trailing) {
return $leading . htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset) . $trailing;
return htmlentities($this->_renderUnescaped($tag_name, $leading, $trailing), ENT_COMPAT, $this->_charset);
}
/**
@@ -648,7 +651,14 @@ class Mustache {
* @return string
*/
protected function _renderUnescaped($tag_name, $leading, $trailing) {
return $leading . $this->_getVariable($tag_name) . $trailing;
$val = $this->_getVariable($tag_name);
if ($this->_varIsCallable($val)) {
$key = is_object($val) ? spl_object_hash($val) : serialize($val);
$val = $this->_renderTemplate(call_user_func($val));
}
return $leading . $val . $trailing;
}
/**
@@ -830,6 +840,23 @@ class Mustache {
protected function _varIsIterable($var) {
return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
}
/**
* Higher order sections helper: tests whether the section $var is a valid callback.
*
* In Mustache.php, a variable is considered 'callable' if the variable is:
*
* 1. an anonymous function.
* 2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')`
* 3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')`
*
* @access protected
* @param mixed $var
* @return bool
*/
protected function _varIsCallable($var) {
return !is_string($var) && is_callable($var);
}
}
@@ -860,4 +887,3 @@ class MustacheException extends Exception {
const UNKNOWN_PRAGMA = 4;
}
+114
View File
@@ -0,0 +1,114 @@
<?php
require_once '../Mustache.php';
class MustacheHigherOrderSectionsTest extends PHPUnit_Framework_TestCase {
public function setUp() {
$this->foo = new Foo();
}
public function testAnonymousFunctionSectionCallback() {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3');
return;
}
$this->foo->wrapper = function($text) {
return sprintf('<div class="anonymous">%s</div>', $text);
};
$this->assertEquals(
sprintf('<div class="anonymous">%s</div>', $this->foo->name),
$this->foo->render('{{#wrapper}}{{name}}{{/wrapper}}')
);
}
public function testSectionCallback() {
$this->assertEquals(sprintf('%s', $this->foo->name), $this->foo->render('{{name}}'));
$this->assertEquals(sprintf('<em>%s</em>', $this->foo->name), $this->foo->render('{{#wrap}}{{name}}{{/wrap}}'));
}
public function testRuntimeSectionCallback() {
$this->foo->double_wrap = array($this->foo, 'wrapWithBoth');
$this->assertEquals(
sprintf('<strong><em>%s</em></strong>', $this->foo->name),
$this->foo->render('{{#double_wrap}}{{name}}{{/double_wrap}}')
);
}
public function testStaticSectionCallback() {
$this->foo->trimmer = array(get_class($this->foo), 'staticTrim');
$this->assertEquals($this->foo->name, $this->foo->render('{{#trimmer}} {{name}} {{/trimmer}}'));
}
public function testViewArraySectionCallback() {
$data = array(
'name' => 'Bob',
'trim' => array(get_class($this->foo), 'staticTrim'),
);
$this->assertEquals($data['name'], $this->foo->render('{{#trim}} {{name}} {{/trim}}', $data));
}
public function testViewArrayAnonymousSectionCallback() {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3');
return;
}
$data = array(
'name' => 'Bob',
'wrap' => function($text) {
return sprintf('[[%s]]', $text);
}
);
$this->assertEquals(
sprintf('[[%s]]', $data['name']),
$this->foo->render('{{#wrap}}{{name}}{{/wrap}}', $data)
);
}
public function testMonsters() {
$frank = new Monster();
$frank->title = 'Dr.';
$frank->name = 'Frankenstein';
$this->assertEquals('Dr. Frankenstein', $frank->render());
$dracula = new Monster();
$dracula->title = 'Count';
$dracula->name = 'Dracula';
$this->assertEquals('Count Dracula', $dracula->render());
}
}
class Foo extends Mustache {
public $name = 'Justin';
public $lorem = 'Lorem ipsum dolor sit amet,';
public $wrap;
public function __construct($template = null, $view = null, $partials = null) {
$this->wrap = array($this, 'wrapWithEm');
parent::__construct($template, $view, $partials);
}
public function wrapWithEm($text) {
return sprintf('<em>%s</em>', $text);
}
public function wrapWithStrong($text) {
return sprintf('<strong>%s</strong>', $text);
}
public function wrapWithBoth($text) {
return self::wrapWithStrong(self::wrapWithEm($text));
}
public static function staticTrim($text) {
return trim($text);
}
}
class Monster extends Mustache {
public $_template = '{{#title}}{{title}} {{/title}}{{name}}';
public $title;
public $name;
}
+49 -20
View File
@@ -57,20 +57,38 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase {
$this->assertEquals($expected, $m->render(), $desc);
}
// /**
// * @group lambdas
// * @dataProvider loadLambdasSpec
// */
// public function testLambdasSpec($desc, $template, $data, $partials, $expected) {
// $this->markTestSkipped("Lambdas for PHP haven't made it into the spec yet, so we'll skip them to avoid a bajillion failed tests.");
//
// if (!version_compare(PHP_VERSION, '5.3.0', '>=')) {
// $this->markTestSkipped('Unable to test Lambdas spec with PHP < 5.3.');
// }
//
// $m = new Mustache($template, $data, $partials);
// $this->assertEquals($expected, $m->render(), $desc);
// }
/**
* @group lambdas
* @dataProvider loadLambdasSpec
*/
public function testLambdasSpec($desc, $template, $data, $partials, $expected) {
if (!version_compare(PHP_VERSION, '5.3.0', '>=')) {
$this->markTestSkipped('Unable to test Lambdas spec with PHP < 5.3.');
}
$data = $this->prepareLambdasSpec($data);
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* Extract and lambdafy any 'lambda' values found in the $data array.
*/
protected function prepareLambdasSpec($data) {
foreach ($data as $key => $val) {
if ($key === 'lambda') {
if (!isset($val['php'])) {
$this->markTestSkipped(sprintf('PHP lambda test not implemented for this test.'));
}
$func = $val['php'];
$data[$key] = function($text = null) use ($func) { return eval($func); };
} else if (is_array($val)) {
$data[$key] = $this->prepareLambdasSpec($val);
}
}
return $data;
}
/**
* @group partials
@@ -106,9 +124,9 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase {
return $this->loadSpec('inverted');
}
// public function loadLambdasSpec() {
// return $this->loadSpec('lambdas');
// }
public function loadLambdasSpec() {
return $this->loadSpec('~lambdas');
}
public function loadPartialsSpec() {
return $this->loadSpec('partials');
@@ -133,12 +151,23 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase {
}
$data = array();
$yaml = new sfYamlParser();
$file = file_get_contents($filename);
$spec = $yaml->parse(file_get_contents($filename));
// @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
if ($name === '~lambdas') {
$file = str_replace(" !code\n", "\n", $file);
}
$spec = $yaml->parse($file);
foreach ($spec['tests'] as $test) {
$data[] = array($test['name'] . ': ' . $test['desc'], $test['template'], $test['data'], isset($test['partials']) ? $test['partials'] : array(), $test['expected']);
$data[] = array(
$test['name'] . ': ' . $test['desc'],
$test['template'],
$test['data'],
isset($test['partials']) ? $test['partials'] : array(),
$test['expected'],
);
}
return $data;
}