Browse Source

Merge branch 'release/2.5.0'

Justin Hileman 12 năm trước cách đây
mục cha
commit
b3d7273078
64 tập tin đã thay đổi với 932 bổ sung201 xóa
  1. 1 0
      .travis.yml
  2. 5 1
      bin/build_bootstrap.php
  3. 1 1
      src/Mustache/Autoloader.php
  4. 38 0
      src/Mustache/Cache.php
  5. 60 0
      src/Mustache/Cache/AbstractCache.php
  6. 159 0
      src/Mustache/Cache/FilesystemCache.php
  7. 49 0
      src/Mustache/Cache/NoopCache.php
  8. 28 10
      src/Mustache/Compiler.php
  9. 4 2
      src/Mustache/Context.php
  10. 95 101
      src/Mustache/Engine.php
  11. 1 1
      src/Mustache/HelperCollection.php
  12. 1 1
      src/Mustache/LambdaHelper.php
  13. 1 1
      src/Mustache/Loader.php
  14. 1 1
      src/Mustache/Loader/ArrayLoader.php
  15. 1 1
      src/Mustache/Loader/FilesystemLoader.php
  16. 1 1
      src/Mustache/Loader/MutableLoader.php
  17. 1 1
      src/Mustache/Loader/StringLoader.php
  18. 20 11
      src/Mustache/Logger.php
  19. 9 9
      src/Mustache/Logger/AbstractLogger.php
  20. 13 13
      src/Mustache/Logger/StreamLogger.php
  21. 4 4
      src/Mustache/Parser.php
  22. 4 4
      src/Mustache/Template.php
  23. 1 1
      src/Mustache/Tokenizer.php
  24. 1 1
      test/Mustache/Test/AutoloaderTest.php
  25. 44 0
      test/Mustache/Test/Cache/AbstractCacheTest.php
  26. 67 0
      test/Mustache/Test/Cache/FilesystemCacheTest.php
  27. 1 1
      test/Mustache/Test/CompilerTest.php
  28. 103 1
      test/Mustache/Test/ContextTest.php
  29. 37 4
      test/Mustache/Test/EngineTest.php
  30. 1 1
      test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php
  31. 1 1
      test/Mustache/Test/FiveThree/Functional/FiltersTest.php
  32. 1 1
      test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php
  33. 1 1
      test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php
  34. 1 1
      test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php
  35. 0 1
      test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php
  36. 101 0
      test/Mustache/Test/FiveThree/Functional/SectionFiltersTest.php
  37. 0 1
      test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php
  38. 1 1
      test/Mustache/Test/Functional/CallTest.php
  39. 1 1
      test/Mustache/Test/Functional/ExamplesTest.php
  40. 30 1
      test/Mustache/Test/Functional/HigherOrderSectionsTest.php
  41. 1 1
      test/Mustache/Test/Functional/MustacheInjectionTest.php
  42. 1 1
      test/Mustache/Test/Functional/MustacheSpecTest.php
  43. 1 1
      test/Mustache/Test/Functional/ObjectSectionTest.php
  44. 1 1
      test/Mustache/Test/HelperCollectionTest.php
  45. 1 1
      test/Mustache/Test/Loader/ArrayLoaderTest.php
  46. 1 1
      test/Mustache/Test/Loader/CascadingLoaderTest.php
  47. 1 1
      test/Mustache/Test/Loader/FilesystemLoaderTest.php
  48. 1 1
      test/Mustache/Test/Loader/InlineLoaderTest.php
  49. 1 1
      test/Mustache/Test/Loader/StringLoaderTest.php
  50. 3 3
      test/Mustache/Test/Logger/AbstractLoggerTest.php
  51. 1 1
      test/Mustache/Test/Logger/StreamLoggerTest.php
  52. 1 1
      test/Mustache/Test/ParserTest.php
  53. 1 1
      test/Mustache/Test/TemplateTest.php
  54. 1 1
      test/Mustache/Test/TokenizerTest.php
  55. 1 1
      test/bootstrap.php
  56. 1 1
      test/fixtures/autoloader/Mustache/Bar.php
  57. 1 1
      test/fixtures/autoloader/Mustache/Foo.php
  58. 1 1
      test/fixtures/autoloader/NonMustacheClass.php
  59. 6 0
      test/fixtures/examples/nested_partials/NestedPartials.php
  60. 3 0
      test/fixtures/examples/nested_partials/nested_partials.mustache
  61. 7 0
      test/fixtures/examples/nested_partials/nested_partials.txt
  62. 1 0
      test/fixtures/examples/nested_partials/partials/fourth.mustache
  63. 3 0
      test/fixtures/examples/nested_partials/partials/second.mustache
  64. 3 0
      test/fixtures/examples/nested_partials/partials/third.mustache

+ 1 - 0
.travis.yml

@@ -3,3 +3,4 @@ php:
   - 5.2
   - 5.3
   - 5.4
+  - 5.5

+ 5 - 1
bin/build_bootstrap.php

