Explorar o código

Merge branch 'release/2.7.0'

Justin Hileman %!s(int64=11) %!d(string=hai) anos
pai
achega
4e1d62f02e
Modificáronse 40 ficheiros con 1528 adicións e 213 borrados
  1. 6 0
      .php_cs
  2. 8 4
      .travis.yml
  3. 6 4
      CONTRIBUTING.md
  4. 4 4
      bin/build_bootstrap.php
  5. 229 76
      src/Mustache/Compiler.php
  6. 66 14
      src/Mustache/Context.php
  7. 49 11
      src/Mustache/Engine.php
  8. 1 1
      src/Mustache/Loader/FilesystemLoader.php
  9. 115 4
      src/Mustache/Parser.php
  10. 18 13
      src/Mustache/Template.php
  11. 19 15
      src/Mustache/Tokenizer.php
  12. 2 2
      test/Mustache/Test/CompilerTest.php
  13. 9 9
      test/Mustache/Test/ContextTest.php
  14. 24 12
      test/Mustache/Test/EngineTest.php
  15. 1 1
      test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php
  16. 50 0
      test/Mustache/Test/FiveThree/Functional/EngineTest.php
  17. 2 2
      test/Mustache/Test/FiveThree/Functional/FiltersTest.php
  18. 3 3
      test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php
  19. 2 2
      test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php
  20. 1 1
      test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php
  21. 1 1
      test/Mustache/Test/Functional/CallTest.php
  22. 3 2
      test/Mustache/Test/Functional/ExamplesTest.php
  23. 7 7
      test/Mustache/Test/Functional/HigherOrderSectionsTest.php
  24. 437 0
      test/Mustache/Test/Functional/InheritanceTest.php
  25. 1 1
      test/Mustache/Test/Functional/MustacheInjectionTest.php
  26. 45 0
      test/Mustache/Test/Functional/NestedPartialIndentTest.php
  27. 7 7
      test/Mustache/Test/Functional/ObjectSectionTest.php
  28. 2 2
      test/Mustache/Test/HelperCollectionTest.php
  29. 1 1
      test/Mustache/Test/Loader/ArrayLoaderTest.php
  30. 1 1
      test/Mustache/Test/Loader/StringLoaderTest.php
  31. 1 1
      test/Mustache/Test/Logger/AbstractLoggerTest.php
  32. 226 2
      test/Mustache/Test/ParserTest.php
  33. 2 2
      test/Mustache/Test/SpecTestCase.php
  34. 3 3
      test/Mustache/Test/TemplateTest.php
  35. 32 3
      test/Mustache/Test/TokenizerTest.php
  36. 88 0
      test/fixtures/examples/filters/Filters.php
  37. 4 0
      test/fixtures/examples/filters/filters.mustache
  38. 50 0
      test/fixtures/examples/filters/filters.txt
  39. 1 1
      test/fixtures/examples/section_magic_objects/SectionMagicObjects.php
  40. 1 1
      test/fixtures/examples/section_objects/SectionObjects.php

+ 6 - 0
.php_cs

@@ -0,0 +1,6 @@
+<?php
+
+$config = new Symfony\CS\Config\Config();
+$config->getFinder()->in(__DIR__)->exclude('bin');
+
+return $config;

+ 8 - 4
.travis.yml

@@ -1,4 +1,12 @@
 language: php
+
+before_script:
+  - curl http://cs.sensiolabs.org/get/php-cs-fixer.phar -o php-cs-fixer.phar
+
+script:
+  - phpunit
+  - if [[ `php -r "echo version_compare(PHP_VERSION, '5.3.6', '>=');"` ]]; then php php-cs-fixer.phar --dry-run -v fix .; fi
+
 php:
   - 5.2
   - 5.3
@@ -6,7 +14,3 @@ php:
   - 5.5
   - 5.6
   - hhvm
-
-matrix:
-  allow_failures:
-    - php: hhvm # See https://github.com/facebook/hhvm/pull/1860

+ 6 - 4
CONTRIBUTING.md

@@ -5,13 +5,15 @@
 
  1. [Fork the repo on GitHub](https://github.com/bobthecow/mustache.php).
 
- 2. Run the test suite. We only take pull requests with passing tests, and it's great to know that you have a clean slate. Make sure you have PHPUnit 3.5+, then run `phpunit` from the project directory.
+ 2. Update submodules: `git submodule update --init`
 
- 3. Add tests for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, add a test!
+ 3. Run the test suite. We only take pull requests with passing tests, and it's great to know that you have a clean slate. Make sure you have PHPUnit 3.5+, then run `phpunit` from the project directory.
 
- 4. Make the tests pass.
+ 4. Add tests for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, add a test!
 
- 5. Push your fork to GitHub and submit a pull request against the `dev` branch.
+ 5. Make the tests pass.
+
+ 6. Push your fork to GitHub and submit a pull request against the `dev` branch.
 
 
 ### You can do some things to increase the chance that your pull request is accepted the first time:

+ 4 - 4
bin/build_bootstrap.php

@@ -95,10 +95,10 @@ EOS;
     /**
      * Loads a list of classes and caches them in one big file.
      *
-     * @param array   $classes    An array of classes to load
-     * @param string  $cacheDir   A cache directory
-     * @param string  $name       The cache name prefix
-     * @param string  $extension  File extension of the resulting file
+     * @param array  $classes   An array of classes to load
+     * @param string $cacheDir  A cache directory
+     * @param string $name      The cache name prefix
+     * @param string $extension File extension of the resulting file
      *
      * @throws InvalidArgumentException When class can't be loaded
      */

+ 229 - 76
src/Mustache/Compiler.php

@@ -16,6 +16,9 @@
  */
 class Mustache_Compiler
 {
+
+    private $pragmas;
+    private $defaultPragmas = array();
     private $sections;
     private $source;
     private $indentNextLine;
@@ -23,7 +26,6 @@ class Mustache_Compiler
     private $entityFlags;
     private $charset;
     private $strictCallables;
-    private $pragmas;
 
     /**
      * Compile a Mustache token parse tree into PHP source code.
@@ -40,7 +42,7 @@ class Mustache_Compiler
      */
     public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT)
     {
-        $this->pragmas         = array();
+        $this->pragmas         = $this->defaultPragmas;
         $this->sections        = array();
         $this->source          = $source;
         $this->indentNextLine  = true;
@@ -52,6 +54,23 @@ class Mustache_Compiler
         return $this->writeCode($tree, $name);
     }
 
+    /**
+     * Enable pragmas across all templates, regardless of the presence of pragma
+     * tags in the individual templates.
+     *
+     * @internal Users should set global pragmas in Mustache_Engine, not here :)
+     *
+     * @param string[] $pragmas
+     */
+    public function setPragmas(array $pragmas)
+    {
+        $this->pragmas = array();
+        foreach ($pragmas as $pragma) {
+            $this->pragmas[$pragma] = true;
+        }
+        $this->defaultPragmas = $this->pragmas;
+    }
+
     /**
      * Helper function for walking the Mustache token parse tree.
      *
@@ -76,6 +95,7 @@ class Mustache_Compiler
                     $code .= $this->section(
                         $node[Mustache_Tokenizer::NODES],
                         $node[Mustache_Tokenizer::NAME],
+                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
                         $node[Mustache_Tokenizer::INDEX],
                         $node[Mustache_Tokenizer::END],
                         $node[Mustache_Tokenizer::OTAG],
@@ -88,12 +108,12 @@ class Mustache_Compiler
                     $code .= $this->invertedSection(
                         $node[Mustache_Tokenizer::NODES],
                         $node[Mustache_Tokenizer::NAME],
+                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
                         $level
                     );
                     break;
 
                 case Mustache_Tokenizer::T_PARTIAL:
-                case Mustache_Tokenizer::T_PARTIAL_2:
                     $code .= $this->partial(
                         $node[Mustache_Tokenizer::NAME],
                         isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
@@ -101,16 +121,51 @@ class Mustache_Compiler
                     );
                     break;
 
-                case Mustache_Tokenizer::T_UNESCAPED:
-                case Mustache_Tokenizer::T_UNESCAPED_2:
-                    $code .= $this->variable($node[Mustache_Tokenizer::NAME], false, $level);
+                case Mustache_Tokenizer::T_PARENT:
+                    $code .= $this->parent(
+                        $node[Mustache_Tokenizer::NAME],
+                        isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
+                        $node[Mustache_Tokenizer::NODES],
+                        $level
+                    );
+                    break;
+
+                case Mustache_Tokenizer::T_BLOCK_ARG:
+                    $code .= $this->blockArg(
+                        $node[Mustache_Tokenizer::NODES],
+                        $node[Mustache_Tokenizer::NAME],
+                        $node[Mustache_Tokenizer::INDEX],
+                        $node[Mustache_Tokenizer::END],
+                        $node[Mustache_Tokenizer::OTAG],
+                        $node[Mustache_Tokenizer::CTAG],
+                        $level
+                    );
+                    break;
+
+                case Mustache_Tokenizer::T_BLOCK_VAR:
+                    $code .= $this->blockVar(
+                        $node[Mustache_Tokenizer::NODES],
+                        $node[Mustache_Tokenizer::NAME],
+                        $node[Mustache_Tokenizer::INDEX],
+                        $node[Mustache_Tokenizer::END],
+                        $node[Mustache_Tokenizer::OTAG],
+                        $node[Mustache_Tokenizer::CTAG],
+                        $level
+                    );
                     break;
 
                 case Mustache_Tokenizer::T_COMMENT:
                     break;
 
                 case Mustache_Tokenizer::T_ESCAPED:
-                    $code .= $this->variable($node[Mustache_Tokenizer::NAME], true, $level);
+                case Mustache_Tokenizer::T_UNESCAPED:
+                case Mustache_Tokenizer::T_UNESCAPED_2:
+                    $code .= $this->variable(
+                        $node[Mustache_Tokenizer::NAME],
+                        isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
+                        $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
+                        $level
+                    );
                     break;
 
                 case Mustache_Tokenizer::T_TEXT:
@@ -135,6 +190,7 @@ class Mustache_Compiler
             {
                 $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
                 $buffer = \'\';
+                $newContext = array();
         %s
 
                 return $buffer;
@@ -149,6 +205,7 @@ class Mustache_Compiler
             public function renderInternal(Mustache_Context $context, $indent = \'\')
             {
                 $buffer = \'\';
+                $newContext = array();
         %s
 
                 return $buffer;
@@ -170,11 +227,68 @@ class Mustache_Compiler
         $code     = $this->walk($tree);
         $sections = implode("\n", $this->sections);
         $klass    = empty($this->sections) ? self::KLASS_NO_LAMBDAS : self::KLASS;
+
         $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
 
         return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections);
     }
 
+    const BLOCK_VAR = '
+        $value = $this->resolveValue($context->findInBlock(%s), $context, $indent);
+        if ($value && !is_array($value) && !is_object($value)) {
+            $buffer .= $value;
+        } else {
+            %s
+        }
+    ';
+
+    /**
+     * Generate Mustache Template inheritance block variable PHP source.
+     *
+     * @param array  $nodes Array of child tokens
+     * @param string $id    Section name
+     * @param int    $start Section start offset
+     * @param int    $end   Section end offset
+     * @param string $otag  Current Mustache opening tag
+     * @param string $ctag  Current Mustache closing tag
+     * @param int    $level
+     *
+     * @return string Generated PHP source code
+     */
+    private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
+    {
+        $id = var_export($id, true);
+
+        return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $this->walk($nodes, 2));
+    }
+
+    const BLOCK_ARG = '
+        // %s block_arg
+        $value = $this->section%s($context, $indent, true);
+        $newContext[%s] = %s$value;
+    ';
+
+    /**
+     * Generate Mustache Template inheritance block argument PHP source.
+     *
+     * @param array  $nodes Array of child tokens
+     * @param string $id    Section name
+     * @param int    $start Section start offset
+     * @param int    $end   Section end offset
+     * @param string $otag  Current Mustache opening tag
+     * @param string $ctag  Current Mustache closing tag
+     * @param int    $level
+     *
+     * @return string Generated PHP source code
+     */
+    private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
+    {
+        $key = $this->section($nodes, $id, array(), $start, $end, $otag, $ctag, $level, true);
+        $id  = var_export($id, true);
+
+        return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key, $id, $this->flushIndent());
+    }
+
     const SECTION_CALL = '
         // %s section
         $value = $context->%s(%s);%s
