Merge branch 'feature/whitespace-overhaul' into dev

This commit is contained in:
Justin Hileman
2010-12-08 08:49:29 -05:00
13 changed files with 132 additions and 111 deletions
+100 -36
View File
@@ -14,9 +14,6 @@
*/ */
class Mustache { class Mustache {
public $_otag = '{{';
public $_ctag = '}}';
/** /**
* Should this Mustache throw exceptions when it finds unexpected tags? * Should this Mustache throw exceptions when it finds unexpected tags?
* *
@@ -85,6 +82,15 @@ class Mustache {
*/ */
const PRAGMA_UNESCAPED = 'UNESCAPED'; const PRAGMA_UNESCAPED = 'UNESCAPED';
/**
* Constants used for section and tag RegEx
*/
const SECTION_TYPES = '\^#\/';
const TAG_TYPES = '#\^\/=!<>\\{&';
public $_otag = '{{';
public $_ctag = '}}';
protected $_tagRegEx; protected $_tagRegEx;
protected $_template = ''; protected $_template = '';
@@ -258,6 +264,23 @@ class Mustache {
return $template; return $template;
} }
/**
* Prepare a section RegEx string for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _prepareSectionRegEx($otag, $ctag) {
return sprintf(
'/(?:(?<=\\n)[ \\t]*)?%s(?<type>[%s])(?<tag_name>.+?)%s\\n?/s',
preg_quote($otag, '/'),
self::SECTION_TYPES,
preg_quote($ctag, '/')
);
}
/** /**
* Extract a section from $template. * Extract a section from $template.
* *
@@ -268,9 +291,7 @@ class Mustache {
* @return array $section, $offset, $type, $tag_name and $content * @return array $section, $offset, $type, $tag_name and $content
*/ */
protected function _findSection($template) { protected function _findSection($template) {
$otag = preg_quote($this->_otag, '/'); $regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/');
$regex = '/' . $otag . '([\\^\\#\\/])\\s*(.+?)\\s*' . $ctag . '\\s*/ms';
$section_start = null; $section_start = null;
$section_type = null; $section_type = null;
@@ -280,12 +301,12 @@ class Mustache {
$section_stack = array(); $section_stack = array();
$matches = array(); $matches = array();
while (preg_match($regex, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) { while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) {
$match = $matches[0][0]; $match = $matches[0][0];
$offset = $matches[0][1]; $offset = $matches[0][1];
$type = $matches[1][0]; $type = $matches['type'][0];
$tag_name = trim($matches[2][0]); $tag_name = trim($matches['tag_name'][0]);
$search_offset = $offset + strlen($match); $search_offset = $offset + strlen($match);
@@ -323,6 +344,22 @@ class Mustache {
} }
} }
/**
* Prepare a pragma RegEx for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _preparePragmaRegEx($otag, $ctag) {
return sprintf(
'/%s%%\\s*(?<pragma_name>[\\w_-]+)(?<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
preg_quote($otag, '/'),
preg_quote($ctag, '/')
);
}
/** /**
* Initialize pragmas and remove all pragma tags. * Initialize pragmas and remove all pragma tags.
* *
@@ -338,10 +375,8 @@ class Mustache {
return $template; return $template;
} }
$otag = preg_quote($this->_otag, '/'); $regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/'); return preg_replace_callback($regEx, array($this, '_renderPragma'), $template);
$regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/s';
return preg_replace_callback($regex, array($this, '_renderPragma'), $template);
} }
/** /**
@@ -354,8 +389,8 @@ class Mustache {
*/ */
protected function _renderPragma($matches) { protected function _renderPragma($matches) {
$pragma = $matches[0]; $pragma = $matches[0];
$pragma_name = $matches[1]; $pragma_name = $matches['pragma_name'];
$options_string = $matches[2]; $options_string = $matches['options_string'];
if (!in_array($pragma_name, $this->_pragmasImplemented)) { if (!in_array($pragma_name, $this->_pragmasImplemented)) {
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
@@ -364,7 +399,7 @@ class Mustache {
$options = array(); $options = array();
foreach (explode(' ', trim($options_string)) as $o) { foreach (explode(' ', trim($options_string)) as $o) {
if ($p = trim($o)) { if ($p = trim($o)) {
$p = explode('=', trim($p)); $p = explode('=', $p);
$options[$p[0]] = $p[1]; $options[$p[0]] = $p[1];
} }
} }
@@ -422,6 +457,23 @@ class Mustache {
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
} }
/**
* Prepare a tag RegEx for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _prepareTagRegEx($otag, $ctag) {
return sprintf(
'/(?<whitespace>(?<=\\n)[ \\t]*)?%s(?<type>[%s]?)(?<tag_name>.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s',
preg_quote($otag, '/'),
self::TAG_TYPES,
preg_quote($ctag, '/')
);
}
/** /**
* Loop through and render individual Mustache tags. * Loop through and render individual Mustache tags.
* *
@@ -437,22 +489,31 @@ class Mustache {
$otag_orig = $this->_otag; $otag_orig = $this->_otag;
$ctag_orig = $this->_ctag; $ctag_orig = $this->_ctag;
$otag = preg_quote($this->_otag, '/'); $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
$ctag = preg_quote($this->_ctag, '/');
$this->_tagRegEx = '/' . $otag . "([#\^\/=!<>\\{&])?(.+?)\\1?" . $ctag . "+/s";
$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)) {
$tag = $matches[0][0]; $tag = $matches[0][0];
$offset = $matches[0][1]; $offset = $matches[0][1];
$modifier = $matches[1][0]; $modifier = $matches['type'][0];
$tag_name = trim($matches[2][0]); $tag_name = trim($matches['tag_name'][0]);
if (isset($matches['whitespace']) && $matches['whitespace'][1] > -1) {
$whitespace = $matches['whitespace'][0];
} else {
$whitespace = null;
}
$html .= substr($template, 0, $offset); $html .= substr($template, 0, $offset);
$html .= $this->_renderTag($modifier, $tag_name);
$template = substr($template, $offset + strlen($tag)); $next_offset = $offset + strlen($tag);
if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) {
$next_offset++;
}
$template = substr($template, $next_offset);
$html .= $this->_renderTag($modifier, $tag_name, $whitespace);
} }
$this->_otag = $otag_orig; $this->_otag = $otag_orig;
@@ -473,7 +534,7 @@ class Mustache {
* @throws MustacheException Unmatched section tag encountered. * @throws MustacheException Unmatched section tag encountered.
* @return string * @return string
*/ */
protected function _renderTag($modifier, $tag_name) { protected function _renderTag($modifier, $tag_name, $whitespace) {
switch ($modifier) { switch ($modifier) {
case '=': case '=':
return $this->_changeDelimiter($tag_name); return $this->_changeDelimiter($tag_name);
@@ -483,9 +544,13 @@ class Mustache {
break; break;
case '>': case '>':
case '<': case '<':
return $this->_renderPartial($tag_name); return $this->_renderPartial($tag_name, $whitespace);
break; break;
case '{': case '{':
// strip the trailing } ...
if ($tag_name[(strlen($tag_name) - 1)] == '}') {
$tag_name = substr($tag_name, 0, -1);
}
case '&': case '&':
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
return $this->_renderEscaped($tag_name); return $this->_renderEscaped($tag_name);
@@ -548,9 +613,10 @@ class Mustache {
* @param string $tag_name * @param string $tag_name
* @return string * @return string
*/ */
protected function _renderPartial($tag_name) { protected function _renderPartial($tag_name, $whitespace = '') {
$view = clone($this); $view = clone($this);
return $view->render($this->_getPartial($tag_name));
return $whitespace . preg_replace('/\n(?!$)/s', "\n" . $whitespace, $view->render($this->_getPartial($tag_name)));
} }
/** /**
@@ -562,13 +628,12 @@ class Mustache {
* @return string * @return string
*/ */
protected function _changeDelimiter($tag_name) { protected function _changeDelimiter($tag_name) {
$tags = explode(' ', $tag_name); list($otag, $ctag) = explode(' ', $tag_name);
$this->_otag = $tags[0]; $this->_otag = $otag;
$this->_ctag = $tags[1]; $this->_ctag = $ctag;
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
$otag = preg_quote($this->_otag, '/');
$ctag = preg_quote($this->_ctag, '/');
$this->_tagRegEx = '/' . $otag . "([#\^\/=!<>\\{&])?(.+?)\\1?" . $ctag . "+/s";
return ''; return '';
} }
@@ -588,7 +653,6 @@ class Mustache {
$this->_context = $new; $this->_context = $new;
} }
/** /**
* Remove the latest context from the stack. * Remove the latest context from the stack.
* *
@@ -621,7 +685,7 @@ class Mustache {
* @return string * @return string
*/ */
protected function _getVariable($tag_name) { protected function _getVariable($tag_name) {
if ($this->_hasPragma(self::PRAGMA_DOT_NOTATION) && $tag_name != '.') { if ($tag_name != '.' && strpos($tag_name, '.') !== false && $this->_hasPragma(self::PRAGMA_DOT_NOTATION)) {
$chunks = explode('.', $tag_name); $chunks = explode('.', $tag_name);
$first = array_shift($chunks); $first = array_shift($chunks);
-1
View File
@@ -83,7 +83,6 @@ Known Issues
* 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.
* Mustache isn't always very good at whitespace.
See Also See Also
+9 -9
View File
@@ -1,16 +1,16 @@
<h1>{{header}}</h1> <h1>{{header}}</h1>
{{#notEmpty}} {{#notEmpty}}
<ul> <ul>
{{#item}} {{#item}}
{{#current}} {{#current}}
<li><strong>{{name}}</strong></li> <li><strong>{{name}}</strong></li>
{{/current}} {{/current}}
{{^current}} {{^current}}
<li><a href="{{url}}">{{name}}</a></li> <li><a href="{{url}}">{{name}}</a></li>
{{/current}} {{/current}}
{{/item}} {{/item}}
</ul> </ul>
{{/notEmpty}} {{/notEmpty}}
{{#isEmpty}} {{#isEmpty}}
<p>The list is empty.</p> <p>The list is empty.</p>
{{/isEmpty}} {{/isEmpty}}
+1 -1
View File
@@ -3,4 +3,4 @@
<li><strong>red</strong></li> <li><strong>red</strong></li>
<li><a href="#Green">green</a></li> <li><a href="#Green">green</a></li>
<li><a href="#Blue">blue</a></li> <li><a href="#Blue">blue</a></li>
</ul> </ul>
@@ -1,7 +1,7 @@
{{#t}} {{#t}}
* first * first
{{/t}} {{/t}}
* {{two}} * {{two}}
{{#t}} {{#t}}
* third * third
{{/t}} {{/t}}
@@ -1,10 +1,7 @@
{{grand_parent_id}} {{grand_parent_id}}
{{#parent_contexts}} {{#parent_contexts}}
{{grand_parent_id}} {{parent_id}} ({{grand_parent_id}})
{{parent_id}} {{#child_contexts}}
{{#child_contexts}} {{child_id}} ({{parent_id}} << {{grand_parent_id}})
{{grand_parent_id}} {{/child_contexts}}
{{parent_id}}
{{child_id}}
{{/child_contexts}}
{{/parent_contexts}} {{/parent_contexts}}
@@ -1,17 +1,7 @@
grand_parent1 grand_parent1
grand_parent1 parent1 (grand_parent1)
parent1 parent1-child1 (parent1 << grand_parent1)
grand_parent1 parent1-child2 (parent1 << grand_parent1)
parent1 parent2 (grand_parent1)
parent1-child1 parent2-child1 (parent2 << grand_parent1)
grand_parent1 parent2-child2 (parent2 << grand_parent1)
parent1
parent1-child2
grand_parent1
parent2
grand_parent1
parent2
parent2-child1
grand_parent1
parent2
parent2-child2
@@ -1,7 +1,7 @@
{{^t}} {{^t}}
* first * first
{{/t}} {{/t}}
* {{two}} * {{two}}
{{^t}} {{^t}}
* third * third
{{/t}} {{/t}}
@@ -1,14 +0,0 @@
<?php
class SectionsSpaces extends Mustache {
public $start = "It worked the first time.";
public function middle() {
return array(
array('item' => "And it worked the second time."),
array('item' => "As well as the third."),
);
}
public $final = "Then, surprisingly, it worked the final time.";
}
@@ -1,9 +0,0 @@
* {{ start }}
{{# middle }}
* {{ item }}
{{/ middle }}
* {{ final }}
* {{ start }}
{{# middle }} * {{ item }}{{/ middle }}
* {{ final }}
@@ -1,9 +0,0 @@
* It worked the first time.
* And it worked the second time.
* As well as the third.
* Then, surprisingly, it worked the final time.
* It worked the first time.
* And it worked the second time.
* As well as the third.
* Then, surprisingly, it worked the final time.
+5 -1
View File
@@ -11,7 +11,11 @@ class MustachePragmaImplicitIteratorTest extends PHPUnit_Framework_TestCase {
$m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}')); $m = $this->getMock('Mustache', array('_renderPragma'), array('{{%IMPLICIT-ITERATOR}}'));
$m->expects($this->exactly(1)) $m->expects($this->exactly(1))
->method('_renderPragma') ->method('_renderPragma')
->with(array('{{%IMPLICIT-ITERATOR}}', 'IMPLICIT-ITERATOR', null)); ->with(array(
0 => '{{%IMPLICIT-ITERATOR}}',
1 => 'IMPLICIT-ITERATOR', 'pragma_name' => 'IMPLICIT-ITERATOR',
2 => null, 'options_string' => null
));
$m->render(); $m->render();
} }
-1
View File
@@ -37,7 +37,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase {
protected $knownIssues = array( protected $knownIssues = array(
'Delimiters' => "Known issue: sections don't respect delimiter changes", 'Delimiters' => "Known issue: sections don't respect delimiter changes",
'SectionsSpaces' => "Known issue: Mustache fails miserably at whitespace",
); );
/** /**