@@ -4,7 +4,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -34,6 +34,10 @@ if (file_exists($file)) {
 // and load the new one
 SymfonyClassCollectionLoader::load(array(
     'Mustache_Engine',
+    'Mustache_Cache',
+    'Mustache_Cache_AbstractCache',
+    'Mustache_Cache_FilesystemCache',
+    'Mustache_Cache_NoopCache',
     'Mustache_Compiler',
     'Mustache_Context',
     'Mustache_Exception',

+ 1 - 1
src/Mustache/Autoloader.php

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

+ 38 - 0
src/Mustache/Cache.php

@@ -0,0 +1,38 @@
+<?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.
+ */
+
+/**
+ * Mustache Cache interface.
+ *
+ * Interface for caching and loading Mustache_Template classes
+ * generated by the Mustache_Compiler.
+ */
+interface Mustache_Cache
+{
+    /**
+     * Load a compiled Mustache_Template class from cache.
+     *
+     * @param string $key
+     *
+     * @return boolean indicates successfully class load
+     */
+    public function load($key);
+
+    /**
+     * Cache and load a compiled Mustache_Template class.
+     *
+     * @param string $key
+     * @param string $value
+     *
+     * @return void
+     */
+    public function cache($key, $value);
+}

+ 60 - 0
src/Mustache/Cache/AbstractCache.php

@@ -0,0 +1,60 @@
+<?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.
+ */
+
+/**
+ * Abstract Mustache Cache class.
+ *
+ * Provides logging support to child implementations.
+ *
+ * @abstract
+ */
+abstract class Mustache_Cache_AbstractCache implements Mustache_Cache
+{
+    private $logger = null;
+
+    /**
+     * Get the current logger instance.
+     *
+     * @return Mustache_Logger|Psr\Log\LoggerInterface
+     */
+    public function getLogger()
+    {
+        return $this->logger;
+    }
+
+    /**
+     * Set a logger instance.
+     *
+     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
+     */
+    public function setLogger($logger = null)
+    {
+        if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
+            throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
+        }
+
+        $this->logger = $logger;
+    }
+
+    /**
+     * Add a log record if logging is enabled.
+     *
+     * @param integer $level   The logging level
+     * @param string  $message The log message
+     * @param array   $context The log context
+     */
+    protected function log($level, $message, array $context = array())
+    {
+        if (isset($this->logger)) {
+            $this->logger->log($level, $message, $context);
+        }
+    }
+}

+ 159 - 0
src/Mustache/Cache/FilesystemCache.php

@@ -0,0 +1,159 @@
+<?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.
+ */
+
+/**
+ * Mustache Cache filesystem implementation.
+ *
+ * A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
+ *
+ *     $cache = new Mustache_Cache_FilesystemCache(dirname(__FILE__).'/cache');
+ *     $cache->cache($className, $compiledSource);
+ *
+ * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
+ */
+class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache
+{
+    private $baseDir;
+    private $fileMode;
+
+    /**
+     * Filesystem cache constructor.
+     *
+     * @param string $baseDir  Directory for compiled templates.
+     * @param int    $fileMode Override default permissions for cache files. Defaults to using the system-defined umask.
+     */
+    public function __construct($baseDir, $fileMode = null)
+    {
+        $this->baseDir = $baseDir;
+        $this->fileMode = $fileMode;
+    }
+
+    /**
+     * Load the class from cache using `require_once`.
+     *
+     * @param string $key
+     *
+     * @return boolean
+     */
+    public function load($key)
+    {
+        $fileName = $this->getCacheFilename($key);
+        if (!is_file($fileName)) {
+            return false;
+        }
+
+        require_once $fileName;
+
+        return true;
+    }
+
+    /**
+     * Cache and load the compiled class
+     *
+     * @param string $key
+     * @param string $value
+     *
+     * @return void
+     */
+    public function cache($key, $value)
+    {
+        $fileName = $this->getCacheFilename($key);
+
+        $this->log(
+            Mustache_Logger::DEBUG,
+            'Writing to template cache: "{fileName}"',
+            array('fileName' => $fileName)
+        );
+
+        $this->writeFile($fileName, $value);
+        $this->load($key);
+    }
+
+    /**
+     * Build the cache filename.
+     * Subclasses should override for custom cache directory structures.
+     *
+     * @param string $name
+     *
+     * @return string
+     */
+    protected function getCacheFilename($name)
+    {
+        return sprintf('%s/%s.php', $this->baseDir, $name);
+    }
+
+    /**
+     * Create cache directory
+     *
+     * @throws Mustache_Exception_RuntimeException If unable to create directory
+     *
+     * @param string $fileName
+     *
+     * @return string
+     */
+    private function buildDirectoryForFilename($fileName)
+    {
+        $dirName = dirname($fileName);
+        if (!is_dir($dirName)) {
+            $this->log(
+                Mustache_Logger::INFO,
+                'Creating Mustache template cache directory: "{dirName}"',
+                array('dirName' => $dirName)
+            );
+
+            @mkdir($dirName, 0777, true);
+            if (!is_dir($dirName)) {
+                throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
+            }
+        }
+
+        return $dirName;
+    }
+
+    /**
+     * Write cache file
+     *
+     * @throws Mustache_Exception_RuntimeException If unable to write file
+     *
+     * @param string $fileName
+     * @param string $value
+     *
+     * @return void
+     */
+    private function writeFile($fileName, $value)
+    {
+        $dirName = $this->buildDirectoryForFilename($fileName);
+
+        $this->log(
+            Mustache_Logger::DEBUG,
+            'Caching compiled template to "{fileName}"',
+            array('fileName' => $fileName)
+        );
+
+        $tempFile = tempnam($dirName, basename($fileName));
+        if (false !== @file_put_contents($tempFile, $value)) {
+            if (@rename($tempFile, $fileName)) {
+                $mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
+                @chmod($fileName, $mode);
+
+                return;
+            }
+
+            $this->log(
+                Mustache_Logger::ERROR,
+                'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
+                array('tempName' => $tempFile, 'fileName' => $fileName)
+            );
+        }
+
+        throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
+    }
+}

+ 49 - 0
src/Mustache/Cache/NoopCache.php

@@ -0,0 +1,49 @@
+<?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.
+ */
+
+/**
+ * Mustache Cache in-memory implementation.
+ *
+ * The in-memory cache is used for uncached lambda section templates. It's also useful during development, but is not
+ * recommended for production use.
+ */
+class Mustache_Cache_NoopCache extends Mustache_Cache_AbstractCache
+{
+    /**
+     * Loads nothing. Move along.
+     *
+     * @param string $key
+     *
+     * @return boolean
+     */
+    public function load($key)
+    {
+        return false;
+    }
+
+    /**
+     * Loads the compiled Mustache Template class without caching.
+     *
+     * @param string $key
+     * @param string $value
+     *
+     * @return void
+     */
+    public function cache($key, $value)
+    {
+        $this->log(
+            Mustache_Logger::WARNING,
+            'Template cache disabled, evaluating "{className}" class at runtime',
+            array('className' => $key)
+        );
+        eval('?>' . $value);
+    }
+}

+ 28 - 10
src/Mustache/Compiler.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -33,9 +33,9 @@ class Mustache_Compiler
      * @param string $tree            Parse tree of Mustache tokens
      * @param string $name            Mustache Template class name
      * @param bool   $customEscape    (default: false)
-     * @param int    $entityFlags     (default: ENT_COMPAT)
      * @param string $charset         (default: 'UTF-8')
      * @param bool   $strictCallables (default: false)
+     * @param int    $entityFlags     (default: ENT_COMPAT)
      *
      * @return string Generated PHP source code
      */
@@ -178,7 +178,8 @@ class Mustache_Compiler
 
     const SECTION_CALL = '
         // %s section
-        $buffer .= $this->section%s($context, $indent, $context->%s(%s));
+        $value = $context->%s(%s);%s
+        $buffer .= $this->section%s($context, $indent, $value);
     ';
 
     const SECTION = '
@@ -187,9 +188,14 @@ class Mustache_Compiler
             $buffer = \'\';
             if (%s) {
                 $source = %s;
-                $buffer .= $this->mustache
-                    ->loadLambda((string) call_user_func($value, $source, $this->lambdaHelper)%s)
-                    ->renderInternal($context);
+                $result = call_user_func($value, $source, $this->lambdaHelper);
+                if (strpos($result, \'{{\') === false) {
+                    $buffer .= $result;
+                } else {
+                    $buffer .= $this->mustache
+                        ->loadLambda((string) $result%s)
+                        ->renderInternal($context);
+                }
             } elseif (!empty($value)) {
                 $values = $this->isIterable($value) ? $value : array($value);
                 foreach ($values as $value) {
@@ -216,6 +222,12 @@ class Mustache_Compiler
      */
     private function section($nodes, $id, $start, $end, $otag, $ctag, $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);
         $source   = var_export(substr($this->source, $start, $end - $start), true);
@@ -233,12 +245,12 @@ class Mustache_Compiler
             $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, $key, $method, $id);
+        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
     }
 
     const INVERTED_SECTION = '
         // %s inverted section
-        $value = $context->%s(%s);
+        $value = $context->%s(%s);%s
         if (empty($value)) {
             %s
         }';
@@ -254,15 +266,21 @@ class Mustache_Compiler
      */
     private function invertedSection($nodes, $id, $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);
 
-        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level));
+        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
     }
 
     const PARTIAL = '
         if ($partial = $this->mustache->loadPartial(%s)) {
-            $buffer .= $partial->renderInternal($context, %s);
+            $buffer .= $partial->renderInternal($context, $indent . %s);
         }
     ';
 

