Justin Hileman 11 лет назад
Родитель
Сommit
43dc9457a2
76 измененных файлов с 552 добавлено и 604 удалено
  1. 6 0
      .travis.yml
  2. 2 1
      LICENSE
  3. 3 1
      README.md
  4. 2 2
      bin/build_bootstrap.php
  5. 1 1
      src/Mustache/Autoloader.php
  6. 1 1
      src/Mustache/Cache.php
  7. 1 1
      src/Mustache/Cache/AbstractCache.php
  8. 1 1
      src/Mustache/Cache/FilesystemCache.php
  9. 1 1
      src/Mustache/Cache/NoopCache.php
  10. 1 1
      src/Mustache/Compiler.php
  11. 1 1
      src/Mustache/Context.php
  12. 12 7
      src/Mustache/Engine.php
  13. 1 1
      src/Mustache/Exception.php
  14. 1 1
      src/Mustache/Exception/InvalidArgumentException.php
  15. 1 1
      src/Mustache/Exception/LogicException.php
  16. 1 1
      src/Mustache/Exception/RuntimeException.php
  17. 8 1
      src/Mustache/Exception/SyntaxException.php
  18. 4 1
      src/Mustache/Exception/UnknownFilterException.php
  19. 4 1
      src/Mustache/Exception/UnknownHelperException.php
  20. 4 1
      src/Mustache/Exception/UnknownTemplateException.php
  21. 1 1
      src/Mustache/HelperCollection.php
  22. 2 2
      src/Mustache/LambdaHelper.php
  23. 1 1
      src/Mustache/Loader.php
  24. 2 1
      src/Mustache/Loader/ArrayLoader.php
  25. 3 3
      src/Mustache/Loader/CascadingLoader.php
  26. 1 1
      src/Mustache/Loader/FilesystemLoader.php
  27. 2 2
      src/Mustache/Loader/InlineLoader.php
  28. 5 1
      src/Mustache/Loader/MutableLoader.php
  29. 1 1
      src/Mustache/Loader/StringLoader.php
  30. 1 1
      src/Mustache/Logger.php
  31. 1 1
      src/Mustache/Logger/AbstractLogger.php
  32. 4 3
      src/Mustache/Logger/StreamLogger.php
  33. 18 6
      src/Mustache/Parser.php
  34. 1 1
      src/Mustache/Template.php
  35. 58 30
      src/Mustache/Tokenizer.php
  36. 1 1
      test/Mustache/Test/AutoloaderTest.php
  37. 1 1
      test/Mustache/Test/Cache/AbstractCacheTest.php
  38. 2 33
      test/Mustache/Test/Cache/FilesystemCacheTest.php
  39. 4 1
      test/Mustache/Test/CompilerTest.php
  40. 1 1
      test/Mustache/Test/ContextTest.php
  41. 16 57
      test/Mustache/Test/EngineTest.php
  42. 1 1
      test/Mustache/Test/Exception/SyntaxExceptionTest.php
  43. 1 1
      test/Mustache/Test/Exception/UnknownFilterExceptionTest.php
  44. 1 1
      test/Mustache/Test/Exception/UnknownHelperExceptionTest.php
  45. 1 1
      test/Mustache/Test/Exception/UnknownTemplateExceptionTest.php
  46. 3 3
      test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php
  47. 101 32
      test/Mustache/Test/FiveThree/Functional/FiltersTest.php
  48. 4 4
      test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php
  49. 2 2
      test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php
  50. 3 56
      test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php
  51. 2 2
      test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php
  52. 0 101
      test/Mustache/Test/FiveThree/Functional/SectionFiltersTest.php
  53. 19 34
      test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php
  54. 1 1
      test/Mustache/Test/Functional/CallTest.php
  55. 1 1
      test/Mustache/Test/Functional/ExamplesTest.php
  56. 57 15
      test/Mustache/Test/Functional/HigherOrderSectionsTest.php
  57. 25 90
      test/Mustache/Test/Functional/MustacheInjectionTest.php
  58. 2 56
      test/Mustache/Test/Functional/MustacheSpecTest.php
  59. 1 1
      test/Mustache/Test/Functional/ObjectSectionTest.php
  60. 47 0
      test/Mustache/Test/FunctionalTestCase.php
  61. 1 1
      test/Mustache/Test/HelperCollectionTest.php
  62. 1 1
      test/Mustache/Test/Loader/ArrayLoaderTest.php
  63. 1 1
      test/Mustache/Test/Loader/CascadingLoaderTest.php
  64. 2 2
      test/Mustache/Test/Loader/FilesystemLoaderTest.php
  65. 3 3
      test/Mustache/Test/Loader/InlineLoaderTest.php
  66. 1 1
      test/Mustache/Test/Loader/StringLoaderTest.php
  67. 1 1
      test/Mustache/Test/Logger/AbstractLoggerTest.php
  68. 13 10
      test/Mustache/Test/Logger/StreamLoggerTest.php
  69. 1 1
      test/Mustache/Test/ParserTest.php
  70. 67 0
      test/Mustache/Test/SpecTestCase.php
  71. 1 1
      test/Mustache/Test/TemplateTest.php
  72. 1 1
      test/Mustache/Test/TokenizerTest.php
  73. 2 1
      test/bootstrap.php
  74. 1 1
      test/fixtures/autoloader/Mustache/Bar.php
  75. 1 1
      test/fixtures/autoloader/Mustache/Foo.php
  76. 1 1
      test/fixtures/autoloader/NonMustacheClass.php

+ 6 - 0
.travis.yml

@@ -4,3 +4,9 @@ php:
   - 5.3
   - 5.4
   - 5.5
+  - 5.6
+  - hhvm
+
+matrix:
+  allow_failures:
+    - php: hhvm # See https://github.com/facebook/hhvm/pull/1860

+ 2 - 1
LICENSE

@@ -1,5 +1,6 @@
 The MIT License (MIT)
-Copyright (c) 2010 Justin Hileman
+
+Copyright (c) 2010-2014 Justin Hileman
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 3 - 1
README.md

@@ -3,7 +3,9 @@ Mustache.php
 
 A [Mustache](http://mustache.github.com/) implementation in PHP.
 
-[![Build Status](https://secure.travis-ci.org/bobthecow/mustache.php.png?branch=dev)](http://travis-ci.org/bobthecow/mustache.php)
+[![Package version](http://img.shields.io/packagist/v/mustache/mustache.svg)](https://packagist.org/packages/mustache/mustache)
+[![Build status](http://img.shields.io/travis/bobthecow/mustache.php/dev.svg)](http://travis-ci.org/bobthecow/mustache.php)
+[![Monthly downloads](http://img.shields.io/packagist/dm/mustache/mustache.svg)](https://packagist.org/packages/mustache/mustache)
 
 
 Usage

+ 2 - 2
bin/build_bootstrap.php

@@ -4,7 +4,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -85,7 +85,7 @@ class SymfonyClassCollectionLoader
 /*
  * This file is part of Mustache.php.
  *
- * (c) %d Justin Hileman
+ * (c) 2010-%d Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Autoloader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Cache.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Cache/AbstractCache.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Cache/FilesystemCache.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Cache/NoopCache.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Compiler.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Context.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 12 - 7
src/Mustache/Engine.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -23,7 +23,7 @@
  */
 class Mustache_Engine
 {
-    const VERSION        = '2.5.1';
+    const VERSION        = '2.6.0';
     const SPEC_VERSION   = '1.1.2';
 
     const PRAGMA_FILTERS = 'FILTERS';
@@ -45,6 +45,11 @@ class Mustache_Engine
     private $logger;
     private $strictCallables = false;
 
+    // Services
+    private $tokenizer;
+    private $parser;
+    private $compiler;
+
     /**
      * Mustache class constructor.
      *
@@ -79,12 +84,12 @@ class Mustache_Engine
      *         // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
      *         // sections), or any other valid Mustache context value. They will be prepended to the context stack,
      *         // so they will be available in any template loaded by this Mustache instance.
-     *         'helpers' => array('i18n' => function($text) {
+     *         'helpers' => array('i18n' => function ($text) {
      *             // do something translatey here...
      *         }),
      *
      *         // An 'escape' callback, responsible for escaping double-mustache variables.
-     *         'escape' => function($value) {
+     *         'escape' => function ($value) {
      *             return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
      *         },
      *
@@ -194,7 +199,7 @@ class Mustache_Engine
     /**
      * Get the current Mustache escape callback.
      *
-     * @return mixed Callable or null
+     * @return callable|null
      */
     public function getEscape()
     {
@@ -657,9 +662,9 @@ class Mustache_Engine
             }
 
             if (!class_exists($className, false)) {
-                if (!$this->getCache()->load($className)) {
+                if (!$cache->load($className)) {
                     $compiled = $this->compile($source);
-                    $this->getCache()->cache($className, $compiled);
+                    $cache->cache($className, $compiled);
                 }
             }
 

+ 1 - 1
src/Mustache/Exception.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Exception/InvalidArgumentException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Exception/LogicException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Exception/RuntimeException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 8 - 1
src/Mustache/Exception/SyntaxException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -16,12 +16,19 @@ class Mustache_Exception_SyntaxException extends LogicException implements Musta
 {
     protected $token;
 
+    /**
+     * @param string $msg
+     * @param array  $token
+     */
     public function __construct($msg, array $token)
     {
         $this->token = $token;
         parent::__construct($msg);
     }
 
+    /**
+     * @return array
+     */
     public function getToken()
     {
         return $this->token;

+ 4 - 1
src/Mustache/Exception/UnknownFilterException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -16,6 +16,9 @@ class Mustache_Exception_UnknownFilterException extends UnexpectedValueException
 {
     protected $filterName;
 
+    /**
+     * @param string $filterName
+     */
     public function __construct($filterName)
     {
         $this->filterName = $filterName;

+ 4 - 1
src/Mustache/Exception/UnknownHelperException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -16,6 +16,9 @@ class Mustache_Exception_UnknownHelperException extends InvalidArgumentException
 {
     protected $helperName;
 
+    /**
+     * @param string $helperName
+     */
     public function __construct($helperName)
     {
         $this->helperName = $helperName;

+ 4 - 1
src/Mustache/Exception/UnknownTemplateException.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -16,6 +16,9 @@ class Mustache_Exception_UnknownTemplateException extends InvalidArgumentExcepti
 {
     protected $templateName;
 
+    /**
+     * @param string $templateName
+     */
     public function __construct($templateName)
     {
         $this->templateName = $templateName;

+ 1 - 1
src/Mustache/HelperCollection.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 2
src/Mustache/LambdaHelper.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -38,7 +38,7 @@ class Mustache_LambdaHelper
      *
      * @param string $string
      *
-     * @return Rendered template.
+     * @return string Rendered template.
      */
     public function render($string)
     {

+ 1 - 1
src/Mustache/Loader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 1
src/Mustache/Loader/ArrayLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -26,6 +26,7 @@
  */
 class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader
 {
+    private $templates;
 
     /**
      * ArrayLoader constructor.

+ 3 - 3
src/Mustache/Loader/CascadingLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -25,7 +25,7 @@ class Mustache_Loader_CascadingLoader implements Mustache_Loader
      *         new Mustache_Loader_FilesystemLoader(__DIR__.'/templates')
      *     ));
      *
-     * @param array $loaders An array of Mustache Loader instances
+     * @param Mustache_Loader[] $loaders
      */
     public function __construct(array $loaders = array())
     {
@@ -38,7 +38,7 @@ class Mustache_Loader_CascadingLoader implements Mustache_Loader
     /**
      * Add a Loader instance.
      *
-     * @param Mustache_Loader $loader A Mustache Loader instance
+     * @param Mustache_Loader $loader
      */
     public function addLoader(Mustache_Loader $loader)
     {

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 2
src/Mustache/Loader/InlineLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -35,7 +35,7 @@
  *         'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
  *     ));
  *
- *     $app->get('/{name}', function($name) use ($app) {
+ *     $app->get('/{name}', function ($name) use ($app) {
  *         return $app['mustache']->render('hello', compact('name'));
  *     })
  *     ->value('name', 'world');

+ 5 - 1
src/Mustache/Loader/MutableLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -19,6 +19,8 @@ interface Mustache_Loader_MutableLoader
      * Set an associative array of Template sources for this loader.
      *
      * @param array $templates
+     *
+     * @return void
      */
     public function setTemplates(array $templates);
 
@@ -27,6 +29,8 @@ interface Mustache_Loader_MutableLoader
      *
      * @param string $name
      * @param string $template Mustache Template source
+     *
+     * @return void
      */
     public function setTemplate($name, $template);
 }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Logger.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
src/Mustache/Logger/AbstractLogger.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 4 - 3
src/Mustache/Logger/StreamLogger.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -31,14 +31,15 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
         self::EMERGENCY => 600,
     );
 
+    protected $level;
     protected $stream = null;
     protected $url    = null;
 
     /**
      * @throws InvalidArgumentException if the logging level is unknown.
      *
-     * @param string  $stream Resource instance or URL
-     * @param integer $level  The minimum logging level at which this handler will be triggered
+     * @param resource|string $stream Resource instance or URL
+     * @param integer         $level  The minimum logging level at which this handler will be triggered
      */
     public function __construct($stream, $level = Mustache_Logger::ERROR)
     {

+ 18 - 6
src/Mustache/Parser.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -71,12 +71,22 @@ class Mustache_Parser
 
                 case Mustache_Tokenizer::T_END_SECTION:
                     if (!isset($parent)) {
-                        $msg = sprintf('Unexpected closing tag: /%s', $token[Mustache_Tokenizer::NAME]);
+                        $msg = sprintf(
+                            'Unexpected closing tag: /%s on line %d',
+                            $token[Mustache_Tokenizer::NAME],
+                            $token[Mustache_Tokenizer::LINE]
+                        );
                         throw new Mustache_Exception_SyntaxException($msg, $token);
                     }
 
                     if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
-                        $msg = sprintf('Nesting error: %s vs. %s', $parent[Mustache_Tokenizer::NAME], $token[Mustache_Tokenizer::NAME]);
+                        $msg = sprintf(
+                            'Nesting error: %s (on line %d) vs. %s (on line %d)',
+                            $parent[Mustache_Tokenizer::NAME],
+                            $parent[Mustache_Tokenizer::LINE],
+                            $token[Mustache_Tokenizer::NAME],
+                            $token[Mustache_Tokenizer::LINE]
+                        );
                         throw new Mustache_Exception_SyntaxException($msg, $token);
                     }
 
@@ -85,7 +95,6 @@ class Mustache_Parser
                     $parent[Mustache_Tokenizer::NODES] = $nodes;
 
                     return $parent;
-                    break;
 
                 case Mustache_Tokenizer::T_PARTIAL:
                 case Mustache_Tokenizer::T_PARTIAL_2:
@@ -109,7 +118,11 @@ class Mustache_Parser
         }
 
         if (isset($parent)) {
-            $msg = sprintf('Missing closing tag: %s', $parent[Mustache_Tokenizer::NAME]);
+            $msg = sprintf(
+                'Missing closing tag: %s opened on line %d',
+                $parent[Mustache_Tokenizer::NAME],
+                $parent[Mustache_Tokenizer::LINE]
+            );
             throw new Mustache_Exception_SyntaxException($msg, $parent);
         }
 
@@ -144,7 +157,6 @@ class Mustache_Parser
             }
         }
 
-        $next = null;
         if ($next = reset($tokens)) {
             // If we're on a new line, bail.
             if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {

+ 1 - 1
src/Mustache/Template.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 58 - 30
src/Mustache/Tokenizer.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -79,6 +79,8 @@ class Mustache_Tokenizer
     private $line;
     private $otag;
     private $ctag;
+    private $otagLen;
+    private $ctagLen;
 
     /**
      * Scan and tokenize template source.
@@ -90,24 +92,30 @@ class Mustache_Tokenizer
      */
     public function scan($text, $delimiters = null)
     {
+        // Setting mbstring.func_overload makes things *really* slow.
+        // Let's do everyone a favor and scan this string as ASCII instead.
+        $encoding = null;
+        if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
+            $encoding = mb_internal_encoding();
+            mb_internal_encoding('ASCII');
+        }
+
         $this->reset();
 
         if ($delimiters = trim($delimiters)) {
-            list($otag, $ctag) = explode(' ', $delimiters);
-            $this->otag = $otag;
-            $this->ctag = $ctag;
+            $this->setDelimiters($delimiters);
         }
 
         $len = strlen($text);
         for ($i = 0; $i < $len; $i++) {
             switch ($this->state) {
                 case self::IN_TEXT:
-                    if ($this->tagChange($this->otag, $text, $i)) {
+                    if ($this->tagChange($this->otag, $this->otagLen, $text, $i)) {
                         $i--;
                         $this->flushBuffer();
                         $this->state = self::IN_TAG_TYPE;
                     } else {
-                        $char = substr($text, $i, 1);
+                        $char = $text[$i];
                         $this->buffer .= $char;
                         if ($char == "\n") {
                             $this->flushBuffer();
@@ -117,8 +125,8 @@ class Mustache_Tokenizer
                     break;
 
                 case self::IN_TAG_TYPE:
-                    $i += strlen($this->otag) - 1;
-                    $char = substr($text, $i + 1, 1);
+                    $i += $this->otagLen - 1;
+                    $char = $text[$i + 1];
                     if (isset(self::$tagTypes[$char])) {
                         $tag = $char;
                         $this->tagType = $tag;
@@ -143,18 +151,18 @@ class Mustache_Tokenizer
                     break;
 
                 default:
-                    if ($this->tagChange($this->ctag, $text, $i)) {
+                    if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) {
                         $this->tokens[] = array(
                             self::TYPE  => $this->tagType,
                             self::NAME  => trim($this->buffer),
                             self::OTAG  => $this->otag,
                             self::CTAG  => $this->ctag,
                             self::LINE  => $this->line,
-                            self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag)
+                            self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen
                         );
 
                         $this->buffer = '';
-                        $i += strlen($this->ctag) - 1;
+                        $i += $this->ctagLen - 1;
                         $this->state = self::IN_TEXT;
                         if ($this->tagType == self::T_UNESCAPED) {
                             if ($this->ctag == '}}') {
@@ -168,7 +176,7 @@ class Mustache_Tokenizer
                             }
                         }
                     } else {
-                        $this->buffer .= substr($text, $i, 1);
+                        $this->buffer .= $text[$i];
                     }
                     break;
             }
@@ -176,6 +184,11 @@ class Mustache_Tokenizer
 
         $this->flushBuffer();
 
+        // Restore the user's encoding...
+        if ($encoding) {
+            mb_internal_encoding($encoding);
+        }
+
         return $this->tokens;
     }
 
@@ -184,15 +197,17 @@ class Mustache_Tokenizer
      */
     private function reset()
     {
-        $this->state     = self::IN_TEXT;
-        $this->tagType   = null;
-        $this->tag       = null;
-        $this->buffer    = '';
-        $this->tokens    = array();
-        $this->seenTag   = false;
-        $this->line      = 0;
-        $this->otag      = '{{';
-        $this->ctag      = '}}';
+        $this->state   = self::IN_TEXT;
+        $this->tagType = null;
+        $this->tag     = null;
+        $this->buffer  = '';
+        $this->tokens  = array();
+        $this->seenTag = false;
+        $this->line    = 0;
+        $this->otag    = '{{';
+        $this->ctag    = '}}';
+        $this->otagLen = 2;
+        $this->ctagLen = 2;
     }
 
     /**
@@ -224,9 +239,7 @@ class Mustache_Tokenizer
         $close      = '='.$this->ctag;
         $closeIndex = strpos($text, $close, $index);
 
-        list($otag, $ctag) = explode(' ', trim(substr($text, $startIndex, $closeIndex - $startIndex)));
-        $this->otag = $otag;
-        $this->ctag = $ctag;
+        $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
 
         $this->tokens[] = array(
             self::TYPE => self::T_DELIM_CHANGE,
@@ -236,6 +249,20 @@ class Mustache_Tokenizer
         return $closeIndex + strlen($close) - 1;
     }
 
+    /**
+     * Set the current Mustache `otag` and `ctag` delimiters.
+     *
+     * @param string $delimiters
+     */
+    private function setDelimiters($delimiters)
+    {
+        list($otag, $ctag) = explode(' ', $delimiters);
+        $this->otag = $otag;
+        $this->ctag = $ctag;
+        $this->otagLen = strlen($otag);
+        $this->ctagLen = strlen($ctag);
+    }
+
     /**
      * Add pragma token.
      *
@@ -259,20 +286,21 @@ class Mustache_Tokenizer
             self::LINE => 0,
         ));
 
-        return $end + strlen($this->ctag) - 1;
+        return $end + $this->ctagLen - 1;
     }
 
     /**
      * Test whether it's time to change tags.
      *
-     * @param string $tag   Current tag name
-     * @param string $text  Mustache template source
-     * @param int    $index Current tokenizer index
+     * @param string $tag    Current tag name
+     * @param int    $tagLen Current tag name length
+     * @param string $text   Mustache template source
+     * @param int    $index  Current tokenizer index
      *
      * @return boolean True if this is a closing section tag
      */
-    private function tagChange($tag, $text, $index)
+    private function tagChange($tag, $tagLen, $text, $index)
     {
-        return substr($text, $index, strlen($tag)) === $tag;
+        return substr($text, $index, $tagLen) === $tag;
     }
 }

+ 1 - 1
test/Mustache/Test/AutoloaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/Mustache/Test/Cache/AbstractCacheTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 33
test/Mustache/Test/Cache/FilesystemCacheTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -12,18 +12,8 @@
 /**
  * @group functional
  */
-class Mustache_Test_Cache_FilesystemCacheTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_Cache_FilesystemCacheTest extends Mustache_Test_FunctionalTestCase
 {
-    private static $tempDir;
-
-    public static function setUpBeforeClass()
-    {
-        self::$tempDir = sys_get_temp_dir() . '/mustache_test';
-        if (file_exists(self::$tempDir)) {
-            self::rmdir(self::$tempDir);
-        }
-    }
-
     public function testCacheGetNone()
     {
         $key = 'some key';
@@ -43,25 +33,4 @@ class Mustache_Test_Cache_FilesystemCacheTest extends PHPUnit_Framework_TestCase
 
         $this->assertTrue($loaded);
     }
-
-    private static function rmdir($path)
-    {
-        $path = rtrim($path, '/').'/';
-        $handle = opendir($path);
-        while (($file = readdir($handle)) !== false) {
-            if ($file == '.' || $file == '..') {
-                continue;
-            }
-
-            $fullpath = $path.$file;
-            if (is_dir($fullpath)) {
-                self::rmdir($fullpath);
-            } else {
-                unlink($fullpath);
-            }
-        }
-
-        closedir($handle);
-        rmdir($path);
-    }
 }

+ 4 - 1
test/Mustache/Test/CompilerTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -142,6 +142,9 @@ class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase
         $compiler->compile('', array(array(Mustache_Tokenizer::TYPE => 'invalid')), 'SomeClass');
     }
 
+    /**
+     * @param string $value
+     */
     private function createTextToken($value)
     {
         return array(

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 16 - 57
test/Mustache/Test/EngineTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -12,19 +12,8 @@
 /**
  * @group unit
  */
-class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_EngineTest extends Mustache_Test_FunctionalTestCase
 {
-
-    private static $tempDir;
-
-    public static function setUpBeforeClass()
-    {
-        self::$tempDir = sys_get_temp_dir() . '/mustache_test';
-        if (file_exists(self::$tempDir)) {
-            self::rmdir(self::$tempDir);
-        }
-    }
-
     public function testConstructor()
     {
         $logger         = new Mustache_Logger_StreamLogger(tmpfile());
@@ -309,65 +298,35 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
 
     public function testCacheWarningLogging()
     {
-        $name     = tempnam(sys_get_temp_dir(), 'mustache-test');
-        $mustache = new Mustache_Engine(array(
-            'logger'   => new Mustache_Logger_StreamLogger($name, Mustache_Logger::WARNING)
-        ));
-
-        $result = $mustache->render('{{ foo }}', array('foo' => 'FOO'));
-        $this->assertEquals('FOO', $result);
-
+        list($name, $mustache) = $this->getLoggedMustache(Mustache_Logger::WARNING);
+        $mustache->render('{{ foo }}', array('foo' => 'FOO'));
         $this->assertContains('WARNING: Template cache disabled, evaluating', file_get_contents($name));
     }
 
     public function testLoggingIsNotTooAnnoying()
     {
-        $name     = tempnam(sys_get_temp_dir(), 'mustache-test');
-        $mustache = new Mustache_Engine(array(
-            'logger'   => new Mustache_Logger_StreamLogger($name)
-        ));
-
-        $result = $mustache->render('{{ foo }}{{> bar }}', array('foo' => 'FOO'));
-        $this->assertEquals('FOO', $result);
-
+        list($name, $mustache) = $this->getLoggedMustache();
+        $mustache->render('{{ foo }}{{> bar }}', array('foo' => 'FOO'));
         $this->assertEmpty(file_get_contents($name));
     }
 
     public function testVerboseLoggingIsVerbose()
     {
-        $name     = tempnam(sys_get_temp_dir(), 'mustache-test');
-        $mustache = new Mustache_Engine(array(
-            'logger'   => new Mustache_Logger_StreamLogger($name, Mustache_Logger::DEBUG)
-        ));
-
-        $result = $mustache->render('{{ foo }}{{> bar }}', array('foo' => 'FOO'));
-        $this->assertEquals('FOO', $result);
-
+        list($name, $mustache) = $this->getLoggedMustache(Mustache_Logger::DEBUG);
+        $mustache->render('{{ foo }}{{> bar }}', array('foo' => 'FOO'));
         $log = file_get_contents($name);
-
-        $this->assertContains("DEBUG: Instantiating template: ", $log);
+        $this->assertContains("DEBUG: Instantiating template: ",     $log);
         $this->assertContains("WARNING: Partial not found: \"bar\"", $log);
     }
 
-    private static function rmdir($path)
+    private function getLoggedMustache($level = Mustache_Logger::ERROR)
     {
-        $path = rtrim($path, '/').'/';
-        $handle = opendir($path);
-        while (($file = readdir($handle)) !== false) {
-            if ($file == '.' || $file == '..') {
-                continue;
-            }
-
-            $fullpath = $path.$file;
-            if (is_dir($fullpath)) {
-                self::rmdir($fullpath);
-            } else {
-                unlink($fullpath);
-            }
-        }
-
-        closedir($handle);
-        rmdir($path);
+        $name     = tempnam(sys_get_temp_dir(), 'mustache-test');
+        $mustache = new Mustache_Engine(array(
+            'logger' => new Mustache_Logger_StreamLogger($name, $level)
+        ));
+
+        return array($name, $mustache);
     }
 }
 

+ 1 - 1
test/Mustache/Test/Exception/SyntaxExceptionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/Mustache/Test/Exception/UnknownFilterExceptionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/Mustache/Test/Exception/UnknownHelperExceptionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/Mustache/Test/Exception/UnknownTemplateExceptionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -13,7 +13,7 @@
  * @group lambdas
  * @group functional
  */
-class Mustache_Test_FiveThree_Functional_ClosuresQuirksTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_FiveThree_Functional_ClosureQuirksTest extends PHPUnit_Framework_TestCase
 {
     private $mustache;
 
@@ -25,6 +25,6 @@ class Mustache_Test_FiveThree_Functional_ClosuresQuirksTest extends PHPUnit_Fram
     public function testClosuresDontLikeItWhenYouTouchTheirProperties()
     {
         $tpl = $this->mustache->loadTemplate('{{ foo.bar }}');
-        $this->assertEquals('', $tpl->render(array('foo' => function() { return 'FOO'; })));
+        $this->assertEquals('', $tpl->render(array('foo' => function () { return 'FOO'; })));
     }
 }

+ 101 - 32
test/Mustache/Test/FiveThree/Functional/FiltersTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -15,7 +15,6 @@
  */
 class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_TestCase
 {
-
     private $mustache;
 
     public function setUp()
@@ -23,29 +22,52 @@ class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_T
         $this->mustache = new Mustache_Engine;
     }
 
-    public function testSingleFilter()
+    /**
+     * @dataProvider singleFilterData
+     */
+    public function testSingleFilter($tpl, $helpers, $data, $expect)
     {
-        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ date | longdate }}');
-
-        $this->mustache->addHelper('longdate', function(\DateTime $value) {
-            return $value->format('Y-m-d h:m:s');
-        });
+        $this->mustache->setHelpers($helpers);
+        $this->assertEquals($expect, $this->mustache->render($tpl, $data));
+    }
 
-        $foo = new \StdClass;
-        $foo->date = new DateTime('1/1/2000');
+    public function singleFilterData()
+    {
+        $helpers = array(
+            'longdate' => function (\DateTime $value) {
+                    return $value->format('Y-m-d h:m:s');
+                },
+            'echo' => function ($value) {
+                    return array($value, $value, $value);
+                },
+        );
 
-        $this->assertEquals('2000-01-01 12:01:00', $tpl->render($foo));
+        return array(
+            array(
+                '{{% FILTERS }}{{ date | longdate }}',
+                $helpers,
+                (object) array('date' => new DateTime('1/1/2000')),
+                '2000-01-01 12:01:00'
+            ),
+
+            array(
+                '{{% FILTERS }}{{# word | echo }}{{ . }}!{{/ word | echo }}',
+                $helpers,
+                array('word' => 'bacon'),
+                'bacon!bacon!bacon!'
+            ),
+        );
     }
 
     public function testChainedFilters()
     {
         $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ date | longdate | withbrackets }}');
 
-        $this->mustache->addHelper('longdate', function(\DateTime $value) {
+        $this->mustache->addHelper('longdate', function (\DateTime $value) {
             return $value->format('Y-m-d h:m:s');
         });
 
-        $this->mustache->addHelper('withbrackets', function($value) {
+        $this->mustache->addHelper('withbrackets', function ($value) {
             return sprintf('[[%s]]', $value);
         });
 
@@ -55,40 +77,87 @@ class Mustache_Test_FiveThree_Functional_FiltersTest extends PHPUnit_Framework_T
         $this->assertEquals('[[2000-01-01 12:01:00]]', $tpl->render($foo));
     }
 
-    public function testInterpolateFirst()
+    const CHAINED_SECTION_FILTERS_TPL = <<<EOS
+{{% FILTERS }}
+{{# word | echo | with_index }}
+{{ key }}: {{ value }}
+{{/ word | echo | with_index }}
+EOS;
+
+    public function testChainedSectionFilters()
     {
-        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{ foo | bar }}');
-        $this->assertEquals('win!', $tpl->render(array(
+        $tpl = $this->mustache->loadTemplate(self::CHAINED_SECTION_FILTERS_TPL);
+
+        $this->mustache->addHelper('echo', function ($value) {
+            return array($value, $value, $value);
+        });
+
+        $this->mustache->addHelper('with_index', function ($value) {
+            return array_map(function ($k, $v) {
+                return array(
+                    'key'   => $k,
+                    'value' => $v,
+                );
+            }, array_keys($value), $value);
+        });
+
+        $this->assertEquals("0: bacon\n1: bacon\n2: bacon\n", $tpl->render(array('word' => 'bacon')));
+    }
+
+    /**
+     * @dataProvider interpolateFirstData
+     */
+    public function testInterpolateFirst($tpl, $data, $expect)
+    {
+        $this->assertEquals($expect, $this->mustache->render($tpl, $data));
+    }
+
+    public function interpolateFirstData()
+    {
+        $data = array(
             'foo' => 'FOO',
-            'bar' => function($value) {
+            'bar' => function ($value) {
                 return ($value === 'FOO') ? 'win!' : 'fail :(';
             },
-        )));
+        );
+
+        return array(
+            array('{{% FILTERS }}{{ foo | bar }}',                         $data, 'win!'),
+            array('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}', $data, 'win!'),
+        );
     }
 
     /**
      * @expectedException Mustache_Exception_UnknownFilterException
-     * @dataProvider getBrokenPipes
+     * @dataProvider brokenPipeData
      */
     public function testThrowsExceptionForBrokenPipes($tpl, $data)
     {
-        $this->mustache
-            ->loadTemplate(sprintf('{{%% FILTERS }}{{ %s }}', $tpl))
-                ->render($data);
+        $this->mustache->render($tpl, $data);
     }
 
-    public function getBrokenPipes()
+    public function brokenPipeData()
     {
         return array(
-            array('foo | bar', array()),
-            array('foo | bar', array('foo' => 'FOO')),
-            array('foo | bar', array('foo' => 'FOO', 'bar' => 'BAR')),
-            array('foo | bar', array('foo' => 'FOO', 'bar' => array(1, 2))),
-            array('foo | bar | baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; })),
-            array('foo | bar | baz', array('foo' => 'FOO', 'baz' => function() { return 'BAZ'; })),
-            array('foo | bar | baz', array('bar' => function() { return 'BAR'; })),
-            array('foo | bar | baz', array('baz' => function() { return 'BAZ'; })),
-            array('foo | bar.baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; }, 'baz' => function() { return 'BAZ'; })),
+            array('{{% FILTERS }}{{ foo | bar }}',       array()),
+            array('{{% FILTERS }}{{ foo | bar }}',       array('foo' => 'FOO')),
+            array('{{% FILTERS }}{{ foo | bar }}',       array('foo' => 'FOO', 'bar' => 'BAR')),
+            array('{{% FILTERS }}{{ foo | bar }}',       array('foo' => 'FOO', 'bar' => array(1, 2))),
+            array('{{% FILTERS }}{{ foo | bar | baz }}', array('foo' => 'FOO', 'bar' => function () { return 'BAR'; })),
+            array('{{% FILTERS }}{{ foo | bar | baz }}', array('foo' => 'FOO', 'baz' => function () { return 'BAZ'; })),
+            array('{{% FILTERS }}{{ foo | bar | baz }}', array('bar' => function () { return 'BAR'; })),
+            array('{{% FILTERS }}{{ foo | bar | baz }}', array('baz' => function () { return 'BAZ'; })),
+            array('{{% FILTERS }}{{ foo | bar.baz }}',   array('foo' => 'FOO', 'bar' => function () { return 'BAR'; }, 'baz' => function () { return 'BAZ'; })),
+
+            array('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}',             array()),
+            array('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}',             array('foo' => 'FOO')),
+            array('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}',             array('foo' => 'FOO', 'bar' => 'BAR')),
+            array('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}',             array('foo' => 'FOO', 'bar' => array(1, 2))),
+            array('{{% FILTERS }}{{# foo | bar | baz }}{{ . }}{{/ foo | bar | baz }}', array('foo' => 'FOO', 'bar' => function () { return 'BAR'; })),
+            array('{{% FILTERS }}{{# foo | bar | baz }}{{ . }}{{/ foo | bar | baz }}', array('foo' => 'FOO', 'baz' => function () { return 'BAZ'; })),
+            array('{{% FILTERS }}{{# foo | bar | baz }}{{ . }}{{/ foo | bar | baz }}', array('bar' => function () { return 'BAR'; })),
+            array('{{% FILTERS }}{{# foo | bar | baz }}{{ . }}{{/ foo | bar | baz }}', array('baz' => function () { return 'BAZ'; })),
+            array('{{% FILTERS }}{{# foo | bar.baz }}{{ . }}{{/ foo | bar.baz }}',     array('foo' => 'FOO', 'bar' => function () { return 'BAR'; }, 'baz' => function () { return 'BAZ'; })),
         );
     }
 }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -28,7 +28,7 @@ class Mustache_Test_FiveThree_Functional_HigherOrderSectionsTest extends PHPUnit
 
         $foo = new Mustache_Test_FiveThree_Functional_Foo;
         $foo->name = 'Mario';
-        $foo->wrapper = function($text) {
+        $foo->wrapper = function ($text) {
             return sprintf('<div class="anonymous">%s</div>', $text);
         };
 
@@ -53,7 +53,7 @@ class Mustache_Test_FiveThree_Functional_HigherOrderSectionsTest extends PHPUnit
 
         $data = array(
             'name' => 'Bob',
-            'wrap' => function($text) {
+            'wrap' => function ($text) {
                 return sprintf('[[%s]]', $text);
             }
         );
@@ -70,7 +70,7 @@ class Mustache_Test_FiveThree_Functional_Foo
 
     public function __construct()
     {
-        $this->wrap = function($text) {
+        $this->wrap = function ($text) {
             return sprintf('<em>%s</em>', $text);
         };
     }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -29,7 +29,7 @@ class Mustache_Test_FiveThree_Functional_LambdaHelperTest extends PHPUnit_Framew
 
         $foo = new StdClass;
         $foo->name = 'Mario';
-        $foo->lambda = function($text, $mustache) {
+        $foo->lambda = function ($text, $mustache) {
             return strtoupper($mustache->render($text));
         };
 

+ 3 - 56
test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -15,15 +15,8 @@
  * @group mustache-spec
  * @group functional
  */
-class Mustache_Test_FiveThree_Functional_MustacheSpecTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_FiveThree_Functional_MustacheSpecTest extends Mustache_Test_SpecTestCase
 {
-    private static $mustache;
-
-    public static function setUpBeforeClass()
-    {
-        self::$mustache = new Mustache_Engine;
-    }
-
     /**
      * For some reason data providers can't mark tests skipped, so this test exists
      * simply to provide a 'skipped' test if the `spec` submodule isn't initialized.
@@ -62,7 +55,7 @@ class Mustache_Test_FiveThree_Functional_MustacheSpecTest extends PHPUnit_Framew
                 }
 
                 $func = $val['php'];
-                $data[$key] = function($text = null) use ($func) {
+                $data[$key] = function ($text = null) use ($func) {
                     return eval($func);
                 };
             } elseif (is_array($val)) {
@@ -72,50 +65,4 @@ class Mustache_Test_FiveThree_Functional_MustacheSpecTest extends PHPUnit_Framew
 
         return $data;
     }
-
-    /**
-     * Data provider for the mustache spec test.
-     *
-     * Loads YAML files from the spec and converts them to PHPisms.
-     *
-     * @access public
-     * @return array
-     */
-    private function loadSpec($name)
-    {
-        $filename = dirname(__FILE__) . '/../../../../../vendor/spec/specs/' . $name . '.yml';
-        if (!file_exists($filename)) {
-            return array();
-        }
-
-        $data = array();
-        $yaml = new sfYamlParser;
-        $file = file_get_contents($filename);
-
-        // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
-        if ($name === '~lambdas') {
-            $file = str_replace(" !code\n", "\n", $file);
-        }
-
-        $spec = $yaml->parse($file);
-
-        foreach ($spec['tests'] as $test) {
-            $data[] = array(
-                $test['name'] . ': ' . $test['desc'],
-                $test['template'],
-                isset($test['partials']) ? $test['partials'] : array(),
-                $test['data'],
-                $test['expected'],
-            );
-        }
-
-        return $data;
-    }
-
-    private static function loadTemplate($source, $partials)
-    {
-        self::$mustache->setPartials($partials);
-
-        return self::$mustache->loadTemplate($source);
-    }
 }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -51,7 +51,7 @@ class Mustache_Test_Functional_ClassWithLambda
 {
     public function _t()
     {
-        return function($val) {
+        return function ($val) {
             return strtoupper($val);
         };
     }

+ 0 - 101
test/Mustache/Test/FiveThree/Functional/SectionFiltersTest.php

@@ -1,101 +0,0 @@
-<?php
-
-/*
- * This file is part of Mustache.php.
- *
- * (c) 2013 Justin Hileman
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-/**
- * @group filters
- * @group functional
- */
-class Mustache_Test_FiveThree_Functional_SectionFiltersTest extends PHPUnit_Framework_TestCase
-{
-
-    private $mustache;
-
-    public function setUp()
-    {
-        $this->mustache = new Mustache_Engine;
-    }
-
-    public function testSingleFilter()
-    {
-        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{# word | echo }}{{ . }}!{{/ word | echo }}');
-
-        $this->mustache->addHelper('echo', function($value) {
-            return array($value, $value, $value);
-        });
-
-        $this->assertEquals('bacon!bacon!bacon!', $tpl->render(array('word' => 'bacon')));
-    }
-
-    const CHAINED_FILTERS_TPL = <<<EOS
-{{% FILTERS }}
-{{# word | echo | with_index }}
-{{ key }}: {{ value }}
-{{/ word | echo | with_index }}
-EOS;
-
-    public function testChainedFilters()
-    {
-        $tpl = $this->mustache->loadTemplate(self::CHAINED_FILTERS_TPL);
-
-        $this->mustache->addHelper('echo', function($value) {
-            return array($value, $value, $value);
-        });
-
-        $this->mustache->addHelper('with_index', function($value) {
-            return array_map(function($k, $v) {
-                return array(
-                    'key'   => $k,
-                    'value' => $v,
-                );
-            }, array_keys($value), $value);
-        });
-
-        $this->assertEquals("0: bacon\n1: bacon\n2: bacon\n", $tpl->render(array('word' => 'bacon')));
-    }
-
-    public function testInterpolateFirst()
-    {
-        $tpl = $this->mustache->loadTemplate('{{% FILTERS }}{{# foo | bar }}{{ . }}{{/ foo | bar }}');
-        $this->assertEquals('win!', $tpl->render(array(
-            'foo' => 'FOO',
-            'bar' => function($value) {
-                return ($value === 'FOO') ? 'win!' : 'fail :(';
-            },
-        )));
-    }
-
-    /**
-     * @expectedException Mustache_Exception_UnknownFilterException
-     * @dataProvider getBrokenPipes
-     */
-    public function testThrowsExceptionForBrokenPipes($tpl, $data)
-    {
-        $this->mustache
-            ->loadTemplate(sprintf('{{%% FILTERS }}{{# %s }}{{ . }}{{/ %s }}', $tpl, $tpl))
-                ->render($data);
-    }
-
-    public function getBrokenPipes()
-    {
-        return array(
-            array('foo | bar', array()),
-            array('foo | bar', array('foo' => 'FOO')),
-            array('foo | bar', array('foo' => 'FOO', 'bar' => 'BAR')),
-            array('foo | bar', array('foo' => 'FOO', 'bar' => array(1, 2))),
-            array('foo | bar | baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; })),
-            array('foo | bar | baz', array('foo' => 'FOO', 'baz' => function() { return 'BAZ'; })),
-            array('foo | bar | baz', array('bar' => function() { return 'BAR'; })),
-            array('foo | bar | baz', array('baz' => function() { return 'BAZ'; })),
-            array('foo | bar.baz', array('foo' => 'FOO', 'bar' => function() { return 'BAR'; }, 'baz' => function() { return 'BAZ'; })),
-        );
-    }
-
-}

+ 19 - 34
test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -18,9 +18,9 @@ class Mustache_Test_FiveThree_Functional_StrictCallablesTest extends PHPUnit_Fra
     /**
      * @dataProvider callables
      */
-    public function testStrictCallablesDisabled($name, $section, $expected)
+    public function testStrictCallables($strict, $name, $section, $expected)
     {
-        $mustache = new Mustache_Engine(array('strict_callables' => false));
+        $mustache = new Mustache_Engine(array('strict_callables' => $strict));
         $tpl      = $mustache->loadTemplate('{{# section }}{{ name }}{{/ section }}');
 
         $data = new StdClass;
@@ -32,91 +32,76 @@ class Mustache_Test_FiveThree_Functional_StrictCallablesTest extends PHPUnit_Fra
 
     public function callables()
     {
-        $lambda = function($tpl, $mustache) {
+        $lambda = function ($tpl, $mustache) {
             return strtoupper($mustache->render($tpl));
         };
 
         return array(
             // Interpolation lambdas
             array(
+                false,
                 array($this, 'instanceName'),
                 $lambda,
                 'YOSHI',
             ),
             array(
+                false,
                 array(__CLASS__, 'staticName'),
                 $lambda,
                 'YOSHI',
             ),
             array(
-                function() { return 'Yoshi'; },
+                false,
+                function () { return 'Yoshi'; },
                 $lambda,
                 'YOSHI',
             ),
 
             // Section lambdas
             array(
+                false,
                 'Yoshi',
                 array($this, 'instanceCallable'),
                 'YOSHI',
             ),
             array(
+                false,
                 'Yoshi',
                 array(__CLASS__, 'staticCallable'),
                 'YOSHI',
             ),
             array(
+                false,
                 'Yoshi',
                 $lambda,
                 'YOSHI',
             ),
-        );
-    }
-
-    /**
-     * @group wip
-     * @dataProvider strictCallables
-     */
-    public function testStrictCallablesEnabled($name, $section, $expected)
-    {
-        $mustache = new Mustache_Engine(array('strict_callables' => true));
-        $tpl      = $mustache->loadTemplate('{{# section }}{{ name }}{{/ section }}');
-
-        $data = new StdClass;
-        $data->name    = $name;
-        $data->section = $section;
 
-        $this->assertEquals($expected, $tpl->render($data));
-    }
-
-    public function strictCallables()
-    {
-        $lambda = function($tpl, $mustache) {
-            return strtoupper($mustache->render($tpl));
-        };
-
-        return array(
-            // Interpolation lambdas
+            // Strict interpolation lambdas
             array(
-                function() { return 'Yoshi'; },
+                true,
+                function () { return 'Yoshi'; },
                 $lambda,
                 'YOSHI',
             ),
 
-            // Section lambdas
+            // Strict section lambdas
             array(
+                true,
                 'Yoshi',
                 array($this, 'instanceCallable'),
                 'YoshiYoshi',
             ),
             array(
+                true,
                 'Yoshi',
                 array(__CLASS__, 'staticCallable'),
                 'YoshiYoshi',
             ),
             array(
+                true,
                 'Yoshi',
-                function($tpl, $mustache) {
+                function ($tpl, $mustache) {
                     return strtoupper($mustache->render($tpl));
                 },
                 'YOSHI',

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 57 - 15
test/Mustache/Test/Functional/HigherOrderSectionsTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -13,9 +13,8 @@
  * @group lambdas
  * @group functional
  */
-class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_Functional_HigherOrderSectionsTest extends Mustache_Test_FunctionalTestCase
 {
-
     private $mustache;
 
     public function setUp()
@@ -23,24 +22,26 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework
         $this->mustache = new Mustache_Engine;
     }
 
-    public function testRuntimeSectionCallback()
+    /**
+     * @dataProvider sectionCallbackData
+     */
+    public function testSectionCallback($data, $tpl, $expect)
     {
-        $tpl = $this->mustache->loadTemplate('{{#doublewrap}}{{name}}{{/doublewrap}}');
-
-        $foo = new Mustache_Test_Functional_Foo;
-        $foo->doublewrap = array($foo, 'wrapWithBoth');
-
-        $this->assertEquals(sprintf('<strong><em>%s</em></strong>', $foo->name), $tpl->render($foo));
+        $this->assertEquals($expect, $this->mustache->render($tpl, $data));
     }
 
-    public function testStaticSectionCallback()
+    public function sectionCallbackData()
     {
-        $tpl = $this->mustache->loadTemplate('{{#trimmer}}    {{name}}    {{/trimmer}}');
-
         $foo = new Mustache_Test_Functional_Foo;
-        $foo->trimmer = array(get_class($foo), 'staticTrim');
+        $foo->doublewrap = array($foo, 'wrapWithBoth');
 
-        $this->assertEquals($foo->name, $tpl->render($foo));
+        $bar = new Mustache_Test_Functional_Foo;
+        $bar->trimmer = array(get_class($bar), 'staticTrim');
+
+        return array(
+            array($foo, '{{#doublewrap}}{{name}}{{/doublewrap}}', sprintf('<strong><em>%s</em></strong>', $foo->name)),
+            array($bar, '{{#trimmer}}   {{name}}   {{/trimmer}}', $bar->name),
+        );
     }
 
     public function testViewArraySectionCallback()
@@ -100,6 +101,44 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework
 
         $this->assertEquals('<em>' . $foo->name . '</em>', $tpl->render($foo));
     }
+
+    /**
+     * @dataProvider cacheLambdaTemplatesData
+     */
+    public function testCacheLambdaTemplatesOptionWorks($dirName, $tplPrefix, $enable, $expect)
+    {
+        $cacheDir = $this->setUpCacheDir($dirName);
+        $mustache = new Mustache_Engine(array(
+            'template_class_prefix'  => $tplPrefix,
+            'cache'                  => $cacheDir,
+            'cache_lambda_templates' => $enable,
+        ));
+
+        $tpl = $mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
+        $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'));
+    }
+
+    public function cacheLambdaTemplatesData()
+    {
+        return array(
+            array('test_enabling_lambda_cache',  '_TestEnablingLambdaCache_',  true,  2),
+            array('test_disabling_lambda_cache', '_TestDisablingLambdaCache_', false, 1),
+        );
+    }
+
+    protected function setUpCacheDir($name)
+    {
+        $cacheDir = self::$tempDir . '/' . $name;
+        if (file_exists($cacheDir)) {
+            self::rmdir($cacheDir);
+        }
+        mkdir($cacheDir, 0777, true);
+
+        return $cacheDir;
+    }
 }
 
 class Mustache_Test_Functional_Foo
@@ -112,6 +151,9 @@ class Mustache_Test_Functional_Foo
         return sprintf('<em>%s</em>', $text);
     }
 
+    /**
+     * @param string $text
+     */
     public function wrapWithStrong($text)
     {
         return sprintf('<strong>%s</strong>', $text);

+ 25 - 90
test/Mustache/Test/Functional/MustacheInjectionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -23,105 +23,53 @@ class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_T
         $this->mustache = new Mustache_Engine;
     }
 
-    // interpolation
-
-    public function testInterpolationInjection()
+    /**
+     * @dataProvider injectionData
+     */
+    public function testInjection($tpl, $data, $partials, $expect)
     {
-        $tpl = $this->mustache->loadTemplate('{{ a }}');
-
-        $data = array(
-            'a' => '{{ b }}',
-            'b' => 'FAIL'
-        );
-
-        $this->assertEquals('{{ b }}', $tpl->render($data));
+        $this->mustache->setPartials($partials);
+        $this->assertEquals($expect, $this->mustache->render($tpl, $data));
     }
 
-    public function testUnescapedInterpolationInjection()
+    public function injectionData()
     {
-        $tpl = $this->mustache->loadTemplate('{{{ a }}}');
-
-        $data = array(
+        $interpolationData = array(
             'a' => '{{ b }}',
             'b' => 'FAIL'
         );
 
-        $this->assertEquals('{{ b }}', $tpl->render($data));
-    }
-
-    // sections
-
-    public function testSectionInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{# a }}{{ b }}{{/ a }}');
-
-        $data = array(
+        $sectionData = array(
             'a' => true,
             'b' => '{{ c }}',
             'c' => 'FAIL'
         );
 
-        $this->assertEquals('{{ c }}', $tpl->render($data));
-    }
-
-    public function testUnescapedSectionInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{# a }}{{{ b }}}{{/ a }}');
-
-        $data = array(
-            'a' => true,
+        $lambdaInterpolationData = array(
+            'a' => array($this, 'lambdaInterpolationCallback'),
             'b' => '{{ c }}',
             'c' => 'FAIL'
         );
 
-        $this->assertEquals('{{ c }}', $tpl->render($data));
-    }
-
-    // partials
-
-    public function testPartialInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{> partial }}');
-        $this->mustache->setPartials(array(
-            'partial' => '{{ a }}',
-        ));
-
-        $data = array(
-            'a' => '{{ b }}',
-            'b' => 'FAIL'
-        );
-
-        $this->assertEquals('{{ b }}', $tpl->render($data));
-    }
-
-    public function testPartialUnescapedInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{> partial }}');
-        $this->mustache->setPartials(array(
-            'partial' => '{{{ a }}}',
-        ));
-
-        $data = array(
-            'a' => '{{ b }}',
-            'b' => 'FAIL'
+        $lambdaSectionData = array(
+            'a' => array($this, 'lambdaSectionCallback'),
+            'b' => '{{ c }}',
+            'c' => 'FAIL'
         );
 
-        $this->assertEquals('{{ b }}', $tpl->render($data));
-    }
+        return array(
+            array('{{ a }}',   $interpolationData, array(), '{{ b }}'),
+            array('{{{ a }}}', $interpolationData, array(), '{{ b }}'),
 
-    // lambdas
+            array('{{# a }}{{ b }}{{/ a }}',   $sectionData, array(), '{{ c }}'),
+            array('{{# a }}{{{ b }}}{{/ a }}', $sectionData, array(), '{{ c }}'),
 
-    public function testLambdaInterpolationInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{ a }}');
+            array('{{> partial }}', $interpolationData, array('partial' => '{{ a }}'),   '{{ b }}'),
+            array('{{> partial }}', $interpolationData, array('partial' => '{{{ a }}}'), '{{ b }}'),
 
-        $data = array(
-            'a' => array($this, 'lambdaInterpolationCallback'),
-            'b' => '{{ c }}',
-            'c' => 'FAIL'
+            array('{{ a }}',           $lambdaInterpolationData, array(), '{{ c }}'),
+            array('{{# a }}b{{/ a }}', $lambdaSectionData,       array(), '{{ c }}'),
         );
-
-        $this->assertEquals('{{ c }}', $tpl->render($data));
     }
 
     public static function lambdaInterpolationCallback()
@@ -129,19 +77,6 @@ class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_T
         return '{{ b }}';
     }
 
-    public function testLambdaSectionInjection()
-    {
-        $tpl = $this->mustache->loadTemplate('{{# a }}b{{/ a }}');
-
-        $data = array(
-            'a' => array($this, 'lambdaSectionCallback'),
-            'b' => '{{ c }}',
-            'c' => 'FAIL'
-        );
-
-        $this->assertEquals('{{ c }}', $tpl->render($data));
-    }
-
     public static function lambdaSectionCallback($text)
     {
         return '{{ ' . $text . ' }}';

+ 2 - 56
test/Mustache/Test/Functional/MustacheSpecTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -15,16 +15,8 @@
  * @group mustache-spec
  * @group functional
  */
-class Mustache_Test_Functional_MustacheSpecTest extends PHPUnit_Framework_TestCase
+class Mustache_Test_Functional_MustacheSpecTest extends Mustache_Test_SpecTestCase
 {
-
-    private static $mustache;
-
-    public static function setUpBeforeClass()
-    {
-        self::$mustache = new Mustache_Engine;
-    }
-
     /**
      * For some reason data providers can't mark tests skipped, so this test exists
      * simply to provide a 'skipped' test if the `spec` submodule isn't initialized.
@@ -126,50 +118,4 @@ class Mustache_Test_Functional_MustacheSpecTest extends PHPUnit_Framework_TestCa
     {
         return $this->loadSpec('sections');
     }
-
-    /**
-     * Data provider for the mustache spec test.
-     *
-     * Loads YAML files from the spec and converts them to PHPisms.
-     *
-     * @access public
-     * @return array
-     */
-    private function loadSpec($name)
-    {
-        $filename = dirname(__FILE__) . '/../../../../vendor/spec/specs/' . $name . '.yml';
-        if (!file_exists($filename)) {
-            return array();
-        }
-
-        $data = array();
-        $yaml = new sfYamlParser;
-        $file = file_get_contents($filename);
-
-        // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
-        if ($name === '~lambdas') {
-            $file = str_replace(" !code\n", "\n", $file);
-        }
-
-        $spec = $yaml->parse($file);
-
-        foreach ($spec['tests'] as $test) {
-            $data[] = array(
-                $test['name'] . ': ' . $test['desc'],
-                $test['template'],
-                isset($test['partials']) ? $test['partials'] : array(),
-                $test['data'],
-                $test['expected'],
-            );
-        }
-
-        return $data;
-    }
-
-    private static function loadTemplate($source, $partials)
-    {
-        self::$mustache->setPartials($partials);
-
-        return self::$mustache->loadTemplate($source);
-    }
 }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 47 - 0
test/Mustache/Test/FunctionalTestCase.php

@@ -0,0 +1,47 @@
+<?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.
+ */
+
+abstract class Mustache_Test_FunctionalTestCase extends PHPUnit_Framework_TestCase
+{
+    protected static $tempDir;
+
+    public static function setUpBeforeClass()
+    {
+        self::$tempDir = sys_get_temp_dir() . '/mustache_test';
+        if (file_exists(self::$tempDir)) {
+            self::rmdir(self::$tempDir);
+        }
+    }
+
+    /**
+     * @param string $path
+     */
+    protected static function rmdir($path)
+    {
+        $path = rtrim($path, '/').'/';
+        $handle = opendir($path);
+        while (($file = readdir($handle)) !== false) {
+            if ($file == '.' || $file == '..') {
+                continue;
+            }
+
+            $fullpath = $path.$file;
+            if (is_dir($fullpath)) {
+                self::rmdir($fullpath);
+            } else {
+                unlink($fullpath);
+            }
+        }
+
+        closedir($handle);
+        rmdir($path);
+    }
+}

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 2
test/Mustache/Test/Loader/FilesystemLoaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -64,7 +64,7 @@ class Mustache_Test_Loader_FilesystemLoaderTest extends PHPUnit_Framework_TestCa
      */
     public function testMissingBaseDirThrowsException()
     {
-        $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/not_a_directory');
+        new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/not_a_directory');
     }
 
     /**

+ 3 - 3
test/Mustache/Test/Loader/InlineLoaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -35,7 +35,7 @@ class Mustache_Test_Loader_InlineLoaderTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidOffsetThrowsException()
     {
-        $loader = new Mustache_Loader_InlineLoader(__FILE__, 'notanumber');
+        new Mustache_Loader_InlineLoader(__FILE__, 'notanumber');
     }
 
     /**
@@ -43,7 +43,7 @@ class Mustache_Test_Loader_InlineLoaderTest extends PHPUnit_Framework_TestCase
      */
     public function testInvalidFileThrowsException()
     {
-        $loader = new Mustache_Loader_InlineLoader('notarealfile', __COMPILER_HALT_OFFSET__);
+        new Mustache_Loader_InlineLoader('notarealfile', __COMPILER_HALT_OFFSET__);
     }
 }
 

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 13 - 10
test/Mustache/Test/Logger/StreamLoggerTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -14,23 +14,26 @@
  */
 class Mustache_Test_Logger_StreamLoggerTest extends PHPUnit_Framework_TestCase
 {
-    public function testAcceptsFilename()
+    /**
+     * @dataProvider acceptsStreamData
+     */
+    public function testAcceptsStream($name, $stream)
     {
-        $name   = tempnam(sys_get_temp_dir(), 'mustache-test');
-        $logger = new Mustache_Logger_StreamLogger($name);
+        $logger = new Mustache_Logger_StreamLogger($stream);
         $logger->log(Mustache_Logger::CRITICAL, 'message');
 
         $this->assertEquals("CRITICAL: message\n", file_get_contents($name));
     }
 
-    public function testAcceptsResource()
+    public function acceptsStreamData()
     {
-        $name   = tempnam(sys_get_temp_dir(), 'mustache-test');
-        $file   = fopen($name, 'a');
-        $logger = new Mustache_Logger_StreamLogger($file);
-        $logger->log(Mustache_Logger::CRITICAL, 'message');
+        $one = tempnam(sys_get_temp_dir(), 'mustache-test');
+        $two = tempnam(sys_get_temp_dir(), 'mustache-test');
 
-        $this->assertEquals("CRITICAL: message\n", file_get_contents($name));
+        return array(
+            array($one, $one),
+            array($two, fopen($two, 'a')),
+        );
     }
 
     /**

+ 1 - 1
test/Mustache/Test/ParserTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 67 - 0
test/Mustache/Test/SpecTestCase.php

@@ -0,0 +1,67 @@
+<?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.
+ */
+
+abstract class Mustache_Test_SpecTestCase extends PHPUnit_Framework_TestCase
+{
+    protected static $mustache;
+
+    public static function setUpBeforeClass()
+    {
+        self::$mustache = new Mustache_Engine;
+    }
+
+    protected static function loadTemplate($source, $partials)
+    {
+        self::$mustache->setPartials($partials);
+
+        return self::$mustache->loadTemplate($source);
+    }
+
+    /**
+     * Data provider for the mustache spec test.
+     *
+     * Loads YAML files from the spec and converts them to PHPisms.
+     *
+     * @param string $name
+     *
+     * @return array
+     */
+    protected function loadSpec($name)
+    {
+        $filename = dirname(__FILE__) . '/../../../vendor/spec/specs/' . $name . '.yml';
+        if (!file_exists($filename)) {
+            return array();
+        }
+
+        $data = array();
+        $yaml = new sfYamlParser;
+        $file = file_get_contents($filename);
+
+        // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
+        if ($name === '~lambdas') {
+            $file = str_replace(" !code\n", "\n", $file);
+        }
+
+        $spec = $yaml->parse($file);
+
+        foreach ($spec['tests'] as $test) {
+            $data[] = array(
+                $test['name'] . ': ' . $test['desc'],
+                $test['template'],
+                isset($test['partials']) ? $test['partials'] : array(),
+                $test['data'],
+                $test['expected'],
+            );
+        }
+
+        return $data;
+    }
+}

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/Mustache/Test/TokenizerTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 2 - 1
test/bootstrap.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -11,5 +11,6 @@
 
 require dirname(__FILE__).'/../src/Mustache/Autoloader.php';
 Mustache_Autoloader::register();
+Mustache_Autoloader::register(dirname(__FILE__).'/../test');
 
 require dirname(__FILE__).'/../vendor/yaml/lib/sfYamlParser.php';

+ 1 - 1
test/fixtures/autoloader/Mustache/Bar.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/fixtures/autoloader/Mustache/Foo.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.

+ 1 - 1
test/fixtures/autoloader/NonMustacheClass.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2013 Justin Hileman
+ * (c) 2010-2014 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.