@@ -198,7 +312,8 @@ class Mustache_Compiler
             } elseif (!empty($value)) {
                 $values = $this->isIterable($value) ? $value : array($value);
                 foreach ($values as $value) {
-                    $context->push($value);%s
+                    $context->push($value);
+                    %s
                     $context->pop();
                 }
             }
@@ -209,26 +324,20 @@ class Mustache_Compiler
     /**
      * Generate Mustache Template section PHP source.
      *
-     * @param array  $nodes Array of child tokens
-     * @param string $id    Section name
-     * @param int    $start Section start offset
-     * @param int    $end   Section end offset
-     * @param string $otag  Current Mustache opening tag
-     * @param string $ctag  Current Mustache closing tag
-     * @param int    $level
+     * @param array    $nodes   Array of child tokens
+     * @param string   $id      Section name
+     * @param string[] $filters Array of filters
+     * @param int      $start   Section start offset
+     * @param int      $end     Section end offset
+     * @param string   $otag    Current Mustache opening tag
+     * @param string   $ctag    Current Mustache closing tag
+     * @param int      $level
+     * @param bool     $arg     (default: false)
      *
      * @return string Generated section PHP source code
      */
-    private function section($nodes, $id, $start, $end, $otag, $ctag, $level)
+    private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level, $arg = false)
     {
-        $filters = '';
-
-        if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
-            list($id, $filters) = $this->getFilters($id, $level);
-        }
-
-        $method   = $this->getFindMethod($id);
-        $id       = var_export($id, true);
         $source   = var_export(substr($this->source, $start, $end - $start), true);
         $callable = $this->getCallable();
 
@@ -238,13 +347,21 @@ class Mustache_Compiler
             $delims = '';
         }
 
-        $key    = ucfirst(md5($delims."\n".$source));
+        $key = ucfirst(md5($delims."\n".$source));
 
         if (!isset($this->sections[$key])) {
             $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $delims, $this->walk($nodes, 2));
         }
 
-        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
+        if ($arg === true) {
+            return $key;
+        } else {
+            $method  = $this->getFindMethod($id);
+            $id      = var_export($id, true);
+            $filters = $this->getFilters($filters, $level);
+
+            return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
+        }
     }
 
     const INVERTED_SECTION = '
@@ -257,29 +374,26 @@ class Mustache_Compiler
     /**
      * Generate Mustache Template inverted section PHP source.
      *
-     * @param array  $nodes Array of child tokens
-     * @param string $id    Section name
-     * @param int    $level
+     * @param array    $nodes   Array of child tokens
+     * @param string   $id      Section name
+     * @param string[] $filters Array of filters
+     * @param int      $level
      *
      * @return string Generated inverted section PHP source code
      */
-    private function invertedSection($nodes, $id, $level)
+    private function invertedSection($nodes, $id, $filters, $level)
     {
-        $filters = '';
-
-        if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
-            list($id, $filters) = $this->getFilters($id, $level);
-        }
-
-        $method = $this->getFindMethod($id);
-        $id     = var_export($id, true);
+        $method  = $this->getFindMethod($id);
+        $id      = var_export($id, true);
+        $filters = $this->getFilters($filters, $level);
 
         return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
     }
 
+    const PARTIAL_INDENT = ', $indent . %s';
     const PARTIAL = '
         if ($partial = $this->mustache->loadPartial(%s)) {
-            $buffer .= $partial->renderInternal($context, $indent . %s);
+            $buffer .= $partial->renderInternal($context%s);
         }
     ';
 
@@ -294,56 +408,86 @@ class Mustache_Compiler
      */
     private function partial($id, $indent, $level)
     {
+        if ($indent !== '') {
+            $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
+        } else {
+            $indentParam = '';
+        }
+
         return sprintf(
             $this->prepare(self::PARTIAL, $level),
             var_export($id, true),
-            var_export($indent, true)
+            $indentParam
         );
     }
 
-    const VARIABLE = '
-        $value = $this->resolveValue($context->%s(%s), $context, $indent);%s
-        $buffer .= %s%s;
+    const PARENT = '
+        %s
+
+        if ($parent = $this->mustache->LoadPartial(%s)) {
+            $context->pushBlockContext($newContext);
+            $buffer .= $parent->renderInternal($context, $indent);
+            $context->popBlockContext();
+        }
     ';
 
     /**
-     * Generate Mustache Template variable interpolation PHP source.
+     * Generate Mustache Template inheritance parent call PHP source.
      *
-     * @param string  $id     Variable name
-     * @param boolean $escape Escape the variable value for output?
-     * @param int     $level
+     * @param string $id       Parent tag name
+     * @param string $indent   Whitespace indent to apply to parent
+     * @param array  $children Child nodes
+     * @param int    $level
      *
-     * @return string Generated variable interpolation PHP source
+     * @return string Generated PHP source code
      */
-    private function variable($id, $escape, $level)
+    private function parent($id, $indent, array $children, $level)
     {
-        $filters = '';
-
-        if (isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS])) {
-            list($id, $filters) = $this->getFilters($id, $level);
-        }
+        $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
 
-        $method = $this->getFindMethod($id);
-        $id     = ($method !== 'last') ? var_export($id, true) : '';
-        $value  = $escape ? $this->getEscape() : '$value';
+        return sprintf(
+            $this->prepare(self::PARENT, $level),
+            $this->walk($realChildren, $level),
+            var_export($id, true),
+            var_export($indent, true)
+        );
+    }
 
-        return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
+    /**
+     * Helper method for filtering out non-block-arg tokens.
+     *
+     * @param array $node
+     *
+     * @return boolean True if $node is a block arg token.
+     */
+    private static function onlyBlockArgs(array $node)
+    {
+        return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
     }
 
+    const VARIABLE = '
+        $value = $this->resolveValue($context->%s(%s), $context, $indent);%s
+        $buffer .= %s%s;
+    ';
+
     /**
-     * Generate Mustache Template variable filtering PHP source.
+     * Generate Mustache Template variable interpolation PHP source.
      *
-     * @param string $id    Variable name
-     * @param int    $level
+     * @param string   $id      Variable name
+     * @param string[] $filters Array of filters
+     * @param boolean  $escape  Escape the variable value for output?
+     * @param int      $level
      *
-     * @return string Generated variable filtering PHP source
+     * @return string Generated variable interpolation PHP source
      */
-    private function getFilters($id, $level)
+    private function variable($id, $filters, $escape, $level)
     {
-        $filters = array_map('trim', explode('|', $id));
-        $id      = array_shift($filters);
+        $method  = $this->getFindMethod($id);
+        $id      = ($method !== 'last') ? var_export($id, true) : '';
+        $filters = $this->getFilters($filters, $level);
+        $value   = $escape ? $this->getEscape() : '$value';
 
-        return array($id, $this->getFilter($filters, $level));
+        return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
     }
 
     const FILTER = '
@@ -355,14 +499,14 @@ class Mustache_Compiler
     ';
 
     /**
-     * Generate PHP source for a single filter.
+     * Generate Mustache Template variable filtering PHP source.
      *
-     * @param array $filters
-     * @param int   $level
+     * @param string[] $filters Array of filters
+     * @param int      $level
      *
      * @return string Generated filter PHP source
      */
-    private function getFilter(array $filters, $level)
+    private function getFilters(array $filters, $level)
     {
         if (empty($filters)) {
             return '';
@@ -374,7 +518,7 @@ class Mustache_Compiler
         $callable = $this->getCallable('$filter');
         $msg      = var_export($name, true);
 
-        return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilter($filters, $level));
+        return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
     }
 
     const LINE = '$buffer .= "\n";';