+ 4 - 2
src/Mustache/Context.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -133,11 +133,13 @@ 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) {
+            if (is_object($stack[$i]) && !($stack[$i] instanceof Closure)) {
                 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];

+ 95 - 101
src/Mustache/Engine.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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.4.1';
+    const VERSION        = '2.5.0';
     const SPEC_VERSION   = '1.1.2';
 
     const PRAGMA_FILTERS = 'FILTERS';
@@ -33,8 +33,9 @@ class Mustache_Engine
 
     // Environment
     private $templateClassPrefix = '__Mustache_';
-    private $cache = null;
-    private $cacheFileMode = null;
+    private $cache;
+    private $lambdaCache;
+    private $cacheLambdaTemplates = false;
     private $loader;
     private $partialsLoader;
     private $helpers;
@@ -53,13 +54,18 @@ class Mustache_Engine
      *         // The class prefix for compiled templates. Defaults to '__Mustache_'.
      *         'template_class_prefix' => '__MyTemplates_',
      *
-     *         // A cache directory for compiled templates. Mustache will not cache templates unless this is set
+     *         // A Mustache cache instance or a cache directory string for compiled templates.
+     *         // Mustache will not cache templates unless this is set.
      *         'cache' => dirname(__FILE__).'/tmp/cache/mustache',
      *
      *         // Override default permissions for cache files. Defaults to using the system-defined umask. It is
      *         // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
      *         'cache_file_mode' => 0666,
      *
+     *         // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda
+     *         // sections are often too dynamic to benefit from caching.
+     *         'cache_lambda_templates' => true,
+     *
      *         // A Mustache template loader instance. Uses a StringLoader if not specified.
      *         'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
      *
@@ -112,11 +118,18 @@ class Mustache_Engine
         }
 
         if (isset($options['cache'])) {
-            $this->cache = $options['cache'];
+            $cache = $options['cache'];
+
+            if (is_string($cache)) {
+                $mode  = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
+                $cache = new Mustache_Cache_FilesystemCache($cache, $mode);
+            }
+
+            $this->setCache($cache);
         }
 
