From db4ff1058d8fb5a834285bf0408e4f89d1a20770 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 23 Apr 2010 11:57:24 -0400 Subject: [PATCH 01/27] Merging changes from TraversableMustache --- Mustache.php | 26 ++++++++++++++++++++- TraversableMustache.php | 51 ----------------------------------------- 2 files changed, 25 insertions(+), 52 deletions(-) delete mode 100644 TraversableMustache.php diff --git a/Mustache.php b/Mustache.php index d8d42f6..806e76a 100644 --- a/Mustache.php +++ b/Mustache.php @@ -24,7 +24,7 @@ class Mustache { // Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8. protected $charset = 'UTF-8'; - + protected $tagRegEx; protected $template = ''; @@ -333,6 +333,30 @@ class Mustache { * @return string */ protected function getVariable($tag_name, &$context) { + $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; + } + + /** + * 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) { if (is_object($view)) { if (isset($view->$tag_name)) { diff --git a/TraversableMustache.php b/TraversableMustache.php deleted file mode 100644 index 79dfc8e..0000000 --- a/TraversableMustache.php +++ /dev/null @@ -1,51 +0,0 @@ - array( - * 'three' => 'wheee!' - * ) - * ); - * - * protected $template = '{{one.two.three}}'; - * } - * $foo = new Foo; - * print $foo; - * @endcode - * - * (The above code prints 'wheee!') - * - * @extends Mustache - */ -class TraversableMustache extends Mustache { - - /** - * Override default getVariable method to allow object traversal via dots. - * This might be cool. Also, might be heinous. - * - * @access protected - * @param string $tag_name - * @param array &$context - * @return string - */ - protected function getVariable($tag_name, &$context) { - $chunks = explode('.', $tag_name); - $first = array_shift($chunks); - - $ret = parent::getVariable($first, $context); - while ($next = array_shift($chunks)) { - $c = array($ret); - $ret = parent::getVariable($next, $c); - } - - return $ret; - } -} \ No newline at end of file From d2ff6fa1accb292ceeb1d1f46af410f33adae532 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 23 Apr 2010 13:46:39 -0400 Subject: [PATCH 02/27] Example for dot notation pragma. --- examples/dot_notation/DotNotation.php | 14 ++++++++++++++ examples/dot_notation/dot_notation.mustache | 1 + examples/dot_notation/dot_notation.txt | 1 + 3 files changed, 16 insertions(+) create mode 100644 examples/dot_notation/DotNotation.php create mode 100644 examples/dot_notation/dot_notation.mustache create mode 100644 examples/dot_notation/dot_notation.txt diff --git a/examples/dot_notation/DotNotation.php b/examples/dot_notation/DotNotation.php new file mode 100644 index 0000000..d5f1dd0 --- /dev/null +++ b/examples/dot_notation/DotNotation.php @@ -0,0 +1,14 @@ + array( + 'baz' => 'Qux', + ) + ); +} diff --git a/examples/dot_notation/dot_notation.mustache b/examples/dot_notation/dot_notation.mustache new file mode 100644 index 0000000..85873f7 --- /dev/null +++ b/examples/dot_notation/dot_notation.mustache @@ -0,0 +1 @@ +Grandchild is "{{foo.bar.baz}}". \ No newline at end of file diff --git a/examples/dot_notation/dot_notation.txt b/examples/dot_notation/dot_notation.txt new file mode 100644 index 0000000..c7b8cd2 --- /dev/null +++ b/examples/dot_notation/dot_notation.txt @@ -0,0 +1 @@ +Grandchild is "Qux". \ No newline at end of file From 818a07a64ae05e45b25cd75b41539fef082000f4 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 23 Apr 2010 14:14:06 -0400 Subject: [PATCH 03/27] Added more complex dot notation example (from defunkt/mustache). --- examples/dot_notation/DotNotation.php | 13 +++++++++---- examples/dot_notation/dot_notation.mustache | 7 ++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/examples/dot_notation/DotNotation.php b/examples/dot_notation/DotNotation.php index d5f1dd0..0274e19 100644 --- a/examples/dot_notation/DotNotation.php +++ b/examples/dot_notation/DotNotation.php @@ -2,13 +2,18 @@ /** * DotNotation example class. Uses DOT_NOTATION pragma. - * + * * @extends Mustache */ class DotNotation extends Mustache { - public $foo = array( - 'bar' => array( - 'baz' => 'Qux', + public $person = array( + 'name' => array('first' => 'Chris', 'last' => 'Firescythe'), + 'age' => 24, + 'hometown' => array( + 'city' => 'Cincinnati', + 'state' => 'OH', ) ); + + public $normal = 'Normal'; } diff --git a/examples/dot_notation/dot_notation.mustache b/examples/dot_notation/dot_notation.mustache index 85873f7..12cbf7f 100644 --- a/examples/dot_notation/dot_notation.mustache +++ b/examples/dot_notation/dot_notation.mustache @@ -1 +1,6 @@ -Grandchild is "{{foo.bar.baz}}". \ No newline at end of file +* {{person.name.first}} {{person.name.last}} +* {{person.age}} +* {{person.hometown.city}}, {{person.hometown.state}} +* {{#person}}{{hometown.city}}, {{hometown.state}}{{/person}} +* {{#person}}{{#hometown}}{{city}}, {{state}}{{/hometown}}{{/person}} +* {{normal}} \ No newline at end of file From 8cbf5ebb0fffc1010c3e23cb48479c1ae370f60e Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Sun, 25 Apr 2010 18:05:54 -0400 Subject: [PATCH 04/27] Added known issue for current pragma limitation. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index bc50bf5..4282594 100644 --- a/README.markdown +++ b/README.markdown @@ -81,6 +81,7 @@ And render it: 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 "unclosed section" exception. * Since `complex` example emulates some fancy swizzling available in Ruby, it fails. Need to convert example to PHPisms (In PHP, Mustache class doesn't maintain current context stack -- available context is passed to methods via params). * Test coverage is incomplete. From f5b4c7ddf1d3c42051c8fac824476e2a123e766d Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 23 Apr 2010 11:48:29 -0400 Subject: [PATCH 05/27] Removing now-deprecated HandlebarMustache class. --- HandlebarMustache.php | 160 ------------------------------------------ 1 file changed, 160 deletions(-) delete mode 100644 HandlebarMustache.php diff --git a/HandlebarMustache.php b/HandlebarMustache.php deleted file mode 100644 index 7dbdb8b..0000000 --- a/HandlebarMustache.php +++ /dev/null @@ -1,160 +0,0 @@ -templateBase)) { - $this->setTemplateBase(dirname(__FILE__)); - } - - // default template name is the underscorified class name. - if (!isset($this->templateName)) { - $this->templateName = strtolower(preg_replace('#(?templateBase = $dir; - } - - /** - * Override the default templateName. - * - * @access public - * @param string $name - * @return void - */ - public function setTemplateName($name) { - $this->templateName = $name; - } - - /** - * Load a template file. This file will be relative to $this->templateBase. - * A '.mustache' file extension is assumed if none is provided in $file. - * - * @access public - * @param string $name - * @return void - */ - public function loadTemplate($name) { - if (strpos($name, '.') === false) { - $name .= '.mustache'; - } - - $filename = $this->templateBase . $name; - if (file_exists($filename)) { - $this->template = file_get_contents($filename); - } else { - $this->template = null; - } - } - - /** - * Load a partial, either from $this->partials or from a file in the templateBase - * directory. - * - * @access protected - * @param string $tag_name - * @return string Partial template. - */ - protected function getPartial($tag_name) { - try { - if ($result = parent::getPartial($tag_name)) { - return $result; - } - } catch (MustacheException $e) { - // Ignore the UNKNOWN_PARTIAL exceptions, we'll just look for a template file. - if ($e->getCode() !== MustacheException::UNKNOWN_PARTIAL) { - throw $e; - } - } - - $filename = $this->templateBase . $tag_name . '.mustache'; - if (file_exists($filename)) { - $this->partials[$tag_name] = file_get_contents($filename); - return $this->partials[$tag_name]; - } else { - if ($this->throwPartialExceptions) { - throw new MustacheException( - 'Unknown partial: ' . $tag_name, - MustacheException::UNKNOWN_PARTIAL - ); - } else { - return ''; - } - } - } - - /** - * Render the given template and view object. - * - * Defaults to the template and view passed to the class constructor unless a new one is provided. - * Optionally, pass an associative array of partials as well. - * - * @access public - * @param string $template (default: null) - * @param mixed $view (default: null) - * @param array $partials (default: null) - * @return string Rendered Mustache template. - */ - public function render($template = null, $view = null, $partials = null) { - if ($template === null && !isset($this->template)) { - $this->loadTemplate($this->templateName); - } - - return parent::render($template, $view, $partials); - } -} \ No newline at end of file From 81fa51c1af71e501debbd996f536edd3aa910e50 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Mon, 26 Apr 2010 20:33:54 -0400 Subject: [PATCH 06/27] Simplified dot notation example, fixed sample output. --- examples/dot_notation/dot_notation.mustache | 2 -- examples/dot_notation/dot_notation.txt | 5 ++++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/dot_notation/dot_notation.mustache b/examples/dot_notation/dot_notation.mustache index 12cbf7f..0566867 100644 --- a/examples/dot_notation/dot_notation.mustache +++ b/examples/dot_notation/dot_notation.mustache @@ -1,6 +1,4 @@ * {{person.name.first}} {{person.name.last}} * {{person.age}} * {{person.hometown.city}}, {{person.hometown.state}} -* {{#person}}{{hometown.city}}, {{hometown.state}}{{/person}} -* {{#person}}{{#hometown}}{{city}}, {{state}}{{/hometown}}{{/person}} * {{normal}} \ No newline at end of file diff --git a/examples/dot_notation/dot_notation.txt b/examples/dot_notation/dot_notation.txt index c7b8cd2..f8cf1fa 100644 --- a/examples/dot_notation/dot_notation.txt +++ b/examples/dot_notation/dot_notation.txt @@ -1 +1,4 @@ -Grandchild is "Qux". \ No newline at end of file +* Chris Firescythe +* 24 +* Cincinnati, OH +* Normal \ No newline at end of file From 9b8454f2ba0a0628af8d3ae1d39fb955856aa916 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Mon, 26 Apr 2010 22:38:33 -0400 Subject: [PATCH 07/27] Simplified tag RegEx. Fixed bug preventing section modifiers inside comments. --- Mustache.php | 5 +++-- examples/comments/comments.mustache | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Mustache.php b/Mustache.php index e1f69cd..48b4e1b 100644 --- a/Mustache.php +++ b/Mustache.php @@ -177,7 +177,7 @@ class Mustache { $otag = $this->prepareRegEx($this->otag); $ctag = $this->prepareRegEx($this->ctag); - $this->tagRegEx = '/' . $otag . "(#|\/|=|!|>|\\{|&)?([^\/#]+?)\\1?" . $ctag . "+/"; + $this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; $html = ''; $matches = array(); while (preg_match($this->tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) { @@ -210,6 +210,7 @@ class Mustache { protected function renderTag($modifier, $tag_name, &$context) { switch ($modifier) { case '#': + case '^': if ($this->throwSectionExceptions) { throw new MustacheException('Unclosed section: ' . $tag_name, MustacheException::UNCLOSED_SECTION); } else { @@ -310,7 +311,7 @@ class Mustache { $otag = $this->prepareRegEx($this->otag); $ctag = $this->prepareRegEx($this->ctag); - $this->tagRegEx = '/' . $otag . "(#|\/|=|!|>|\\{|&)?([^\/#\^]+?)\\1?" . $ctag . "+/"; + $this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; return ''; } diff --git a/examples/comments/comments.mustache b/examples/comments/comments.mustache index b6feac8..846e449 100644 --- a/examples/comments/comments.mustache +++ b/examples/comments/comments.mustache @@ -1 +1 @@ -

{{title}}{{! just something interesting... or not... }}

\ No newline at end of file +

{{title}}{{! just something interesting... #or ^not... }}

\ No newline at end of file From 69a514916cc3b1f1780da633d4a2b749004913c5 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Mon, 26 Apr 2010 23:19:20 -0400 Subject: [PATCH 08/27] Updated section short-circuit logic to actually run when only inverted sections exist. --- Mustache.php | 2 +- examples/inverted_double_section/InvertedDoubleSection.php | 6 ++++++ .../inverted_double_section.mustache | 7 +++++++ .../inverted_double_section/inverted_double_section.txt | 3 +++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 examples/inverted_double_section/InvertedDoubleSection.php create mode 100644 examples/inverted_double_section/inverted_double_section.mustache create mode 100644 examples/inverted_double_section/inverted_double_section.txt diff --git a/Mustache.php b/Mustache.php index 48b4e1b..01f16ee 100644 --- a/Mustache.php +++ b/Mustache.php @@ -114,7 +114,7 @@ class Mustache { * @return string */ protected function renderSection($template, &$context) { - if (strpos($template, $this->otag . '#') === false) { + if (strpos($template, $this->otag . '#') === false && strpos($template, $this->otag . '^') === false) { return $template; } diff --git a/examples/inverted_double_section/InvertedDoubleSection.php b/examples/inverted_double_section/InvertedDoubleSection.php new file mode 100644 index 0000000..3dc2231 --- /dev/null +++ b/examples/inverted_double_section/InvertedDoubleSection.php @@ -0,0 +1,6 @@ + Date: Mon, 26 Apr 2010 23:27:21 -0400 Subject: [PATCH 09/27] Removing renderSection short-circuit, as it doesn't actually improve performance. --- Mustache.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Mustache.php b/Mustache.php index 01f16ee..8754c68 100644 --- a/Mustache.php +++ b/Mustache.php @@ -114,10 +114,6 @@ class Mustache { * @return string */ protected function renderSection($template, &$context) { - if (strpos($template, $this->otag . '#') === false && strpos($template, $this->otag . '^') === false) { - return $template; - } - $otag = $this->prepareRegEx($this->otag); $ctag = $this->prepareRegEx($this->ctag); $regex = '/' . $otag . '(\\^|\\#)(.+?)' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\2' . $ctag . '\\s*/m'; From 537ebc44c8df4ba45f5a2f9c28887a47122b5c07 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Wed, 28 Apr 2010 21:51:54 -0400 Subject: [PATCH 10/27] Initial work adding pragma detection to Mustache.php Currently stores pragmas and options, but does nothing with them. Also throws exceptions when it encounters unknown pragmas. Test coverage only for exceptions and pragma tag removal. --- Mustache.php | 66 +++++++++++++++++++++++++++++++++++-- test/MustachePragmaTest.php | 40 ++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 test/MustachePragmaTest.php diff --git a/Mustache.php b/Mustache.php index e1f69cd..6bdc6a4 100644 --- a/Mustache.php +++ b/Mustache.php @@ -30,6 +30,9 @@ class Mustache { protected $template = ''; protected $context = array(); protected $partials = array(); + protected $pragmas = array(); + + protected $pragmasImplemented = array('DOT-NOTATION'); /** * Mustache class constructor. @@ -101,6 +104,7 @@ class Mustache { * @return string Rendered Mustache template. */ protected function _render($template, &$context) { + $template = $this->renderPragmas($template, $context); $template = $this->renderSection($template, $context); return $this->renderTags($template, $context); } @@ -162,6 +166,60 @@ class Mustache { 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 ''; + } + /** * Loop through and render individual Mustache tags. * @@ -175,8 +233,8 @@ class Mustache { return $template; } - $otag = $this->prepareRegEx($this->otag); - $ctag = $this->prepareRegEx($this->ctag); + $otag = $this->prepareRegEx($this->otag); + $ctag = $this->prepareRegEx($this->ctag); $this->tagRegEx = '/' . $otag . "(#|\/|=|!|>|\\{|&)?([^\/#]+?)\\1?" . $ctag . "+/"; $html = ''; $matches = array(); @@ -442,4 +500,8 @@ class MustacheException extends Exception { // with no associated partial. 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; + } \ No newline at end of file diff --git a/test/MustachePragmaTest.php b/test/MustachePragmaTest.php new file mode 100644 index 0000000..49937d1 --- /dev/null +++ b/test/MustachePragmaTest.php @@ -0,0 +1,40 @@ +render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}'); + } catch (Exception $e) { + $caught_exception = $e; + } + + $this->assertNotNull($caught_exception, 'No exception caught'); + $this->assertType('MustacheException', $caught_exception); + $this->assertEquals($caught_exception->getCode(), MustacheException::UNKNOWN_PRAGMA, 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA'); + } + + 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'); + } +} \ No newline at end of file From e962cbac8c1e90574ae64c37f15afffe114f57c5 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 19:24:44 -0400 Subject: [PATCH 11/27] Cleaned up UNKNOWN PRAGMA exception test. --- test/MustachePragmaTest.php | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/MustachePragmaTest.php b/test/MustachePragmaTest.php index 49937d1..db21269 100644 --- a/test/MustachePragmaTest.php +++ b/test/MustachePragmaTest.php @@ -7,17 +7,15 @@ class MustachePragmaTest extends PHPUnit_Framework_TestCase { public function testUnknownPragmaException() { $m = new Mustache(); - $caught_exception = null; try { $m->render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}'); - } catch (Exception $e) { - $caught_exception = $e; + } catch (MustacheException $e) { + $this->assertEquals(MustacheException::UNKNOWN_PRAGMA, $e->getCode(), 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA'); + return; } - $this->assertNotNull($caught_exception, 'No exception caught'); - $this->assertType('MustacheException', $caught_exception); - $this->assertEquals($caught_exception->getCode(), MustacheException::UNKNOWN_PRAGMA, 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA'); + $this->fail('Mustache should have thrown an unknown pragma exception'); } public function testPragmaReplace() { @@ -37,4 +35,5 @@ class MustachePragmaTest extends PHPUnit_Framework_TestCase { $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'); } + } \ No newline at end of file From 2ed23003e7b7cbad29fc83a710307424ba9aa3df Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 20:46:12 -0400 Subject: [PATCH 12/27] Made dot-notation pragma toggleable, added test cases. --- Mustache.php | 43 +++++++++++++++++++------- test/MustachePragmaDotNotationTest.php | 34 ++++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 test/MustachePragmaDotNotationTest.php diff --git a/Mustache.php b/Mustache.php index d30de53..e47e532 100644 --- a/Mustache.php +++ b/Mustache.php @@ -25,6 +25,8 @@ class Mustache { // Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8. protected $charset = 'UTF-8'; + const PRAGMA_DOT_NOTATION = 'DOT-NOTATION'; + protected $tagRegEx; protected $template = ''; @@ -32,7 +34,9 @@ class Mustache { protected $partials = array(); protected $pragmas = array(); - protected $pragmasImplemented = array('DOT-NOTATION'); + protected $pragmasImplemented = array( + self::PRAGMA_DOT_NOTATION + ); /** * Mustache class constructor. @@ -168,7 +172,7 @@ class Mustache { /** * Initialize pragmas and remove all pragma tags. - * + * * @access protected * @param string $template * @param array &$context @@ -220,6 +224,20 @@ class Mustache { 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. * @@ -408,17 +426,20 @@ class Mustache { * @return string */ protected function getVariable($tag_name, &$context) { - $chunks = explode('.', $tag_name); - $first = array_shift($chunks); + 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); + $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); } - - return $ret; } /** diff --git a/test/MustachePragmaDotNotationTest.php b/test/MustachePragmaDotNotationTest.php new file mode 100644 index 0000000..d6d31b7 --- /dev/null +++ b/test/MustachePragmaDotNotationTest.php @@ -0,0 +1,34 @@ + 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'); + } + +} \ No newline at end of file From 0ff5a335059b982e987d1e2a7db594eb4a539dcb Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 20:46:35 -0400 Subject: [PATCH 13/27] Updated dot-notation example to include {{%PRAGMA}} tag. --- examples/dot_notation/dot_notation.mustache | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/dot_notation/dot_notation.mustache b/examples/dot_notation/dot_notation.mustache index 0566867..4831386 100644 --- a/examples/dot_notation/dot_notation.mustache +++ b/examples/dot_notation/dot_notation.mustache @@ -1,3 +1,4 @@ +{{%DOT-NOTATION}} * {{person.name.first}} {{person.name.last}} * {{person.age}} * {{person.hometown.city}}, {{person.hometown.state}} From a9ea1db32284e53d537e52a7d679806afc878a56 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 21:24:03 -0400 Subject: [PATCH 14/27] Added unit tests for constuctor and render() methods. --- test/MustacheTest.php | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/MustacheTest.php b/test/MustacheTest.php index b6a6384..bf5c5c9 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -34,6 +34,40 @@ require_once 'PHPUnit/Framework.php'; */ class MustacheTest extends PHPUnit_Framework_TestCase { + + /** + * Test Mustache constructor. + * + * @access public + * @return void + */ + public function test__construct() { + $template = '{{#mustaches}}{{#last}}and {{/last}}{{type}}{{^last}}, {{/last}}{{/mustaches}}'; + $data = array( + 'mustaches' => array( + array('type' => 'Natural'), + array('type' => 'Hungarian'), + array('type' => 'Dali'), + array('type' => 'English'), + array('type' => 'Imperial'), + array('type' => 'Freestyle', 'last' => 'true'), + ) + ); + $output = 'Natural, Hungarian, Dali, English, Imperial, and Freestyle'; + + $m1 = new Mustache(); + $this->assertEquals($output, $m1->render($template, $data)); + + $m2 = new Mustache($template); + $this->assertEquals($output, $m2->render(null, $data)); + + $m3 = new Mustache($template, $data); + $this->assertEquals($output, $m3->render()); + + $m4 = new Mustache(null, $data); + $this->assertEquals($output, $m4->render($template)); + } + /** * Test everything in the `examples` directory. * @@ -50,6 +84,41 @@ class MustacheTest extends PHPUnit_Framework_TestCase { } + /** + * Test render(). + * + * @access public + * @return void + */ + public function testRender() { + $m = new Mustache(); + + $this->assertEquals('', $m->render('')); + $this->assertEquals('foo', $m->render('foo')); + $this->assertEquals('', $m->render(null)); + + $m2 = new Mustache('foo'); + $this->assertEquals('foo', $m2->render()); + + $m3 = new Mustache(''); + $this->assertEquals('', $m3->render()); + + $m3 = new Mustache(); + $this->assertEquals('', $m3->render(null)); + } + + /** + * Test render() with data. + * + * @access public + * @return void + */ + public function testRenderWithData() { + $m = new Mustache('{{first_name}} {{last_name}}'); + $this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin'))); + $this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa'))); + } + /** * Data provider for testExamples method. * From 4d40070c6900ade3e57c4200fcb8cb162b5ca192 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 21:35:41 -0400 Subject: [PATCH 15/27] Added __toString() test. --- test/MustacheTest.php | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test/MustacheTest.php b/test/MustacheTest.php index bf5c5c9..f43a49a 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -68,21 +68,19 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals($output, $m4->render($template)); } + /** - * Test everything in the `examples` directory. + * Test __toString() function. * - * @dataProvider getExamples * @access public - * @param mixed $class - * @param mixed $template - * @param mixed $output * @return void */ - public function testExamples($class, $template, $output) { - $m = new $class; - $this->assertEquals($output, $m->render($template)); - } + public function test__toString() { + $m = new Mustache('{{first_name}} {{last_name}}', array('first_name' => 'Karl', 'last_name' => 'Marx')); + $this->assertEquals('Karl Marx', $m->__toString()); + $this->assertEquals('Karl Marx', (string) $m); + } /** * Test render(). @@ -119,6 +117,21 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa'))); } + /** + * Test everything in the `examples` directory. + * + * @dataProvider getExamples + * @access public + * @param mixed $class + * @param mixed $template + * @param mixed $output + * @return void + */ + public function testExamples($class, $template, $output) { + $m = new $class; + $this->assertEquals($output, $m->render($template)); + } + /** * Data provider for testExamples method. * From f68335c845bac2bf46e99410db81ab27f5d78193 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 21:39:50 -0400 Subject: [PATCH 16/27] Return false instead of null in hasPragma. Added documentation for pragma methods. --- Mustache.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 0d06f22..ee5dffb 100644 --- a/Mustache.php +++ b/Mustache.php @@ -98,7 +98,6 @@ class Mustache { } } - /** * Internal render function, used for recursive calls. * @@ -220,12 +219,29 @@ class Mustache { return ''; } + /** + * Check whether this Mustache has a specific pragma. + * + * @access protected + * @param string $pragma_name + * @return bool + */ protected function hasPragma($pragma_name) { if (array_key_exists($pragma_name, $this->pragmas) && $this->pragmas[$pragma_name]) { return true; + } else { + return false; } } + /** + * Return pragma options, if any. + * + * @access protected + * @param string $pragma_name + * @return mixed + * @throws MustacheException Unknown pragma + */ protected function getPragmaOptions($pragma_name) { if (!$this->hasPragma()) { throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); From cbfef47de03ea3e178f4878f052a8e8650aba219 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Thu, 29 Apr 2010 23:50:41 -0400 Subject: [PATCH 17/27] Fixed assert param order in pragma test suite. --- test/MustachePragmaTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/MustachePragmaTest.php b/test/MustachePragmaTest.php index db21269..9ef8864 100644 --- a/test/MustachePragmaTest.php +++ b/test/MustachePragmaTest.php @@ -20,20 +20,20 @@ class MustachePragmaTest extends PHPUnit_Framework_TestCase { public function testPragmaReplace() { $m = new Mustache(); - $this->assertEquals($m->render('{{%DOT-NOTATION}}'), '', 'Pragma tag not removed'); + $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'); + $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'); + $this->assertEquals('', $m->render("{{%DOT-NOTATION}}\n"), 'Trailing newline after pragma tag not removed'); + $this->assertEquals("\n", $m->render("\n{{%DOT-NOTATION}}\n"), 'Too many newlines removed with pragma tag'); + $this->assertEquals("1\n23", $m->render("1\n2{{%DOT-NOTATION}}\n3"), 'Wrong newline removed with pragma tag'); } } \ No newline at end of file From a834ef7845e8afc3e65165b2e36648c218ee1493 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 00:09:27 -0400 Subject: [PATCH 18/27] Added unescaped pragma. --- Mustache.php | 27 ++++++++++++++++--- examples/pragma_unescaped/PragmaUnescaped.php | 5 ++++ .../pragma_unescaped.mustache | 3 +++ .../pragma_unescaped/pragma_unescaped.txt | 2 ++ test/MustachePragmaUnescapedTest.php | 15 +++++++++++ 5 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 examples/pragma_unescaped/PragmaUnescaped.php create mode 100644 examples/pragma_unescaped/pragma_unescaped.mustache create mode 100644 examples/pragma_unescaped/pragma_unescaped.txt create mode 100644 test/MustachePragmaUnescapedTest.php diff --git a/Mustache.php b/Mustache.php index e47e532..8e87331 100644 --- a/Mustache.php +++ b/Mustache.php @@ -27,6 +27,18 @@ class Mustache { const PRAGMA_DOT_NOTATION = 'DOT-NOTATION'; + /** + * The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}} + * Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be + * escaped while the {{{unescaped}}} tag will be escaped. + * + * Pragmas apply only to the current template. Partials, even those included after the + * {{%UNESCAPED}} call, will need their own pragma declaration. + * + * his may be useful in non-HTML Mustache situations. + */ + const PRAGMA_UNESCAPED = 'UNESCAPED'; + protected $tagRegEx; protected $template = ''; @@ -35,7 +47,8 @@ class Mustache { protected $pragmas = array(); protected $pragmasImplemented = array( - self::PRAGMA_DOT_NOTATION + self::PRAGMA_DOT_NOTATION, + self::PRAGMA_UNESCAPED ); /** @@ -310,11 +323,19 @@ class Mustache { break; case '{': case '&': - return $this->renderUnescaped($tag_name, $context); + if ($this->hasPragma(self::PRAGMA_UNESCAPED)) { + return $this->renderEscaped($tag_name, $context); + } else { + return $this->renderUnescaped($tag_name, $context); + } break; case '': default: - return $this->renderEscaped($tag_name, $context); + if ($this->hasPragma(self::PRAGMA_UNESCAPED)) { + return $this->renderUnescaped($tag_name, $context); + } else { + return $this->renderEscaped($tag_name, $context); + } break; } } diff --git a/examples/pragma_unescaped/PragmaUnescaped.php b/examples/pragma_unescaped/PragmaUnescaped.php new file mode 100644 index 0000000..59681a8 --- /dev/null +++ b/examples/pragma_unescaped/PragmaUnescaped.php @@ -0,0 +1,5 @@ + Shark'; +} \ No newline at end of file diff --git a/examples/pragma_unescaped/pragma_unescaped.mustache b/examples/pragma_unescaped/pragma_unescaped.mustache new file mode 100644 index 0000000..76095d7 --- /dev/null +++ b/examples/pragma_unescaped/pragma_unescaped.mustache @@ -0,0 +1,3 @@ +{{%UNESCAPED}} +{{vs}} +{{{vs}}} \ No newline at end of file diff --git a/examples/pragma_unescaped/pragma_unescaped.txt b/examples/pragma_unescaped/pragma_unescaped.txt new file mode 100644 index 0000000..2860f61 --- /dev/null +++ b/examples/pragma_unescaped/pragma_unescaped.txt @@ -0,0 +1,2 @@ +Bear > Shark +Bear > Shark \ No newline at end of file diff --git a/test/MustachePragmaUnescapedTest.php b/test/MustachePragmaUnescapedTest.php new file mode 100644 index 0000000..559eeab --- /dev/null +++ b/test/MustachePragmaUnescapedTest.php @@ -0,0 +1,15 @@ + 'Bear > Shark')); + + $this->assertEquals('Bear > Shark', $m->render('{{%UNESCAPED}}{{title}}')); + $this->assertEquals('Bear > Shark', $m->render('{{%UNESCAPED}}{{{title}}}')); + } + +} \ No newline at end of file From 37cf2706a96c70a5849288743622d4cb59a2fbde Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 00:15:17 -0400 Subject: [PATCH 19/27] Allow whitespace in pragma tags. --- Mustache.php | 2 +- test/MustachePragmaTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 8e87331..119d120 100644 --- a/Mustache.php +++ b/Mustache.php @@ -199,7 +199,7 @@ class Mustache { $otag = $this->prepareRegEx($this->otag); $ctag = $this->prepareRegEx($this->ctag); - $regex = '/' . $otag . '%([\\w_-]+)((?: [\\w]+=[\\w]+)*)' . $ctag . '\\n?/'; + $regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/'; return preg_replace_callback($regex, array($this, 'renderPragma'), $template); } diff --git a/test/MustachePragmaTest.php b/test/MustachePragmaTest.php index db21269..534aab3 100644 --- a/test/MustachePragmaTest.php +++ b/test/MustachePragmaTest.php @@ -25,6 +25,8 @@ class MustachePragmaTest extends PHPUnit_Framework_TestCase { public function testPragmaReplaceMultiple() { $m = new Mustache(); + $this->assertEquals('', $m->render('{{% DOT-NOTATION }}'), 'Pragmas should allow whitespace'); + $this->assertEquals('', $m->render('{{% DOT-NOTATION foo=bar }}'), 'Pragmas should allow whitespace'); $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'); } From fdcd284ed5b661ee4cea683ed0342ef300c049d4 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 00:35:07 -0400 Subject: [PATCH 20/27] Updating known issues in readme. --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 61cd5bb..1a029cc 100644 --- a/README.markdown +++ b/README.markdown @@ -81,8 +81,8 @@ And render it: 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. + * Pragmas don't un-apply... Instead of applying only to a specific template, pragmas are applied + to all subsequent templates and partials rendered by this Mustache instance. * Sections don't respect delimiter changes -- `delimiters` example currently fails with an "unclosed section" exception. * Test coverage is incomplete. From 9a30573718678da59d63574941df9b80af3e190d Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 11:16:43 -0400 Subject: [PATCH 21/27] Fixed additional instance of 'only variables should be passed by reference' warning --- Mustache.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 52e101b..6d2aeab 100644 --- a/Mustache.php +++ b/Mustache.php @@ -170,7 +170,8 @@ class Mustache { } } else if ($val) { if (is_array($val) || is_object($val)) { - $replace .= $this->_render($content, $this->getContext($context, $val)); + $c = $this->getContext($context, $val); + $replace .= $this->_render($content, $c); } else { $replace .= $content; } From ac365c052ea068edc9cd3b92ea933df4c43b75bb Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 20:05:43 -0400 Subject: [PATCH 22/27] Further testing for Mustache constructor. --- test/MustacheTest.php | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/test/MustacheTest.php b/test/MustacheTest.php index f43a49a..f4d9672 100644 --- a/test/MustacheTest.php +++ b/test/MustacheTest.php @@ -34,6 +34,7 @@ require_once 'PHPUnit/Framework.php'; */ class MustacheTest extends PHPUnit_Framework_TestCase { + const TEST_CLASS = 'Mustache'; /** * Test Mustache constructor. @@ -68,7 +69,6 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals($output, $m4->render($template)); } - /** * Test __toString() function. * @@ -80,6 +80,26 @@ class MustacheTest extends PHPUnit_Framework_TestCase { $this->assertEquals('Karl Marx', $m->__toString()); $this->assertEquals('Karl Marx', (string) $m); + + $m2 = $this->getMock(self::TEST_CLASS, array('render'), array()); + $m2->expects($this->once()) + ->method('render') + ->will($this->returnValue('foo')); + + $this->assertEquals('foo', $m2->render()); + } + + public function test__toStringException() { + $m = $this->getMock(self::TEST_CLASS, array('render'), array()); + $m->expects($this->once()) + ->method('render') + ->will($this->throwException(new Exception)); + + try { + $out = (string) $m; + } catch (Exception $e) { + $this->fail('__toString should catch all exceptions'); + } } /** From 15324163069534cc936622aec067c2c3b94931e5 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 20:12:37 -0400 Subject: [PATCH 23/27] Prefix Mustache reserved member variables with underscore to prevent namespace collisions with actual view values. --- Mustache.php | 88 ++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/Mustache.php b/Mustache.php index ee5dffb..ae3668d 100644 --- a/Mustache.php +++ b/Mustache.php @@ -14,8 +14,8 @@ */ class Mustache { - public $otag = '{{'; - public $ctag = '}}'; + public $_otag = '{{'; + public $_ctag = '}}'; // Should this Mustache throw exceptions when it finds unexpected tags? protected $throwSectionExceptions = true; @@ -23,18 +23,18 @@ class Mustache { protected $throwVariableExceptions = false; // 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 $context = array(); - protected $partials = array(); - protected $pragmas = array(); + protected $_template = ''; + protected $_context = array(); + protected $_partials = array(); + protected $_pragmas = array(); - protected $pragmasImplemented = array( + protected $_pragmasImplemented = array( self::PRAGMA_DOT_NOTATION ); @@ -51,9 +51,9 @@ class Mustache { * @return void */ public function __construct($template = null, $view = null, $partials = null) { - if ($template !== null) $this->template = $template; - if ($partials !== null) $this->partials = $partials; - if ($view !== null) $this->context = array($view); + if ($template !== null) $this->_template = $template; + if ($partials !== null) $this->_partials = $partials; + if ($view !== null) $this->_context = array($view); } /** @@ -69,16 +69,16 @@ class Mustache { * @return string Rendered Mustache template. */ public function render($template = null, $view = null, $partials = null) { - if ($template === null) $template = $this->template; - if ($partials !== null) $this->partials = $partials; + if ($template === null) $template = $this->_template; + if ($partials !== null) $this->_partials = $partials; if ($view) { - $this->context = array($view); - } else if (empty($this->context)) { - $this->context = array($this); + $this->_context = array($view); + } else if (empty($this->_context)) { + $this->_context = array($this); } - return $this->_render($template, $this->context); + return $this->_render($template, $this->_context); } /** @@ -121,8 +121,8 @@ class Mustache { * @return string */ protected function renderSection($template, &$context) { - $otag = $this->prepareRegEx($this->otag); - $ctag = $this->prepareRegEx($this->ctag); + $otag = $this->prepareRegEx($this->_otag); + $ctag = $this->prepareRegEx($this->_ctag); $regex = '/' . $otag . '(\\^|\\#)(.+?)' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\2' . $ctag . '\\s*/m'; $matches = array(); @@ -175,12 +175,12 @@ class Mustache { */ protected function renderPragmas($template, &$context) { // no pragmas - if (strpos($template, $this->otag . '%') === false) { + if (strpos($template, $this->_otag . '%') === false) { return $template; } - $otag = $this->prepareRegEx($this->otag); - $ctag = $this->prepareRegEx($this->ctag); + $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); } @@ -198,7 +198,7 @@ class Mustache { $pragma_name = $matches[1]; $options_string = $matches[2]; - if (!in_array($pragma_name, $this->pragmasImplemented)) { + if (!in_array($pragma_name, $this->_pragmasImplemented)) { throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); } @@ -211,9 +211,9 @@ class Mustache { } if (empty($options)) { - $this->pragmas[$pragma_name] = true; + $this->_pragmas[$pragma_name] = true; } else { - $this->pragmas[$pragma_name] = $options; + $this->_pragmas[$pragma_name] = $options; } return ''; @@ -227,7 +227,7 @@ class Mustache { * @return bool */ protected function hasPragma($pragma_name) { - if (array_key_exists($pragma_name, $this->pragmas) && $this->pragmas[$pragma_name]) { + if (array_key_exists($pragma_name, $this->_pragmas) && $this->_pragmas[$pragma_name]) { return true; } else { return false; @@ -247,7 +247,7 @@ class Mustache { throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); } - return $this->pragmas[$pragma_name]; + return $this->_pragmas[$pragma_name]; } /** @@ -259,18 +259,18 @@ class Mustache { * @return void */ protected function renderTags($template, &$context) { - if (strpos($template, $this->otag) === false) { + if (strpos($template, $this->_otag) === false) { return $template; } - $otag = $this->prepareRegEx($this->otag); - $ctag = $this->prepareRegEx($this->ctag); + $otag = $this->prepareRegEx($this->_otag); + $ctag = $this->prepareRegEx($this->_ctag); - $this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; + $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; $html = ''; $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]; $offset = $matches[0][1]; $modifier = $matches[1][0]; @@ -343,7 +343,7 @@ class Mustache { * @return string */ protected function renderEscaped($tag_name, &$context) { - return htmlentities($this->getVariable($tag_name, $context), null, $this->charset); + return htmlentities($this->getVariable($tag_name, $context), null, $this->_charset); } /** @@ -379,9 +379,9 @@ class Mustache { * @return string */ protected function renderPartial($tag_name, &$context) { - $view = new self($this->getPartial($tag_name), $context, $this->partials); - $view->otag = $this->otag; - $view->ctag = $this->ctag; + $view = new self($this->getPartial($tag_name), $context, $this->_partials); + $view->_otag = $this->_otag; + $view->_ctag = $this->_ctag; return $view->render(); } @@ -396,12 +396,12 @@ class Mustache { */ protected function changeDelimiter($tag_name, &$context) { $tags = explode(' ', $tag_name); - $this->otag = $tags[0]; - $this->ctag = $tags[1]; + $this->_otag = $tags[0]; + $this->_ctag = $tags[1]; - $otag = $this->prepareRegEx($this->otag); - $ctag = $this->prepareRegEx($this->ctag); - $this->tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; + $otag = $this->prepareRegEx($this->_otag); + $ctag = $this->prepareRegEx($this->_ctag); + $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; return ''; } @@ -498,8 +498,8 @@ class Mustache { * @return string */ protected function getPartial($tag_name) { - if (is_array($this->partials) && isset($this->partials[$tag_name])) { - return $this->partials[$tag_name]; + if (is_array($this->_partials) && isset($this->_partials[$tag_name])) { + return $this->_partials[$tag_name]; } if ($this->throwPartialExceptions) { From 5a1143efcef224cf821843fa0851da4b9ed3ac60 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 20:23:25 -0400 Subject: [PATCH 24/27] Added _throwsException() check and refactored exception checks. --- Mustache.php | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/Mustache.php b/Mustache.php index bed4693..80e526f 100644 --- a/Mustache.php +++ b/Mustache.php @@ -17,10 +17,18 @@ class Mustache { public $_otag = '{{'; public $_ctag = '}}'; - // Should this Mustache throw exceptions when it finds unexpected tags? - protected $throwSectionExceptions = true; - protected $throwPartialExceptions = false; - protected $throwVariableExceptions = false; + /** + * Should this Mustache throw exceptions when it finds unexpected tags? + * + * @see self::_throwsException() + */ + protected $_throwsExceptions = array( + MustacheException::UNKNOWN_VARIABLE => false, + MustacheException::UNCLOSED_SECTION => true, + MustacheException::UNEXPECTED_CLOSE_SECTION => true, + MustacheException::UNKNOWN_PARTIAL => false, + MustacheException::UNKNOWN_PRAGMA => true, + ); // Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8. protected $_charset = 'UTF-8'; @@ -265,6 +273,20 @@ class Mustache { return $this->_pragmas[$pragma_name]; } + + /** + * Check whether this Mustache instance throws a given exception. + * + * Expects exceptions to be MustacheException error codes (i.e. class constants). + * + * @access protected + * @param mixed $exception + * @return void + */ + protected function _throwsException($exception) { + return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]); + } + /** * Loop through and render individual Mustache tags. * @@ -316,14 +338,14 @@ class Mustache { switch ($modifier) { case '#': case '^': - if ($this->throwSectionExceptions) { + if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) { throw new MustacheException('Unclosed section: ' . $tag_name, MustacheException::UNCLOSED_SECTION); } else { return ''; } break; case '/': - if ($this->throwSectionExceptions) { + if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) { throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION); } else { return ''; @@ -503,7 +525,7 @@ class Mustache { } } - if ($this->throwVariableExceptions) { + if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) { throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE); } else { return ''; @@ -525,7 +547,7 @@ class Mustache { return $this->_partials[$tag_name]; } - if ($this->throwPartialExceptions) { + if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) { throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL); } else { return ''; From 7304344c51298e61d5b424fcf829f9c154f22bc9 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 20:34:31 -0400 Subject: [PATCH 25/27] Changing all protected functions to start with underscore, freeing up namespace for use in Mustache view classes. --- Mustache.php | 104 +++++++++++++++++++++++++-------------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/Mustache.php b/Mustache.php index 80e526f..e0dc769 100644 --- a/Mustache.php +++ b/Mustache.php @@ -128,9 +128,9 @@ class Mustache { * @return string Rendered Mustache template. */ protected function _render($template, &$context) { - $template = $this->renderPragmas($template, $context); - $template = $this->renderSection($template, $context); - return $this->renderTags($template, $context); + $template = $this->_renderPragmas($template, $context); + $template = $this->_renderSection($template, $context); + return $this->_renderTags($template, $context); } /** @@ -141,9 +141,9 @@ class Mustache { * @param array $context * @return string */ - protected function renderSection($template, &$context) { - $otag = $this->prepareRegEx($this->_otag); - $ctag = $this->prepareRegEx($this->_ctag); + protected function _renderSection($template, &$context) { + $otag = $this->_prepareRegEx($this->_otag); + $ctag = $this->_prepareRegEx($this->_ctag); $regex = '/' . $otag . '(\\^|\\#)(.+?)' . $ctag . '\\s*([\\s\\S]+?)' . $otag . '\\/\\2' . $ctag . '\\s*/m'; $matches = array(); @@ -155,7 +155,7 @@ class Mustache { $content = $matches[3][0]; $replace = ''; - $val = $this->getVariable($tag_name, $context); + $val = $this->_getVariable($tag_name, $context); switch($type) { // inverted section case '^': @@ -166,14 +166,14 @@ class Mustache { // regular section case '#': - if ($this->varIsIterable($val)) { + if ($this->_varIsIterable($val)) { foreach ($val as $local_context) { - $c = $this->getContext($context, $local_context); + $c = $this->_getContext($context, $local_context); $replace .= $this->_render($content, $c); } } else if ($val) { if (is_array($val) || is_object($val)) { - $c = $this->getContext($context, $val); + $c = $this->_getContext($context, $val); $replace .= $this->_render($content, $c); } else { $replace .= $content; @@ -196,16 +196,16 @@ class Mustache { * @param array &$context * @return string */ - protected function renderPragmas($template, &$context) { + 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); + $otag = $this->_prepareRegEx($this->_otag); + $ctag = $this->_prepareRegEx($this->_ctag); $regex = '/' . $otag . '%\\s*([\\w_-]+)((?: [\\w]+=[\\w]+)*)\\s*' . $ctag . '\\n?/'; - return preg_replace_callback($regex, array($this, 'renderPragma'), $template); + return preg_replace_callback($regex, array($this, '_renderPragma'), $template); } /** @@ -216,7 +216,7 @@ class Mustache { * @return void * @throws MustacheException unknown pragma */ - protected function renderPragma($matches) { + protected function _renderPragma($matches) { $pragma = $matches[0]; $pragma_name = $matches[1]; $options_string = $matches[2]; @@ -249,7 +249,7 @@ class Mustache { * @param string $pragma_name * @return bool */ - protected function hasPragma($pragma_name) { + protected function _hasPragma($pragma_name) { if (array_key_exists($pragma_name, $this->_pragmas) && $this->_pragmas[$pragma_name]) { return true; } else { @@ -265,8 +265,8 @@ class Mustache { * @return mixed * @throws MustacheException Unknown pragma */ - protected function getPragmaOptions($pragma_name) { - if (!$this->hasPragma()) { + protected function _getPragmaOptions($pragma_name) { + if (!$this->_hasPragma()) { throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA); } @@ -295,13 +295,13 @@ class Mustache { * @param array $context * @return void */ - protected function renderTags($template, &$context) { + protected function _renderTags($template, &$context) { if (strpos($template, $this->_otag) === false) { return $template; } - $otag = $this->prepareRegEx($this->_otag); - $ctag = $this->prepareRegEx($this->_ctag); + $otag = $this->_prepareRegEx($this->_otag); + $ctag = $this->_prepareRegEx($this->_ctag); $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; @@ -314,7 +314,7 @@ class Mustache { $tag_name = trim($matches[2][0]); $html .= substr($template, 0, $offset); - $html .= $this->renderTag($modifier, $tag_name, $context); + $html .= $this->_renderTag($modifier, $tag_name, $context); $template = substr($template, $offset + strlen($tag)); } @@ -334,7 +334,7 @@ class Mustache { * @throws MustacheException Unmatched section tag encountered. * @return string */ - protected function renderTag($modifier, $tag_name, &$context) { + protected function _renderTag($modifier, $tag_name, &$context) { switch ($modifier) { case '#': case '^': @@ -352,28 +352,28 @@ class Mustache { } break; case '=': - return $this->changeDelimiter($tag_name, $context); + return $this->_changeDelimiter($tag_name, $context); break; case '!': - return $this->renderComment($tag_name, $context); + return $this->_renderComment($tag_name, $context); break; case '>': - return $this->renderPartial($tag_name, $context); + return $this->_renderPartial($tag_name, $context); break; case '{': case '&': - if ($this->hasPragma(self::PRAGMA_UNESCAPED)) { - return $this->renderEscaped($tag_name, $context); + if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { + return $this->_renderEscaped($tag_name, $context); } else { - return $this->renderUnescaped($tag_name, $context); + return $this->_renderUnescaped($tag_name, $context); } break; case '': default: - if ($this->hasPragma(self::PRAGMA_UNESCAPED)) { - return $this->renderUnescaped($tag_name, $context); + if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) { + return $this->_renderUnescaped($tag_name, $context); } else { - return $this->renderEscaped($tag_name, $context); + return $this->_renderEscaped($tag_name, $context); } break; } @@ -387,8 +387,8 @@ class Mustache { * @param array $context * @return string */ - protected function renderEscaped($tag_name, &$context) { - return htmlentities($this->getVariable($tag_name, $context), null, $this->_charset); + protected function _renderEscaped($tag_name, &$context) { + return htmlentities($this->_getVariable($tag_name, $context), null, $this->_charset); } /** @@ -399,7 +399,7 @@ class Mustache { * @param array $context * @return string */ - protected function renderComment($tag_name, &$context) { + protected function _renderComment($tag_name, &$context) { return ''; } @@ -411,8 +411,8 @@ class Mustache { * @param array $context * @return string */ - protected function renderUnescaped($tag_name, &$context) { - return $this->getVariable($tag_name, $context); + protected function _renderUnescaped($tag_name, &$context) { + return $this->_getVariable($tag_name, $context); } /** @@ -423,8 +423,8 @@ class Mustache { * @param array $context * @return string */ - protected function renderPartial($tag_name, &$context) { - $view = new self($this->getPartial($tag_name), $context, $this->_partials); + protected function _renderPartial($tag_name, &$context) { + $view = new self($this->_getPartial($tag_name), $context, $this->_partials); $view->_otag = $this->_otag; $view->_ctag = $this->_ctag; return $view->render(); @@ -439,13 +439,13 @@ class Mustache { * @param array $context * @return string */ - protected function changeDelimiter($tag_name, &$context) { + protected function _changeDelimiter($tag_name, &$context) { $tags = explode(' ', $tag_name); $this->_otag = $tags[0]; $this->_ctag = $tags[1]; - $otag = $this->prepareRegEx($this->_otag); - $ctag = $this->prepareRegEx($this->_ctag); + $otag = $this->_prepareRegEx($this->_otag); + $ctag = $this->_prepareRegEx($this->_ctag); $this->_tagRegEx = '/' . $otag . "([#\^\/=!>\\{&])?(.+?)\\1?" . $ctag . "+/"; return ''; } @@ -461,7 +461,7 @@ class Mustache { * @param mixed $local_context * @return void */ - protected function getContext(&$context, &$local_context) { + protected function _getContext(&$context, &$local_context) { $ret = array(); $ret[] =& $local_context; foreach ($context as $view) { @@ -485,20 +485,20 @@ class Mustache { * @throws MustacheException Unknown variable name. * @return string */ - protected function getVariable($tag_name, &$context) { - if ($this->hasPragma(self::PRAGMA_DOT_NOTATION)) { + 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); + $ret = $this->_findVariableInContext($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); + $ret = $this->_findVariableInContext($next, $c); } return $ret; } else { - return $this->_getVariable($tag_name, $context); + return $this->_findVariableInContext($tag_name, $context); } } @@ -512,7 +512,7 @@ class Mustache { * @throws MustacheException Unknown variable name. * @return string */ - protected function _getVariable($tag_name, &$context) { + protected function _findVariableInContext($tag_name, &$context) { foreach ($context as $view) { if (is_object($view)) { if (isset($view->$tag_name)) { @@ -542,7 +542,7 @@ class Mustache { * @throws MustacheException Unknown partial name. * @return string */ - protected function getPartial($tag_name) { + protected function _getPartial($tag_name) { if (is_array($this->_partials) && isset($this->_partials[$tag_name])) { return $this->_partials[$tag_name]; } @@ -561,7 +561,7 @@ class Mustache { * @param mixed $var * @return bool */ - protected function varIsIterable($var) { + protected function _varIsIterable($var) { return is_object($var) || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var)))); } @@ -572,7 +572,7 @@ class Mustache { * @param string $str * @return string */ - protected function prepareRegEx($str) { + protected function _prepareRegEx($str) { $replace = array( '\\' => '\\\\', '^' => '\^', '.' => '\.', '$' => '\$', '|' => '\|', '(' => '\(', ')' => '\)', '[' => '\[', ']' => '\]', '*' => '\*', '+' => '\+', '?' => '\?', From c6061baf49d2c2ff40c89fdfb0c181ec7616e441 Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 20:36:18 -0400 Subject: [PATCH 26/27] Changed protected _render function to _renderTemplate to avoid ambiguity. --- Mustache.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Mustache.php b/Mustache.php index e0dc769..6578862 100644 --- a/Mustache.php +++ b/Mustache.php @@ -99,7 +99,7 @@ class Mustache { $this->_context = array($this); } - return $this->_render($template, $this->_context); + return $this->_renderTemplate($template, $this->_context); } /** @@ -127,7 +127,7 @@ class Mustache { * @param array &$context * @return string Rendered Mustache template. */ - protected function _render($template, &$context) { + protected function _renderTemplate($template, &$context) { $template = $this->_renderPragmas($template, $context); $template = $this->_renderSection($template, $context); return $this->_renderTags($template, $context); @@ -169,12 +169,12 @@ class Mustache { if ($this->_varIsIterable($val)) { foreach ($val as $local_context) { $c = $this->_getContext($context, $local_context); - $replace .= $this->_render($content, $c); + $replace .= $this->_renderTemplate($content, $c); } } else if ($val) { if (is_array($val) || is_object($val)) { $c = $this->_getContext($context, $val); - $replace .= $this->_render($content, $c); + $replace .= $this->_renderTemplate($content, $c); } else { $replace .= $content; } From ca8e96de97ef5cb9df0a6304bccc4e16c5e2280e Mon Sep 17 00:00:00 2001 From: Justin Hileman Date: Fri, 30 Apr 2010 21:12:13 -0400 Subject: [PATCH 27/27] Minor documentation update --- Mustache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mustache.php b/Mustache.php index 6578862..85dae70 100644 --- a/Mustache.php +++ b/Mustache.php @@ -598,7 +598,7 @@ class MustacheException extends Exception { const UNCLOSED_SECTION = 1; // An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears - // without a corresponding {{#section}}. + // without a corresponding {{#section}} or {{^section}}. const UNEXPECTED_CLOSE_SECTION = 2; // An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears