From cb30ad23896ade5ee7cc9b77b86d9f887ceef673 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 26 Apr 2011 17:27:14 -0400 Subject: [PATCH 1/5] Make spec test failure messages more readable. --- test/MustacheSpecTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/MustacheSpecTest.php b/test/MustacheSpecTest.php index d26c701..9f7f8b7 100644 --- a/test/MustacheSpecTest.php +++ b/test/MustacheSpecTest.php @@ -25,7 +25,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group comments * @dataProvider loadCommentSpec */ - public function testCommentSpec($template, $data, $partials, $expected, $desc) { + public function testCommentSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -34,7 +34,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group delimiters * @dataProvider loadDelimitersSpec */ - public function testDelimitersSpec($template, $data, $partials, $expected, $desc) { + public function testDelimitersSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -43,7 +43,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group interpolation * @dataProvider loadInterpolationSpec */ - public function testInterpolationSpec($template, $data, $partials, $expected, $desc) { + public function testInterpolationSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -52,7 +52,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group inverted-sections * @dataProvider loadInvertedSpec */ - public function testInvertedSpec($template, $data, $partials, $expected, $desc) { + public function testInvertedSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -61,7 +61,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { // * @group lambdas // * @dataProvider loadLambdasSpec // */ - // public function testLambdasSpec($template, $data, $partials, $expected, $desc) { + // 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', '>=')) { @@ -76,7 +76,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group partials * @dataProvider loadPartialsSpec */ - public function testPartialsSpec($template, $data, $partials, $expected, $desc) { + public function testPartialsSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -85,7 +85,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { * @group sections * @dataProvider loadSectionsSpec */ - public function testSectionsSpec($template, $data, $partials, $expected, $desc) { + public function testSectionsSpec($desc, $template, $data, $partials, $expected) { $m = new Mustache($template, $data, $partials); $this->assertEquals($expected, $m->render(), $desc); } @@ -138,7 +138,7 @@ class MustacheSpecTest extends PHPUnit_Framework_TestCase { $spec = $yaml->parse(file_get_contents($filename)); foreach ($spec['tests'] as $test) { - $data[] = array($test['template'], $test['data'], isset($test['partials']) ? $test['partials'] : array(), $test['expected'], $test['desc']); + $data[] = array($test['name'] . ': ' . $test['desc'], $test['template'], $test['data'], isset($test['partials']) ? $test['partials'] : array(), $test['expected']); } return $data; } From 31ba792e33110bbd018177d38ed769b22c142753 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 26 Apr 2011 18:55:05 -0400 Subject: [PATCH 2/5] update spec to 1.1.2 --- test/spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec b/test/spec index 3383fa6..bf6288e 160000 --- a/test/spec +++ b/test/spec @@ -1 +1 @@ -Subproject commit 3383fa66e808a07fde1c291aa16a588d0a1a2a6d +Subproject commit bf6288ed6bd0ce8ccea6f1dac070b3d779132c3b From 60e7fbbb34484f4f1ba1dd53f7874409ead8217b Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 26 Apr 2011 18:56:03 -0400 Subject: [PATCH 3/5] Fix whitespace bugs in partials, interpolation and comments. --- Mustache.php | 124 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 93 insertions(+), 31 deletions(-) diff --git a/Mustache.php b/Mustache.php index 4455b1e..4c12603 100644 --- a/Mustache.php +++ b/Mustache.php @@ -463,9 +463,10 @@ class Mustache { * @param string $ctag * @return string */ - protected function _prepareTagRegEx($otag, $ctag) { + protected function _prepareTagRegEx($otag, $ctag, $first = false) { return sprintf( - '/(?P(?<=\\n)[ \\t]*)?%s(?P[%s]?)(?P.+?)(?:\\2|})?%s(?:\\s*(?=\\n))?/s', + '/(?P(?:%s\\r?\\n)[ \\t]*)?%s(?P[%s]?)(?P.+?)(?:\\2|})?%s(?P\\s*(?:\\r?\\n|\\Z))?/s', + ($first ? '\\A|' : ''), preg_quote($otag, '/'), self::TAG_TYPES, preg_quote($ctag, '/') @@ -487,7 +488,8 @@ class Mustache { $otag_orig = $this->_otag; $ctag_orig = $this->_ctag; - $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); + $first = true; + $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag, true); $html = ''; $matches = array(); @@ -497,10 +499,16 @@ class Mustache { $modifier = $matches['type'][0]; $tag_name = trim($matches['tag_name'][0]); - if (isset($matches['whitespace']) && $matches['whitespace'][1] > -1) { - $whitespace = $matches['whitespace'][0]; + if (isset($matches['leading']) && $matches['leading'][1] > -1) { + $leading = $matches['leading'][0]; } else { - $whitespace = null; + $leading = null; + } + + if (isset($matches['trailing']) && $matches['trailing'][1] > -1) { + $trailing = $matches['trailing'][0]; + } else { + $trailing = null; } $html .= substr($template, 0, $offset); @@ -511,7 +519,12 @@ class Mustache { } $template = substr($template, $next_offset); - $html .= $this->_renderTag($modifier, $tag_name, $whitespace); + $html .= $this->_renderTag($modifier, $tag_name, $leading, $trailing); + + if ($first == true) { + $first = false; + $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); + } } $this->_otag = $otag_orig; @@ -529,20 +542,22 @@ class Mustache { * @access protected * @param string $modifier * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @throws MustacheException Unmatched section tag encountered. * @return string */ - protected function _renderTag($modifier, $tag_name, $whitespace) { + protected function _renderTag($modifier, $tag_name, $leading, $trailing) { switch ($modifier) { case '=': - return $this->_changeDelimiter($tag_name); + return $this->_changeDelimiter($tag_name, $leading, $trailing); break; case '!': - return $this->_renderComment($tag_name); + return $this->_renderComment($tag_name, $leading, $trailing); break; case '>': case '<': - return $this->_renderPartial($tag_name, $whitespace); + return $this->_renderPartial($tag_name, $leading, $trailing); break; case '{': // strip the trailing } ... @@ -551,24 +566,41 @@ class Mustache { } case '&': if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { - return $this->_renderEscaped($tag_name); + return $this->_renderEscaped($tag_name, $leading, $trailing); } else { - return $this->_renderUnescaped($tag_name); + return $this->_renderUnescaped($tag_name, $leading, $trailing); } break; case '#': case '^': case '/': // remove any leftovers from _renderSections - return ''; + return $leading . $trailing; + break; + default: + if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { + return $this->_renderUnescaped($modifier . $tag_name, $leading, $trailing); + } else { + return $this->_renderEscaped($modifier . $tag_name, $leading, $trailing); + } break; } + } - if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { - return $this->_renderUnescaped($modifier . $tag_name); - } else { - return $this->_renderEscaped($modifier . $tag_name); + /** + * Returns true if any of its args contains the "\r" character. + * + * @access protected + * @param string $str + * @return boolean + */ + protected function _stringHasR($str) { + foreach (func_get_args() as $arg) { + if (strpos($arg, "\r") !== false) { + return true; + } } + return false; } /** @@ -576,10 +608,12 @@ class Mustache { * * @access protected * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @return string */ - protected function _renderEscaped($tag_name) { - return htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset); + protected function _renderEscaped($tag_name, $leading, $trailing) { + return $leading . htmlentities($this->_getVariable($tag_name), ENT_COMPAT, $this->_charset) . $trailing; } /** @@ -587,10 +621,18 @@ class Mustache { * * @access protected * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @return string */ - protected function _renderComment($tag_name) { - return ''; + protected function _renderComment($tag_name, $leading, $trailing) { + if ($leading !== null && $trailing !== null) { + if (strpos($leading, "\n") === false) { + return ''; + } + return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n"; + } + return $leading . $trailing; } /** @@ -598,10 +640,12 @@ class Mustache { * * @access protected * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @return string */ - protected function _renderUnescaped($tag_name) { - return $this->_getVariable($tag_name); + protected function _renderUnescaped($tag_name, $leading, $trailing) { + return $leading . $this->_getVariable($tag_name) . $trailing; } /** @@ -609,14 +653,24 @@ class Mustache { * * @access protected * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @return string */ - protected function _renderPartial($tag_name, $whitespace = '') { + protected function _renderPartial($tag_name, $leading, $trailing) { + $partial = $this->_getPartial($tag_name); + if ($leading !== null && $trailing !== null) { + $whitespace = trim($leading, "\r\n"); + $partial = preg_replace('/(\\r?\\n)(?!$)/s', "\\1" . $whitespace, $partial); + } + $view = clone($this); - - $partial = $whitespace . preg_replace('/\n(?!$)/s', "\n" . $whitespace, $this->_getPartial($tag_name)); - - return $view->render($partial); + + if ($leading !== null && $trailing !== null) { + return $leading . $view->render($partial); + } else { + return $leading . $view->render($partial) . $trailing; + } } /** @@ -625,16 +679,24 @@ class Mustache { * * @access protected * @param string $tag_name + * @param string $leading Whitespace + * @param string $trailing Whitespace * @return string */ - protected function _changeDelimiter($tag_name) { + protected function _changeDelimiter($tag_name, $leading, $trailing) { list($otag, $ctag) = explode(' ', $tag_name); $this->_otag = $otag; $this->_ctag = $ctag; $this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag); - return ''; + if ($leading !== null && $trailing !== null) { + if (strpos($leading, "\n") === false) { + return ''; + } + return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n"; + } + return $leading . $trailing; } /** From 9c73e5bb4f3f4220a20917df906777ca7d6e2c58 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Tue, 26 Apr 2011 18:59:22 -0400 Subject: [PATCH 4/5] Add whitespace bug to known issues. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 3662d3c..d49f2fd 100644 --- a/README.markdown +++ b/README.markdown @@ -81,6 +81,7 @@ And render it: Known Issues ------------ + * As of v1.1.2, there are a couple of whitespace bugs around section tags. * Things get weird when you change delimiters inside a section -- `delimiters` example currently fails with an "unclosed section" exception. From 49b500f789c562d98c33393acb3632894ab003d4 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sun, 12 Jun 2011 23:00:54 -0400 Subject: [PATCH 5/5] Note known whitespace bug with spec tests. --- README.markdown | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 3662d3c..fc88e02 100644 --- a/README.markdown +++ b/README.markdown @@ -83,10 +83,12 @@ Known Issues * Things get weird when you change delimiters inside a section -- `delimiters` example currently fails with an "unclosed section" exception. + * The current spec test exposes several whitespace bugs (which are mostly instances of the exact same whitespace + bug) ... Despite these failing tests, this version is actually *closer* to correct than previous releases. See Also -------- * [Readme for the Ruby Mustache implementation](http://github.com/defunkt/mustache/blob/master/README.md). - * [mustache(1)](http://defunkt.github.com/mustache/mustache.1.html) and [mustache(5)](http://defunkt.github.com/mustache/mustache.5.html) man pages. \ No newline at end of file + * [mustache(1)](http://defunkt.github.com/mustache/mustache.1.html) and [mustache(5)](http://defunkt.github.com/mustache/mustache.5.html) man pages.