-        if (isset($options['cache_file_mode'])) {
-            $this->cacheFileMode = $options['cache_file_mode'];
+        if (isset($options['cache_lambda_templates'])) {
+            $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
         }
 
         if (isset($options['loader'])) {
@@ -169,7 +182,7 @@ class Mustache_Engine
      * @see Mustache_Template::render
      *
      * @param string $template
-     * @param mixed  $context (default: array())
+     * @param mixed  $context  (default: array())
      *
      * @return string Rendered template
      */
@@ -388,6 +401,10 @@ class Mustache_Engine
             throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
         }
 
+        if ($this->getCache()->getLogger() === null) {
+            $this->getCache()->setLogger($logger);
+        }
+
         $this->logger = $logger;
     }
 
@@ -479,6 +496,58 @@ class Mustache_Engine
         return $this->compiler;
     }
 
+    /**
+     * Set the Mustache Cache instance.
+     *
+     * @param Mustache_Cache $cache
+     */
+    public function setCache(Mustache_Cache $cache)
+    {
+        if (isset($this->logger) && $cache->getLogger() === null) {
+            $cache->setLogger($this->getLogger());
+        }
+
+        $this->cache = $cache;
+    }
+
+    /**
+     * Get the current Mustache Cache instance.
+     *
+     * If no Cache instance has been explicitly specified, this method will instantiate and return a new one.
+     *
+     * @return Mustache_Cache
+     */
+    public function getCache()
+    {
+        if (!isset($this->cache)) {
+            $this->setCache(new Mustache_Cache_NoopCache());
+        }
+
+        return $this->cache;
+    }
+
+    /**
+     * Get the current Lambda Cache instance.
+     *
+     * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache.
+     *
+     * @see Mustache_Engine::getCache
+     *
+     * @return Mustache_Cache
+     */
+    protected function getLambdaCache()
+    {
+        if ($this->cacheLambdaTemplates) {
+            return $this->getCache();
+        }
+
+        if (!isset($this->lambdaCache)) {
+            $this->lambdaCache = new Mustache_Cache_NoopCache();
+        }
+
+        return $this->lambdaCache;
+    }
+
     /**
      * Helper method to generate a Mustache template class.
      *
@@ -560,46 +629,37 @@ class Mustache_Engine
             $source = $delims . "\n" . $source;
         }
 
-        return $this->loadSource($source);
+        return $this->loadSource($source, $this->getLambdaCache());
     }
 
     /**
      * Instantiate and return a Mustache Template instance by source.
      *
+     * Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect
+     * the 'cache_lambda_templates' configuration option.
+     *
      * @see Mustache_Engine::loadTemplate
      * @see Mustache_Engine::loadPartial
      * @see Mustache_Engine::loadLambda
      *
-     * @param string $source
+     * @param string         $source
+     * @param Mustache_Cache $cache  (default: null)
      *
      * @return Mustache_Template
      */
