Merge pull request #102 from bobthecow/feature/filters
Filters implementation.
This commit is contained in:
@@ -22,6 +22,7 @@ class Mustache_Compiler
|
|||||||
private $indentNextLine;
|
private $indentNextLine;
|
||||||
private $customEscape;
|
private $customEscape;
|
||||||
private $charset;
|
private $charset;
|
||||||
|
private $pragmas;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compile a Mustache token parse tree into PHP source code.
|
* Compile a Mustache token parse tree into PHP source code.
|
||||||
@@ -36,6 +37,7 @@ class Mustache_Compiler
|
|||||||
*/
|
*/
|
||||||
public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8')
|
public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8')
|
||||||
{
|
{
|
||||||
|
$this->pragmas = array();
|
||||||
$this->sections = array();
|
$this->sections = array();
|
||||||
$this->source = $source;
|
$this->source = $source;
|
||||||
$this->indentNextLine = true;
|
$this->indentNextLine = true;
|
||||||
@@ -61,6 +63,10 @@ class Mustache_Compiler
|
|||||||
$level++;
|
$level++;
|
||||||
foreach ($tree as $node) {
|
foreach ($tree as $node) {
|
||||||
switch ($node[Mustache_Tokenizer::TYPE]) {
|
switch ($node[Mustache_Tokenizer::TYPE]) {
|
||||||
|
case Mustache_Tokenizer::T_PRAGMA:
|
||||||
|
$this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case Mustache_Tokenizer::T_SECTION:
|
case Mustache_Tokenizer::T_SECTION:
|
||||||
$code .= $this->section(
|
$code .= $this->section(
|
||||||
$node[Mustache_Tokenizer::NODES],
|
$node[Mustache_Tokenizer::NODES],
|
||||||
@@ -263,7 +269,7 @@ class Mustache_Compiler
|
|||||||
$value = $this->mustache
|
$value = $this->mustache
|
||||||
->loadLambda((string) call_user_func($value))
|
->loadLambda((string) call_user_func($value))
|
||||||
->renderInternal($context, $indent);
|
->renderInternal($context, $indent);
|
||||||
}
|
}%s
|
||||||
$buffer .= %s%s;
|
$buffer .= %s%s;
|
||||||
';
|
';
|
||||||
|
|
||||||
@@ -278,11 +284,63 @@ class Mustache_Compiler
|
|||||||
*/
|
*/
|
||||||
private function variable($id, $escape, $level)
|
private function variable($id, $escape, $level)
|
||||||
{
|
{
|
||||||
|
$filters = '';
|
||||||
|
|
||||||
|
if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
|
||||||
|
list($id, $filters) = $this->getFilters($id, $level);
|
||||||
|
}
|
||||||
|
|
||||||
$method = $this->getFindMethod($id);
|
$method = $this->getFindMethod($id);
|
||||||
$id = ($method !== 'last') ? var_export($id, true) : '';
|
$id = ($method !== 'last') ? var_export($id, true) : '';
|
||||||
$value = $escape ? $this->getEscape() : '$value';
|
$value = $escape ? $this->getEscape() : '$value';
|
||||||
|
|
||||||
return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $this->flushIndent(), $value);
|
return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Mustache Template variable filtering PHP source.
|
||||||
|
*
|
||||||
|
* @param string $id Variable name
|
||||||
|
* @param int $level
|
||||||
|
*
|
||||||
|
* @return string Generated variable filtering PHP source
|
||||||
|
*/
|
||||||
|
private function getFilters($id, $level)
|
||||||
|
{
|
||||||
|
$filters = array_map('trim', explode('|', $id));
|
||||||
|
$id = array_shift($filters);
|
||||||
|
|
||||||
|
return array($id, $this->getFilter($filters, $level));
|
||||||
|
}
|
||||||
|
|
||||||
|
const FILTER = '
|
||||||
|
$filter = $context->%s(%s);
|
||||||
|
if (is_string($filter) || !is_callable($filter)) {
|
||||||
|
throw new UnexpectedValueException(%s);
|
||||||
|
}
|
||||||
|
$value = call_user_func($filter, $value);%s
|
||||||
|
';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate PHP source for a single filter.
|
||||||
|
*
|
||||||
|
* @param array $filters
|
||||||
|
* @param int $level
|
||||||
|
*
|
||||||
|
* @return string Generated filter PHP source
|
||||||
|
*/
|
||||||
|
private function getFilter(array $filters, $level)
|
||||||
|
{
|
||||||
|
if (empty($filters)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = array_shift($filters);
|
||||||
|
$method = $this->getFindMethod($name);
|
||||||
|
$filter = ($method !== 'last') ? var_export($name, true) : '';
|
||||||
|
$msg = var_export(sprintf('Filter not found: %s', $name), true);
|
||||||
|
|
||||||
|
return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $msg, $this->getFilter($filters, $level));
|
||||||
}
|
}
|
||||||
|
|
||||||
const LINE = '$buffer .= "\n";';
|
const LINE = '$buffer .= "\n";';
|
||||||
|
|||||||
@@ -23,8 +23,10 @@
|
|||||||
*/
|
*/
|
||||||
class Mustache_Engine
|
class Mustache_Engine
|
||||||
{
|
{
|
||||||
const VERSION = '2.0.2';
|
const VERSION = '2.0.2';
|
||||||
const SPEC_VERSION = '1.1.2';
|
const SPEC_VERSION = '1.1.2';
|
||||||
|
|
||||||
|
const PRAGMA_FILTERS = 'FILTERS';
|
||||||
|
|
||||||
// Template cache
|
// Template cache
|
||||||
private $templates = array();
|
private $templates = array();
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class Mustache_Tokenizer
|
|||||||
const T_UNESCAPED = '{';
|
const T_UNESCAPED = '{';
|
||||||
const T_UNESCAPED_2 = '&';
|
const T_UNESCAPED_2 = '&';
|
||||||
const T_TEXT = '_t';
|
const T_TEXT = '_t';
|
||||||
|
const T_PRAGMA = '%';
|
||||||
|
|
||||||
// Valid token types
|
// Valid token types
|
||||||
private static $tagTypes = array(
|
private static $tagTypes = array(
|
||||||
@@ -47,6 +48,7 @@ class Mustache_Tokenizer
|
|||||||
self::T_ESCAPED => true,
|
self::T_ESCAPED => true,
|
||||||
self::T_UNESCAPED => true,
|
self::T_UNESCAPED => true,
|
||||||
self::T_UNESCAPED_2 => true,
|
self::T_UNESCAPED_2 => true,
|
||||||
|
self::T_PRAGMA => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Interpolated tags
|
// Interpolated tags
|
||||||
@@ -67,6 +69,7 @@ class Mustache_Tokenizer
|
|||||||
const NODES = 'nodes';
|
const NODES = 'nodes';
|
||||||
const VALUE = 'value';
|
const VALUE = 'value';
|
||||||
|
|
||||||
|
private $pragmas;
|
||||||
private $state;
|
private $state;
|
||||||
private $tagType;
|
private $tagType;
|
||||||
private $tag;
|
private $tag;
|
||||||
@@ -126,6 +129,9 @@ class Mustache_Tokenizer
|
|||||||
if ($this->tagType === self::T_DELIM_CHANGE) {
|
if ($this->tagType === self::T_DELIM_CHANGE) {
|
||||||
$i = $this->changeDelimiters($text, $i);
|
$i = $this->changeDelimiters($text, $i);
|
||||||
$this->state = self::IN_TEXT;
|
$this->state = self::IN_TEXT;
|
||||||
|
} elseif ($this->tagType === self::T_PRAGMA) {
|
||||||
|
$i = $this->addPragma($text, $i);
|
||||||
|
$this->state = self::IN_TEXT;
|
||||||
} else {
|
} else {
|
||||||
if ($tag !== null) {
|
if ($tag !== null) {
|
||||||
$i++;
|
$i++;
|
||||||
@@ -168,6 +174,13 @@ class Mustache_Tokenizer
|
|||||||
|
|
||||||
$this->filterLine(true);
|
$this->filterLine(true);
|
||||||
|
|
||||||
|
foreach ($this->pragmas as $pragma) {
|
||||||
|
array_unshift($this->tokens, array(
|
||||||
|
self::TYPE => self::T_PRAGMA,
|
||||||
|
self::NAME => $pragma,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return $this->tokens;
|
return $this->tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +198,7 @@ class Mustache_Tokenizer
|
|||||||
$this->lineStart = 0;
|
$this->lineStart = 0;
|
||||||
$this->otag = '{{';
|
$this->otag = '{{';
|
||||||
$this->ctag = '}}';
|
$this->ctag = '}}';
|
||||||
|
$this->pragmas = array();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -270,6 +284,14 @@ class Mustache_Tokenizer
|
|||||||
return $closeIndex + strlen($close) - 1;
|
return $closeIndex + strlen($close) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function addPragma($text, $index)
|
||||||
|
{
|
||||||
|
$end = strpos($text, $this->ctag, $index);
|
||||||
|
$this->pragmas[] = trim(substr($text, $index + 2, $end - $index - 2));
|
||||||
|
|
||||||
|
return $end + strlen($this->ctag) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether it's time to change tags.
|
* Test whether it's time to change tags.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Mustache.php.
|
||||||
|
*
|
||||||
|
* (c) 2012 Justin Hileman
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group filters
|
||||||
|
* @group functional
|
||||||
|
*/
|
||||||
|
class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
private $mustache;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->mustache = new Mustache_Engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleFilter()
|
||||||
|
{
|
||||||
|
$tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ date | longdate }}');
|
||||||
|
|
||||||
|
$this->mustache->addHelper('longdate', function(\DateTime $value) {
|
||||||
|
return $value->format('Y-m-d h:m:s');
|
||||||
|
});
|
||||||
|
|
||||||
|
$foo = new \StdClass;
|
||||||
|
$foo->date = new DateTime('1/1/2000');
|
||||||
|
|
||||||
|
$this->assertEquals('2000-01-01 12:01:00', $tpl->render($foo));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testChainedFilters()
|
||||||
|
{
|
||||||
|
$tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ date | longdate | withbrackets }}');
|
||||||
|
|
||||||
|
$this->mustache->addHelper('longdate', function(\DateTime $value) {
|
||||||
|
return $value->format('Y-m-d h:m:s');
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->mustache->addHelper('withbrackets', function($value) {
|
||||||
|
return sprintf('[[%s]]', $value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$foo = new \StdClass;
|
||||||
|
$foo->date = new DateTime('1/1/2000');
|
||||||
|
|
||||||
|
$this->assertEquals('[[2000-01-01 12:01:00]]', $tpl->render($foo));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInterpolateFirst()
|
||||||
|
{
|
||||||
|
$tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ foo | bar }}');
|
||||||
|
$this->assertEquals('win!', $tpl->render(array(
|
||||||
|
'foo' => 'FOO',
|
||||||
|
'bar' => function($value) {
|
||||||
|
return ($value === 'FOO') ? 'win!' : 'fail :(';
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException UnexpectedValueException
|
||||||
|
* @dataProvider getBrokenPipes
|
||||||
|
*/
|
||||||
|
public function testThrowsExceptionForBrokenPipes($tpl, $data)
|
||||||
|
{
|
||||||
|
$this->mustache
|
||||||
|
->loadTemplate(sprintf('{{%% FILTERS }}{{ %s }}', $tpl))
|
||||||
|
->render($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBrokenPipes()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('foo | bar', array()),
|
||||||
|
array('foo | bar', array('foo' => 'FOO')),
|
||||||
|
array('foo | bar', array('foo' => 'FOO', 'bar' => 'BAR')),
|
||||||
|
array('foo | bar', array('foo' => 'FOO', 'bar' => array(1, 2))),
|
||||||
|
array('foo | bar | baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; })),
|
||||||
|
array('foo | bar | baz', array('foo' => 'FOO', 'baz' => function() { return 'BAZ'; })),
|
||||||
|
array('foo | bar | baz', array('bar' => function() { return 'BAR'; })),
|
||||||
|
array('foo | bar | baz', array('baz' => function() { return 'BAZ'; })),
|
||||||
|
array('foo | bar.baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; }, 'baz' => function() { return 'BAZ'; })),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user