Dynamic names implementation.
This commit is contained in:
@@ -117,6 +117,7 @@ class Mustache_Compiler
|
||||
case Mustache_Tokenizer::T_PARTIAL:
|
||||
$code .= $this->partial(
|
||||
$node[Mustache_Tokenizer::NAME],
|
||||
isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false,
|
||||
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
|
||||
$level
|
||||
);
|
||||
@@ -125,6 +126,7 @@ class Mustache_Compiler
|
||||
case Mustache_Tokenizer::T_PARENT:
|
||||
$code .= $this->parent(
|
||||
$node[Mustache_Tokenizer::NAME],
|
||||
isset($node[Mustache_Tokenizer::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false,
|
||||
isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
|
||||
$node[Mustache_Tokenizer::NODES],
|
||||
$level
|
||||
@@ -419,6 +421,30 @@ class Mustache_Compiler
|
||||
return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $filters, $this->walk($nodes, $level));
|
||||
}
|
||||
|
||||
const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s), $context)';
|
||||
|
||||
/**
|
||||
* Generate Mustache Template dynamic name resolution PHP source.
|
||||
*
|
||||
* @param string $id Tag name
|
||||
* @param bool $dynamic True if the name is dynamic
|
||||
*
|
||||
* @return string Dynamic name resolution PHP source code
|
||||
*/
|
||||
private function resolveDynamicName($id, $dynamic)
|
||||
{
|
||||
if (!$dynamic) {
|
||||
return var_export($id, true);
|
||||
}
|
||||
|
||||
$method = $this->getFindMethod($id);
|
||||
$id = ($method !== 'last') ? var_export($id, true) : '';
|
||||
|
||||
// TODO: filters?
|
||||
|
||||
return sprintf(self::DYNAMIC_NAME, $method, $id);
|
||||
}
|
||||
|
||||
const PARTIAL_INDENT = ', $indent . %s';
|
||||
const PARTIAL = '
|
||||
if ($partial = $this->mustache->loadPartial(%s)) {
|
||||
@@ -430,12 +456,13 @@ class Mustache_Compiler
|
||||
* Generate Mustache Template partial call PHP source.
|
||||
*
|
||||
* @param string $id Partial name
|
||||
* @param bool $dynamic Partial name is dynamic
|
||||
* @param string $indent Whitespace indent to apply to partial
|
||||
* @param int $level
|
||||
*
|
||||
* @return string Generated partial call PHP source code
|
||||
*/
|
||||
private function partial($id, $indent, $level)
|
||||
private function partial($id, $dynamic, $indent, $level)
|
||||
{
|
||||
if ($indent !== '') {
|
||||
$indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
|
||||
@@ -445,7 +472,7 @@ class Mustache_Compiler
|
||||
|
||||
return sprintf(
|
||||
$this->prepare(self::PARTIAL, $level),
|
||||
var_export($id, true),
|
||||
$this->resolveDynamicName($id, $dynamic),
|
||||
$indentParam
|
||||
);
|
||||
}
|
||||
@@ -469,23 +496,25 @@ class Mustache_Compiler
|
||||
* Generate Mustache Template inheritance parent call PHP source.
|
||||
*
|
||||
* @param string $id Parent tag name
|
||||
* @param bool $dynamic Tag name is dynamic
|
||||
* @param string $indent Whitespace indent to apply to parent
|
||||
* @param array $children Child nodes
|
||||
* @param int $level
|
||||
*
|
||||
* @return string Generated PHP source code
|
||||
*/
|
||||
private function parent($id, $indent, array $children, $level)
|
||||
private function parent($id, $dynamic, $indent, array $children, $level)
|
||||
{
|
||||
$realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
|
||||
$partialName = $this->resolveDynamicName($id, $dynamic);
|
||||
|
||||
if (empty($realChildren)) {
|
||||
return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true));
|
||||
return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), $partialName);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
$this->prepare(self::PARENT, $level),
|
||||
var_export($id, true),
|
||||
$partialName,
|
||||
$this->walk($realChildren, $level + 1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ class Mustache_Engine
|
||||
const PRAGMA_FILTERS = 'FILTERS';
|
||||
const PRAGMA_BLOCKS = 'BLOCKS';
|
||||
const PRAGMA_ANCHORED_DOT = 'ANCHORED-DOT';
|
||||
const PRAGMA_DYNAMIC_NAMES = 'DYNAMIC-NAMES';
|
||||
|
||||
// Known pragmas
|
||||
private static $knownPragmas = array(
|
||||
self::PRAGMA_FILTERS => true,
|
||||
self::PRAGMA_BLOCKS => true,
|
||||
self::PRAGMA_ANCHORED_DOT => true,
|
||||
self::PRAGMA_DYNAMIC_NAMES => true,
|
||||
);
|
||||
|
||||
// Template cache
|
||||
|
||||
@@ -23,6 +23,7 @@ class Mustache_Parser
|
||||
|
||||
private $pragmaFilters;
|
||||
private $pragmaBlocks;
|
||||
private $pragmaDynamicNames;
|
||||
|
||||
/**
|
||||
* Process an array of Mustache tokens and convert them into a parse tree.
|
||||
@@ -39,6 +40,7 @@ class Mustache_Parser
|
||||
|
||||
$this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
|
||||
$this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);
|
||||
$this->pragmaDynamicNames = isset($this->pragmas[Mustache_Engine::PRAGMA_DYNAMIC_NAMES]);
|
||||
|
||||
return $this->buildTree($tokens);
|
||||
}
|
||||
@@ -84,6 +86,14 @@ class Mustache_Parser
|
||||
$this->lineTokens = 0;
|
||||
}
|
||||
|
||||
if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) {
|
||||
list($name, $isDynamic) = $this->getDynamicName($token);
|
||||
if ($isDynamic) {
|
||||
$token[Mustache_Tokenizer::NAME] = $name;
|
||||
$token[Mustache_Tokenizer::DYNAMIC] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
|
||||
list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
|
||||
if (!empty($filters)) {
|
||||
@@ -280,6 +290,57 @@ class Mustache_Parser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse dynamic names.
|
||||
*
|
||||
* @throws Mustache_Exception_SyntaxException when a tag does not allow *
|
||||
* @throws Mustache_Exception_SyntaxException on multiple *s, or dots or filters with *
|
||||
*/
|
||||
private function getDynamicName(array $token)
|
||||
{
|
||||
$name = $token[Mustache_Tokenizer::NAME];
|
||||
$isDynamic = false;
|
||||
|
||||
if (preg_match('/^\s*\*\s*/', $name)) {
|
||||
$this->ensureTagAllowsDynamicNames($token);
|
||||
$name = preg_replace('/^\s*\*\s*/', '', $name);
|
||||
$isDynamic = true;
|
||||
}
|
||||
|
||||
// Two stars is two many!
|
||||
if (preg_match('/^\s*\*\s*/', $name) || preg_match('/[|.]\s*\*/', $name)) {
|
||||
$msg = sprintf('Invalid dynamic name: %s', $name);
|
||||
throw new Mustache_Exception_SyntaxException($msg, $token);
|
||||
}
|
||||
|
||||
return array($name, $isDynamic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given token supports dynamic tag names.
|
||||
*
|
||||
* @throws Mustache_Exception_SyntaxException when a tag does not allow *
|
||||
*
|
||||
* @param array $token
|
||||
*/
|
||||
private function ensureTagAllowsDynamicNames(array $token)
|
||||
{
|
||||
switch ($token[Mustache_Tokenizer::TYPE]) {
|
||||
case Mustache_Tokenizer::T_PARTIAL:
|
||||
case Mustache_Tokenizer::T_PARENT:
|
||||
return;
|
||||
}
|
||||
|
||||
$msg = sprintf(
|
||||
'Invalid dynamic name: %s in %s tag',
|
||||
$token[Mustache_Tokenizer::NAME],
|
||||
$token[Mustache_Tokenizer::TYPE]
|
||||
);
|
||||
|
||||
throw new Mustache_Exception_SyntaxException($msg, $token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Split a tag name into name and filters.
|
||||
*
|
||||
@@ -312,6 +373,10 @@ class Mustache_Parser
|
||||
case Mustache_Engine::PRAGMA_FILTERS:
|
||||
$this->pragmaFilters = true;
|
||||
break;
|
||||
|
||||
case Mustache_Engine::PRAGMA_DYNAMIC_NAMES:
|
||||
$this->pragmaDynamicNames = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class Mustache_Tokenizer
|
||||
// Token properties
|
||||
const TYPE = 'type';
|
||||
const NAME = 'name';
|
||||
const DYNAMIC = 'dynamic';
|
||||
const OTAG = 'otag';
|
||||
const CTAG = 'ctag';
|
||||
const LINE = 'line';
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Mustache.php.
|
||||
*
|
||||
* (c) 2010-2017 Justin Hileman
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A PHPUnit test case wrapping the Mustache Spec.
|
||||
*
|
||||
* @group mustache-spec
|
||||
* @group functional
|
||||
*/
|
||||
class Mustache_Test_Functional_MustacheDynamicNamesSpecTest extends Mustache_Test_SpecTestCase
|
||||
{
|
||||
public static function setUpBeforeClass()
|
||||
{
|
||||
self::$mustache = new Mustache_Engine(array(
|
||||
'pragmas' => array(Mustache_Engine::PRAGMA_DYNAMIC_NAMES),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* For some reason data providers can't mark tests skipped, so this test exists
|
||||
* simply to provide a 'skipped' test if the `spec` submodule isn't initialized.
|
||||
*/
|
||||
public function testSpecInitialized()
|
||||
{
|
||||
if (!file_exists(dirname(__FILE__) . '/../../../../vendor/spec/specs/')) {
|
||||
$this->markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @group dynamic-names
|
||||
* @dataProvider loadDynamicNamesSpec
|
||||
*/
|
||||
public function testDynamicNamesSpec($desc, $source, $partials, $data, $expected)
|
||||
{
|
||||
$template = self::loadTemplate($source, $partials);
|
||||
$this->assertEquals($expected, $template->render($data), $desc);
|
||||
}
|
||||
|
||||
public function loadDynamicNamesSpec()
|
||||
{
|
||||
return $this->loadSpec('~dynamic-names');
|
||||
}
|
||||
}
|
||||
Vendored
+1
-1
Submodule vendor/spec updated: b2aeb3c283...1704964eb6
Reference in New Issue
Block a user