-    private function loadSource($source)
+    private function loadSource($source, Mustache_Cache $cache = null)
     {
         $className = $this->getTemplateClassName($source);
 
         if (!isset($this->templates[$className])) {
+            if ($cache === null) {
+                $cache = $this->getCache();
+            }
+
             if (!class_exists($className, false)) {
-                if ($fileName = $this->getCacheFilename($source)) {
-                    if (!is_file($fileName)) {
-                        $this->log(
-                            Mustache_Logger::DEBUG,
-                            'Writing "{className}" class to template cache: "{fileName}"',
-                            array('className' => $className, 'fileName' => $fileName)
-                        );
-
-                        $this->writeCacheFile($fileName, $this->compile($source));
-                    }
-
-                    require_once $fileName;
-                } else {
-                    $this->log(
-                        Mustache_Logger::WARNING,
-                        'Template cache disabled, evaluating "{className}" class at runtime',
-                        array('className' => $className)
-                    );
-
-                    eval('?>'.$this->compile($source));
+                if (!$this->getCache()->load($className)) {
+                    $compiled = $this->compile($source);
+                    $this->getCache()->cache($className, $compiled);
                 }
             }
 
@@ -666,78 +726,12 @@ class Mustache_Engine
         return $this->getCompiler()->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
     }
 
-    /**
-     * Helper method to generate a Mustache Template class cache filename.
-     *
-     * @param string $source
-     *
-     * @return string Mustache Template class cache filename
-     */
-    private function getCacheFilename($source)
-    {
-        if ($this->cache) {
-            return sprintf('%s/%s.php', $this->cache, $this->getTemplateClassName($source));
-        }
-    }
-
-    /**
-     * Helper method to dump a generated Mustache Template subclass to the file cache.
-     *
-     * @throws Mustache_Exception_RuntimeException if unable to create the cache directory or write to $fileName.
-     *
-     * @param string $fileName
-     * @param string $source
-     *
-     * @codeCoverageIgnore
-     */
-    private function writeCacheFile($fileName, $source)
-    {
-        $dirName = dirname($fileName);
-        if (!is_dir($dirName)) {
-            $this->log(
-                Mustache_Logger::INFO,
-                'Creating Mustache template cache directory: "{dirName}"',
-                array('dirName' => $dirName)
-            );
-
-            @mkdir($dirName, 0777, true);
-            if (!is_dir($dirName)) {
-                throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
-            }
-
-        }
-
-        $this->log(
-            Mustache_Logger::DEBUG,
-            'Caching compiled template to "{fileName}"',
-            array('fileName' => $fileName)
-        );
-
-        $tempFile = tempnam($dirName, basename($fileName));
-        if (false !== @file_put_contents($tempFile, $source)) {
-            if (@rename($tempFile, $fileName)) {
-                $mode = isset($this->cacheFileMode) ? $this->cacheFileMode : (0666 & ~umask());
-                @chmod($fileName, $mode);
-
-                return;
-            }
-
-            $this->log(
-                Mustache_Logger::ERROR,
-                'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
-                array('tempName' => $tempFile, 'fileName' => $fileName)
-            );
-        }
-
-        throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
-    }
-
     /**
      * Add a log record if logging is enabled.
      *
-     * @param  integer $level   The logging level
-     * @param  string  $message The log message
-     * @param  array   $context The log context
+     * @param integer $level   The logging level
+     * @param string  $message The log message
+     * @param array   $context The log context
      */
     private function log($level, $message, array $context = array())
     {

+ 1 - 1
src/Mustache/HelperCollection.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/LambdaHelper.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Loader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Loader/ArrayLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Loader/FilesystemLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Loader/MutableLoader.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Loader/StringLoader.php

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

+ 20 - 11
src/Mustache/Logger.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -44,7 +44,8 @@ interface Mustache_Logger
      * System is unusable.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function emergency($message, array $context = array());
@@ -56,7 +57,8 @@ interface Mustache_Logger
      * trigger the SMS alerts and wake you up.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function alert($message, array $context = array());
@@ -67,7 +69,8 @@ interface Mustache_Logger
      * Example: Application component unavailable, unexpected exception.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function critical($message, array $context = array());
@@ -77,7 +80,8 @@ interface Mustache_Logger
      * be logged and monitored.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function error($message, array $context = array());
@@ -89,7 +93,8 @@ interface Mustache_Logger
      * that are not necessarily wrong.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function warning($message, array $context = array());
@@ -98,7 +103,8 @@ interface Mustache_Logger
      * Normal but significant events.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function notice($message, array $context = array());
@@ -109,7 +115,8 @@ interface Mustache_Logger
      * Example: User logs in, SQL logs.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function info($message, array $context = array());
@@ -118,7 +125,8 @@ interface Mustache_Logger
      * Detailed debug information.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function debug($message, array $context = array());
@@ -126,9 +134,10 @@ interface Mustache_Logger
     /**
      * Logs with an arbitrary level.
      *
-     * @param mixed $level
+     * @param mixed  $level
      * @param string $message
-     * @param array $context
+     * @param array  $context
+     *
      * @return null
      */
     public function log($level, $message, array $context = array());

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -24,7 +24,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * System is unusable.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function emergency($message, array $context = array())
     {
@@ -38,7 +38,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * trigger the SMS alerts and wake you up.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function alert($message, array $context = array())
     {
@@ -51,7 +51,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * Example: Application component unavailable, unexpected exception.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function critical($message, array $context = array())
     {
@@ -63,7 +63,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * be logged and monitored.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function error($message, array $context = array())
     {
@@ -77,7 +77,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * that are not necessarily wrong.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function warning($message, array $context = array())
     {
@@ -88,7 +88,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * Normal but significant events.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function notice($message, array $context = array())
     {
@@ -101,7 +101,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * Example: User logs in, SQL logs.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function info($message, array $context = array())
     {
@@ -112,7 +112,7 @@ abstract class Mustache_Logger_AbstractLogger implements Mustache_Logger
      * Detailed debug information.
      *
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function debug($message, array $context = array())
     {

+ 13 - 13
src/Mustache/Logger/StreamLogger.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -66,7 +66,7 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
      *
      * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
      *
-     * @param  integer $level The minimum logging level which will be written
+     * @param integer $level The minimum logging level which will be written
      */
     public function setLevel($level)
     {
@@ -92,9 +92,9 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
      *
      * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown.
      *
-     * @param mixed $level
+     * @param mixed  $level
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function log($level, $message, array $context = array())
     {
@@ -113,9 +113,9 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
      * @throws Mustache_Exception_LogicException   If neither a stream resource nor url is present.
      * @throws Mustache_Exception_RuntimeException If the stream url cannot be opened.
      *
-     * @param  integer $level   The logging level
-     * @param  string  $message The log message
-     * @param  array   $context The log context
+     * @param integer $level   The logging level
+     * @param string  $message The log message
+     * @param array   $context The log context
      */
     protected function writeLog($level, $message, array $context = array())
     {
@@ -140,7 +140,7 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
      *
      * @throws InvalidArgumentException if the logging level is unknown.
      *
-     * @param  integer $level
+     * @param integer $level
      *
      * @return string
      */
@@ -152,9 +152,9 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
     /**
      * Format a log line for output.
      *
-     * @param  integer $level   The logging level
-     * @param  string  $message The log message
-     * @param  array   $context The log context
+     * @param integer $level   The logging level
+     * @param string  $message The log message
+     * @param array   $context The log context
      *
      * @return string
      */
@@ -170,8 +170,8 @@ class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
     /**
      * Interpolate context values into the message placeholders.
      *
-     * @param  string $message
-     * @param  array  $context
+     * @param string $message
+     * @param array  $context
      *
      * @return string
      */

+ 4 - 4
src/Mustache/Parser.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -40,7 +40,7 @@ class Mustache_Parser
      * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered.
      *
      * @param array &$tokens Set of Mustache tokens
-     * @param array  $parent Parent token (default: null)
+     * @param array $parent  Parent token (default: null)
      *
      * @return array Mustache Token parse tree
      */
@@ -121,8 +121,8 @@ class Mustache_Parser
      *
      * Returns a whitespace token for indenting partials, if applicable.
      *
-     * @param array  $nodes  Parsed nodes.
-     * @param array  $tokens Tokens to be parsed.
+     * @param array $nodes  Parsed nodes.
+     * @param array $tokens Tokens to be parsed.
      *
      * @return array Resulting indent token, if any.
      */

+ 4 - 4
src/Mustache/Template.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -158,9 +158,9 @@ abstract class Mustache_Template
      *
      * Invoke the value if it is callable, otherwise return the value.
      *
-     * @param  mixed            $value
-     * @param  Mustache_Context $context
-     * @param  string           $indent
+     * @param mixed            $value
+     * @param Mustache_Context $context
+     * @param string           $indent
      *
      * @return string
      */

+ 1 - 1
src/Mustache/Tokenizer.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/AutoloaderTest.php

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

+ 44 - 0
test/Mustache/Test/Cache/AbstractCacheTest.php

@@ -0,0 +1,44 @@
+<?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.
+ */
+
+class Mustache_Test_Cache_AbstractCacheTest extends PHPUnit_Framework_TestCase
+{
+    public function testGetSetLogger()
+    {
+         $cache  = new CacheStub();
+         $logger = new Mustache_Logger_StreamLogger('php://stdout');
+         $cache->setLogger($logger);
+         $this->assertSame($logger, $cache->getLogger());
+    }
+
+    /**
+     * @expectedException Mustache_Exception_InvalidArgumentException
+     */
+    public function testSetLoggerThrowsExceptions()
+    {
+        $cache  = new CacheStub();
+        $logger = new StdClass();
+        $cache->setLogger($logger);
+    }
+}
+
+class CacheStub extends Mustache_Cache_AbstractCache
+{
+    public function load($key)
+    {
+        // nada
+    }
+
+    public function cache($key, $value)
+    {
+        // nada
+    }
+}

+ 67 - 0
test/Mustache/Test/Cache/FilesystemCacheTest.php

@@ -0,0 +1,67 @@
+<?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 functional
+ */
+class Mustache_Test_Cache_FilesystemCacheTest extends PHPUnit_Framework_TestCase
+{
+    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';
+        $cache = new Mustache_Cache_FilesystemCache(self::$tempDir);;
+        $loaded = $cache->load($key);
+
+        $this->assertFalse($loaded);
+    }
+
+    public function testCachePut()
+    {
+        $key = 'some key';
+        $value = '<?php /* some value */';
+        $cache = new Mustache_Cache_FilesystemCache(self::$tempDir);;
+        $cache->cache($key, $value);
+        $loaded = $cache->load($key);
+
+        $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);
+    }
+}

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

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

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -96,6 +96,29 @@ class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('<foo>', $context->find('foo'));
         $this->assertEquals('<bar>', $context->findDot('bar'));
     }
+
+    public function testArrayAccessFind()
+    {
+        $access = new Mustache_Test_TestArrayAccess(array(
+            'a' => array('b' => array('c' => 'see')),
+            'b' => 'bee',
+        ));
+
+        $context = new Mustache_Context($access);
+        $this->assertEquals('bee', $context->find('b'));
+        $this->assertEquals('see', $context->findDot('a.b.c'));
+        $this->assertEquals(null, $context->findDot('a.b.c.d'));
+    }
+
+    public function testAccessorPriority()
+    {
+        $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');
+        $this->assertEquals('win', $context->find('baz'), 'ArrayAccess stands alone');
+        $this->assertEquals('win', $context->find('qux'), 'ArrayAccess beats private property');
+    }
 }
 
 class Mustache_Test_TestDummy
@@ -117,3 +140,82 @@ class Mustache_Test_TestDummy
         return '<bar>';
     }
 }
+
+class Mustache_Test_TestArrayAccess implements ArrayAccess
+{
+    private $container = array();
+
+    public function __construct($array)
+    {
+        foreach ($array as $key => $value) {
+            $this->container[$key] = $value;
+        }
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        if (is_null($offset)) {
+            $this->container[] = $value;
+        } else {
+            $this->container[$offset] = $value;
+        }
+    }
+
+    public function offsetExists($offset)
+    {
+        return isset($this->container[$offset]);
+    }
+
+    public function offsetUnset($offset)
+    {
+        unset($this->container[$offset]);
+    }
+
+    public function offsetGet($offset)
+    {
+        return isset($this->container[$offset]) ? $this->container[$offset] : null;
+    }
+}
+
+class Mustache_Test_AllTheThings implements ArrayAccess
+{
+    public $foo  = 'fail';
+    public $bar  = 'win';
+    private $qux = 'fail';
+
+    public function foo()
+    {
+        return 'win';
+    }
+
+    public function offsetExists($offset)
+    {
+        return true;
+    }
+
+    public function offsetGet($offset)
+    {
+        switch ($offset) {
+            case 'foo':
+            case 'bar':
+                return 'fail';
+
+            case 'baz':
+            case 'qux':
+                return 'win';
+
+            default:
+                return 'lolwhut';
+        }
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        // nada
+    }
+
+    public function offsetUnset($offset)
+    {
+        // nada
+    }
+}

+ 37 - 4
test/Mustache/Test/EngineTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -45,6 +45,7 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
                 'bar' => 'BAR',
             ),
             'escape'  => 'strtoupper',
+            'entity_flags' => ENT_QUOTES,
             'charset' => 'ISO-8859-1',
         ));
 
@@ -54,10 +55,12 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('{{ foo }}', $partialsLoader->load('foo'));
         $this->assertContains('__whot__', $mustache->getTemplateClassName('{{ foo }}'));
         $this->assertEquals('strtoupper', $mustache->getEscape());
+        $this->assertEquals(ENT_QUOTES, $mustache->getEntityFlags());
         $this->assertEquals('ISO-8859-1', $mustache->getCharset());
         $this->assertTrue($mustache->hasHelper('foo'));
         $this->assertTrue($mustache->hasHelper('bar'));
         $this->assertFalse($mustache->hasHelper('baz'));
+        $this->assertInstanceOf('Mustache_Cache_FilesystemCache', $mustache->getCache());
     }
 
     public static function getFoo()
@@ -95,6 +98,7 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
         $parser    = new Mustache_Parser;
         $compiler  = new Mustache_Compiler;
         $mustache  = new Mustache_Engine;
+        $cache     = new Mustache_Cache_FilesystemCache(self::$tempDir);
 
         $this->assertNotSame($logger, $mustache->getLogger());
         $mustache->setLogger($logger);
@@ -119,6 +123,10 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
         $this->assertNotSame($compiler, $mustache->getCompiler());
         $mustache->setCompiler($compiler);
         $this->assertSame($compiler, $mustache->getCompiler());
+
+        $this->assertNotSame($cache, $mustache->getCache());
+        $mustache->setCache($cache);
+        $this->assertSame($cache, $mustache->getCache());
     }
 
     /**
@@ -134,10 +142,29 @@ class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
         $source    = '{{ foo }}';
         $template  = $mustache->loadTemplate($source);
         $className = $mustache->getTemplateClassName($source);
-        $fileName  = self::$tempDir . '/' . $className . '.php';
+
         $this->assertInstanceOf($className, $template);
-        $this->assertFileExists($fileName);
-        $this->assertContains("\nclass $className extends Mustache_Template", file_get_contents($fileName));
+    }
+
+    public function testLambdaCache()
+    {
+        $mustache = new MustacheStub(array(
+            'cache' => self::$tempDir,
+            'cache_lambda_templates' => true,
+        ));
+
+        $this->assertNotInstanceOf('Mustache_Cache_NoopCache', $mustache->getProtectedLambdaCache());
+        $this->assertSame($mustache->getCache(), $mustache->getProtectedLambdaCache());
+    }
+
+    public function testWithoutLambdaCache()
+    {
+        $mustache = new MustacheStub(array(
+            'cache' => self::$tempDir
+        ));
+
+        $this->assertInstanceOf('Mustache_Cache_NoopCache', $mustache->getProtectedLambdaCache());
+        $this->assertNotSame($mustache->getCache(), $mustache->getProtectedLambdaCache());
     }
 
     /**
@@ -348,10 +375,16 @@ class MustacheStub extends Mustache_Engine
 {
     public $source;
     public $template;
+
     public function loadTemplate($source)
     {
         $this->source = $source;
 
         return $this->template;
     }
+
+    public function getProtectedLambdaCache()
+    {
+        return $this->getLambdaCache();
+    }
 }

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/FiveThree/Functional/FiltersTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/FiveThree/Functional/HigherOrderSectionsTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/FiveThree/Functional/LambdaHelperTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/FiveThree/Functional/MustacheSpecTest.php

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

+ 0 - 1
test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php

@@ -42,7 +42,6 @@ EOS;
 
         $tpl = $m->loadTemplate($src);
 
-
         $data = new Mustache_Test_Functional_ClassWithLambda();
         $this->assertEquals($expected, $tpl->render($data));
     }

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

@@ -0,0 +1,101 @@
+<?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'; })),
+        );
+    }
+
+}

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

@@ -73,7 +73,6 @@ class Mustache_Test_FiveThree_Functional_StrictCallablesTest extends PHPUnit_Fra
         );
     }
 
-
     /**
      * @group wip
      * @dataProvider strictCallables

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

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

+ 30 - 1
test/Mustache/Test/Functional/HigherOrderSectionsTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -71,6 +71,35 @@ class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework
         $dracula->name  = 'Dracula';
         $this->assertEquals('Count Dracula', $tpl->render($dracula));
     }
+
+    public function testPassthroughOptimization()
+    {
+        $mustache = $this->getMock('Mustache_Engine', array('loadLambda'));
+        $mustache->expects($this->never())
+            ->method('loadLambda');
+
+        $tpl = $mustache->loadTemplate('{{#wrap}}NAME{{/wrap}}');
+
+        $foo = new Mustache_Test_Functional_Foo;
+        $foo->wrap = array($foo, 'wrapWithEm');
+
+        $this->assertEquals('<em>NAME</em>', $tpl->render($foo));
+    }
+
+    public function testWithoutPassthroughOptimization()
+    {
+        $mustache = $this->getMock('Mustache_Engine', array('loadLambda'));
+        $mustache->expects($this->once())
+            ->method('loadLambda')
+            ->will($this->returnValue($mustache->loadTemplate('<em>{{ name }}</em>')));
+
+        $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));
+    }
 }
 
 class Mustache_Test_Functional_Foo

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/MustacheSpecTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/ObjectSectionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/HelperCollectionTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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) 2012 Justin Hileman
+ * (c) 2013 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) 2012 Justin Hileman
+ * (c) 2013 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/FilesystemLoaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/InlineLoaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/StringLoaderTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Logger/AbstractLoggerTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 Justin Hileman
  *
  * For the full copyright and license information, please view the LICENSE
  * file that was distributed with this source code.
@@ -49,9 +49,9 @@ class Mustache_Test_Logger_TestLogger extends Mustache_Logger_AbstractLogger
     /**
      * Logs with an arbitrary level.
      *
-     * @param mixed $level
+     * @param mixed  $level
      * @param string $message
-     * @param array $context
+     * @param array  $context
      */
     public function log($level, $message, array $context = array())
     {

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

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/ParserTest.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/TemplateTest.php

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

+ 1 - 1
test/bootstrap.php

@@ -3,7 +3,7 @@
 /*
  * This file is part of Mustache.php.
  *
- * (c) 2012 Justin Hileman
+ * (c) 2013 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/Bar.php

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

+ 6 - 0
test/fixtures/examples/nested_partials/NestedPartials.php

@@ -0,0 +1,6 @@
+<?php
+
+class NestedPartials
+{
+    public $val = 'FOURTH!';
+}

+ 3 - 0
test/fixtures/examples/nested_partials/nested_partials.mustache

@@ -0,0 +1,3 @@
+<first>
+  {{> second }}
+</first>

+ 7 - 0
test/fixtures/examples/nested_partials/nested_partials.txt

@@ -0,0 +1,7 @@
+<first>
+  <second>
+    <third>
+      FOURTH!
+    </third>
+  </second>
+</first>

+ 1 - 0
test/fixtures/examples/nested_partials/partials/fourth.mustache

@@ -0,0 +1 @@
+{{ val }}

+ 3 - 0
test/fixtures/examples/nested_partials/partials/second.mustache

@@ -0,0 +1,3 @@
+<second>
+  {{> third }}
+</second>

+ 3 - 0
test/fixtures/examples/nested_partials/partials/third.mustache

@@ -0,0 +1,3 @@
+<third>
+  {{> fourth }}
+</third>