@@ -434,9 +578,9 @@ class Mustache_Compiler
     {
         if ($this->customEscape) {
             return sprintf(self::CUSTOM_ESCAPE, $value);
-        } else {
-            return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
         }
+
+        return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
     }
 
     /**
@@ -456,16 +600,25 @@ class Mustache_Compiler
     {
         if ($id === '.') {
             return 'last';
-        } elseif (strpos($id, '.') === false) {
+        }
+
+        if (strpos($id, '.') === false) {
             return 'find';
-        } else {
-            return 'findDot';
         }
+
+        return 'findDot';
     }
 
     const IS_CALLABLE        = '!is_string(%s) && is_callable(%s)';
     const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
 
+    /**
+     * Helper function to compile strict vs lax "is callable" logic.
+     *
+     * @param string $variable (default: '$value')
+     *
+     * @return string "is callable" logic
+     */
     private function getCallable($variable = '$value')
     {
         $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;

+ 66 - 14
src/Mustache/Context.php

@@ -14,7 +14,8 @@
  */
 class Mustache_Context
 {
-    private $stack = array();
+    private $stack      = array();
+    private $blockStack = array();
 
     /**
      * Mustache rendering Context constructor.
@@ -38,6 +39,16 @@ class Mustache_Context
         array_push($this->stack, $value);
     }
 
+    /**
+     * Push a new Context frame onto the block context stack.
+     *
+     * @param mixed $value Object or array to use for block context
+     */
+    public function pushBlockContext($value)
+    {
+        array_push($this->blockStack, $value);
+    }
+
     /**
      * Pop the last Context frame from the stack.
      *
@@ -48,6 +59,16 @@ class Mustache_Context
         return array_pop($this->stack);
     }
 
+    /**
+     * Pop the last block Context frame from the stack.
+     *
+     * @return mixed Last block Context frame (object or array)
+     */
+    public function popBlockContext()
+    {
+        return array_pop($this->blockStack);
+    }
+
     /**
      * Get the last Context frame.
      *
@@ -120,6 +141,24 @@ class Mustache_Context
         return $value;
     }
 
+    /**
+     * Find an argument in the block context stack.
+     *
+     * @param string $id
+     *
+     * @return mixed Variable value, or '' if not found.
+     */
+    public function findInBlock($id)
+    {
+        foreach ($this->blockStack as $context) {
+            if (array_key_exists($id, $context)) {
+                return $context[$id];
+            }
+        }
+
+        return '';
+    }
+
     /**
      * Helper function to find a variable in the Context stack.
      *
@@ -133,19 +172,32 @@ class Mustache_Context
     private function findVariableInStack($id, array $stack)
     {
         for ($i = count($stack) - 1; $i >= 0; $i--) {
-            if (is_object($stack[$i]) && !($stack[$i] instanceof Closure)) {
-
-                // Note that is_callable() *will not work here*
-                // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
-                if (method_exists($stack[$i], $id)) {
-                    return $stack[$i]->$id();
-                } elseif (isset($stack[$i]->$id)) {
-                    return $stack[$i]->$id;
-                } elseif ($stack[$i] instanceof ArrayAccess && isset($stack[$i][$id])) {
-                    return $stack[$i][$id];
-                }
-            } elseif (is_array($stack[$i]) && array_key_exists($id, $stack[$i])) {
-                return $stack[$i][$id];
+            $frame = &$stack[$i];
+
+            switch (gettype($frame)) {
+                case 'object':
+                    if (!($frame instanceof Closure)) {
+                        // Note that is_callable() *will not work here*
+                        // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
+                        if (method_exists($frame, $id)) {
+                            return $frame->$id();
+                        }
+
+                        if (isset($frame->$id)) {
+                            return $frame->$id;
+                        }
+
+                        if ($frame instanceof ArrayAccess && isset($frame[$id])) {
+                            return $frame[$id];
+                        }
+                    }
+                    break;
+
+                case 'array':
+                    if (array_key_exists($id, $frame)) {
+                        return $frame[$id];
+                    }
+                    break;
             }
         }
 

+ 49 - 11
src/Mustache/Engine.php

@@ -23,10 +23,17 @@
  */
 class Mustache_Engine
 {
-    const VERSION        = '2.6.1';
+    const VERSION        = '2.7.0';
     const SPEC_VERSION   = '1.1.2';
 
     const PRAGMA_FILTERS = 'FILTERS';
+    const PRAGMA_BLOCKS  = 'BLOCKS';
+
+    // Known pragmas
+    private static $knownPragmas = array(
+        self::PRAGMA_FILTERS => true,
+        self::PRAGMA_BLOCKS  => true,
+    );
 
     // Template cache
     private $templates = array();
@@ -44,6 +51,7 @@ class Mustache_Engine
     private $charset = 'UTF-8';
     private $logger;
     private $strictCallables = false;
+    private $pragmas = array();
 
     // Services
     private $tokenizer;
@@ -110,6 +118,10 @@ class Mustache_Engine
      *         // helps protect against arbitrary code execution when user input is passed directly into the template.
      *         // This currently defaults to false, but will default to true in v3.0.
      *         'strict_callables' => true,
+     *
+     *         // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
+     *         // templates.
+     *         'pragmas' => [Mustache_Engine::PRAGMA_FILTERS],
      *     );
      *
      * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable.
@@ -176,6 +188,15 @@ class Mustache_Engine
         if (isset($options['strict_callables'])) {
             $this->strictCallables = $options['strict_callables'];
         }
+
+        if (isset($options['pragmas'])) {
+            foreach ($options['pragmas'] as $pragma) {
+                if (!isset(self::$knownPragmas[$pragma])) {
+                    throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma));
+                }
+                $this->pragmas[$pragma] = true;
+            }
+        }
     }
 
     /**
@@ -226,6 +247,16 @@ class Mustache_Engine
         return $this->charset;
     }
 
+    /**
+     * Get the current globally enabled pragmas.
+     *
+     * @return array
+     */
+    public function getPragmas()
+    {
+        return array_keys($this->pragmas);
+    }
+
     /**
      * Set the Mustache template Loader instance.
      *
@@ -247,7 +278,7 @@ class Mustache_Engine
     public function getLoader()
     {
         if (!isset($this->loader)) {
-            $this->loader = new Mustache_Loader_StringLoader;
+            $this->loader = new Mustache_Loader_StringLoader();
         }
 
         return $this->loader;
@@ -274,7 +305,7 @@ class Mustache_Engine
     public function getPartialsLoader()
     {
         if (!isset($this->partialsLoader)) {
-            $this->partialsLoader = new Mustache_Loader_ArrayLoader;
+            $this->partialsLoader = new Mustache_Loader_ArrayLoader();
         }
 
         return $this->partialsLoader;
@@ -290,7 +321,7 @@ class Mustache_Engine
     public function setPartials(array $partials = array())
     {
         if (!isset($this->partialsLoader)) {
-            $this->partialsLoader = new Mustache_Loader_ArrayLoader;
+            $this->partialsLoader = new Mustache_Loader_ArrayLoader();
         }
 
         if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
@@ -334,7 +365,7 @@ class Mustache_Engine
     public function getHelpers()
     {
         if (!isset($this->helpers)) {
-            $this->helpers = new Mustache_HelperCollection;
+            $this->helpers = new Mustache_HelperCollection();
         }
 
         return $this->helpers;
@@ -443,7 +474,7 @@ class Mustache_Engine
     public function getTokenizer()
     {
         if (!isset($this->tokenizer)) {
-            $this->tokenizer = new Mustache_Tokenizer;
+            $this->tokenizer = new Mustache_Tokenizer();
         }
 
         return $this->tokenizer;
@@ -469,7 +500,7 @@ class Mustache_Engine
     public function getParser()
     {
         if (!isset($this->parser)) {
-            $this->parser = new Mustache_Parser;
+            $this->parser = new Mustache_Parser();
         }
 
         return $this->parser;
@@ -495,7 +526,7 @@ class Mustache_Engine
     public function getCompiler()
     {
         if (!isset($this->compiler)) {
-            $this->compiler = new Mustache_Compiler;
+            $this->compiler = new Mustache_Compiler();
         }
 
         return $this->compiler;
@@ -563,12 +594,13 @@ class Mustache_Engine
     public function getTemplateClassName($source)
     {
         return $this->templateClassPrefix . md5(sprintf(
-            'version:%s,escape:%s,entity_flags:%i,charset:%s,strict_callables:%s,source:%s',
+            'version:%s,escape:%s,entity_flags:%i,charset:%s,strict_callables:%s,pragmas:%s,source:%s',
             self::VERSION,
             isset($this->escape) ? 'custom' : 'default',
             $this->entityFlags,
             $this->charset,
             $this->strictCallables ? 'true' : 'false',
+            implode(' ', $this->getPragmas()),
             $source
         ));
     }
@@ -705,7 +737,10 @@ class Mustache_Engine
      */
     private function parse($source)
     {
-        return $this->getParser()->parse($this->tokenize($source));
+        $parser = $this->getParser();
+        $parser->setPragmas($this->getPragmas());
+
+        return $parser->parse($this->tokenize($source));
     }
 
     /**
@@ -728,7 +763,10 @@ class Mustache_Engine
             array('className' => $name)
         );
 
-        return $this->getCompiler()->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
+        $compiler = $this->getCompiler();
+        $compiler->setPragmas($this->getPragmas());
+
+        return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
     }
 
     /**

+ 1 - 1
src/Mustache/Loader/FilesystemLoader.php

@@ -49,7 +49,7 @@ class Mustache_Loader_FilesystemLoader implements Mustache_Loader
     {
         $this->baseDir = $baseDir;
 
-        if (strpos($this->baseDir, '://') === -1) {
+        if (strpos($this->baseDir, '://') === false) {
             $this->baseDir = realpath($this->baseDir);
         }
 

+ 115 - 4
src/Mustache/Parser.php

@@ -18,6 +18,11 @@ class Mustache_Parser
 {
     private $lineNum;
     private $lineTokens;
+    private $pragmas;
+    private $defaultPragmas = array();
+
+    private $pragmaFilters;
+    private $pragmaBlocks;
 
     /**
      * Process an array of Mustache tokens and convert them into a parse tree.
@@ -30,10 +35,31 @@ class Mustache_Parser
     {
         $this->lineNum    = -1;
         $this->lineTokens = 0;
+        $this->pragmas    = $this->defaultPragmas;
+
+        $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
+        $this->pragmaBlocks  = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);
 
         return $this->buildTree($tokens);
     }
 
+    /**
+     * Enable pragmas across all templates, regardless of the presence of pragma
+     * tags in the individual templates.
+     *
+     * @internal Users should set global pragmas in Mustache_Engine, not here :)
+     *
+     * @param string[] $pragmas
+     */
+    public function setPragmas(array $pragmas)
+    {
+        $this->pragmas = array();
+        foreach ($pragmas as $pragma) {
+            $this->enablePragma($pragma);
+        }
+        $this->defaultPragmas = $this->pragmas;
+    }
+
     /**
      * Helper method for recursively building a parse tree.
      *
@@ -58,13 +84,23 @@ class Mustache_Parser
                 $this->lineTokens = 0;
             }
 
+            if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
+                list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
+                if (!empty($filters)) {
+                    $token[Mustache_Tokenizer::NAME]    = $name;
+                    $token[Mustache_Tokenizer::FILTERS] = $filters;
+                }
+            }
+
             switch ($token[Mustache_Tokenizer::TYPE]) {
                 case Mustache_Tokenizer::T_DELIM_CHANGE:
+                    $this->checkIfTokenIsAllowedInParent($parent, $token);
                     $this->clearStandaloneLines($nodes, $tokens);
                     break;
 
                 case Mustache_Tokenizer::T_SECTION:
                 case Mustache_Tokenizer::T_INVERTED:
+                    $this->checkIfTokenIsAllowedInParent($parent, $token);
                     $this->clearStandaloneLines($nodes, $tokens);
                     $nodes[] = $this->buildTree($tokens, $token);
                     break;
@@ -97,15 +133,40 @@ class Mustache_Parser
                     return $parent;
 
                 case Mustache_Tokenizer::T_PARTIAL:
-                case Mustache_Tokenizer::T_PARTIAL_2:
-                    // store the whitespace prefix for laters!
+                    $this->checkIfTokenIsAllowedInParent($parent, $token);
+                    //store the whitespace prefix for laters!
                     if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
                         $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
                     }
                     $nodes[] = $token;
                     break;
 
+                case Mustache_Tokenizer::T_PARENT:
+                    $this->checkIfTokenIsAllowedInParent($parent, $token);
+                    $nodes[] = $this->buildTree($tokens, $token);
+                    break;
+
+                case Mustache_Tokenizer::T_BLOCK_VAR:
+                    if ($this->pragmaBlocks) {
+                        // BLOCKS pragma is enabled, let's do this!
+                        if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
+                            $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
+                        }
+                        $this->clearStandaloneLines($nodes, $tokens);
+                        $nodes[] = $this->buildTree($tokens, $token);
+                    } else {
+                        // pretend this was just a normal "escaped" token...
+                        $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
+                        // TODO: figure out how to figure out if there was a space after this dollar:
+                        $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME];
+                        $nodes[] = $token;
+                    }
+                    break;
+
                 case Mustache_Tokenizer::T_PRAGMA:
+                    $this->enablePragma($token[Mustache_Tokenizer::NAME]);
+                    // no break
+
                 case Mustache_Tokenizer::T_COMMENT:
                     $this->clearStandaloneLines($nodes, $tokens);
                     $nodes[] = $token;
@@ -137,7 +198,7 @@ class Mustache_Parser
      * @param array $nodes  Parsed nodes.
      * @param array $tokens Tokens to be parsed.
      *
-     * @return array Resulting indent token, if any.
+     * @return array|null Resulting indent token, if any.
      */
     private function clearStandaloneLines(array &$nodes, array &$tokens)
     {
@@ -197,10 +258,60 @@ class Mustache_Parser
      */
     private function tokenIsWhitespace(array $token)
     {
-        if ($token[Mustache_Tokenizer::TYPE] == Mustache_Tokenizer::T_TEXT) {
+        if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
             return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
         }
 
         return false;
     }
+
+    /**
+     * Check whether a token is allowed inside a parent tag.
+     *
+     * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag.
+     *
+     * @param array|null $parent
+     * @param array      $token
+     */
+    private function checkIfTokenIsAllowedInParent($parent, array $token)
+    {
+        if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
+            throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token);
+        }
+    }
+
+    /**
+     * Split a tag name into name and filters.
+     *
+     * @param string $name
+     *
+     * @return array [Tag name, Array of filters]
+     */
+    private function getNameAndFilters($name)
+    {
+        $filters = array_map('trim', explode('|', $name));
+        $name    = array_shift($filters);
+
+        return array($name, $filters);
+    }
+
+    /**
+     * Enable a pragma.
+     *
+     * @param string $name
+     */
+    private function enablePragma($name)
+    {
+        $this->pragmas[$name] = true;
+
+        switch ($name) {
+            case Mustache_Engine::PRAGMA_BLOCKS:
+                $this->pragmaBlocks = true;
+                break;
+
+            case Mustache_Engine::PRAGMA_FILTERS:
+                $this->pragmaFilters = true;
+                break;
+        }
+    }
 }

