Merge branch 'dev' into feature/test-coverage
Conflicts: Mustache.php
This commit is contained in:
+111
-2
@@ -25,11 +25,18 @@ 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';
|
||||||
|
|
||||||
protected $tagRegEx;
|
protected $tagRegEx;
|
||||||
|
|
||||||
protected $template = '';
|
protected $template = '';
|
||||||
protected $context = array();
|
protected $context = array();
|
||||||
protected $partials = array();
|
protected $partials = array();
|
||||||
|
protected $pragmas = array();
|
||||||
|
|
||||||
|
protected $pragmasImplemented = array(
|
||||||
|
self::PRAGMA_DOT_NOTATION
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mustache class constructor.
|
* Mustache class constructor.
|
||||||
@@ -101,6 +108,7 @@ class Mustache {
|
|||||||
* @return string Rendered Mustache template.
|
* @return string Rendered Mustache template.
|
||||||
*/
|
*/
|
||||||
protected function _render($template, &$context) {
|
protected function _render($template, &$context) {
|
||||||
|
$template = $this->renderPragmas($template, $context);
|
||||||
$template = $this->renderSection($template, $context);
|
$template = $this->renderSection($template, $context);
|
||||||
return $this->renderTags($template, $context);
|
return $this->renderTags($template, $context);
|
||||||
}
|
}
|
||||||
@@ -158,6 +166,74 @@ class Mustache {
|
|||||||
return $template;
|
return $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize pragmas and remove all pragma tags.
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @param string $template
|
||||||
|
* @param array &$context
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function renderPragmas($template, &$context) {
|
||||||
|
// no pragmas
|
||||||
|
if (strpos($template, $this->otag . '%') === false) {
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
|
$otag = $this->prepareRegEx($this->otag);
|
||||||
|
$ctag = $this->prepareRegEx($this->ctag);
|
||||||
|
$regex = '/' . $otag . '%([\\w_-]+)((?: [\\w]+=[\\w]+)*)' . $ctag . '\\n?/';
|
||||||
|
return preg_replace_callback($regex, array($this, 'renderPragma'), $template);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma.
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @param mixed $matches
|
||||||
|
* @return void
|
||||||
|
* @throws MustacheException unknown pragma
|
||||||
|
*/
|
||||||
|
protected function renderPragma($matches) {
|
||||||
|
$pragma = $matches[0];
|
||||||
|
$pragma_name = $matches[1];
|
||||||
|
$options_string = $matches[2];
|
||||||
|
|
||||||
|
if (!in_array($pragma_name, $this->pragmasImplemented)) {
|
||||||
|
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = array();
|
||||||
|
foreach (explode(' ', trim($options_string)) as $o) {
|
||||||
|
if ($p = trim($o)) {
|
||||||
|
$p = explode('=', trim($p));
|
||||||
|
$options[$p[0]] = $p[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($options)) {
|
||||||
|
$this->pragmas[$pragma_name] = true;
|
||||||
|
} else {
|
||||||
|
$this->pragmas[$pragma_name] = $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasPragma($pragma_name) {
|
||||||
|
if (array_key_exists($pragma_name, $this->pragmas) && $this->pragmas[$pragma_name]) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getPragmaOptions($pragma_name) {
|
||||||
|
if (!$this->hasPragma()) {
|
||||||
|
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pragmas[$pragma_name];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loop through and render individual Mustache tags.
|
* Loop through and render individual Mustache tags.
|
||||||
*
|
*
|
||||||
@@ -171,9 +247,11 @@ class Mustache {
|
|||||||
return $template;
|
return $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
$otag = $this->prepareRegEx($this->otag);
|
$otag = $this->prepareRegEx($this->otag);
|
||||||
$ctag = $this->prepareRegEx($this->ctag);
|
$ctag = $this->prepareRegEx($this->ctag);
|
||||||
|
|
||||||
$this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/";
|
$this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/";
|
||||||
|
|
||||||
$html = '';
|
$html = '';
|
||||||
$matches = array();
|
$matches = array();
|
||||||
while (preg_match($this->tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
|
while (preg_match($this->tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
|
||||||
@@ -347,6 +425,33 @@ class Mustache {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function getVariable($tag_name, &$context) {
|
protected function getVariable($tag_name, &$context) {
|
||||||
|
if ($this->hasPragma(self::PRAGMA_DOT_NOTATION)) {
|
||||||
|
$chunks = explode('.', $tag_name);
|
||||||
|
$first = array_shift($chunks);
|
||||||
|
|
||||||
|
$ret = $this->_getVariable($first, $context);
|
||||||
|
while ($next = array_shift($chunks)) {
|
||||||
|
// Slice off a chunk of context for dot notation traversal.
|
||||||
|
$c = array($ret);
|
||||||
|
$ret = $this->_getVariable($next, $c);
|
||||||
|
}
|
||||||
|
return $ret;
|
||||||
|
} else {
|
||||||
|
return $this->_getVariable($tag_name, $context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a variable from the context array. Internal helper used by getVariable() to abstract
|
||||||
|
* variable traversal for dot notation.
|
||||||
|
*
|
||||||
|
* @access protected
|
||||||
|
* @param string $tag_name
|
||||||
|
* @param array &$context
|
||||||
|
* @throws MustacheException Unknown variable name.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function _getVariable($tag_name, &$context) {
|
||||||
foreach ($context as $view) {
|
foreach ($context as $view) {
|
||||||
if (is_object($view)) {
|
if (is_object($view)) {
|
||||||
if (isset($view->$tag_name)) {
|
if (isset($view->$tag_name)) {
|
||||||
@@ -439,4 +544,8 @@ class MustacheException extends Exception {
|
|||||||
// with no associated partial.
|
// with no associated partial.
|
||||||
const UNKNOWN_PARTIAL = 3;
|
const UNKNOWN_PARTIAL = 3;
|
||||||
|
|
||||||
|
// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears
|
||||||
|
// which can't be handled by this Mustache instance.
|
||||||
|
const UNKNOWN_PRAGMA = 4;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -81,6 +81,8 @@ And render it:
|
|||||||
Known Issues
|
Known Issues
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
* There's no way to toggle a pragma other than checking out the feature branch for each pragma...
|
||||||
|
Need a clean way to do this.
|
||||||
* Sections don't respect delimiter changes -- `delimiters` example currently fails with an
|
* Sections don't respect delimiter changes -- `delimiters` example currently fails with an
|
||||||
"unclosed section" exception.
|
"unclosed section" exception.
|
||||||
* Test coverage is incomplete.
|
* Test coverage is incomplete.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DotNotation example class. Uses DOT_NOTATION pragma.
|
||||||
|
*
|
||||||
|
* @extends Mustache
|
||||||
|
*/
|
||||||
|
class DotNotation extends Mustache {
|
||||||
|
public $person = array(
|
||||||
|
'name' => array('first' => 'Chris', 'last' => 'Firescythe'),
|
||||||
|
'age' => 24,
|
||||||
|
'hometown' => array(
|
||||||
|
'city' => 'Cincinnati',
|
||||||
|
'state' => 'OH',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
public $normal = 'Normal';
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{{%DOT-NOTATION}}
|
||||||
|
* {{person.name.first}} {{person.name.last}}
|
||||||
|
* {{person.age}}
|
||||||
|
* {{person.hometown.city}}, {{person.hometown.state}}
|
||||||
|
* {{normal}}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* Chris Firescythe
|
||||||
|
* 24
|
||||||
|
* Cincinnati, OH
|
||||||
|
* Normal
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once '../Mustache.php';
|
||||||
|
require_once 'PHPUnit/Framework.php';
|
||||||
|
|
||||||
|
class MustachePragmaDotNotationTest extends PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
|
public function testDotTraversal() {
|
||||||
|
$m = new Mustache('', array('foo' => array('bar' => 'this worked')));
|
||||||
|
|
||||||
|
$this->assertEquals($m->render('{{foo.bar}}'), '',
|
||||||
|
'Dot notation not enabled, variable should have been replaced with nothing');
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}}{{foo.bar}}'), 'this worked',
|
||||||
|
'Dot notation enabled, variable should have been replaced by "this worked"');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDeepTraversal() {
|
||||||
|
$data = array(
|
||||||
|
'foo' => array('bar' => array('baz' => array('qux' => array('quux' => 'WIN!')))),
|
||||||
|
'a' => array('b' => array('c' => array('d' => array('e' => 'abcs')))),
|
||||||
|
'one' => array(
|
||||||
|
'one' => 'one-one',
|
||||||
|
'two' => 'one-two',
|
||||||
|
'three' => 'one-three',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
$m = new Mustache('', $data);
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}}{{foo.bar.baz.qux.quux}}'), 'WIN!');
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}}{{a.b.c.d.e}}'), 'abcs');
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}}{{one.one}}|{{one.two}}|{{one.three}}'), 'one-one|one-two|one-three');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
require_once '../Mustache.php';
|
||||||
|
require_once 'PHPUnit/Framework.php';
|
||||||
|
|
||||||
|
class MustachePragmaTest extends PHPUnit_Framework_TestCase {
|
||||||
|
|
||||||
|
public function testUnknownPragmaException() {
|
||||||
|
$m = new Mustache();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$m->render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}');
|
||||||
|
} catch (MustacheException $e) {
|
||||||
|
$this->assertEquals(MustacheException::UNKNOWN_PRAGMA, $e->getCode(), 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fail('Mustache should have thrown an unknown pragma exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPragmaReplace() {
|
||||||
|
$m = new Mustache();
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}}'), '', 'Pragma tag not removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPragmaReplaceMultiple() {
|
||||||
|
$m = new Mustache();
|
||||||
|
$this->assertEquals($m->render("{{%DOT-NOTATION}}\n{{%DOT-NOTATION}}"), '', 'Multiple pragma tags not removed');
|
||||||
|
$this->assertEquals($m->render('{{%DOT-NOTATION}} {{%DOT-NOTATION}}'), ' ', 'Multiple pragma tags not removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPragmaReplaceNewline() {
|
||||||
|
$m = new Mustache();
|
||||||
|
$this->assertEquals($m->render("{{%DOT-NOTATION}}\n"), '', 'Trailing newline after pragma tag not removed');
|
||||||
|
$this->assertEquals($m->render("\n{{%DOT-NOTATION}}\n"), "\n", 'Too many newlines removed with pragma tag');
|
||||||
|
$this->assertEquals($m->render("1\n2{{%DOT-NOTATION}}\n3"), "1\n23", 'Wrong newline removed with pragma tag');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user