+ 18 - 13
src/Mustache/Template.php

@@ -63,7 +63,9 @@ abstract class Mustache_Template
      */
     public function render($context = array())
     {
-        return $this->renderInternal($this->prepareContextStack($context));
+        return $this->renderInternal(
+            $this->prepareContextStack($context)
+        );
     }
 
     /**
@@ -111,19 +113,22 @@ abstract class Mustache_Template
      */
     protected function isIterable($value)
     {
-        if (is_object($value)) {
-            return $value instanceof Traversable;
-        } elseif (is_array($value)) {
-            $i = 0;
-            foreach ($value as $k => $v) {
-                if ($k !== $i++) {
-                    return false;
+        switch (gettype($value)) {
+            case 'object':
+                return $value instanceof Traversable;
+
+            case 'array':
+                $i = 0;
+                foreach ($value as $k => $v) {
+                    if ($k !== $i++) {
+                        return false;
+                    }
                 }
-            }
 
-            return true;
-        } else {
-            return false;
+                return true;
+
+            default:
+                return false;
         }
     }
 
@@ -138,7 +143,7 @@ abstract class Mustache_Template
      */
     protected function prepareContextStack($context = null)
     {
-        $stack = new Mustache_Context;
+        $stack = new Mustache_Context();
 
         $helpers = $this->mustache->getHelpers();
         if (!$helpers->isEmpty()) {

+ 19 - 15
src/Mustache/Tokenizer.php

@@ -27,13 +27,15 @@ class Mustache_Tokenizer
     const T_END_SECTION  = '/';
     const T_COMMENT      = '!';
     const T_PARTIAL      = '>';
-    const T_PARTIAL_2    = '<';
+    const T_PARENT       = '<';
     const T_DELIM_CHANGE = '=';
     const T_ESCAPED      = '_v';
     const T_UNESCAPED    = '{';
     const T_UNESCAPED_2  = '&';
     const T_TEXT         = '_t';
     const T_PRAGMA       = '%';
+    const T_BLOCK_VAR    = '$';
+    const T_BLOCK_ARG    = '$arg';
 
     // Valid token types
     private static $tagTypes = array(
@@ -42,32 +44,34 @@ class Mustache_Tokenizer
         self::T_END_SECTION  => true,
         self::T_COMMENT      => true,
         self::T_PARTIAL      => true,
-        self::T_PARTIAL_2    => true,
+        self::T_PARENT       => true,
         self::T_DELIM_CHANGE => true,
         self::T_ESCAPED      => true,
         self::T_UNESCAPED    => true,
         self::T_UNESCAPED_2  => true,
         self::T_PRAGMA       => true,
+        self::T_BLOCK_VAR    => true,
     );
 
     // Interpolated tags
     private static $interpolatedTags = array(
-        self::T_ESCAPED      => true,
-        self::T_UNESCAPED    => true,
-        self::T_UNESCAPED_2  => true,
+        self::T_ESCAPED     => true,
+        self::T_UNESCAPED   => true,
+        self::T_UNESCAPED_2 => true,
     );
 
     // Token properties
-    const TYPE   = 'type';
-    const NAME   = 'name';
-    const OTAG   = 'otag';
-    const CTAG   = 'ctag';
-    const LINE   = 'line';
-    const INDEX  = 'index';
-    const END    = 'end';
-    const INDENT = 'indent';
-    const NODES  = 'nodes';
-    const VALUE  = 'value';
+    const TYPE    = 'type';
+    const NAME    = 'name';
+    const OTAG    = 'otag';
+    const CTAG    = 'ctag';
+    const LINE    = 'line';
+    const INDEX   = 'index';
+    const END     = 'end';
+    const INDENT  = 'indent';
+    const NODES   = 'nodes';
+    const VALUE   = 'value';
+    const FILTERS = 'filters';
 
     private $state;
     private $tagType;

+ 2 - 2
test/Mustache/Test/CompilerTest.php

@@ -20,7 +20,7 @@ class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase
      */
     public function testCompile($source, array $tree, $name, $customEscaper, $entityFlags, $charset, $expected)
     {
-        $compiler = new Mustache_Compiler;
+        $compiler = new Mustache_Compiler();
 
         $compiled = $compiler->compile($source, $tree, $name, $customEscaper, $charset, false, $entityFlags);
         foreach ($expected as $contains) {
@@ -138,7 +138,7 @@ class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase
      */
     public function testCompilerThrowsSyntaxException()
     {
-        $compiler = new Mustache_Compiler;
+        $compiler = new Mustache_Compiler();
         $compiler->compile('', array(array(Mustache_Tokenizer::TYPE => 'invalid')), 'SomeClass');
     }
 

+ 9 - 9
test/Mustache/Test/ContextTest.php

@@ -16,7 +16,7 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
 {
     public function testConstructor()
     {
-        $one = new Mustache_Context;
+        $one = new Mustache_Context();
         $this->assertSame('', $one->find('foo'));
         $this->assertSame('', $one->find('bar'));
 
@@ -27,7 +27,7 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('FOO', $two->find('foo'));
         $this->assertEquals('<BAR>', $two->find('bar'));
 
-        $obj = new StdClass;
+        $obj = new StdClass();
         $obj->name = 'NAME';
         $three = new Mustache_Context($obj);
         $this->assertSame($obj, $three->last());
@@ -36,16 +36,16 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
 
     public function testPushPopAndLast()
     {
-        $context = new Mustache_Context;
+        $context = new Mustache_Context();
         $this->assertFalse($context->last());
 
-        $dummy = new Mustache_Test_TestDummy;
+        $dummy = new Mustache_Test_TestDummy();
         $context->push($dummy);
         $this->assertSame($dummy, $context->last());
         $this->assertSame($dummy, $context->pop());
         $this->assertFalse($context->last());
 
-        $obj = new StdClass;
+        $obj = new StdClass();
         $context->push($dummy);
         $this->assertSame($dummy, $context->last());
         $context->push($obj);
@@ -57,11 +57,11 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
 
     public function testFind()
     {
-        $context = new Mustache_Context;
+        $context = new Mustache_Context();
 
-        $dummy = new Mustache_Test_TestDummy;
+        $dummy = new Mustache_Test_TestDummy();
 
-        $obj = new StdClass;
+        $obj = new StdClass();
         $obj->name = 'obj';
 
         $arr = array(
@@ -112,7 +112,7 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
 
     public function testAccessorPriority()
     {
-        $context = new Mustache_Context(new Mustache_Test_AllTheThings);
+        $context = new Mustache_Context(new Mustache_Test_AllTheThings());
 
         $this->assertEquals('win', $context->find('foo'), 'method beats property');
         $this->assertEquals('win', $context->find('bar'), 'property beats ArrayAccess');

+ 24 - 12
test/Mustache/Test/EngineTest.php

@@ -17,8 +17,8 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
     public function testConstructor()
     {
         $logger         = new Mustache_Logger_StreamLogger(tmpfile());
-        $loader         = new Mustache_Loader_StringLoader;
-        $partialsLoader = new Mustache_Loader_ArrayLoader;
+        $loader         = new Mustache_Loader_StringLoader();
+        $partialsLoader = new Mustache_Loader_ArrayLoader();
         $mustache       = new Mustache_Engine(array(
             'template_class_prefix' => '__whot__',
             'cache'  => self::$tempDir,
@@ -36,6 +36,7 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
             'escape'  => 'strtoupper',
             'entity_flags' => ENT_QUOTES,
             'charset' => 'ISO-8859-1',
+            'pragmas' => array(Mustache_Engine::PRAGMA_FILTERS),
         ));
 
         $this->assertSame($logger, $mustache->getLogger());
@@ -50,6 +51,7 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
         $this->assertTrue($mustache->hasHelper('bar'));
         $this->assertFalse($mustache->hasHelper('baz'));
         $this->assertInstanceOf('Mustache_Cache_FilesystemCache', $mustache->getCache());
+        $this->assertEquals(array(Mustache_Engine::PRAGMA_FILTERS), $mustache->getPragmas());
     }
 
     public static function getFoo()
@@ -67,7 +69,7 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
             ->disableOriginalConstructor()
             ->getMock();
 
-        $mustache = new MustacheStub;
+        $mustache = new MustacheStub();
         $mustache->template = $template;
 
         $template->expects($this->once())
@@ -82,11 +84,11 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
     public function testSettingServices()
     {
         $logger    = new Mustache_Logger_StreamLogger(tmpfile());
-        $loader    = new Mustache_Loader_StringLoader;
-        $tokenizer = new Mustache_Tokenizer;
-        $parser    = new Mustache_Parser;
-        $compiler  = new Mustache_Compiler;
-        $mustache  = new Mustache_Engine;
+        $loader    = new Mustache_Loader_StringLoader();
+        $tokenizer = new Mustache_Tokenizer();
+        $parser    = new Mustache_Parser();
+        $compiler  = new Mustache_Compiler();
+        $mustache  = new Mustache_Engine();
         $cache     = new Mustache_Cache_FilesystemCache(self::$tempDir);
 
         $this->assertNotSame($logger, $mustache->getLogger());
@@ -179,7 +181,7 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
     public function testImmutablePartialsLoadersThrowException()
     {
         $mustache = new Mustache_Engine(array(
-            'partials_loader' => new Mustache_Loader_StringLoader,
+            'partials_loader' => new Mustache_Loader_StringLoader(),
         ));
 
         $mustache->setPartials(array('foo' => '{{ foo }}'));
@@ -243,7 +245,7 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
      */
     public function testSetHelpersThrowsExceptions()
     {
-        $mustache = new Mustache_Engine;
+        $mustache = new Mustache_Engine();
         $mustache->setHelpers('monkeymonkeymonkey');
     }
 
@@ -252,8 +254,8 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
      */
     public function testSetLoggerThrowsExceptions()
     {
-        $mustache = new Mustache_Engine;
-        $mustache->setLogger(new StdClass);
+        $mustache = new Mustache_Engine();
+        $mustache->setLogger(new StdClass());
     }
 
     public function testLoadPartialCascading()
@@ -319,6 +321,16 @@ class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
         $this->assertContains("WARNING: Partial not found: \"bar\"", $log);
     }
 
+    /**
+     * @expectedException Mustache_Exception_InvalidArgumentException
+     */
+    public function testUnknownPragmaThrowsException()
+    {
+        new Mustache_Engine(array(
+            'pragmas' => array('UNKNOWN')
+        ));
+    }
+
     private function getLoggedMustache($level = Mustache_Logger::ERROR)
     {
         $name     = tempnam(sys_get_temp_dir(), 'mustache-test');

+ 1 - 1
test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php

@@ -19,7 +19,7 @@ class Mustache_Test_FiveThree_Functional_ClosureQuirksTest extends PHPUnit_Frame
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     public function testClosuresDontLikeItWhenYouTouchTheirProperties()

+ 50 - 0
test/Mustache/Test/FiveThree/Functional/EngineTest.php

@@ -0,0 +1,50 @@
+<?php
+
+/*
+ * This file is part of Mustache.php.
+ *
+ * (c) 2010-2014 Justin Hileman
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * @group pragmas
+ * @group functional
+ */
+class Mustache_Test_FiveThree_Functional_EngineTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider pragmaData
+     */
+    public function testPragmasConstructorOption($pragmas, $helpers, $data, $tpl, $expect)
+    {
+        $mustache = new Mustache_Engine(array(
+            'pragmas' => $pragmas,
+            'helpers' => $helpers,
+        ));
+
+        $this->assertEquals($expect, $mustache->render($tpl, $data));
+    }
+
+    public function pragmaData()
+    {
+        $helpers = array(
+            'longdate' => function (\DateTime $value) {
+                return $value->format('Y-m-d h:m:s');
+            }
+        );
+
+        $data = array(
+            'date' => new DateTime('1/1/2000', new DateTimeZone('UTC')),
+        );
+
+        $tpl = '{{ date | longdate }}';
+
+        return array(
+            array(array(Mustache_Engine::PRAGMA_FILTERS), $helpers, $data, $tpl, '2000-01-01 12:01:00'),
+            array(array(),                                $helpers, $data, $tpl, ''                   ),
+        );
+    }
+}

+ 2 - 2
test/Mustache/Test/FiveThree/Functional/FiltersTest.php

@@ -19,7 +19,7 @@ class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_T
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     /**
@@ -71,7 +71,7 @@ class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_T
             return sprintf('[[%s]]', $value);
         });
 
-        $foo = new \StdClass;
+        $foo = new \StdClass();
         $foo->date = new DateTime('1/1/2000', new DateTimeZone("UTC"));
 
         $this->assertEquals('[[2000-01-01 12:01:00]]', $tpl->render($foo));

+ 3 - 3
test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php

@@ -19,14 +19,14 @@ class Mustache_Test_FiveThree_Functional_HigherOrderSectionsTest extends PHPUnit
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     public function testAnonymousFunctionSectionCallback()
     {
         $tpl = $this->mustache->loadTemplate('{{#wrapper}}{{name}}{{/wrapper}}');
 
-        $foo = new Mustache_Test_FiveThree_Functional_Foo;
+        $foo = new Mustache_Test_FiveThree_Functional_Foo();
         $foo->name = 'Mario';
         $foo->wrapper = function ($text) {
             return sprintf('<div class="anonymous">%s</div>', $text);
@@ -40,7 +40,7 @@ class Mustache_Test_FiveThree_Functional_HigherOrderSectionsTest extends PHPUnit
         $one = $this->mustache->loadTemplate('{{name}}');
         $two = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
 
-        $foo = new Mustache_Test_FiveThree_Functional_Foo;
+        $foo = new Mustache_Test_FiveThree_Functional_Foo();
         $foo->name = 'Luigi';
 
         $this->assertEquals($foo->name, $one->render($foo));

+ 2 - 2
test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php

@@ -19,7 +19,7 @@ class Mustache_Test_FiveThree_Functional_LambdaHelperTest extends PHPUnit_Framew
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     public function testSectionLambdaHelper()
@@ -27,7 +27,7 @@ class Mustache_Test_FiveThree_Functional_LambdaHelperTest extends PHPUnit_Framew
         $one = $this->mustache->loadTemplate('{{name}}');
         $two = $this->mustache->loadTemplate('{{#lambda}}{{name}}{{/lambda}}');
 
-        $foo = new StdClass;
+        $foo = new StdClass();
         $foo->name = 'Mario';
         $foo->lambda = function ($text, $mustache) {
             return strtoupper($mustache->render($text));

+ 1 - 1
test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php

@@ -23,7 +23,7 @@ class Mustache_Test_FiveThree_Functional_StrictCallablesTest extends PHPUnit_Fra
         $mustache = new Mustache_Engine(array('strict_callables' => $strict));
         $tpl      = $mustache->loadTemplate('{{# section }}{{ name }}{{/ section }}');
 
-        $data = new StdClass;
+        $data = new StdClass();
         $data->name    = $name;
         $data->section = $section;
 

+ 1 - 1
test/Mustache/Test/Functional/CallTest.php

@@ -18,7 +18,7 @@ class Mustache_Test_Functional_CallTest extends PHPUnit_Framework_TestCase
 
     public function testCallEatsContext()
     {
-        $m = new Mustache_Engine;
+        $m = new Mustache_Engine();
         $tpl = $m->loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}');
 
         $foo = new Mustache_Test_Functional_ClassWithCall();

+ 3 - 2
test/Mustache/Test/Functional/ExamplesTest.php

@@ -92,8 +92,9 @@ class Mustache_Test_Functional_ExamplesTest extends PHPUnit_Framework_TestCase
                 // load other files
                 switch ($info['extension']) {
                     case 'php':
-                        require_once($fullpath);
-                        $context = new $info['filename'];
+                        require_once $fullpath;
+                        $className = $info['filename'];
+                        $context   = new $className();
                         break;
 
                     case 'mustache':

+ 7 - 7
test/Mustache/Test/Functional/HigherOrderSectionsTest.php

@@ -19,7 +19,7 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     /**
@@ -32,10 +32,10 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
 
     public function sectionCallbackData()
     {
-        $foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo();
         $foo->doublewrap = array($foo, 'wrapWithBoth');
 
-        $bar = new Mustache_Test_Functional_Foo;
+        $bar = new Mustache_Test_Functional_Foo();
         $bar->trimmer = array(get_class($bar), 'staticTrim');
 
         return array(
@@ -48,7 +48,7 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
     {
         $tpl = $this->mustache->loadTemplate('{{#trim}}    {{name}}    {{/trim}}');
 
-        $foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo();
 
         $data = array(
             'name' => 'Bob',
@@ -81,7 +81,7 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
 
         $tpl = $mustache->loadTemplate('{{#wrap}}NAME{{/wrap}}');
 
-        $foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo();
         $foo->wrap = array($foo, 'wrapWithEm');
 
         $this->assertEquals('<em>NAME</em>', $tpl->render($foo));
@@ -96,7 +96,7 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
 
         $tpl = $mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
 
-        $foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo();
         $foo->wrap = array($foo, 'wrapWithEm');
 
         $this->assertEquals('<em>' . $foo->name . '</em>', $tpl->render($foo));
@@ -115,7 +115,7 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_Fun
         ));
 
         $tpl = $mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
-        $foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo();
         $foo->wrap = array($foo, 'wrapWithEm');
         $this->assertEquals('<em>' . $foo->name . '</em>', $tpl->render($foo));
         $this->assertCount($expect, glob($cacheDir . '/*.php'));

+ 437 - 0
test/Mustache/Test/Functional/InheritanceTest.php

@@ -0,0 +1,437 @@
+<?php
+
+/**
+ * @group inheritance
+ * @group functional
+ */
+
+class Mustache_Test_Functional_InheritanceTest extends PHPUnit_Framework_TestCase
+{
+    private $mustache;
+
+    public function setUp()
+    {
+        $this->mustache = new Mustache_Engine(array(
+            'pragmas' => array(Mustache_Engine::PRAGMA_BLOCKS),
+        ));
+    }
+
+    public function getIllegalInheritanceExamples()
+    {
+        return array(
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}',
+                ),
+                array(
+                    'bar' => 'set by user'
+                ),
+                '{{< foo }}{{# bar }}{{$ baz }}{{/ baz }}{{/ bar }}{{/ foo }}',
+            ),
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}'
+                ),
+                array(
+                ),
+                '{{<foo}}{{^bar}}{{$baz}}set by template{{/baz}}{{/bar}}{{/foo}}',
+            ),
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}',
+                    'qux' => 'I am a partial'
+                ),
+                array(
+                ),
+                '{{<foo}}{{>qux}}{{$baz}}set by template{{/baz}}{{/foo}}'
+            ),
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}'
+                ),
+                array(),
+                '{{<foo}}{{=<% %>=}}<%={{ }}=%>{{/foo}}'
+            )
+        );
+    }
+
+    public function getLegalInheritanceExamples()
+    {
+        return array(
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}',
+                ),
+                array(
+                    'bar' => 'set by user'
+                ),
+                '{{<foo}}{{bar}}{{$baz}}override{{/baz}}{{/foo}}',
+                'override'
+            ),
+            array(
+                array(
+                    'foo' => '{{$baz}}default content{{/baz}}'
+                ),
+                array(
+                ),
+                '{{<foo}}{{! ignore me }}{{$baz}}set by template{{/baz}}{{/foo}}',
+                'set by template'
+            ),
+            array(
+                array(
+                    'foo' => '{{$baz}}defualt content{{/baz}}'
+                ),
+                array(),
+                '{{<foo}}set by template{{$baz}}also set by template{{/baz}}{{/foo}}',
+                'also set by template'
+            )
+        );
+    }
+
+    public function testDefaultContent()
+    {
+        $tpl = $this->mustache->loadTemplate('{{$title}}Default title{{/title}}');
+
+        $data = array();
+
+        $this->assertEquals('Default title', $tpl->render($data));
+    }
+
+    public function testDefaultContentRendersVariables()
+    {
+        $tpl = $this->mustache->loadTemplate('{{$foo}}default {{bar}} content{{/foo}}');
+
+        $data = array(
+            'bar' => 'baz'
+        );
+
+        $this->assertEquals('default baz content', $tpl->render($data));
+    }
+
+    public function testDefaultContentRendersTripleMustacheVariables()
+    {
+        $tpl = $this->mustache->loadTemplate('{{$foo}}default {{{bar}}} content{{/foo}}');
+
+        $data = array(
+            'bar' => '<baz>'
+        );
+
+        $this->assertEquals('default <baz> content', $tpl->render($data));
+    }
+
+    public function testDefaultContentRendersSections()
+    {
+        $tpl = $this->mustache->loadTemplate(
+            '{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}'
+        );
+
+        $data = array(
+            'bar' => array('baz' => 'qux')
+        );
+
+        $this->assertEquals('default qux content', $tpl->render($data));
+    }
+
+    public function testDefaultContentRendersNegativeSections()
+    {
+        $tpl = $this->mustache->loadTemplate(
+            '{{$foo}}default {{^bar}}{{baz}}{{/bar}} content{{/foo}}'
+        );
+
+        $data = array(
+            'foo' => array('bar' => 'qux'),
+            'baz' => 'three'
+        );
+
+        $this->assertEquals('default three content', $tpl->render($data));
+
+    }
+
+    public function testMustacheInjectionInDefaultContent()
+    {
+        $tpl = $this->mustache->loadTemplate(
+            '{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}'
+        );
+
+        $data = array(
+            'bar' => array('baz' => '{{qux}}')
+        );
+
+        $this->assertEquals('default {{qux}} content', $tpl->render($data));
+    }
+
+    public function testDefaultContentRenderedInsideIncludedTemplates()
+    {
+        $partials = array(
+            'include' => '{{$foo}}default content{{/foo}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}}{{/include}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('default content', $tpl->render($data));
+    }
+
+    public function testOverriddenContent()
+    {
+        $partials = array(
+            'super' => '...{{$title}}Default title{{/title}}...'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<super}}{{$title}}sub template title{{/title}}{{/super}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('...sub template title...', $tpl->render($data));
+    }
+
+    public function testOverriddenPartial()
+    {
+        $partials = array(
+            'partial' => '|{{$stuff}}...{{/stuff}}{{$default}} default{{/default}}|'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            'test {{<partial}}{{$stuff}}override1{{/stuff}}{{/partial}} {{<partial}}{{$stuff}}override2{{/stuff}}{{/partial}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('test |override1 default| |override2 default|', $tpl->render($data));
+    }
+
+    public function testDataDoesNotOverrideBlock()
+    {
+        $partials = array(
+            'include' => '{{$var}}var in include{{/var}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}}{{$var}}var in template{{/var}}{{/include}}'
+        );
+
+        $data = array(
+            'var' => 'var in data'
+        );
+
+        $this->assertEquals('var in template', $tpl->render($data));
+    }
+
+    public function testDataDoesNotOverrideDefaultBlockValue()
+    {
+        $partials = array(
+            'include' => '{{$var}}var in include{{/var}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}}{{/include}}'
+        );
+
+        $data = array(
+            'var' => 'var in data'
+        );
+
+        $this->assertEquals('var in include', $tpl->render($data));
+    }
+
+    public function testOverridePartialWithNewlines()
+    {
+         $partials = array(
+            'partial' => '{{$ballmer}}peaking{{/ballmer}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            "{{<partial}}{{\$ballmer}}\npeaked\n\n:(\n{{/ballmer}}{{/partial}}"
+        );
+
+        $data = array();
+
+        $this->assertEquals("peaked\n\n:(\n", $tpl->render($data));
+    }
+
+    public function testInheritIndentationWhenOverridingAPartial()
+    {
+        $partials = array(
+            'partial' =>
+                'stop:
+                    {{$nineties}}collaborate and listen{{/nineties}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<partial}}{{$nineties}}hammer time{{/nineties}}{{/partial}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals(
+            'stop:
+                    hammer time',
+            $tpl->render($data)
+        );
+    }
+
+    public function testOverrideOneSubstitutionButNotTheOther()
+    {
+        $partials = array(
+            'partial' => '{{$stuff}}default one{{/stuff}}, {{$stuff2}}default two{{/stuff2}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<partial}}{{$stuff2}}override two{{/stuff2}}{{/partial}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('default one, override two', $tpl->render($data));
+    }
+
+    public function testSuperTemplatesWithNoParameters()
+    {
+        $partials = array(
+            'include' => '{{$foo}}default content{{/foo}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{>include}}|{{<include}}{{/include}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('default content|default content', $tpl->render($data));
+    }
+
+    public function testRecursionInInheritedTemplates()
+    {
+        $partials = array(
+            'include' => '{{$foo}}default content{{/foo}} {{$bar}}{{<include2}}{{/include2}}{{/bar}}',
+            'include2' => '{{$foo}}include2 default content{{/foo}} {{<include}}{{$bar}}don\'t recurse{{/bar}}{{/include}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}}{{$foo}}override{{/foo}}{{/include}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('override override override don\'t recurse', $tpl->render($data));
+    }
+
+    public function testTopLevelSubstitutionsTakePrecedenceInMultilevelInheritance()
+    {
+        $partials = array(
+            'parent' => '{{<older}}{{$a}}p{{/a}}{{/older}}',
+            'older' => '{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}',
+            'grandParent' => '{{$a}}g{{/a}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<parent}}{{$a}}c{{/a}}{{/parent}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('c', $tpl->render($data));
+    }
+
+    public function testMultiLevelInheritanceNoSubChild()
+    {
+        $partials = array(
+            'parent' => '{{<older}}{{$a}}p{{/a}}{{/older}}',
+            'older' => '{{<grandParent}}{{$a}}o{{/a}}{{/grandParent}}',
+            'grandParent' => '{{$a}}g{{/a}}'
+        );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<parent}}{{/parent}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('p', $tpl->render($data));
+    }
+
+    public function testIgnoreTextInsideSuperTemplatesButParseArgs()
+    {
+        $partials = array(
+            'include' => '{{$foo}}default content{{/foo}}'
+         );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}} asdfasd {{$foo}}hmm{{/foo}} asdfasdfasdf {{/include}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('hmm', $tpl->render($data));
+    }
+
+    public function testIgnoreTextInsideSuperTemplates()
+    {
+        $partials = array(
+            'include' => '{{$foo}}default content{{/foo}}'
+         );
+
+        $this->mustache->setPartials($partials);
+
+        $tpl = $this->mustache->loadTemplate(
+            '{{<include}} asdfasd asdfasdfasdf {{/include}}'
+        );
+
+        $data = array();
+
+        $this->assertEquals('default content', $tpl->render($data));
+    }
+
+    /**
+     * @dataProvider getIllegalInheritanceExamples
+     * @expectedException Mustache_Exception_SyntaxException
+     * @expectedExceptionMessage Illegal content in < parent tag
+     */
+    public function testIllegalInheritanceExamples($partials, $data, $template)
+    {
+        $this->mustache->setPartials($partials);
+        $tpl = $this->mustache->loadTemplate($template);
+        $tpl->render($data);
+    }
+
+    /**
+     * @dataProvider getLegalInheritanceExamples
+     */
+    public function testLegalInheritanceExamples($partials, $data, $template, $expect)
+    {
+        $this->mustache->setPartials($partials);
+        $tpl = $this->mustache->loadTemplate($template);
+        $this->assertSame($expect, $tpl->render($data));
+    }
+}

+ 1 - 1
test/Mustache/Test/Functional/MustacheInjectionTest.php

@@ -20,7 +20,7 @@ class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_T
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     /**

+ 45 - 0
test/Mustache/Test/Functional/NestedPartialIndentTest.php

@@ -0,0 +1,45 @@
+<?php
+
+/*
+ * This file is part of Mustache.php.
+ *
+ * (c) 2010-2014 Justin Hileman
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * @group functional
+ * @group partials
+ */
+class Mustache_Test_Functional_NestedPartialIndentTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @dataProvider partialsAndStuff
+     */
+    public function testNestedPartialsAreIndentedProperly($src, array $partials, $expected)
+    {
+        $m = new Mustache_Engine(array(
+            'partials' => $partials
+        ));
+        $tpl = $m->loadTemplate($src);
+        $this->assertEquals($expected, $tpl->render());
+    }
+
+    public function partialsAndStuff()
+    {
+        $partials = array(
+            'a' => ' {{> b }}',
+            'b' => ' {{> d }}',
+            'c' => ' {{> d }}{{> d }}',
+            'd' => 'D!',
+        );
+
+        return array(
+            array(' {{> a }}', $partials, '   D!'),
+            array(' {{> b }}', $partials, '  D!'),
+            array(' {{> c }}', $partials, '  D!D!'),
+        );
+    }
+}

+ 7 - 7
test/Mustache/Test/Functional/ObjectSectionTest.php

@@ -19,13 +19,13 @@ class Mustache_Test_Functional_ObjectSectionTest extends PHPUnit_Framework_TestC
 
     public function setUp()
     {
-        $this->mustache = new Mustache_Engine;
+        $this->mustache = new Mustache_Engine();
     }
 
     public function testBasicObject()
     {
         $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
-        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Alpha));
+        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Alpha()));
     }
 
     /**
@@ -34,7 +34,7 @@ class Mustache_Test_Functional_ObjectSectionTest extends PHPUnit_Framework_TestC
     public function testObjectWithGet()
     {
         $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
-        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Beta));
+        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Beta()));
     }
 
     /**
@@ -43,14 +43,14 @@ class Mustache_Test_Functional_ObjectSectionTest extends PHPUnit_Framework_TestC
     public function testSectionObjectWithGet()
     {
         $tpl = $this->mustache->loadTemplate('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}');
-        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Gamma));
+        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Gamma()));
     }
 
     public function testSectionObjectWithFunction()
     {
         $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
-        $alpha = new Mustache_Test_Functional_Alpha;
-        $alpha->foo = new Mustache_Test_Functional_Delta;
+        $alpha = new Mustache_Test_Functional_Alpha();
+        $alpha->foo = new Mustache_Test_Functional_Delta();
         $this->assertEquals('Foo', $tpl->render($alpha));
     }
 }
@@ -95,7 +95,7 @@ class Mustache_Test_Functional_Gamma
 
     public function __construct()
     {
-        $this->bar = new Mustache_Test_Functional_Beta;
+        $this->bar = new Mustache_Test_Functional_Beta();
     }
 }
 

+ 2 - 2
test/Mustache/Test/HelperCollectionTest.php

@@ -35,7 +35,7 @@ class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase
         $foo = array($this, 'getFoo');
         $bar = 'BAR';
 
-        $helpers = new Mustache_HelperCollection;
+        $helpers = new Mustache_HelperCollection();
         $this->assertTrue($helpers->isEmpty());
         $this->assertFalse($helpers->has('foo'));
         $this->assertFalse($helpers->has('bar'));
@@ -61,7 +61,7 @@ class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase
         $foo = array($this, 'getFoo');
         $bar = 'BAR';
 
-        $helpers = new Mustache_HelperCollection;
+        $helpers = new Mustache_HelperCollection();
         $this->assertTrue($helpers->isEmpty());
         $this->assertFalse($helpers->has('foo'));
         $this->assertFalse($helpers->has('bar'));

+ 1 - 1
test/Mustache/Test/Loader/ArrayLoaderTest.php

@@ -46,7 +46,7 @@ class Mustache_Test_Loader_ArrayLoaderTest extends PHPUnit_Framework_TestCase
      */
     public function testMissingTemplatesThrowExceptions()
     {
-        $loader = new Mustache_Loader_ArrayLoader;
+        $loader = new Mustache_Loader_ArrayLoader();
         $loader->load('not_a_real_template');
     }
 }

+ 1 - 1
test/Mustache/Test/Loader/StringLoaderTest.php

@@ -16,7 +16,7 @@ class Mustache_Test_Loader_StringLoaderTest extends PHPUnit_Framework_TestCase
 {
     public function testLoadTemplates()
     {
-        $loader = new Mustache_Loader_StringLoader;
+        $loader = new Mustache_Loader_StringLoader();
 
         $this->assertEquals('foo', $loader->load('foo'));
         $this->assertEquals('{{ bar }}', $loader->load('{{ bar }}'));

+ 1 - 1
test/Mustache/Test/Logger/AbstractLoggerTest.php

@@ -16,7 +16,7 @@ class Mustache_Test_Logger_AbstractLoggerTest extends PHPUnit_Framework_TestCase
 {
     public function testEverything()
     {
-        $logger = new Mustache_Test_Logger_TestLogger;
+        $logger = new Mustache_Test_Logger_TestLogger();
 
         $logger->emergency('emergency message');
         $logger->alert('alert message');

+ 226 - 2
test/Mustache/Test/ParserTest.php

@@ -20,7 +20,7 @@ class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testParse($tokens, $expected)
     {
-        $parser = new Mustache_Parser;
+        $parser = new Mustache_Parser();
         $this->assertEquals($expected, $parser->parse($tokens));
     }
 
@@ -88,6 +88,7 @@ class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
                         Mustache_Tokenizer::VALUE => 'bar'
                     ),
                 ),
+
                 array(
                     array(
                         Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
@@ -116,6 +117,202 @@ class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
                 ),
             ),
 
+            // This *would* be an invalid inheritance parse tree, but that pragma
+            // isn't enabled so it'll thunk it back into an "escaped" token:
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                ),
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME => '$foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                ),
+            ),
+
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => "  ",
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_DELIM_CHANGE,
+                        Mustache_Tokenizer::LINE => 0,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => "  \n",
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '[[',
+                        Mustache_Tokenizer::CTAG => ']]',
+                        Mustache_Tokenizer::LINE => 1,
+                    ),
+                ),
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '[[',
+                        Mustache_Tokenizer::CTAG => ']]',
+                        Mustache_Tokenizer::LINE => 1,
+                    ),
+                ),
+            ),
+
+        );
+    }
+
+    /**
+     * @dataProvider getInheritanceTokenSets
+     */
+    public function testParseWithInheritance($tokens, $expected)
+    {
+        $parser = new Mustache_Parser();
+        $parser->setPragmas(array(Mustache_Engine::PRAGMA_BLOCKS));
+        $this->assertEquals($expected, $parser->parse($tokens));
+    }
+
+    public function getInheritanceTokenSets()
+    {
+        return array(
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_PARENT,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 8
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'bar',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 16
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => 'baz'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME => 'bar',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 19
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 27
+                    )
+                ),
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_PARENT,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 8,
+                        Mustache_Tokenizer::END => 27,
+                        Mustache_Tokenizer::NODES => array(
+                            array(
+                                Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_ARG,
+                                Mustache_Tokenizer::NAME => 'bar',
+                                Mustache_Tokenizer::OTAG => '{{',
+                                Mustache_Tokenizer::CTAG => '}}',
+                                Mustache_Tokenizer::LINE => 0,
+                                Mustache_Tokenizer::INDEX => 16,
+                                Mustache_Tokenizer::END => 19,
+                                Mustache_Tokenizer::NODES => array(
+                                    array(
+                                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                                        Mustache_Tokenizer::LINE => 0,
+                                        Mustache_Tokenizer::VALUE => 'baz'
+                                    )
+                                )
+                            )
+                        )
+                    )
+                )
+            ),
+
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 11,
+                    ),
+                ),
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::END => 11,
+                        Mustache_Tokenizer::NODES => array(
+                            array(
+                                Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                                Mustache_Tokenizer::LINE => 0,
+                                Mustache_Tokenizer::VALUE => 'bar'
+                            )
+                        )
+                    )
+                )
+            ),
         );
     }
 
@@ -125,7 +322,7 @@ class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
      */
     public function testParserThrowsExceptions($tokens)
     {
-        $parser = new Mustache_Parser;
+        $parser = new Mustache_Parser();
         $parser->parse($tokens);
     }
 
@@ -197,6 +394,33 @@ class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
                     ),
                 ),
             ),
+
+            // This *would* be a valid inheritance parse tree, but that pragma
+            // isn't enabled here so it's going to fail :)
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME => 'foo',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 11,
+                    ),
+                ),
+            ),
         );
     }
 }

+ 2 - 2
test/Mustache/Test/SpecTestCase.php

@@ -15,7 +15,7 @@ abstract class Mustache_Test_SpecTestCase extends PHPUnit_Framework_TestCase
 
     public static function setUpBeforeClass()
     {
-        self::$mustache = new Mustache_Engine;
+        self::$mustache = new Mustache_Engine();
     }
 
     protected static function loadTemplate($source, $partials)
@@ -42,7 +42,7 @@ abstract class Mustache_Test_SpecTestCase extends PHPUnit_Framework_TestCase
         }
 
         $data = array();
-        $yaml = new sfYamlParser;
+        $yaml = new sfYamlParser();
         $file = file_get_contents($filename);
 
         // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.

+ 3 - 3
test/Mustache/Test/TemplateTest.php

@@ -16,7 +16,7 @@ class Mustache_Test_TemplateTest extends PHPUnit_Framework_TestCase
 {
     public function testConstructor()
     {
-        $mustache = new Mustache_Engine;
+        $mustache = new Mustache_Engine();
         $template = new Mustache_Test_TemplateStub($mustache);
         $this->assertSame($mustache, $template->getMustache());
     }
@@ -24,10 +24,10 @@ class Mustache_Test_TemplateTest extends PHPUnit_Framework_TestCase
     public function testRendering()
     {
         $rendered = '<< wheee >>';
-        $mustache = new Mustache_Engine;
+        $mustache = new Mustache_Engine();
         $template = new Mustache_Test_TemplateStub($mustache);
         $template->rendered = $rendered;
-        $context  = new Mustache_Context;
+        $context  = new Mustache_Context();
 
         if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
             $this->assertEquals($rendered, $template());

+ 32 - 3
test/Mustache/Test/TokenizerTest.php

@@ -20,7 +20,7 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase
      */
     public function testScan($text, $delimiters, $expected)
     {
-        $tokenizer = new Mustache_Tokenizer;
+        $tokenizer = new Mustache_Tokenizer();
         $this->assertSame($expected, $tokenizer->scan($text, $delimiters));
     }
 
@@ -29,7 +29,7 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase
      */
     public function testUnevenBracesThrowExceptions()
     {
-        $tokenizer = new Mustache_Tokenizer;
+        $tokenizer = new Mustache_Tokenizer();
 
         $text = "{{{ name }}";
         $tokenizer->scan($text, null);
@@ -40,7 +40,7 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase
      */
     public function testUnevenBracesWithCustomDelimiterThrowExceptions()
     {
-        $tokenizer = new Mustache_Tokenizer;
+        $tokenizer = new Mustache_Tokenizer();
 
         $text = "<%{ name %>";
         $tokenizer->scan($text, "<% %>");
@@ -273,6 +273,35 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase
                     )
                 )
             ),
+
+            // Ensure that $arg token is not picked up during tokenization
+            array(
+                '{{$arg}}default{{/arg}}',
+                null,
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+                        Mustache_Tokenizer::NAME => 'arg',
+                        Mustache_Tokenizer::OTAG => '{{',
+                        Mustache_Tokenizer::CTAG => '}}',
+                        Mustache_Tokenizer::LINE => 0,
+                        Mustache_Tokenizer::INDEX => 8
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::LINE  => 0,
+                        Mustache_Tokenizer::VALUE => "default",
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME  => 'arg',
+                        Mustache_Tokenizer::OTAG  => '{{',
+                        Mustache_Tokenizer::CTAG  => '}}',
+                        Mustache_Tokenizer::LINE  => 0,
+                        Mustache_Tokenizer::INDEX => 15,
+                    )
+                )
+            ),
         );
     }
 }

+ 88 - 0
test/fixtures/examples/filters/Filters.php

@@ -0,0 +1,88 @@
+<?php
+
+class Filters
+{
+    public $states = array(
+        'al' => 'Alabama',
+        'ak' => 'Alaska',
+        'az' => 'Arizona',
+        'ar' => 'Arkansas',
+        'ca' => 'California',
+        'co' => 'Colorado',
+        'ct' => 'Connecticut',
+        'de' => 'Delaware',
+        'fl' => 'Florida',
+        'ga' => 'Georgia',
+        'hi' => 'Hawaii',
+        'id' => 'Idaho',
+        'il' => 'Illinois',
+        'in' => 'Indiana',
+        'ia' => 'Iowa',
+        'ks' => 'Kansas',
+        'ky' => 'Kentucky',
+        'la' => 'Louisiana',
+        'me' => 'Maine',
+        'md' => 'Maryland',
+        'ma' => 'Massachusetts',
+        'mi' => 'Michigan',
+        'mn' => 'Minnesota',
+        'ms' => 'Mississippi',
+        'mo' => 'Missouri',
+        'mt' => 'Montana',
+        'ne' => 'Nebraska',
+        'nv' => 'Nevada',
+        'nh' => 'New Hampshire',
+        'nj' => 'New Jersey',
+        'nm' => 'New Mexico',
+        'ny' => 'New York',
+        'nc' => 'North Carolina',
+        'nd' => 'North Dakota',
+        'oh' => 'Ohio',
+        'ok' => 'Oklahoma',
+        'or' => 'Oregon',
+        'pa' => 'Pennsylvania',
+        'ri' => 'Rhode Island',
+        'sc' => 'South Carolina',
+        'sd' => 'South Dakota',
+        'tn' => 'Tennessee',
+        'tx' => 'Texas',
+        'ut' => 'Utah',
+        'vt' => 'Vermont',
+        'va' => 'Virginia',
+        'wa' => 'Washington',
+        'wv' => 'West Virginia',
+        'wi' => 'Wisconsin',
+        'wy' => 'Wyoming',
+    );
+
+    // The next few functions are ugly, because they have to work in PHP 5.2...
+    // for everyone who doesn't have to support 5.2, please, for the love, make
+    // your ViewModel return closures rather than `array($this, '...')`
+    //
+    // :)
+
+    public function upcase()
+    {
+        return array($this, '_upcase');
+    }
+
+    public function _upcase($val)
+    {
+        return strtoupper($val);
+    }
+
+    public function eachPair()
+    {
+        return array($this, '_eachPair');
+    }
+
+    public function _eachPair($val)
+    {
+        $ret = array();
+        foreach ($val as $key => $value) {
+            array_push($ret, compact('key', 'value'));
+        }
+
+        return $ret;
+    }
+}

+ 4 - 0
test/fixtures/examples/filters/filters.mustache

@@ -0,0 +1,4 @@
+{{%FILTERS}}
+{{# states | eachPair }}
+{{ key | upcase }}: {{ value }}
+{{/ states }}

+ 50 - 0
test/fixtures/examples/filters/filters.txt

@@ -0,0 +1,50 @@
+AL: Alabama
+AK: Alaska
+AZ: Arizona
+AR: Arkansas
+CA: California
+CO: Colorado
+CT: Connecticut
+DE: Delaware
+FL: Florida
+GA: Georgia
+HI: Hawaii
+ID: Idaho
+IL: Illinois
+IN: Indiana
+IA: Iowa
+KS: Kansas
+KY: Kentucky
+LA: Louisiana
+ME: Maine
+MD: Maryland
+MA: Massachusetts
+MI: Michigan
+MN: Minnesota
+MS: Mississippi
+MO: Missouri
+MT: Montana
+NE: Nebraska
+NV: Nevada
+NH: New Hampshire
+NJ: New Jersey
+NM: New Mexico
+NY: New York
+NC: North Carolina
+ND: North Dakota
+OH: Ohio
+OK: Oklahoma
+OR: Oregon
+PA: Pennsylvania
+RI: Rhode Island
+SC: South Carolina
+SD: South Dakota
+TN: Tennessee
+TX: Texas
+UT: Utah
+VT: Vermont
+VA: Virginia
+WA: Washington
+WV: West Virginia
+WI: Wisconsin
+WY: Wyoming

+ 1 - 1
test/fixtures/examples/section_magic_objects/SectionMagicObjects.php

@@ -21,7 +21,7 @@ class MagicObject
 
     public function __get($key)
     {
-        return isset($this->_data[$key]) ? $this->_data[$key] : NULL;
+        return isset($this->_data[$key]) ? $this->_data[$key] : null;
     }
 
     public function __isset($key)

+ 1 - 1
test/fixtures/examples/section_objects/SectionObjects.php

@@ -6,7 +6,7 @@ class SectionObjects
 
     public function middle()
     {
-        return new SectionObject;
+        return new SectionObject();
     }
 
     public $final = "Then, surprisingly, it worked the final time.";