diff --git a/LICENCE.txt b/LICENCE.txt
new file mode 100644
index 0000000..98282c6
--- /dev/null
+++ b/LICENCE.txt
@@ -0,0 +1,7 @@
+Copyright 2019 Blue Tomato GmbH
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..78dd647
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+TemplateEngineMustache
+====================
+ProcessWire module adding [Mustache](https://mustache.github.io/) templates to the TemplateEngineFactory.
+
+## Installation
+Install the module just like any other ProcessWire module. Check out the following guide: http://modules.processwire.com/install-uninstall/
+
+This module requires TemplateEngineFactory: https://github.com/wanze/TemplateEngineFactory
+
+After installing, don't forget to enable Mustache as engine in the TemplateEngineFactory module's settings.
+
+## Configuration (over TemplateEngineProcesswire Module Configuration)
+* **Path to templates** Path to folder where you want to store your Smarty template files.
+* **Template files suffix** The suffix of the template files, default is *mustache*.
+
+## Setting Helpers
+
+```php
+$view->setHelpers([
+ 'myHelperFunction' => function($text) {
+ return trim($text);
+ }
+]);
+
+```
+
+## Examples
+
+
+First expose data (in this case all story pages) to the mustache view being rendered next.
+```php
+// In file: /site/templates/stories.php
+
+$stories = $pages->find('template=blogstory');
+$view->set('stories', $stories);
+```
+
+Then use the passed in data (story pages) in your mustache template file.
+```html
+
+
+
Stories
+
+```
\ No newline at end of file
diff --git a/TemplateEngineMustache.module b/TemplateEngineMustache.module
new file mode 100644
index 0000000..4ea0153
--- /dev/null
+++ b/TemplateEngineMustache.module
@@ -0,0 +1,69 @@
+ 'Template Engine Mustache',
+ 'summary' => 'Mustache templates for the TemplateEngineFactory',
+ 'version' => 100,
+ 'author' => 'Blue Tomato',
+ 'href' => 'https://github.com/blue-tomato/TemplateEngineMustache',
+ 'singular' => false,
+ 'autoload' => false,
+ 'requires' => array('TemplateEngineFactory'),
+ );
+ }
+
+ public static function getDefaultConfig() {
+ /*
+ * Upcoming $config from parent's default includes configurations for
+ * - templates_path
+ * - global_template
+ * - template_files_suffix
+ */
+ $config = parent::getDefaultConfig();
+
+ return array_merge($config, array(
+ 'template_files_suffix' => 'mustache',
+ ));
+ }
+
+ public function getTemplatesPath() {
+ $path = ltrim($this->getConfig('templates_path'), '/');
+ return $this->wire('config')->paths->site . rtrim($path, '/') . '/';
+ }
+
+ protected $mustache;
+ protected $data;
+
+ public function initEngine() {
+ $config = array(
+ 'loader' => new Mustache_Loader_FilesystemLoader($this->getTemplatesPath()),
+ 'cache' => $this->wire('config')->paths->assets . '/cache/mustache',
+ );
+
+ $partailsPath = $this->getTemplatesPath().'/partials';
+ if(is_dir($partailsPath)) {
+ $config['partials_loader'] = new Mustache_Loader_FilesystemLoader($partailsPath);
+ }
+
+ $this->mustache = new Mustache_Engine($config);
+ $this->data = (object) [];
+ }
+
+ public function setHelpers($helpers = []) {
+ $this->mustache->setHelpers($helpers);
+ }
+
+ public function set($key, $value) {
+ $this->data->$key = $value;
+ }
+
+ public function render() {
+ return $this->mustache->render($this->getFilename(), $this->data);
+ }
+}
diff --git a/mustache/.gitignore b/mustache/.gitignore
new file mode 100755
index 0000000..3861f00
--- /dev/null
+++ b/mustache/.gitignore
@@ -0,0 +1,4 @@
+.php_cs.cache
+composer.lock
+mustache.php
+vendor
diff --git a/mustache/.gitmodules b/mustache/.gitmodules
new file mode 100755
index 0000000..042ea4d
--- /dev/null
+++ b/mustache/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "vendor/spec"]
+ path = vendor/spec
+ url = https://github.com/mustache/spec.git
+[submodule "vendor/yaml"]
+ path = vendor/yaml
+ url = https://github.com/fabpot/yaml.git
diff --git a/mustache/.php_cs b/mustache/.php_cs
new file mode 100755
index 0000000..d924712
--- /dev/null
+++ b/mustache/.php_cs
@@ -0,0 +1,26 @@
+level(Symfony\CS\FixerInterface::SYMFONY_LEVEL)
+ ->fixers(array(
+ '-concat_without_spaces',
+ '-pre_increment',
+ '-unalign_double_arrow',
+ '-unalign_equals',
+ 'align_double_arrow',
+ 'concat_with_spaces',
+ 'ordered_use',
+ 'strict',
+ ))
+ ->setUsingLinter(false);
+
+$finder = $config->getFinder()
+ ->in('bin')
+ ->in('src')
+ ->in('test');
+
+return $config;
diff --git a/mustache/.styleci.yml b/mustache/.styleci.yml
new file mode 100755
index 0000000..1aebcc3
--- /dev/null
+++ b/mustache/.styleci.yml
@@ -0,0 +1,13 @@
+preset: symfony
+
+enabled:
+ - align_double_arrow
+ - concat_with_spaces
+ - ordered_use
+ - strict
+
+disabled:
+ - concat_without_spaces
+ - pre_increment
+ - unalign_double_arrow
+ - unalign_equals
diff --git a/mustache/.travis.yml b/mustache/.travis.yml
new file mode 100755
index 0000000..99de817
--- /dev/null
+++ b/mustache/.travis.yml
@@ -0,0 +1,21 @@
+language: php
+
+sudo: false
+
+matrix:
+ include:
+ - php: 5.2
+ - php: 5.3
+ - php: 5.4
+ - php: 5.5
+ - php: 5.6
+ - php: 7.0
+ - php: 7.1
+ - php: hhvm
+ dist: trusty
+
+script:
+ - '[[ "$TRAVIS_PHP_VERSION" = 5.2* ]] && phpunit || vendor/bin/phpunit --verbose'
+
+install:
+ - '[[ "$TRAVIS_PHP_VERSION" = 5.2* ]] || composer install'
diff --git a/mustache/CONTRIBUTING.md b/mustache/CONTRIBUTING.md
new file mode 100755
index 0000000..c0b323d
--- /dev/null
+++ b/mustache/CONTRIBUTING.md
@@ -0,0 +1,35 @@
+# Contributions welcome!
+
+
+### Here's a quick guide:
+
+ 1. [Fork the repo on GitHub](https://github.com/bobthecow/mustache.php).
+
+ 2. Update submodules: `git submodule update --init`
+
+ 3. Run the test suite. We only take pull requests with passing tests, and it's great to know that you have a clean slate. Make sure you have PHPUnit 3.5+, then run `phpunit` from the project directory.
+
+ 4. Add tests for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, add a test!
+
+ 5. Make the tests pass.
+
+ 6. Push your fork to GitHub and submit a pull request against the `dev` branch.
+
+
+### You can do some things to increase the chance that your pull request is accepted the first time:
+
+ * Submit one pull request per fix or feature.
+ * To help with that, do all your work in a feature branch (e.g. `feature/my-alsome-feature`).
+ * Follow the conventions you see used in the project.
+ * Use `phpcs --standard=PSR2` to check your changes against the coding standard.
+ * Write tests that fail without your code, and pass with it.
+ * Don't bump version numbers. Those will be updated — per [semver](http://semver.org) — once your change is merged into `master`.
+ * Update any documentation: docblocks, README, examples, etc.
+ * ... Don't update the wiki until your change is merged and released, but make a note in your pull request so we don't forget.
+
+
+### Mustache.php follows the PSR-* coding standards:
+
+ * [PSR-0: Class and file naming conventions](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md)
+ * [PSR-1: Basic coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md)
+ * [PSR-2: Coding style guide](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)
diff --git a/mustache/LICENSE b/mustache/LICENSE
new file mode 100755
index 0000000..e0aecc9
--- /dev/null
+++ b/mustache/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2010-2015 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
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/mustache/README.md b/mustache/README.md
new file mode 100755
index 0000000..a596ace
--- /dev/null
+++ b/mustache/README.md
@@ -0,0 +1,72 @@
+Mustache.php
+============
+
+A [Mustache](http://mustache.github.com/) implementation in PHP.
+
+[![Package version](http://img.shields.io/packagist/v/mustache/mustache.svg?style=flat-square)](https://packagist.org/packages/mustache/mustache)
+[![Build status](http://img.shields.io/travis/bobthecow/mustache.php/dev.svg?style=flat-square)](http://travis-ci.org/bobthecow/mustache.php)
+[![StyleCI](https://styleci.io/repos/569670/shield)](https://styleci.io/repos/569670)
+[![Monthly downloads](http://img.shields.io/packagist/dm/mustache/mustache.svg?style=flat-square)](https://packagist.org/packages/mustache/mustache)
+
+
+Usage
+-----
+
+A quick example:
+
+```php
+render('Hello {{planet}}', array('planet' => 'World!')); // "Hello World!"
+```
+
+
+And a more in-depth example -- this is the canonical Mustache template:
+
+```html+jinja
+Hello {{name}}
+You have just won {{value}} dollars!
+{{#in_ca}}
+Well, {{taxed_value}} dollars, after taxes.
+{{/in_ca}}
+```
+
+
+Create a view "context" object -- which could also be an associative array, but those don't do functions quite as well:
+
+```php
+value - ($this->value * 0.4);
+ }
+
+ public $in_ca = true;
+}
+```
+
+
+And render it:
+
+```php
+render($template, $chris);
+```
+
+
+And That's Not All!
+-------------------
+
+Read [the Mustache.php documentation](https://github.com/bobthecow/mustache.php/wiki/Home) for more information.
+
+
+See Also
+--------
+
+ * [Readme for the Ruby Mustache implementation](http://github.com/defunkt/mustache/blob/master/README.md).
+ * [mustache(5)](http://mustache.github.com/mustache.5.html) man page.
diff --git a/mustache/bin/build_bootstrap.php b/mustache/bin/build_bootstrap.php
new file mode 100755
index 0000000..cc23b2b
--- /dev/null
+++ b/mustache/bin/build_bootstrap.php
@@ -0,0 +1,178 @@
+#!/usr/bin/env php
+
+ */
+class SymfonyClassCollectionLoader
+{
+ private static $loaded;
+
+ const HEADER = <<<'EOS'
+\s*$/'), '', file_get_contents($r->getFileName()));
+ }
+
+ $cache = $cacheDir . '/' . $name . $extension;
+ $header = sprintf(self::HEADER, strftime('%Y'));
+ self::writeCacheFile($cache, $header . substr(self::stripComments('=5.2.4"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "~3.7|~4.0|~5.0",
+ "friendsofphp/php-cs-fixer": "~1.11"
+ },
+ "autoload": {
+ "psr-0": { "Mustache": "src/" }
+ }
+}
diff --git a/mustache/phpunit.xml.dist b/mustache/phpunit.xml.dist
new file mode 100755
index 0000000..3c620b6
--- /dev/null
+++ b/mustache/phpunit.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+ ./test
+ ./test/Mustache/Test/FiveThree
+
+
+
+ ./test/Mustache/Test/FiveThree
+
+
+
+
+ ./src/Mustache
+
+
+
\ No newline at end of file
diff --git a/mustache/src/Mustache/Autoloader.php b/mustache/src/Mustache/Autoloader.php
new file mode 100755
index 0000000..e8ea3f4
--- /dev/null
+++ b/mustache/src/Mustache/Autoloader.php
@@ -0,0 +1,88 @@
+baseDir = $realDir;
+ } else {
+ $this->baseDir = $baseDir;
+ }
+ }
+
+ /**
+ * Register a new instance as an SPL autoloader.
+ *
+ * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
+ *
+ * @return Mustache_Autoloader Registered Autoloader instance
+ */
+ public static function register($baseDir = null)
+ {
+ $key = $baseDir ? $baseDir : 0;
+
+ if (!isset(self::$instances[$key])) {
+ self::$instances[$key] = new self($baseDir);
+ }
+
+ $loader = self::$instances[$key];
+ spl_autoload_register(array($loader, 'autoload'));
+
+ return $loader;
+ }
+
+ /**
+ * Autoload Mustache classes.
+ *
+ * @param string $class
+ */
+ public function autoload($class)
+ {
+ if ($class[0] === '\\') {
+ $class = substr($class, 1);
+ }
+
+ if (strpos($class, 'Mustache') !== 0) {
+ return;
+ }
+
+ $file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
+ if (is_file($file)) {
+ require $file;
+ }
+ }
+}
diff --git a/mustache/src/Mustache/Cache.php b/mustache/src/Mustache/Cache.php
new file mode 100755
index 0000000..3b5b3f1
--- /dev/null
+++ b/mustache/src/Mustache/Cache.php
@@ -0,0 +1,36 @@
+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 int $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);
+ }
+ }
+}
diff --git a/mustache/src/Mustache/Cache/FilesystemCache.php b/mustache/src/Mustache/Cache/FilesystemCache.php
new file mode 100755
index 0000000..3e742b7
--- /dev/null
+++ b/mustache/src/Mustache/Cache/FilesystemCache.php
@@ -0,0 +1,161 @@
+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 bool
+ */
+ 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
+ */
+ 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);
+ // @codeCoverageIgnoreStart
+ if (!is_dir($dirName)) {
+ throw new Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".', $dirName));
+ }
+ // @codeCoverageIgnoreEnd
+ }
+
+ return $dirName;
+ }
+
+ /**
+ * Write cache file.
+ *
+ * @throws Mustache_Exception_RuntimeException If unable to write file
+ *
+ * @param string $fileName
+ * @param string $value
+ */
+ 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;
+ }
+
+ // @codeCoverageIgnoreStart
+ $this->log(
+ Mustache_Logger::ERROR,
+ 'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
+ array('tempName' => $tempFile, 'fileName' => $fileName)
+ );
+ // @codeCoverageIgnoreEnd
+ }
+
+ // @codeCoverageIgnoreStart
+ throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
+ // @codeCoverageIgnoreEnd
+ }
+}
diff --git a/mustache/src/Mustache/Cache/NoopCache.php b/mustache/src/Mustache/Cache/NoopCache.php
new file mode 100755
index 0000000..ed9eec9
--- /dev/null
+++ b/mustache/src/Mustache/Cache/NoopCache.php
@@ -0,0 +1,47 @@
+log(
+ Mustache_Logger::WARNING,
+ 'Template cache disabled, evaluating "{className}" class at runtime',
+ array('className' => $key)
+ );
+ eval('?>' . $value);
+ }
+}
diff --git a/mustache/src/Mustache/Compiler.php b/mustache/src/Mustache/Compiler.php
new file mode 100755
index 0000000..610369e
--- /dev/null
+++ b/mustache/src/Mustache/Compiler.php
@@ -0,0 +1,692 @@
+pragmas = $this->defaultPragmas;
+ $this->sections = array();
+ $this->blocks = array();
+ $this->source = $source;
+ $this->indentNextLine = true;
+ $this->customEscape = $customEscape;
+ $this->entityFlags = $entityFlags;
+ $this->charset = $charset;
+ $this->strictCallables = $strictCallables;
+
+ return $this->writeCode($tree, $name);
+ }
+
+ /**
+ * Enable pragmas across all templates, regardless of the presence of pragma
+ * tags in the individual templates.
+ *
+ * @internal Users should set global pragmas in Mustache_Engine, not here :)
+ *
+ * @param string[] $pragmas
+ */
+ public function setPragmas(array $pragmas)
+ {
+ $this->pragmas = array();
+ foreach ($pragmas as $pragma) {
+ $this->pragmas[$pragma] = true;
+ }
+ $this->defaultPragmas = $this->pragmas;
+ }
+
+ /**
+ * Helper function for walking the Mustache token parse tree.
+ *
+ * @throws Mustache_Exception_SyntaxException upon encountering unknown token types
+ *
+ * @param array $tree Parse tree of Mustache tokens
+ * @param int $level (default: 0)
+ *
+ * @return string Generated PHP source code
+ */
+ private function walk(array $tree, $level = 0)
+ {
+ $code = '';
+ $level++;
+ foreach ($tree as $node) {
+ switch ($node[Mustache_Tokenizer::TYPE]) {
+ case Mustache_Tokenizer::T_PRAGMA:
+ $this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
+ break;
+
+ case Mustache_Tokenizer::T_SECTION:
+ $code .= $this->section(
+ $node[Mustache_Tokenizer::NODES],
+ $node[Mustache_Tokenizer::NAME],
+ isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
+ $node[Mustache_Tokenizer::INDEX],
+ $node[Mustache_Tokenizer::END],
+ $node[Mustache_Tokenizer::OTAG],
+ $node[Mustache_Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_INVERTED:
+ $code .= $this->invertedSection(
+ $node[Mustache_Tokenizer::NODES],
+ $node[Mustache_Tokenizer::NAME],
+ isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_PARTIAL:
+ $code .= $this->partial(
+ $node[Mustache_Tokenizer::NAME],
+ isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_PARENT:
+ $code .= $this->parent(
+ $node[Mustache_Tokenizer::NAME],
+ isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
+ $node[Mustache_Tokenizer::NODES],
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_BLOCK_ARG:
+ $code .= $this->blockArg(
+ $node[Mustache_Tokenizer::NODES],
+ $node[Mustache_Tokenizer::NAME],
+ $node[Mustache_Tokenizer::INDEX],
+ $node[Mustache_Tokenizer::END],
+ $node[Mustache_Tokenizer::OTAG],
+ $node[Mustache_Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_BLOCK_VAR:
+ $code .= $this->blockVar(
+ $node[Mustache_Tokenizer::NODES],
+ $node[Mustache_Tokenizer::NAME],
+ $node[Mustache_Tokenizer::INDEX],
+ $node[Mustache_Tokenizer::END],
+ $node[Mustache_Tokenizer::OTAG],
+ $node[Mustache_Tokenizer::CTAG],
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_COMMENT:
+ break;
+
+ case Mustache_Tokenizer::T_ESCAPED:
+ case Mustache_Tokenizer::T_UNESCAPED:
+ case Mustache_Tokenizer::T_UNESCAPED_2:
+ $code .= $this->variable(
+ $node[Mustache_Tokenizer::NAME],
+ isset($node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
+ $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
+ $level
+ );
+ break;
+
+ case Mustache_Tokenizer::T_TEXT:
+ $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
+ break;
+
+ default:
+ throw new Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s', $node[Mustache_Tokenizer::TYPE]), $node);
+ }
+ }
+
+ return $code;
+ }
+
+ const KLASS = 'lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
+ $buffer = \'\';
+ %s
+
+ return $buffer;
+ }
+ %s
+ %s
+ }';
+
+ const KLASS_NO_LAMBDAS = 'walk($tree);
+ $sections = implode("\n", $this->sections);
+ $blocks = implode("\n", $this->blocks);
+ $klass = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS : self::KLASS;
+
+ $callable = $this->strictCallables ? $this->prepare(self::STRICT_CALLABLE) : '';
+
+ return sprintf($this->prepare($klass, 0, false, true), $name, $callable, $code, $sections, $blocks);
+ }
+
+ const BLOCK_VAR = '
+ $blockFunction = $context->findInBlock(%s);
+ if (is_callable($blockFunction)) {
+ $buffer .= call_user_func($blockFunction, $context);
+ %s}
+ ';
+
+ const BLOCK_VAR_ELSE = '} else {%s';
+
+ /**
+ * Generate Mustache Template inheritance block variable PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function blockVar($nodes, $id, $start, $end, $otag, $ctag, $level)
+ {
+ $id = var_export($id, true);
+
+ $else = $this->walk($nodes, $level);
+ if ($else !== '') {
+ $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $else);
+ }
+
+ return sprintf($this->prepare(self::BLOCK_VAR, $level), $id, $else);
+ }
+
+ const BLOCK_ARG = '%s => array($this, \'block%s\'),';
+
+ /**
+ * Generate Mustache Template inheritance block argument PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function blockArg($nodes, $id, $start, $end, $otag, $ctag, $level)
+ {
+ $key = $this->block($nodes);
+ $keystr = var_export($key, true);
+ $id = var_export($id, true);
+
+ return sprintf($this->prepare(self::BLOCK_ARG, $level), $id, $key);
+ }
+
+ const BLOCK_FUNCTION = '
+ public function block%s($context)
+ {
+ $indent = $buffer = \'\';%s
+
+ return $buffer;
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inheritance block function PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ *
+ * @return string key of new block function
+ */
+ private function block($nodes)
+ {
+ $code = $this->walk($nodes, 0);
+ $key = ucfirst(md5($code));
+
+ if (!isset($this->blocks[$key])) {
+ $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
+ }
+
+ return $key;
+ }
+
+ const SECTION_CALL = '
+ // %s section
+ $value = $context->%s(%s);%s
+ $buffer .= $this->section%s($context, $indent, $value);
+ ';
+
+ const SECTION = '
+ private function section%s(Mustache_Context $context, $indent, $value)
+ {
+ $buffer = \'\';
+
+ if (%s) {
+ $source = %s;
+ $result = call_user_func($value, $source, %s);
+ 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) {
+ $context->push($value);
+ %s
+ $context->pop();
+ }
+ }
+
+ return $buffer;
+ }
+ ';
+
+ /**
+ * Generate Mustache Template section PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param string[] $filters Array of filters
+ * @param int $start Section start offset
+ * @param int $end Section end offset
+ * @param string $otag Current Mustache opening tag
+ * @param string $ctag Current Mustache closing tag
+ * @param int $level
+ *
+ * @return string Generated section PHP source code
+ */
+ private function section($nodes, $id, $filters, $start, $end, $otag, $ctag, $level)
+ {
+ $source = var_export(substr($this->source, $start, $end - $start), true);
+ $callable = $this->getCallable();
+
+ if ($otag !== '{{' || $ctag !== '}}') {
+ $delimTag = var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
+ $helper = sprintf('$this->lambdaHelper->withDelimiters(%s)', $delimTag);
+ $delims = ', ' . $delimTag;
+ } else {
+ $helper = '$this->lambdaHelper';
+ $delims = '';
+ }
+
+ $key = ucfirst(md5($delims . "\n" . $source));
+
+ if (!isset($this->sections[$key])) {
+ $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2));
+ }
+
+ $method = $this->getFindMethod($id);
+ $id = var_export($id, true);
+ $filters = $this->getFilters($filters, $level);
+
+ return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
+ }
+
+ const INVERTED_SECTION = '
+ // %s inverted section
+ $value = $context->%s(%s);%s
+ if (empty($value)) {
+ %s
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inverted section PHP source.
+ *
+ * @param array $nodes Array of child tokens
+ * @param string $id Section name
+ * @param string[] $filters Array of filters
+ * @param int $level
+ *
+ * @return string Generated inverted section PHP source code
+ */
+ private function invertedSection($nodes, $id, $filters, $level)
+ {
+ $method = $this->getFindMethod($id);
+ $id = var_export($id, true);
+ $filters = $this->getFilters($filters, $level);
+
+ return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $filters, $this->walk($nodes, $level));
+ }
+
+ const PARTIAL_INDENT = ', $indent . %s';
+ const PARTIAL = '
+ if ($partial = $this->mustache->loadPartial(%s)) {
+ $buffer .= $partial->renderInternal($context%s);
+ }
+ ';
+
+ /**
+ * Generate Mustache Template partial call PHP source.
+ *
+ * @param string $id Partial name
+ * @param string $indent Whitespace indent to apply to partial
+ * @param int $level
+ *
+ * @return string Generated partial call PHP source code
+ */
+ private function partial($id, $indent, $level)
+ {
+ if ($indent !== '') {
+ $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
+ } else {
+ $indentParam = '';
+ }
+
+ return sprintf(
+ $this->prepare(self::PARTIAL, $level),
+ var_export($id, true),
+ $indentParam
+ );
+ }
+
+ const PARENT = '
+ if ($parent = $this->mustache->loadPartial(%s)) {
+ $context->pushBlockContext(array(%s
+ ));
+ $buffer .= $parent->renderInternal($context, $indent);
+ $context->popBlockContext();
+ }
+ ';
+
+ const PARENT_NO_CONTEXT = '
+ if ($parent = $this->mustache->loadPartial(%s)) {
+ $buffer .= $parent->renderInternal($context, $indent);
+ }
+ ';
+
+ /**
+ * Generate Mustache Template inheritance parent call PHP source.
+ *
+ * @param string $id Parent tag name
+ * @param string $indent Whitespace indent to apply to parent
+ * @param array $children Child nodes
+ * @param int $level
+ *
+ * @return string Generated PHP source code
+ */
+ private function parent($id, $indent, array $children, $level)
+ {
+ $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
+
+ if (empty($realChildren)) {
+ return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), var_export($id, true));
+ }
+
+ return sprintf(
+ $this->prepare(self::PARENT, $level),
+ var_export($id, true),
+ $this->walk($realChildren, $level + 1)
+ );
+ }
+
+ /**
+ * Helper method for filtering out non-block-arg tokens.
+ *
+ * @param array $node
+ *
+ * @return bool True if $node is a block arg token
+ */
+ private static function onlyBlockArgs(array $node)
+ {
+ return $node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
+ }
+
+ const VARIABLE = '
+ $value = $this->resolveValue($context->%s(%s), $context);%s
+ $buffer .= %s%s;
+ ';
+
+ /**
+ * Generate Mustache Template variable interpolation PHP source.
+ *
+ * @param string $id Variable name
+ * @param string[] $filters Array of filters
+ * @param bool $escape Escape the variable value for output?
+ * @param int $level
+ *
+ * @return string Generated variable interpolation PHP source
+ */
+ private function variable($id, $filters, $escape, $level)
+ {
+ $method = $this->getFindMethod($id);
+ $id = ($method !== 'last') ? var_export($id, true) : '';
+ $filters = $this->getFilters($filters, $level);
+ $value = $escape ? $this->getEscape() : '$value';
+
+ return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $filters, $this->flushIndent(), $value);
+ }
+
+ const FILTER = '
+ $filter = $context->%s(%s);
+ if (!(%s)) {
+ throw new Mustache_Exception_UnknownFilterException(%s);
+ }
+ $value = call_user_func($filter, $value);%s
+ ';
+
+ /**
+ * Generate Mustache Template variable filtering PHP source.
+ *
+ * @param string[] $filters Array of filters
+ * @param int $level
+ *
+ * @return string Generated filter PHP source
+ */
+ private function getFilters(array $filters, $level)
+ {
+ if (empty($filters)) {
+ return '';
+ }
+
+ $name = array_shift($filters);
+ $method = $this->getFindMethod($name);
+ $filter = ($method !== 'last') ? var_export($name, true) : '';
+ $callable = $this->getCallable('$filter');
+ $msg = var_export($name, true);
+
+ return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $callable, $msg, $this->getFilters($filters, $level));
+ }
+
+ const LINE = '$buffer .= "\n";';
+ const TEXT = '$buffer .= %s%s;';
+
+ /**
+ * Generate Mustache Template output Buffer call PHP source.
+ *
+ * @param string $text
+ * @param int $level
+ *
+ * @return string Generated output Buffer call PHP source
+ */
+ private function text($text, $level)
+ {
+ $indentNextLine = (substr($text, -1) === "\n");
+ $code = sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
+ $this->indentNextLine = $indentNextLine;
+
+ return $code;
+ }
+
+ /**
+ * Prepare PHP source code snippet for output.
+ *
+ * @param string $text
+ * @param int $bonus Additional indent level (default: 0)
+ * @param bool $prependNewline Prepend a newline to the snippet? (default: true)
+ * @param bool $appendNewline Append a newline to the snippet? (default: false)
+ *
+ * @return string PHP source code snippet
+ */
+ private function prepare($text, $bonus = 0, $prependNewline = true, $appendNewline = false)
+ {
+ $text = ($prependNewline ? "\n" : '') . trim($text);
+ if ($prependNewline) {
+ $bonus++;
+ }
+ if ($appendNewline) {
+ $text .= "\n";
+ }
+
+ return preg_replace("/\n( {8})?/", "\n" . str_repeat(' ', $bonus * 4), $text);
+ }
+
+ const DEFAULT_ESCAPE = 'htmlspecialchars(%s, %s, %s)';
+ const CUSTOM_ESCAPE = 'call_user_func($this->mustache->getEscape(), %s)';
+
+ /**
+ * Get the current escaper.
+ *
+ * @param string $value (default: '$value')
+ *
+ * @return string Either a custom callback, or an inline call to `htmlspecialchars`
+ */
+ private function getEscape($value = '$value')
+ {
+ if ($this->customEscape) {
+ return sprintf(self::CUSTOM_ESCAPE, $value);
+ }
+
+ return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
+ }
+
+ /**
+ * Select the appropriate Context `find` method for a given $id.
+ *
+ * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`.
+ *
+ * @see Mustache_Context::find
+ * @see Mustache_Context::findDot
+ * @see Mustache_Context::last
+ *
+ * @param string $id Variable name
+ *
+ * @return string `find` method name
+ */
+ private function getFindMethod($id)
+ {
+ if ($id === '.') {
+ return 'last';
+ }
+
+ if (isset($this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) {
+ if (substr($id, 0, 1) === '.') {
+ return 'findAnchoredDot';
+ }
+ }
+
+ if (strpos($id, '.') === false) {
+ return 'find';
+ }
+
+ return 'findDot';
+ }
+
+ const IS_CALLABLE = '!is_string(%s) && is_callable(%s)';
+ const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';
+
+ /**
+ * Helper function to compile strict vs lax "is callable" logic.
+ *
+ * @param string $variable (default: '$value')
+ *
+ * @return string "is callable" logic
+ */
+ private function getCallable($variable = '$value')
+ {
+ $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;
+
+ return sprintf($tpl, $variable, $variable);
+ }
+
+ const LINE_INDENT = '$indent . ';
+
+ /**
+ * Get the current $indent prefix to write to the buffer.
+ *
+ * @return string "$indent . " or ""
+ */
+ private function flushIndent()
+ {
+ if (!$this->indentNextLine) {
+ return '';
+ }
+
+ $this->indentNextLine = false;
+
+ return self::LINE_INDENT;
+ }
+}
diff --git a/mustache/src/Mustache/Context.php b/mustache/src/Mustache/Context.php
new file mode 100755
index 0000000..69c02e0
--- /dev/null
+++ b/mustache/src/Mustache/Context.php
@@ -0,0 +1,242 @@
+stack = array($context);
+ }
+ }
+
+ /**
+ * Push a new Context frame onto the stack.
+ *
+ * @param mixed $value Object or array to use for context
+ */
+ public function push($value)
+ {
+ array_push($this->stack, $value);
+ }
+
+ /**
+ * Push a new Context frame onto the block context stack.
+ *
+ * @param mixed $value Object or array to use for block context
+ */
+ public function pushBlockContext($value)
+ {
+ array_push($this->blockStack, $value);
+ }
+
+ /**
+ * Pop the last Context frame from the stack.
+ *
+ * @return mixed Last Context frame (object or array)
+ */
+ public function pop()
+ {
+ return array_pop($this->stack);
+ }
+
+ /**
+ * Pop the last block Context frame from the stack.
+ *
+ * @return mixed Last block Context frame (object or array)
+ */
+ public function popBlockContext()
+ {
+ return array_pop($this->blockStack);
+ }
+
+ /**
+ * Get the last Context frame.
+ *
+ * @return mixed Last Context frame (object or array)
+ */
+ public function last()
+ {
+ return end($this->stack);
+ }
+
+ /**
+ * Find a variable in the Context stack.
+ *
+ * Starting with the last Context frame (the context of the innermost section), and working back to the top-level
+ * rendering context, look for a variable with the given name:
+ *
+ * * If the Context frame is an associative array which contains the key $id, returns the value of that element.
+ * * If the Context frame is an object, this will check first for a public method, then a public property named
+ * $id. Failing both of these, it will try `__isset` and `__get` magic methods.
+ * * If a value named $id is not found in any Context frame, returns an empty string.
+ *
+ * @param string $id Variable name
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function find($id)
+ {
+ return $this->findVariableInStack($id, $this->stack);
+ }
+
+ /**
+ * Find a 'dot notation' variable in the Context stack.
+ *
+ * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
+ * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
+ * result. For example, given the following context stack:
+ *
+ * $data = array(
+ * 'name' => 'Fred',
+ * 'child' => array(
+ * 'name' => 'Bob'
+ * ),
+ * );
+ *
+ * ... and the Mustache following template:
+ *
+ * {{ child.name }}
+ *
+ * ... the `name` value is only searched for within the `child` value of the global Context, not within parent
+ * Context frames.
+ *
+ * @param string $id Dotted variable selector
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findDot($id)
+ {
+ $chunks = explode('.', $id);
+ $first = array_shift($chunks);
+ $value = $this->findVariableInStack($first, $this->stack);
+
+ foreach ($chunks as $chunk) {
+ if ($value === '') {
+ return $value;
+ }
+
+ $value = $this->findVariableInStack($chunk, array($value));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Find an 'anchored dot notation' variable in the Context stack.
+ *
+ * This is the same as findDot(), except it looks in the top of the context
+ * stack for the first value, rather than searching the whole context stack
+ * and starting from there.
+ *
+ * @see Mustache_Context::findDot
+ *
+ * @throws Mustache_Exception_InvalidArgumentException if given an invalid anchored dot $id
+ *
+ * @param string $id Dotted variable selector
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findAnchoredDot($id)
+ {
+ $chunks = explode('.', $id);
+ $first = array_shift($chunks);
+ if ($first !== '') {
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s', $id));
+ }
+
+ $value = $this->last();
+
+ foreach ($chunks as $chunk) {
+ if ($value === '') {
+ return $value;
+ }
+
+ $value = $this->findVariableInStack($chunk, array($value));
+ }
+
+ return $value;
+ }
+
+ /**
+ * Find an argument in the block context stack.
+ *
+ * @param string $id
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ public function findInBlock($id)
+ {
+ foreach ($this->blockStack as $context) {
+ if (array_key_exists($id, $context)) {
+ return $context[$id];
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Helper function to find a variable in the Context stack.
+ *
+ * @see Mustache_Context::find
+ *
+ * @param string $id Variable name
+ * @param array $stack Context stack
+ *
+ * @return mixed Variable value, or '' if not found
+ */
+ private function findVariableInStack($id, array $stack)
+ {
+ for ($i = count($stack) - 1; $i >= 0; $i--) {
+ $frame = &$stack[$i];
+
+ switch (gettype($frame)) {
+ case 'object':
+ if (!($frame instanceof Closure)) {
+ // Note that is_callable() *will not work here*
+ // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
+ if (method_exists($frame, $id)) {
+ return $frame->$id();
+ }
+
+ if (isset($frame->$id)) {
+ return $frame->$id;
+ }
+
+ if ($frame instanceof ArrayAccess && isset($frame[$id])) {
+ return $frame[$id];
+ }
+ }
+ break;
+
+ case 'array':
+ if (array_key_exists($id, $frame)) {
+ return $frame[$id];
+ }
+ break;
+ }
+ }
+
+ return '';
+ }
+}
diff --git a/mustache/src/Mustache/Engine.php b/mustache/src/Mustache/Engine.php
new file mode 100755
index 0000000..9110977
--- /dev/null
+++ b/mustache/src/Mustache/Engine.php
@@ -0,0 +1,829 @@
+ true,
+ self::PRAGMA_BLOCKS => true,
+ self::PRAGMA_ANCHORED_DOT => true,
+ );
+
+ // Template cache
+ private $templates = array();
+
+ // Environment
+ private $templateClassPrefix = '__Mustache_';
+ private $cache;
+ private $lambdaCache;
+ private $cacheLambdaTemplates = false;
+ private $loader;
+ private $partialsLoader;
+ private $helpers;
+ private $escape;
+ private $entityFlags = ENT_COMPAT;
+ private $charset = 'UTF-8';
+ private $logger;
+ private $strictCallables = false;
+ private $pragmas = array();
+ private $delimiters;
+
+ // Services
+ private $tokenizer;
+ private $parser;
+ private $compiler;
+
+ /**
+ * Mustache class constructor.
+ *
+ * Passing an $options array allows overriding certain Mustache options during instantiation:
+ *
+ * $options = array(
+ * // The class prefix for compiled templates. Defaults to '__Mustache_'.
+ * 'template_class_prefix' => '__MyTemplates_',
+ *
+ * // 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,
+ *
+ * // Customize the tag delimiters used by this engine instance. Note that overriding here changes the
+ * // delimiters used to parse all templates and partials loaded by this instance. To override just for a
+ * // single template, use an inline "change delimiters" tag at the start of the template file:
+ * //
+ * // {{=<% %>=}}
+ * //
+ * 'delimiters' => '<% %>',
+ *
+ * // A Mustache template loader instance. Uses a StringLoader if not specified.
+ * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
+ *
+ * // A Mustache loader instance for partials.
+ * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
+ *
+ * // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
+ * // efficient or lazy as a Filesystem (or database) loader.
+ * 'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')),
+ *
+ * // 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) {
+ * // do something translatey here...
+ * }),
+ *
+ * // An 'escape' callback, responsible for escaping double-mustache variables.
+ * 'escape' => function ($value) {
+ * return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
+ * },
+ *
+ * // Type argument for `htmlspecialchars`. Defaults to ENT_COMPAT. You may prefer ENT_QUOTES.
+ * 'entity_flags' => ENT_QUOTES,
+ *
+ * // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
+ * 'charset' => 'ISO-8859-1',
+ *
+ * // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
+ * // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
+ * // available as well:
+ * 'logger' => new Mustache_Logger_StreamLogger('php://stderr'),
+ *
+ * // Only treat Closure instances and invokable classes as callable. If true, values like
+ * // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally
+ * // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
+ * // helps protect against arbitrary code execution when user input is passed directly into the template.
+ * // This currently defaults to false, but will default to true in v3.0.
+ * 'strict_callables' => true,
+ *
+ * // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
+ * // templates.
+ * 'pragmas' => [Mustache_Engine::PRAGMA_FILTERS],
+ * );
+ *
+ * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable
+ *
+ * @param array $options (default: array())
+ */
+ public function __construct(array $options = array())
+ {
+ if (isset($options['template_class_prefix'])) {
+ if ((string) $options['template_class_prefix'] === '') {
+ throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty');
+ }
+
+ $this->templateClassPrefix = $options['template_class_prefix'];
+ }
+
+ if (isset($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_lambda_templates'])) {
+ $this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
+ }
+
+ if (isset($options['loader'])) {
+ $this->setLoader($options['loader']);
+ }
+
+ if (isset($options['partials_loader'])) {
+ $this->setPartialsLoader($options['partials_loader']);
+ }
+
+ if (isset($options['partials'])) {
+ $this->setPartials($options['partials']);
+ }
+
+ if (isset($options['helpers'])) {
+ $this->setHelpers($options['helpers']);
+ }
+
+ if (isset($options['escape'])) {
+ if (!is_callable($options['escape'])) {
+ throw new Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable');
+ }
+
+ $this->escape = $options['escape'];
+ }
+
+ if (isset($options['entity_flags'])) {
+ $this->entityFlags = $options['entity_flags'];
+ }
+
+ if (isset($options['charset'])) {
+ $this->charset = $options['charset'];
+ }
+
+ if (isset($options['logger'])) {
+ $this->setLogger($options['logger']);
+ }
+
+ if (isset($options['strict_callables'])) {
+ $this->strictCallables = $options['strict_callables'];
+ }
+
+ if (isset($options['delimiters'])) {
+ $this->delimiters = $options['delimiters'];
+ }
+
+ if (isset($options['pragmas'])) {
+ foreach ($options['pragmas'] as $pragma) {
+ if (!isset(self::$knownPragmas[$pragma])) {
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".', $pragma));
+ }
+ $this->pragmas[$pragma] = true;
+ }
+ }
+ }
+
+ /**
+ * Shortcut 'render' invocation.
+ *
+ * Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
+ *
+ * @see Mustache_Engine::loadTemplate
+ * @see Mustache_Template::render
+ *
+ * @param string $template
+ * @param mixed $context (default: array())
+ *
+ * @return string Rendered template
+ */
+ public function render($template, $context = array())
+ {
+ return $this->loadTemplate($template)->render($context);
+ }
+
+ /**
+ * Get the current Mustache escape callback.
+ *
+ * @return callable|null
+ */
+ public function getEscape()
+ {
+ return $this->escape;
+ }
+
+ /**
+ * Get the current Mustache entitity type to escape.
+ *
+ * @return int
+ */
+ public function getEntityFlags()
+ {
+ return $this->entityFlags;
+ }
+
+ /**
+ * Get the current Mustache character set.
+ *
+ * @return string
+ */
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ /**
+ * Get the current globally enabled pragmas.
+ *
+ * @return array
+ */
+ public function getPragmas()
+ {
+ return array_keys($this->pragmas);
+ }
+
+ /**
+ * Set the Mustache template Loader instance.
+ *
+ * @param Mustache_Loader $loader
+ */
+ public function setLoader(Mustache_Loader $loader)
+ {
+ $this->loader = $loader;
+ }
+
+ /**
+ * Get the current Mustache template Loader instance.
+ *
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
+ * a StringLoader instance.
+ *
+ * @return Mustache_Loader
+ */
+ public function getLoader()
+ {
+ if (!isset($this->loader)) {
+ $this->loader = new Mustache_Loader_StringLoader();
+ }
+
+ return $this->loader;
+ }
+
+ /**
+ * Set the Mustache partials Loader instance.
+ *
+ * @param Mustache_Loader $partialsLoader
+ */
+ public function setPartialsLoader(Mustache_Loader $partialsLoader)
+ {
+ $this->partialsLoader = $partialsLoader;
+ }
+
+ /**
+ * Get the current Mustache partials Loader instance.
+ *
+ * If no Loader instance has been explicitly specified, this method will instantiate and return
+ * an ArrayLoader instance.
+ *
+ * @return Mustache_Loader
+ */
+ public function getPartialsLoader()
+ {
+ if (!isset($this->partialsLoader)) {
+ $this->partialsLoader = new Mustache_Loader_ArrayLoader();
+ }
+
+ return $this->partialsLoader;
+ }
+
+ /**
+ * Set partials for the current partials Loader instance.
+ *
+ * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable
+ *
+ * @param array $partials (default: array())
+ */
+ public function setPartials(array $partials = array())
+ {
+ if (!isset($this->partialsLoader)) {
+ $this->partialsLoader = new Mustache_Loader_ArrayLoader();
+ }
+
+ if (!$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
+ throw new Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
+ }
+
+ $this->partialsLoader->setTemplates($partials);
+ }
+
+ /**
+ * Set an array of Mustache helpers.
+ *
+ * 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.
+ *
+ * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable
+ *
+ * @param array|Traversable $helpers
+ */
+ public function setHelpers($helpers)
+ {
+ if (!is_array($helpers) && !$helpers instanceof Traversable) {
+ throw new Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers');
+ }
+
+ $this->getHelpers()->clear();
+
+ foreach ($helpers as $name => $helper) {
+ $this->addHelper($name, $helper);
+ }
+ }
+
+ /**
+ * Get the current set of Mustache helpers.
+ *
+ * @see Mustache_Engine::setHelpers
+ *
+ * @return Mustache_HelperCollection
+ */
+ public function getHelpers()
+ {
+ if (!isset($this->helpers)) {
+ $this->helpers = new Mustache_HelperCollection();
+ }
+
+ return $this->helpers;
+ }
+
+ /**
+ * Add a new Mustache helper.
+ *
+ * @see Mustache_Engine::setHelpers
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function addHelper($name, $helper)
+ {
+ $this->getHelpers()->add($name, $helper);
+ }
+
+ /**
+ * Get a Mustache helper by name.
+ *
+ * @see Mustache_Engine::setHelpers
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function getHelper($name)
+ {
+ return $this->getHelpers()->get($name);
+ }
+
+ /**
+ * Check whether this Mustache instance has a helper.
+ *
+ * @see Mustache_Engine::setHelpers
+ *
+ * @param string $name
+ *
+ * @return bool True if the helper is present
+ */
+ public function hasHelper($name)
+ {
+ return $this->getHelpers()->has($name);
+ }
+
+ /**
+ * Remove a helper by name.
+ *
+ * @see Mustache_Engine::setHelpers
+ *
+ * @param string $name
+ */
+ public function removeHelper($name)
+ {
+ $this->getHelpers()->remove($name);
+ }
+
+ /**
+ * Set the Mustache Logger instance.
+ *
+ * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface
+ *
+ * @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.');
+ }
+
+ if ($this->getCache()->getLogger() === null) {
+ $this->getCache()->setLogger($logger);
+ }
+
+ $this->logger = $logger;
+ }
+
+ /**
+ * Get the current Mustache Logger instance.
+ *
+ * @return Mustache_Logger|Psr\Log\LoggerInterface
+ */
+ public function getLogger()
+ {
+ return $this->logger;
+ }
+
+ /**
+ * Set the Mustache Tokenizer instance.
+ *
+ * @param Mustache_Tokenizer $tokenizer
+ */
+ public function setTokenizer(Mustache_Tokenizer $tokenizer)
+ {
+ $this->tokenizer = $tokenizer;
+ }
+
+ /**
+ * Get the current Mustache Tokenizer instance.
+ *
+ * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Mustache_Tokenizer
+ */
+ public function getTokenizer()
+ {
+ if (!isset($this->tokenizer)) {
+ $this->tokenizer = new Mustache_Tokenizer();
+ }
+
+ return $this->tokenizer;
+ }
+
+ /**
+ * Set the Mustache Parser instance.
+ *
+ * @param Mustache_Parser $parser
+ */
+ public function setParser(Mustache_Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ /**
+ * Get the current Mustache Parser instance.
+ *
+ * If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Mustache_Parser
+ */
+ public function getParser()
+ {
+ if (!isset($this->parser)) {
+ $this->parser = new Mustache_Parser();
+ }
+
+ return $this->parser;
+ }
+
+ /**
+ * Set the Mustache Compiler instance.
+ *
+ * @param Mustache_Compiler $compiler
+ */
+ public function setCompiler(Mustache_Compiler $compiler)
+ {
+ $this->compiler = $compiler;
+ }
+
+ /**
+ * Get the current Mustache Compiler instance.
+ *
+ * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
+ *
+ * @return Mustache_Compiler
+ */
+ public function getCompiler()
+ {
+ if (!isset($this->compiler)) {
+ $this->compiler = new Mustache_Compiler();
+ }
+
+ 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.
+ *
+ * This method must be updated any time options are added which make it so
+ * the same template could be parsed and compiled multiple different ways.
+ *
+ * @param string|Mustache_Source $source
+ *
+ * @return string Mustache Template class name
+ */
+ public function getTemplateClassName($source)
+ {
+ // For the most part, adding a new option here should do the trick.
+ //
+ // Pick a value here which is unique for each possible way the template
+ // could be compiled... but not necessarily unique per option value. See
+ // escape below, which only needs to differentiate between 'custom' and
+ // 'default' escapes.
+ //
+ // Keep this list in alphabetical order :)
+ $chunks = array(
+ 'charset' => $this->charset,
+ 'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}',
+ 'entityFlags' => $this->entityFlags,
+ 'escape' => isset($this->escape) ? 'custom' : 'default',
+ 'key' => ($source instanceof Mustache_Source) ? $source->getKey() : 'source',
+ 'pragmas' => $this->getPragmas(),
+ 'strictCallables' => $this->strictCallables,
+ 'version' => self::VERSION,
+ );
+
+ $key = json_encode($chunks);
+
+ // Template Source instances have already provided their own source key. For strings, just include the whole
+ // source string in the md5 hash.
+ if (!$source instanceof Mustache_Source) {
+ $key .= "\n" . $source;
+ }
+
+ return $this->templateClassPrefix . md5($key);
+ }
+
+ /**
+ * Load a Mustache Template by name.
+ *
+ * @param string $name
+ *
+ * @return Mustache_Template
+ */
+ public function loadTemplate($name)
+ {
+ return $this->loadSource($this->getLoader()->load($name));
+ }
+
+ /**
+ * Load a Mustache partial Template by name.
+ *
+ * This is a helper method used internally by Template instances for loading partial templates. You can most likely
+ * ignore it completely.
+ *
+ * @param string $name
+ *
+ * @return Mustache_Template
+ */
+ public function loadPartial($name)
+ {
+ try {
+ if (isset($this->partialsLoader)) {
+ $loader = $this->partialsLoader;
+ } elseif (isset($this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) {
+ $loader = $this->loader;
+ } else {
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+
+ return $this->loadSource($loader->load($name));
+ } catch (Mustache_Exception_UnknownTemplateException $e) {
+ // If the named partial cannot be found, log then return null.
+ $this->log(
+ Mustache_Logger::WARNING,
+ 'Partial not found: "{name}"',
+ array('name' => $e->getTemplateName())
+ );
+ }
+ }
+
+ /**
+ * Load a Mustache lambda Template by source.
+ *
+ * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
+ * likely ignore it completely.
+ *
+ * @param string $source
+ * @param string $delims (default: null)
+ *
+ * @return Mustache_Template
+ */
+ public function loadLambda($source, $delims = null)
+ {
+ if ($delims !== null) {
+ $source = $delims . "\n" . $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|Mustache_Source $source
+ * @param Mustache_Cache $cache (default: null)
+ *
+ * @return Mustache_Template
+ */
+ 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 (!$cache->load($className)) {
+ $compiled = $this->compile($source);
+ $cache->cache($className, $compiled);
+ }
+ }
+
+ $this->log(
+ Mustache_Logger::DEBUG,
+ 'Instantiating template: "{className}"',
+ array('className' => $className)
+ );
+
+ $this->templates[$className] = new $className($this);
+ }
+
+ return $this->templates[$className];
+ }
+
+ /**
+ * Helper method to tokenize a Mustache template.
+ *
+ * @see Mustache_Tokenizer::scan
+ *
+ * @param string $source
+ *
+ * @return array Tokens
+ */
+ private function tokenize($source)
+ {
+ return $this->getTokenizer()->scan($source, $this->delimiters);
+ }
+
+ /**
+ * Helper method to parse a Mustache template.
+ *
+ * @see Mustache_Parser::parse
+ *
+ * @param string $source
+ *
+ * @return array Token tree
+ */
+ private function parse($source)
+ {
+ $parser = $this->getParser();
+ $parser->setPragmas($this->getPragmas());
+
+ return $parser->parse($this->tokenize($source));
+ }
+
+ /**
+ * Helper method to compile a Mustache template.
+ *
+ * @see Mustache_Compiler::compile
+ *
+ * @param string|Mustache_Source $source
+ *
+ * @return string generated Mustache template class code
+ */
+ private function compile($source)
+ {
+ $name = $this->getTemplateClassName($source);
+
+ $this->log(
+ Mustache_Logger::INFO,
+ 'Compiling template to "{className}" class',
+ array('className' => $name)
+ );
+
+ if ($source instanceof Mustache_Source) {
+ $source = $source->getSource();
+ }
+ $tree = $this->parse($source);
+
+ $compiler = $this->getCompiler();
+ $compiler->setPragmas($this->getPragmas());
+
+ return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags);
+ }
+
+ /**
+ * Add a log record if logging is enabled.
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ */
+ private function log($level, $message, array $context = array())
+ {
+ if (isset($this->logger)) {
+ $this->logger->log($level, $message, $context);
+ }
+ }
+}
diff --git a/mustache/src/Mustache/Exception.php b/mustache/src/Mustache/Exception.php
new file mode 100755
index 0000000..d4001a9
--- /dev/null
+++ b/mustache/src/Mustache/Exception.php
@@ -0,0 +1,18 @@
+token = $token;
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ parent::__construct($msg, 0, $previous);
+ } else {
+ parent::__construct($msg); // @codeCoverageIgnore
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function getToken()
+ {
+ return $this->token;
+ }
+}
diff --git a/mustache/src/Mustache/Exception/UnknownFilterException.php b/mustache/src/Mustache/Exception/UnknownFilterException.php
new file mode 100755
index 0000000..0651c17
--- /dev/null
+++ b/mustache/src/Mustache/Exception/UnknownFilterException.php
@@ -0,0 +1,38 @@
+filterName = $filterName;
+ $message = sprintf('Unknown filter: %s', $filterName);
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ parent::__construct($message, 0, $previous);
+ } else {
+ parent::__construct($message); // @codeCoverageIgnore
+ }
+ }
+
+ public function getFilterName()
+ {
+ return $this->filterName;
+ }
+}
diff --git a/mustache/src/Mustache/Exception/UnknownHelperException.php b/mustache/src/Mustache/Exception/UnknownHelperException.php
new file mode 100755
index 0000000..193be78
--- /dev/null
+++ b/mustache/src/Mustache/Exception/UnknownHelperException.php
@@ -0,0 +1,38 @@
+helperName = $helperName;
+ $message = sprintf('Unknown helper: %s', $helperName);
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ parent::__construct($message, 0, $previous);
+ } else {
+ parent::__construct($message); // @codeCoverageIgnore
+ }
+ }
+
+ public function getHelperName()
+ {
+ return $this->helperName;
+ }
+}
diff --git a/mustache/src/Mustache/Exception/UnknownTemplateException.php b/mustache/src/Mustache/Exception/UnknownTemplateException.php
new file mode 100755
index 0000000..32a778a
--- /dev/null
+++ b/mustache/src/Mustache/Exception/UnknownTemplateException.php
@@ -0,0 +1,38 @@
+templateName = $templateName;
+ $message = sprintf('Unknown template: %s', $templateName);
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ parent::__construct($message, 0, $previous);
+ } else {
+ parent::__construct($message); // @codeCoverageIgnore
+ }
+ }
+
+ public function getTemplateName()
+ {
+ return $this->templateName;
+ }
+}
diff --git a/mustache/src/Mustache/HelperCollection.php b/mustache/src/Mustache/HelperCollection.php
new file mode 100755
index 0000000..5d8f73c
--- /dev/null
+++ b/mustache/src/Mustache/HelperCollection.php
@@ -0,0 +1,172 @@
+ $helper` pairs.
+ *
+ * @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
+ *
+ * @param array|Traversable $helpers (default: null)
+ */
+ public function __construct($helpers = null)
+ {
+ if ($helpers === null) {
+ return;
+ }
+
+ if (!is_array($helpers) && !$helpers instanceof Traversable) {
+ throw new Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers');
+ }
+
+ foreach ($helpers as $name => $helper) {
+ $this->add($name, $helper);
+ }
+ }
+
+ /**
+ * Magic mutator.
+ *
+ * @see Mustache_HelperCollection::add
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function __set($name, $helper)
+ {
+ $this->add($name, $helper);
+ }
+
+ /**
+ * Add a helper to this collection.
+ *
+ * @param string $name
+ * @param mixed $helper
+ */
+ public function add($name, $helper)
+ {
+ $this->helpers[$name] = $helper;
+ }
+
+ /**
+ * Magic accessor.
+ *
+ * @see Mustache_HelperCollection::get
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function __get($name)
+ {
+ return $this->get($name);
+ }
+
+ /**
+ * Get a helper by name.
+ *
+ * @throws Mustache_Exception_UnknownHelperException If helper does not exist
+ *
+ * @param string $name
+ *
+ * @return mixed Helper
+ */
+ public function get($name)
+ {
+ if (!$this->has($name)) {
+ throw new Mustache_Exception_UnknownHelperException($name);
+ }
+
+ return $this->helpers[$name];
+ }
+
+ /**
+ * Magic isset().
+ *
+ * @see Mustache_HelperCollection::has
+ *
+ * @param string $name
+ *
+ * @return bool True if helper is present
+ */
+ public function __isset($name)
+ {
+ return $this->has($name);
+ }
+
+ /**
+ * Check whether a given helper is present in the collection.
+ *
+ * @param string $name
+ *
+ * @return bool True if helper is present
+ */
+ public function has($name)
+ {
+ return array_key_exists($name, $this->helpers);
+ }
+
+ /**
+ * Magic unset().
+ *
+ * @see Mustache_HelperCollection::remove
+ *
+ * @param string $name
+ */
+ public function __unset($name)
+ {
+ $this->remove($name);
+ }
+
+ /**
+ * Check whether a given helper is present in the collection.
+ *
+ * @throws Mustache_Exception_UnknownHelperException if the requested helper is not present
+ *
+ * @param string $name
+ */
+ public function remove($name)
+ {
+ if (!$this->has($name)) {
+ throw new Mustache_Exception_UnknownHelperException($name);
+ }
+
+ unset($this->helpers[$name]);
+ }
+
+ /**
+ * Clear the helper collection.
+ *
+ * Removes all helpers from this collection
+ */
+ public function clear()
+ {
+ $this->helpers = array();
+ }
+
+ /**
+ * Check whether the helper collection is empty.
+ *
+ * @return bool True if the collection is empty
+ */
+ public function isEmpty()
+ {
+ return empty($this->helpers);
+ }
+}
diff --git a/mustache/src/Mustache/LambdaHelper.php b/mustache/src/Mustache/LambdaHelper.php
new file mode 100755
index 0000000..e93dbfa
--- /dev/null
+++ b/mustache/src/Mustache/LambdaHelper.php
@@ -0,0 +1,76 @@
+ =}}`. (default: null)
+ */
+ public function __construct(Mustache_Engine $mustache, Mustache_Context $context, $delims = null)
+ {
+ $this->mustache = $mustache;
+ $this->context = $context;
+ $this->delims = $delims;
+ }
+
+ /**
+ * Render a string as a Mustache template with the current rendering context.
+ *
+ * @param string $string
+ *
+ * @return string Rendered template
+ */
+ public function render($string)
+ {
+ return $this->mustache
+ ->loadLambda((string) $string, $this->delims)
+ ->renderInternal($this->context);
+ }
+
+ /**
+ * Render a string as a Mustache template with the current rendering context.
+ *
+ * @param string $string
+ *
+ * @return string Rendered template
+ */
+ public function __invoke($string)
+ {
+ return $this->render($string);
+ }
+
+ /**
+ * Get a Lambda Helper with custom delimiters.
+ *
+ * @param string $delims Custom delimiters, in the format `{{= <% %> =}}`
+ *
+ * @return Mustache_LambdaHelper
+ */
+ public function withDelimiters($delims)
+ {
+ return new self($this->mustache, $this->context, $delims);
+ }
+}
diff --git a/mustache/src/Mustache/Loader.php b/mustache/src/Mustache/Loader.php
new file mode 100755
index 0000000..23adba1
--- /dev/null
+++ b/mustache/src/Mustache/Loader.php
@@ -0,0 +1,27 @@
+ '{{ bar }}',
+ * 'baz' => 'Hey {{ qux }}!'
+ * );
+ *
+ * $tpl = $loader->load('foo'); // '{{ bar }}'
+ *
+ * The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials
+ * is set. It can also be used as a quick-and-dirty Template loader.
+ */
+class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader
+{
+ private $templates;
+
+ /**
+ * ArrayLoader constructor.
+ *
+ * @param array $templates Associative array of Template source (default: array())
+ */
+ public function __construct(array $templates = array())
+ {
+ $this->templates = $templates;
+ }
+
+ /**
+ * Load a Template.
+ *
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ if (!isset($this->templates[$name])) {
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Set an associative array of Template sources for this loader.
+ *
+ * @param array $templates
+ */
+ public function setTemplates(array $templates)
+ {
+ $this->templates = $templates;
+ }
+
+ /**
+ * Set a Template source by name.
+ *
+ * @param string $name
+ * @param string $template Mustache Template source
+ */
+ public function setTemplate($name, $template)
+ {
+ $this->templates[$name] = $template;
+ }
+}
diff --git a/mustache/src/Mustache/Loader/CascadingLoader.php b/mustache/src/Mustache/Loader/CascadingLoader.php
new file mode 100755
index 0000000..3fb6353
--- /dev/null
+++ b/mustache/src/Mustache/Loader/CascadingLoader.php
@@ -0,0 +1,69 @@
+loaders = array();
+ foreach ($loaders as $loader) {
+ $this->addLoader($loader);
+ }
+ }
+
+ /**
+ * Add a Loader instance.
+ *
+ * @param Mustache_Loader $loader
+ */
+ public function addLoader(Mustache_Loader $loader)
+ {
+ $this->loaders[] = $loader;
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ foreach ($this->loaders as $loader) {
+ try {
+ return $loader->load($name);
+ } catch (Mustache_Exception_UnknownTemplateException $e) {
+ // do nothing, check the next loader.
+ }
+ }
+
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+}
diff --git a/mustache/src/Mustache/Loader/FilesystemLoader.php b/mustache/src/Mustache/Loader/FilesystemLoader.php
new file mode 100755
index 0000000..e366df7
--- /dev/null
+++ b/mustache/src/Mustache/Loader/FilesystemLoader.php
@@ -0,0 +1,135 @@
+load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache');
+ *
+ * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
+ *
+ * $m = new Mustache(array(
+ * 'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
+ * 'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
+ * ));
+ */
+class Mustache_Loader_FilesystemLoader implements Mustache_Loader
+{
+ private $baseDir;
+ private $extension = '.mustache';
+ private $templates = array();
+
+ /**
+ * Mustache filesystem Loader constructor.
+ *
+ * Passing an $options array allows overriding certain Loader options during instantiation:
+ *
+ * $options = array(
+ * // The filename extension used for Mustache templates. Defaults to '.mustache'
+ * 'extension' => '.ms',
+ * );
+ *
+ * @throws Mustache_Exception_RuntimeException if $baseDir does not exist
+ *
+ * @param string $baseDir Base directory containing Mustache template files
+ * @param array $options Array of Loader options (default: array())
+ */
+ public function __construct($baseDir, array $options = array())
+ {
+ $this->baseDir = $baseDir;
+
+ if (strpos($this->baseDir, '://') === false) {
+ $this->baseDir = realpath($this->baseDir);
+ }
+
+ if ($this->shouldCheckPath() && !is_dir($this->baseDir)) {
+ throw new Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s', $baseDir));
+ }
+
+ if (array_key_exists('extension', $options)) {
+ if (empty($options['extension'])) {
+ $this->extension = '';
+ } else {
+ $this->extension = '.' . ltrim($options['extension'], '.');
+ }
+ }
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
+ * $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache";
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ if (!isset($this->templates[$name])) {
+ $this->templates[$name] = $this->loadFile($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Helper function for loading a Mustache file by name.
+ *
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ protected function loadFile($name)
+ {
+ $fileName = $this->getFileName($name);
+
+ if ($this->shouldCheckPath() && !file_exists($fileName)) {
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+
+ return file_get_contents($fileName);
+ }
+
+ /**
+ * Helper function for getting a Mustache template file name.
+ *
+ * @param string $name
+ *
+ * @return string Template file name
+ */
+ protected function getFileName($name)
+ {
+ $fileName = $this->baseDir . '/' . $name;
+ if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
+ $fileName .= $this->extension;
+ }
+
+ return $fileName;
+ }
+
+ /**
+ * Only check if baseDir is a directory and requested templates are files if
+ * baseDir is using the filesystem stream wrapper.
+ *
+ * @return bool Whether to check `is_dir` and `file_exists`
+ */
+ protected function shouldCheckPath()
+ {
+ return strpos($this->baseDir, '://') === false || strpos($this->baseDir, 'file://') === 0;
+ }
+}
diff --git a/mustache/src/Mustache/Loader/InlineLoader.php b/mustache/src/Mustache/Loader/InlineLoader.php
new file mode 100755
index 0000000..ae297fe
--- /dev/null
+++ b/mustache/src/Mustache/Loader/InlineLoader.php
@@ -0,0 +1,123 @@
+load('hello');
+ * $goodbye = $loader->load('goodbye');
+ *
+ * __halt_compiler();
+ *
+ * @@ hello
+ * Hello, {{ planet }}!
+ *
+ * @@ goodbye
+ * Goodbye, cruel {{ planet }}
+ *
+ * Templates are deliniated by lines containing only `@@ name`.
+ *
+ * The InlineLoader is well-suited to micro-frameworks such as Silex:
+ *
+ * $app->register(new MustacheServiceProvider, array(
+ * 'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
+ * ));
+ *
+ * $app->get('/{name}', function ($name) use ($app) {
+ * return $app['mustache']->render('hello', compact('name'));
+ * })
+ * ->value('name', 'world');
+ *
+ * // ...
+ *
+ * __halt_compiler();
+ *
+ * @@ hello
+ * Hello, {{ name }}!
+ */
+class Mustache_Loader_InlineLoader implements Mustache_Loader
+{
+ protected $fileName;
+ protected $offset;
+ protected $templates;
+
+ /**
+ * The InlineLoader requires a filename and offset to process templates.
+ *
+ * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually
+ * perfectly suited to the job:
+ *
+ * $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
+ *
+ * Note that this only works if the loader is instantiated inside the same
+ * file as the inline templates. If the templates are located in another
+ * file, it would be necessary to manually specify the filename and offset.
+ *
+ * @param string $fileName The file to parse for inline templates
+ * @param int $offset A string offset for the start of the templates.
+ * This usually coincides with the `__halt_compiler`
+ * call, and the `__COMPILER_HALT_OFFSET__`
+ */
+ public function __construct($fileName, $offset)
+ {
+ if (!is_file($fileName)) {
+ throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.');
+ }
+
+ if (!is_int($offset) || $offset < 0) {
+ throw new Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.');
+ }
+
+ $this->fileName = $fileName;
+ $this->offset = $offset;
+ }
+
+ /**
+ * Load a Template by name.
+ *
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
+ *
+ * @param string $name
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ $this->loadTemplates();
+
+ if (!array_key_exists($name, $this->templates)) {
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+
+ return $this->templates[$name];
+ }
+
+ /**
+ * Parse and load templates from the end of a source file.
+ */
+ protected function loadTemplates()
+ {
+ if ($this->templates === null) {
+ $this->templates = array();
+ $data = file_get_contents($this->fileName, false, null, $this->offset);
+ foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
+ if (trim($chunk)) {
+ list($name, $content) = explode("\n", $chunk, 2);
+ $this->templates[trim($name)] = trim($content);
+ }
+ }
+ }
+ }
+}
diff --git a/mustache/src/Mustache/Loader/MutableLoader.php b/mustache/src/Mustache/Loader/MutableLoader.php
new file mode 100755
index 0000000..57fe5be
--- /dev/null
+++ b/mustache/src/Mustache/Loader/MutableLoader.php
@@ -0,0 +1,31 @@
+ '.ms',
+ * 'stat_props' => array('size', 'mtime'),
+ * );
+ *
+ * Specifying 'stat_props' overrides the stat properties used to invalidate the template cache. By default, this
+ * uses 'mtime' and 'size', but this can be set to any of the properties supported by stat():
+ *
+ * http://php.net/manual/en/function.stat.php
+ *
+ * You can also disable filesystem stat entirely:
+ *
+ * $options = array('stat_props' => null);
+ *
+ * But with great power comes great responsibility. Namely, if you disable stat-based cache invalidation,
+ * YOU MUST CLEAR THE TEMPLATE CACHE YOURSELF when your templates change. Make it part of your build or deploy
+ * process so you don't forget!
+ *
+ * @throws Mustache_Exception_RuntimeException if $baseDir does not exist.
+ *
+ * @param string $baseDir Base directory containing Mustache template files.
+ * @param array $options Array of Loader options (default: array())
+ */
+ public function __construct($baseDir, array $options = array())
+ {
+ parent::__construct($baseDir, $options);
+
+ if (array_key_exists('stat_props', $options)) {
+ if (empty($options['stat_props'])) {
+ $this->statProps = array();
+ } else {
+ $this->statProps = $options['stat_props'];
+ }
+ } else {
+ $this->statProps = array('size', 'mtime');
+ }
+ }
+
+ /**
+ * Helper function for loading a Mustache file by name.
+ *
+ * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
+ *
+ * @param string $name
+ *
+ * @return Mustache_Source Mustache Template source
+ */
+ protected function loadFile($name)
+ {
+ $fileName = $this->getFileName($name);
+
+ if (!file_exists($fileName)) {
+ throw new Mustache_Exception_UnknownTemplateException($name);
+ }
+
+ return new Mustache_Source_FilesystemSource($fileName, $this->statProps);
+ }
+}
diff --git a/mustache/src/Mustache/Loader/StringLoader.php b/mustache/src/Mustache/Loader/StringLoader.php
new file mode 100755
index 0000000..7012c03
--- /dev/null
+++ b/mustache/src/Mustache/Loader/StringLoader.php
@@ -0,0 +1,39 @@
+load('{{ foo }}'); // '{{ foo }}'
+ *
+ * This is the default Template Loader instance used by Mustache:
+ *
+ * $m = new Mustache;
+ * $tpl = $m->loadTemplate('{{ foo }}');
+ * echo $tpl->render(array('foo' => 'bar')); // "bar"
+ */
+class Mustache_Loader_StringLoader implements Mustache_Loader
+{
+ /**
+ * Load a Template by source.
+ *
+ * @param string $name Mustache Template source
+ *
+ * @return string Mustache Template source
+ */
+ public function load($name)
+ {
+ return $name;
+ }
+}
diff --git a/mustache/src/Mustache/Logger.php b/mustache/src/Mustache/Logger.php
new file mode 100755
index 0000000..cb4037a
--- /dev/null
+++ b/mustache/src/Mustache/Logger.php
@@ -0,0 +1,126 @@
+log(Mustache_Logger::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(Mustache_Logger::DEBUG, $message, $context);
+ }
+}
diff --git a/mustache/src/Mustache/Logger/StreamLogger.php b/mustache/src/Mustache/Logger/StreamLogger.php
new file mode 100755
index 0000000..402a148
--- /dev/null
+++ b/mustache/src/Mustache/Logger/StreamLogger.php
@@ -0,0 +1,194 @@
+ 100,
+ self::INFO => 200,
+ self::NOTICE => 250,
+ self::WARNING => 300,
+ self::ERROR => 400,
+ self::CRITICAL => 500,
+ self::ALERT => 550,
+ self::EMERGENCY => 600,
+ );
+
+ protected $level;
+ protected $stream = null;
+ protected $url = null;
+
+ /**
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param resource|string $stream Resource instance or URL
+ * @param int $level The minimum logging level at which this handler will be triggered
+ */
+ public function __construct($stream, $level = Mustache_Logger::ERROR)
+ {
+ $this->setLevel($level);
+
+ if (is_resource($stream)) {
+ $this->stream = $stream;
+ } else {
+ $this->url = $stream;
+ }
+ }
+
+ /**
+ * Close stream resources.
+ */
+ public function __destruct()
+ {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ }
+
+ /**
+ * Set the minimum logging level.
+ *
+ * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown
+ *
+ * @param int $level The minimum logging level which will be written
+ */
+ public function setLevel($level)
+ {
+ if (!array_key_exists($level, self::$levels)) {
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
+ }
+
+ $this->level = $level;
+ }
+
+ /**
+ * Get the current minimum logging level.
+ *
+ * @return int
+ */
+ public function getLevel()
+ {
+ return $this->level;
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ */
+ public function log($level, $message, array $context = array())
+ {
+ if (!array_key_exists($level, self::$levels)) {
+ throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s', $level));
+ }
+
+ if (self::$levels[$level] >= self::$levels[$this->level]) {
+ $this->writeLog($level, $message, $context);
+ }
+ }
+
+ /**
+ * Write a record to the log.
+ *
+ * @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 int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ */
+ protected function writeLog($level, $message, array $context = array())
+ {
+ if (!is_resource($this->stream)) {
+ if (!isset($this->url)) {
+ throw new Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
+ }
+
+ $this->stream = fopen($this->url, 'a');
+ if (!is_resource($this->stream)) {
+ // @codeCoverageIgnoreStart
+ throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.', $this->url));
+ // @codeCoverageIgnoreEnd
+ }
+ }
+
+ fwrite($this->stream, self::formatLine($level, $message, $context));
+ }
+
+ /**
+ * Gets the name of the logging level.
+ *
+ * @throws InvalidArgumentException if the logging level is unknown
+ *
+ * @param int $level
+ *
+ * @return string
+ */
+ protected static function getLevelName($level)
+ {
+ return strtoupper($level);
+ }
+
+ /**
+ * Format a log line for output.
+ *
+ * @param int $level The logging level
+ * @param string $message The log message
+ * @param array $context The log context
+ *
+ * @return string
+ */
+ protected static function formatLine($level, $message, array $context = array())
+ {
+ return sprintf(
+ "%s: %s\n",
+ self::getLevelName($level),
+ self::interpolateMessage($message, $context)
+ );
+ }
+
+ /**
+ * Interpolate context values into the message placeholders.
+ *
+ * @param string $message
+ * @param array $context
+ *
+ * @return string
+ */
+ protected static function interpolateMessage($message, array $context = array())
+ {
+ if (strpos($message, '{') === false) {
+ return $message;
+ }
+
+ // build a replacement array with braces around the context keys
+ $replace = array();
+ foreach ($context as $key => $val) {
+ $replace['{' . $key . '}'] = $val;
+ }
+
+ // interpolate replacement values into the the message and return
+ return strtr($message, $replace);
+ }
+}
diff --git a/mustache/src/Mustache/Parser.php b/mustache/src/Mustache/Parser.php
new file mode 100755
index 0000000..c36a84a
--- /dev/null
+++ b/mustache/src/Mustache/Parser.php
@@ -0,0 +1,317 @@
+lineNum = -1;
+ $this->lineTokens = 0;
+ $this->pragmas = $this->defaultPragmas;
+
+ $this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
+ $this->pragmaBlocks = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);
+
+ return $this->buildTree($tokens);
+ }
+
+ /**
+ * Enable pragmas across all templates, regardless of the presence of pragma
+ * tags in the individual templates.
+ *
+ * @internal Users should set global pragmas in Mustache_Engine, not here :)
+ *
+ * @param string[] $pragmas
+ */
+ public function setPragmas(array $pragmas)
+ {
+ $this->pragmas = array();
+ foreach ($pragmas as $pragma) {
+ $this->enablePragma($pragma);
+ }
+ $this->defaultPragmas = $this->pragmas;
+ }
+
+ /**
+ * Helper method for recursively building a parse tree.
+ *
+ * @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)
+ *
+ * @return array Mustache Token parse tree
+ */
+ private function buildTree(array &$tokens, array $parent = null)
+ {
+ $nodes = array();
+
+ while (!empty($tokens)) {
+ $token = array_shift($tokens);
+
+ if ($token[Mustache_Tokenizer::LINE] === $this->lineNum) {
+ $this->lineTokens++;
+ } else {
+ $this->lineNum = $token[Mustache_Tokenizer::LINE];
+ $this->lineTokens = 0;
+ }
+
+ if ($this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
+ list($name, $filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
+ if (!empty($filters)) {
+ $token[Mustache_Tokenizer::NAME] = $name;
+ $token[Mustache_Tokenizer::FILTERS] = $filters;
+ }
+ }
+
+ switch ($token[Mustache_Tokenizer::TYPE]) {
+ case Mustache_Tokenizer::T_DELIM_CHANGE:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $this->clearStandaloneLines($nodes, $tokens);
+ break;
+
+ case Mustache_Tokenizer::T_SECTION:
+ case Mustache_Tokenizer::T_INVERTED:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $this->buildTree($tokens, $token);
+ break;
+
+ case Mustache_Tokenizer::T_END_SECTION:
+ if (!isset($parent)) {
+ $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 (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);
+ }
+
+ $this->clearStandaloneLines($nodes, $tokens);
+ $parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX];
+ $parent[Mustache_Tokenizer::NODES] = $nodes;
+
+ return $parent;
+
+ case Mustache_Tokenizer::T_PARTIAL:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ //store the whitespace prefix for laters!
+ if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
+ $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
+ }
+ $nodes[] = $token;
+ break;
+
+ case Mustache_Tokenizer::T_PARENT:
+ $this->checkIfTokenIsAllowedInParent($parent, $token);
+ $nodes[] = $this->buildTree($tokens, $token);
+ break;
+
+ case Mustache_Tokenizer::T_BLOCK_VAR:
+ if ($this->pragmaBlocks) {
+ // BLOCKS pragma is enabled, let's do this!
+ if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
+ $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
+ }
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $this->buildTree($tokens, $token);
+ } else {
+ // pretend this was just a normal "escaped" token...
+ $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
+ // TODO: figure out how to figure out if there was a space after this dollar:
+ $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME];
+ $nodes[] = $token;
+ }
+ break;
+
+ case Mustache_Tokenizer::T_PRAGMA:
+ $this->enablePragma($token[Mustache_Tokenizer::NAME]);
+ // no break
+
+ case Mustache_Tokenizer::T_COMMENT:
+ $this->clearStandaloneLines($nodes, $tokens);
+ $nodes[] = $token;
+ break;
+
+ default:
+ $nodes[] = $token;
+ break;
+ }
+ }
+
+ if (isset($parent)) {
+ $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);
+ }
+
+ return $nodes;
+ }
+
+ /**
+ * Clear standalone line tokens.
+ *
+ * Returns a whitespace token for indenting partials, if applicable.
+ *
+ * @param array $nodes Parsed nodes
+ * @param array $tokens Tokens to be parsed
+ *
+ * @return array|null Resulting indent token, if any
+ */
+ private function clearStandaloneLines(array &$nodes, array &$tokens)
+ {
+ if ($this->lineTokens > 1) {
+ // this is the third or later node on this line, so it can't be standalone
+ return;
+ }
+
+ $prev = null;
+ if ($this->lineTokens === 1) {
+ // this is the second node on this line, so it can't be standalone
+ // unless the previous node is whitespace.
+ if ($prev = end($nodes)) {
+ if (!$this->tokenIsWhitespace($prev)) {
+ return;
+ }
+ }
+ }
+
+ if ($next = reset($tokens)) {
+ // If we're on a new line, bail.
+ if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
+ return;
+ }
+
+ // If the next token isn't whitespace, bail.
+ if (!$this->tokenIsWhitespace($next)) {
+ return;
+ }
+
+ if (count($tokens) !== 1) {
+ // Unless it's the last token in the template, the next token
+ // must end in newline for this to be standalone.
+ if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
+ return;
+ }
+ }
+
+ // Discard the whitespace suffix
+ array_shift($tokens);
+ }
+
+ if ($prev) {
+ // Return the whitespace prefix, if any
+ return array_pop($nodes);
+ }
+ }
+
+ /**
+ * Check whether token is a whitespace token.
+ *
+ * True if token type is T_TEXT and value is all whitespace characters.
+ *
+ * @param array $token
+ *
+ * @return bool True if token is a whitespace token
+ */
+ private function tokenIsWhitespace(array $token)
+ {
+ if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
+ return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
+ }
+
+ return false;
+ }
+
+ /**
+ * Check whether a token is allowed inside a parent tag.
+ *
+ * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag
+ *
+ * @param array|null $parent
+ * @param array $token
+ */
+ private function checkIfTokenIsAllowedInParent($parent, array $token)
+ {
+ if ($parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
+ throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token);
+ }
+ }
+
+ /**
+ * Split a tag name into name and filters.
+ *
+ * @param string $name
+ *
+ * @return array [Tag name, Array of filters]
+ */
+ private function getNameAndFilters($name)
+ {
+ $filters = array_map('trim', explode('|', $name));
+ $name = array_shift($filters);
+
+ return array($name, $filters);
+ }
+
+ /**
+ * Enable a pragma.
+ *
+ * @param string $name
+ */
+ private function enablePragma($name)
+ {
+ $this->pragmas[$name] = true;
+
+ switch ($name) {
+ case Mustache_Engine::PRAGMA_BLOCKS:
+ $this->pragmaBlocks = true;
+ break;
+
+ case Mustache_Engine::PRAGMA_FILTERS:
+ $this->pragmaFilters = true;
+ break;
+ }
+ }
+}
diff --git a/mustache/src/Mustache/Source.php b/mustache/src/Mustache/Source.php
new file mode 100755
index 0000000..278c2cb
--- /dev/null
+++ b/mustache/src/Mustache/Source.php
@@ -0,0 +1,40 @@
+fileName = $fileName;
+ $this->statProps = $statProps;
+ }
+
+ /**
+ * Get the Source key (used to generate the compiled class name).
+ *
+ * @throws RuntimeException when a source file cannot be read
+ *
+ * @return string
+ */
+ public function getKey()
+ {
+ $chunks = array(
+ 'fileName' => $this->fileName,
+ );
+
+ if (!empty($this->statProps)) {
+ if (!isset($this->stat)) {
+ $this->stat = @stat($this->fileName);
+ }
+
+ if ($this->stat === false) {
+ throw new RuntimeException(sprintf('Failed to read source file "%s".', $this->fileName));
+ }
+
+ foreach ($this->statProps as $prop) {
+ $chunks[$prop] = $this->stat[$prop];
+ }
+ }
+
+ return json_encode($chunks);
+ }
+
+ /**
+ * Get the template Source.
+ *
+ * @return string
+ */
+ public function getSource()
+ {
+ return file_get_contents($this->fileName);
+ }
+}
diff --git a/mustache/src/Mustache/Template.php b/mustache/src/Mustache/Template.php
new file mode 100755
index 0000000..4de8239
--- /dev/null
+++ b/mustache/src/Mustache/Template.php
@@ -0,0 +1,180 @@
+mustache = $mustache;
+ }
+
+ /**
+ * Mustache Template instances can be treated as a function and rendered by simply calling them.
+ *
+ * $m = new Mustache_Engine;
+ * $tpl = $m->loadTemplate('Hello, {{ name }}!');
+ * echo $tpl(array('name' => 'World')); // "Hello, World!"
+ *
+ * @see Mustache_Template::render
+ *
+ * @param mixed $context Array or object rendering context (default: array())
+ *
+ * @return string Rendered template
+ */
+ public function __invoke($context = array())
+ {
+ return $this->render($context);
+ }
+
+ /**
+ * Render this template given the rendering context.
+ *
+ * @param mixed $context Array or object rendering context (default: array())
+ *
+ * @return string Rendered template
+ */
+ public function render($context = array())
+ {
+ return $this->renderInternal(
+ $this->prepareContextStack($context)
+ );
+ }
+
+ /**
+ * Internal rendering method implemented by Mustache Template concrete subclasses.
+ *
+ * This is where the magic happens :)
+ *
+ * NOTE: This method is not part of the Mustache.php public API.
+ *
+ * @param Mustache_Context $context
+ * @param string $indent (default: '')
+ *
+ * @return string Rendered template
+ */
+ abstract public function renderInternal(Mustache_Context $context, $indent = '');
+
+ /**
+ * Tests whether a value should be iterated over (e.g. in a section context).
+ *
+ * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
+ * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
+ * Java, Python, etc.
+ *
+ * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
+ * between between a list of things (numeric, normalized array) and a set of variables to be used as section context
+ * (associative array). In other words, this will be iterated over:
+ *
+ * $items = array(
+ * array('name' => 'foo'),
+ * array('name' => 'bar'),
+ * array('name' => 'baz'),
+ * );
+ *
+ * ... but this will be used as a section context block:
+ *
+ * $items = array(
+ * 1 => array('name' => 'foo'),
+ * 'banana' => array('name' => 'bar'),
+ * 42 => array('name' => 'baz'),
+ * );
+ *
+ * @param mixed $value
+ *
+ * @return bool True if the value is 'iterable'
+ */
+ protected function isIterable($value)
+ {
+ switch (gettype($value)) {
+ case 'object':
+ return $value instanceof Traversable;
+
+ case 'array':
+ $i = 0;
+ foreach ($value as $k => $v) {
+ if ($k !== $i++) {
+ return false;
+ }
+ }
+
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Helper method to prepare the Context stack.
+ *
+ * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
+ *
+ * @param mixed $context Optional first context frame (default: null)
+ *
+ * @return Mustache_Context
+ */
+ protected function prepareContextStack($context = null)
+ {
+ $stack = new Mustache_Context();
+
+ $helpers = $this->mustache->getHelpers();
+ if (!$helpers->isEmpty()) {
+ $stack->push($helpers);
+ }
+
+ if (!empty($context)) {
+ $stack->push($context);
+ }
+
+ return $stack;
+ }
+
+ /**
+ * Resolve a context value.
+ *
+ * Invoke the value if it is callable, otherwise return the value.
+ *
+ * @param mixed $value
+ * @param Mustache_Context $context
+ *
+ * @return string
+ */
+ protected function resolveValue($value, Mustache_Context $context)
+ {
+ if (($this->strictCallables ? is_object($value) : !is_string($value)) && is_callable($value)) {
+ return $this->mustache
+ ->loadLambda((string) call_user_func($value))
+ ->renderInternal($context);
+ }
+
+ return $value;
+ }
+}
diff --git a/mustache/src/Mustache/Tokenizer.php b/mustache/src/Mustache/Tokenizer.php
new file mode 100755
index 0000000..b1f3d73
--- /dev/null
+++ b/mustache/src/Mustache/Tokenizer.php
@@ -0,0 +1,327 @@
+';
+ const T_PARENT = '<';
+ const T_DELIM_CHANGE = '=';
+ const T_ESCAPED = '_v';
+ const T_UNESCAPED = '{';
+ const T_UNESCAPED_2 = '&';
+ const T_TEXT = '_t';
+ const T_PRAGMA = '%';
+ const T_BLOCK_VAR = '$';
+ const T_BLOCK_ARG = '$arg';
+
+ // Valid token types
+ private static $tagTypes = array(
+ self::T_SECTION => true,
+ self::T_INVERTED => true,
+ self::T_END_SECTION => true,
+ self::T_COMMENT => true,
+ self::T_PARTIAL => true,
+ self::T_PARENT => true,
+ self::T_DELIM_CHANGE => true,
+ self::T_ESCAPED => true,
+ self::T_UNESCAPED => true,
+ self::T_UNESCAPED_2 => true,
+ self::T_PRAGMA => true,
+ self::T_BLOCK_VAR => true,
+ );
+
+ // Token properties
+ const TYPE = 'type';
+ const NAME = 'name';
+ const OTAG = 'otag';
+ const CTAG = 'ctag';
+ const LINE = 'line';
+ const INDEX = 'index';
+ const END = 'end';
+ const INDENT = 'indent';
+ const NODES = 'nodes';
+ const VALUE = 'value';
+ const FILTERS = 'filters';
+
+ private $state;
+ private $tagType;
+ private $buffer;
+ private $tokens;
+ private $seenTag;
+ private $line;
+ private $otag;
+ private $ctag;
+ private $otagLen;
+ private $ctagLen;
+
+ /**
+ * Scan and tokenize template source.
+ *
+ * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered
+ *
+ * @param string $text Mustache template source to tokenize
+ * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null)
+ *
+ * @return array Set of Mustache tokens
+ */
+ 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.
+ //
+ // @codeCoverageIgnoreStart
+ $encoding = null;
+ if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
+ $encoding = mb_internal_encoding();
+ mb_internal_encoding('ASCII');
+ }
+ // @codeCoverageIgnoreEnd
+
+ $this->reset();
+
+ if ($delimiters = trim($delimiters)) {
+ $this->setDelimiters($delimiters);
+ }
+
+ $len = strlen($text);
+ for ($i = 0; $i < $len; $i++) {
+ switch ($this->state) {
+ case self::IN_TEXT:
+ if ($this->tagChange($this->otag, $this->otagLen, $text, $i)) {
+ $i--;
+ $this->flushBuffer();
+ $this->state = self::IN_TAG_TYPE;
+ } else {
+ $char = $text[$i];
+ $this->buffer .= $char;
+ if ($char === "\n") {
+ $this->flushBuffer();
+ $this->line++;
+ }
+ }
+ break;
+
+ case self::IN_TAG_TYPE:
+ $i += $this->otagLen - 1;
+ $char = $text[$i + 1];
+ if (isset(self::$tagTypes[$char])) {
+ $tag = $char;
+ $this->tagType = $tag;
+ } else {
+ $tag = null;
+ $this->tagType = self::T_ESCAPED;
+ }
+
+ if ($this->tagType === self::T_DELIM_CHANGE) {
+ $i = $this->changeDelimiters($text, $i);
+ $this->state = self::IN_TEXT;
+ } elseif ($this->tagType === self::T_PRAGMA) {
+ $i = $this->addPragma($text, $i);
+ $this->state = self::IN_TEXT;
+ } else {
+ if ($tag !== null) {
+ $i++;
+ }
+ $this->state = self::IN_TAG;
+ }
+ $this->seenTag = $i;
+ break;
+
+ default:
+ if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) {
+ $token = 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 - $this->otagLen : $i + $this->ctagLen,
+ );
+
+ if ($this->tagType === self::T_UNESCAPED) {
+ // Clean up `{{{ tripleStache }}}` style tokens.
+ if ($this->ctag === '}}') {
+ if (($i + 2 < $len) && $text[$i + 2] === '}') {
+ $i++;
+ } else {
+ $msg = sprintf(
+ 'Mismatched tag delimiters: %s on line %d',
+ $token[self::NAME],
+ $token[self::LINE]
+ );
+
+ throw new Mustache_Exception_SyntaxException($msg, $token);
+ }
+ } else {
+ $lastName = $token[self::NAME];
+ if (substr($lastName, -1) === '}') {
+ $token[self::NAME] = trim(substr($lastName, 0, -1));
+ } else {
+ $msg = sprintf(
+ 'Mismatched tag delimiters: %s on line %d',
+ $token[self::NAME],
+ $token[self::LINE]
+ );
+
+ throw new Mustache_Exception_SyntaxException($msg, $token);
+ }
+ }
+ }
+
+ $this->buffer = '';
+ $i += $this->ctagLen - 1;
+ $this->state = self::IN_TEXT;
+ $this->tokens[] = $token;
+ } else {
+ $this->buffer .= $text[$i];
+ }
+ break;
+ }
+ }
+
+ $this->flushBuffer();
+
+ // Restore the user's encoding...
+ // @codeCoverageIgnoreStart
+ if ($encoding) {
+ mb_internal_encoding($encoding);
+ }
+ // @codeCoverageIgnoreEnd
+
+ return $this->tokens;
+ }
+
+ /**
+ * Helper function to reset tokenizer internal state.
+ */
+ private function reset()
+ {
+ $this->state = self::IN_TEXT;
+ $this->tagType = null;
+ $this->buffer = '';
+ $this->tokens = array();
+ $this->seenTag = false;
+ $this->line = 0;
+ $this->otag = '{{';
+ $this->ctag = '}}';
+ $this->otagLen = 2;
+ $this->ctagLen = 2;
+ }
+
+ /**
+ * Flush the current buffer to a token.
+ */
+ private function flushBuffer()
+ {
+ if (strlen($this->buffer) > 0) {
+ $this->tokens[] = array(
+ self::TYPE => self::T_TEXT,
+ self::LINE => $this->line,
+ self::VALUE => $this->buffer,
+ );
+ $this->buffer = '';
+ }
+ }
+
+ /**
+ * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
+ *
+ * @param string $text Mustache template source
+ * @param int $index Current tokenizer index
+ *
+ * @return int New index value
+ */
+ private function changeDelimiters($text, $index)
+ {
+ $startIndex = strpos($text, '=', $index) + 1;
+ $close = '=' . $this->ctag;
+ $closeIndex = strpos($text, $close, $index);
+
+ $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
+
+ $this->tokens[] = array(
+ self::TYPE => self::T_DELIM_CHANGE,
+ self::LINE => $this->line,
+ );
+
+ 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.
+ *
+ * Pragmas are hoisted to the front of the template, so all pragma tokens
+ * will appear at the front of the token list.
+ *
+ * @param string $text
+ * @param int $index
+ *
+ * @return int New index value
+ */
+ private function addPragma($text, $index)
+ {
+ $end = strpos($text, $this->ctag, $index);
+ $pragma = trim(substr($text, $index + 2, $end - $index - 2));
+
+ // Pragmas are hoisted to the front of the template.
+ array_unshift($this->tokens, array(
+ self::TYPE => self::T_PRAGMA,
+ self::NAME => $pragma,
+ self::LINE => 0,
+ ));
+
+ return $end + $this->ctagLen - 1;
+ }
+
+ /**
+ * Test whether it's time to change tags.
+ *
+ * @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 bool True if this is a closing section tag
+ */
+ private function tagChange($tag, $tagLen, $text, $index)
+ {
+ return substr($text, $index, $tagLen) === $tag;
+ }
+}
diff --git a/mustache/test/Mustache/Test/AutoloaderTest.php b/mustache/test/Mustache/Test/AutoloaderTest.php
new file mode 100755
index 0000000..c72effe
--- /dev/null
+++ b/mustache/test/Mustache/Test/AutoloaderTest.php
@@ -0,0 +1,51 @@
+assertTrue(spl_autoload_unregister(array($loader, 'autoload')));
+ }
+
+ public function testAutoloader()
+ {
+ $loader = new Mustache_Autoloader(dirname(__FILE__) . '/../../fixtures/autoloader');
+
+ $this->assertNull($loader->autoload('NonMustacheClass'));
+ $this->assertFalse(class_exists('NonMustacheClass'));
+
+ $loader->autoload('Mustache_Foo');
+ $this->assertTrue(class_exists('Mustache_Foo'));
+
+ $loader->autoload('\Mustache_Bar');
+ $this->assertTrue(class_exists('Mustache_Bar'));
+ }
+
+ /**
+ * Test that the autoloader won't register multiple times.
+ */
+ public function testRegisterMultiple()
+ {
+ $numLoaders = count(spl_autoload_functions());
+
+ Mustache_Autoloader::register();
+ Mustache_Autoloader::register();
+
+ $expectedNumLoaders = $numLoaders + 1;
+
+ $this->assertCount($expectedNumLoaders, spl_autoload_functions());
+ }
+}
diff --git a/mustache/test/Mustache/Test/Cache/AbstractCacheTest.php b/mustache/test/Mustache/Test/Cache/AbstractCacheTest.php
new file mode 100755
index 0000000..ac47e64
--- /dev/null
+++ b/mustache/test/Mustache/Test/Cache/AbstractCacheTest.php
@@ -0,0 +1,44 @@
+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
+ }
+}
diff --git a/mustache/test/Mustache/Test/Cache/FilesystemCacheTest.php b/mustache/test/Mustache/Test/Cache/FilesystemCacheTest.php
new file mode 100755
index 0000000..6bcc483
--- /dev/null
+++ b/mustache/test/Mustache/Test/Cache/FilesystemCacheTest.php
@@ -0,0 +1,36 @@
+load($key);
+
+ $this->assertFalse($loaded);
+ }
+
+ public function testCachePut()
+ {
+ $key = 'some key';
+ $value = 'cache($key, $value);
+ $loaded = $cache->load($key);
+
+ $this->assertTrue($loaded);
+ }
+}
diff --git a/mustache/test/Mustache/Test/CompilerTest.php b/mustache/test/Mustache/Test/CompilerTest.php
new file mode 100755
index 0000000..6fd1986
--- /dev/null
+++ b/mustache/test/Mustache/Test/CompilerTest.php
@@ -0,0 +1,154 @@
+compile($source, $tree, $name, $customEscaper, $charset, false, $entityFlags);
+ foreach ($expected as $contains) {
+ $this->assertContains($contains, $compiled);
+ }
+ }
+
+ public function getCompileValues()
+ {
+ return array(
+ array('', array(), 'Banana', false, ENT_COMPAT, 'ISO-8859-1', array(
+ "\nclass Banana extends Mustache_Template",
+ 'return $buffer;',
+ )),
+
+ array('', array($this->createTextToken('TEXT')), 'Monkey', false, ENT_COMPAT, 'UTF-8', array(
+ "\nclass Monkey extends Mustache_Template",
+ '$buffer .= $indent . \'TEXT\';',
+ 'return $buffer;',
+ )),
+
+ array(
+ '',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ ),
+ 'Monkey',
+ true,
+ ENT_COMPAT,
+ 'ISO-8859-1',
+ array(
+ "\nclass Monkey extends Mustache_Template",
+ '$value = $this->resolveValue($context->find(\'name\'), $context);',
+ '$buffer .= $indent . call_user_func($this->mustache->getEscape(), $value);',
+ 'return $buffer;',
+ ),
+ ),
+
+ array(
+ '',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ ),
+ 'Monkey',
+ false,
+ ENT_COMPAT,
+ 'ISO-8859-1',
+ array(
+ "\nclass Monkey extends Mustache_Template",
+ '$value = $this->resolveValue($context->find(\'name\'), $context);',
+ '$buffer .= $indent . htmlspecialchars($value, ' . ENT_COMPAT . ', \'ISO-8859-1\');',
+ 'return $buffer;',
+ ),
+ ),
+
+ array(
+ '',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ ),
+ 'Monkey',
+ false,
+ ENT_QUOTES,
+ 'ISO-8859-1',
+ array(
+ "\nclass Monkey extends Mustache_Template",
+ '$value = $this->resolveValue($context->find(\'name\'), $context);',
+ '$buffer .= $indent . htmlspecialchars($value, ' . ENT_QUOTES . ', \'ISO-8859-1\');',
+ 'return $buffer;',
+ ),
+ ),
+
+ array(
+ '',
+ array(
+ $this->createTextToken("foo\n"),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => '.',
+ ),
+ $this->createTextToken("'bar'"),
+ ),
+ 'Monkey',
+ false,
+ ENT_COMPAT,
+ 'UTF-8',
+ array(
+ "\nclass Monkey extends Mustache_Template",
+ "\$buffer .= \$indent . 'foo\n';",
+ '$value = $this->resolveValue($context->find(\'name\'), $context);',
+ '$buffer .= htmlspecialchars($value, ' . ENT_COMPAT . ', \'UTF-8\');',
+ '$value = $this->resolveValue($context->last(), $context);',
+ '$buffer .= \'\\\'bar\\\'\';',
+ 'return $buffer;',
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @expectedException Mustache_Exception_SyntaxException
+ */
+ public function testCompilerThrowsSyntaxException()
+ {
+ $compiler = new Mustache_Compiler();
+ $compiler->compile('', array(array(Mustache_Tokenizer::TYPE => 'invalid')), 'SomeClass');
+ }
+
+ /**
+ * @param string $value
+ */
+ private function createTextToken($value)
+ {
+ return array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::VALUE => $value,
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/ContextTest.php b/mustache/test/Mustache/Test/ContextTest.php
new file mode 100755
index 0000000..447ea16
--- /dev/null
+++ b/mustache/test/Mustache/Test/ContextTest.php
@@ -0,0 +1,282 @@
+assertSame('', $one->find('foo'));
+ $this->assertSame('', $one->find('bar'));
+
+ $two = new Mustache_Context(array(
+ 'foo' => 'FOO',
+ 'bar' => '',
+ ));
+ $this->assertEquals('FOO', $two->find('foo'));
+ $this->assertEquals('', $two->find('bar'));
+
+ $obj = new StdClass();
+ $obj->name = 'NAME';
+ $three = new Mustache_Context($obj);
+ $this->assertSame($obj, $three->last());
+ $this->assertEquals('NAME', $three->find('name'));
+ }
+
+ public function testPushPopAndLast()
+ {
+ $context = new Mustache_Context();
+ $this->assertFalse($context->last());
+
+ $dummy = new Mustache_Test_TestDummy();
+ $context->push($dummy);
+ $this->assertSame($dummy, $context->last());
+ $this->assertSame($dummy, $context->pop());
+ $this->assertFalse($context->last());
+
+ $obj = new StdClass();
+ $context->push($dummy);
+ $this->assertSame($dummy, $context->last());
+ $context->push($obj);
+ $this->assertSame($obj, $context->last());
+ $this->assertSame($obj, $context->pop());
+ $this->assertSame($dummy, $context->pop());
+ $this->assertFalse($context->last());
+ }
+
+ public function testFind()
+ {
+ $context = new Mustache_Context();
+
+ $dummy = new Mustache_Test_TestDummy();
+
+ $obj = new StdClass();
+ $obj->name = 'obj';
+
+ $arr = array(
+ 'a' => array('b' => array('c' => 'see')),
+ 'b' => 'bee',
+ );
+
+ $string = 'some arbitrary string';
+
+ $context->push($dummy);
+ $this->assertEquals('dummy', $context->find('name'));
+
+ $context->push($obj);
+ $this->assertEquals('obj', $context->find('name'));
+
+ $context->pop();
+ $this->assertEquals('dummy', $context->find('name'));
+
+ $dummy->name = 'dummyer';
+ $this->assertEquals('dummyer', $context->find('name'));
+
+ $context->push($arr);
+ $this->assertEquals('bee', $context->find('b'));
+ $this->assertEquals('see', $context->findDot('a.b.c'));
+
+ $dummy->name = 'dummy';
+
+ $context->push($string);
+ $this->assertSame($string, $context->last());
+ $this->assertEquals('dummy', $context->find('name'));
+ $this->assertEquals('see', $context->findDot('a.b.c'));
+ $this->assertEquals('', $context->find('foo'));
+ $this->assertEquals('', $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');
+ }
+
+ public function testAnchoredDotNotation()
+ {
+ $context = new Mustache_Context();
+
+ $a = array(
+ 'name' => 'a',
+ 'number' => 1,
+ );
+
+ $b = array(
+ 'number' => 2,
+ 'child' => array(
+ 'name' => 'baby bee',
+ ),
+ );
+
+ $c = array(
+ 'name' => 'cee',
+ );
+
+ $context->push($a);
+ $this->assertEquals('a', $context->find('name'));
+ $this->assertEquals('', $context->findDot('.name'));
+ $this->assertEquals('a', $context->findAnchoredDot('.name'));
+ $this->assertEquals(1, $context->find('number'));
+ $this->assertEquals('', $context->findDot('.number'));
+ $this->assertEquals(1, $context->findAnchoredDot('.number'));
+
+ $context->push($b);
+ $this->assertEquals('a', $context->find('name'));
+ $this->assertEquals(2, $context->find('number'));
+ $this->assertEquals('', $context->findDot('.name'));
+ $this->assertEquals('', $context->findDot('.number'));
+ $this->assertEquals('', $context->findAnchoredDot('.name'));
+ $this->assertEquals(2, $context->findAnchoredDot('.number'));
+ $this->assertEquals('baby bee', $context->findDot('child.name'));
+ $this->assertEquals('', $context->findDot('.child.name'));
+ $this->assertEquals('baby bee', $context->findAnchoredDot('.child.name'));
+
+ $context->push($c);
+ $this->assertEquals('cee', $context->find('name'));
+ $this->assertEquals('', $context->findDot('.name'));
+ $this->assertEquals('cee', $context->findAnchoredDot('.name'));
+ $this->assertEquals(2, $context->find('number'));
+ $this->assertEquals('', $context->findDot('.number'));
+ $this->assertEquals('', $context->findAnchoredDot('.number'));
+ $this->assertEquals('baby bee', $context->findDot('child.name'));
+ $this->assertEquals('', $context->findDot('.child.name'));
+ $this->assertEquals('', $context->findAnchoredDot('.child.name'));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testAnchoredDotNotationThrowsExceptions()
+ {
+ $context = new Mustache_Context();
+ $context->push(array('a' => 1));
+ $context->findAnchoredDot('a');
+ }
+}
+
+class Mustache_Test_TestDummy
+{
+ public $name = 'dummy';
+
+ public function __invoke()
+ {
+ // nothing
+ }
+
+ public static function foo()
+ {
+ return '';
+ }
+
+ public function bar()
+ {
+ return '';
+ }
+}
+
+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
+ }
+}
diff --git a/mustache/test/Mustache/Test/EngineTest.php b/mustache/test/Mustache/Test/EngineTest.php
new file mode 100755
index 0000000..3e4d193
--- /dev/null
+++ b/mustache/test/Mustache/Test/EngineTest.php
@@ -0,0 +1,397 @@
+ '__whot__',
+ 'cache' => self::$tempDir,
+ 'cache_file_mode' => 777,
+ 'logger' => $logger,
+ 'loader' => $loader,
+ 'partials_loader' => $partialsLoader,
+ 'partials' => array(
+ 'foo' => '{{ foo }}',
+ ),
+ 'helpers' => array(
+ 'foo' => array($this, 'getFoo'),
+ 'bar' => 'BAR',
+ ),
+ 'escape' => 'strtoupper',
+ 'entity_flags' => ENT_QUOTES,
+ 'charset' => 'ISO-8859-1',
+ 'pragmas' => array(Mustache_Engine::PRAGMA_FILTERS),
+ ));
+
+ $this->assertSame($logger, $mustache->getLogger());
+ $this->assertSame($loader, $mustache->getLoader());
+ $this->assertSame($partialsLoader, $mustache->getPartialsLoader());
+ $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());
+ $this->assertEquals(array(Mustache_Engine::PRAGMA_FILTERS), $mustache->getPragmas());
+ }
+
+ public static function getFoo()
+ {
+ return 'foo';
+ }
+
+ public function testRender()
+ {
+ $source = '{{ foo }}';
+ $data = array('bar' => 'baz');
+ $output = 'TEH OUTPUT';
+
+ $template = $this->getMockBuilder('Mustache_Template')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $mustache = new MustacheStub();
+ $mustache->template = $template;
+
+ $template->expects($this->once())
+ ->method('render')
+ ->with($data)
+ ->will($this->returnValue($output));
+
+ $this->assertEquals($output, $mustache->render($source, $data));
+ $this->assertEquals($source, $mustache->source);
+ }
+
+ public function testSettingServices()
+ {
+ $logger = new Mustache_Logger_StreamLogger(tmpfile());
+ $loader = new Mustache_Loader_StringLoader();
+ $tokenizer = new Mustache_Tokenizer();
+ $parser = new Mustache_Parser();
+ $compiler = new Mustache_Compiler();
+ $mustache = new Mustache_Engine();
+ $cache = new Mustache_Cache_FilesystemCache(self::$tempDir);
+
+ $this->assertNotSame($logger, $mustache->getLogger());
+ $mustache->setLogger($logger);
+ $this->assertSame($logger, $mustache->getLogger());
+
+ $this->assertNotSame($loader, $mustache->getLoader());
+ $mustache->setLoader($loader);
+ $this->assertSame($loader, $mustache->getLoader());
+
+ $this->assertNotSame($loader, $mustache->getPartialsLoader());
+ $mustache->setPartialsLoader($loader);
+ $this->assertSame($loader, $mustache->getPartialsLoader());
+
+ $this->assertNotSame($tokenizer, $mustache->getTokenizer());
+ $mustache->setTokenizer($tokenizer);
+ $this->assertSame($tokenizer, $mustache->getTokenizer());
+
+ $this->assertNotSame($parser, $mustache->getParser());
+ $mustache->setParser($parser);
+ $this->assertSame($parser, $mustache->getParser());
+
+ $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());
+ }
+
+ /**
+ * @group functional
+ */
+ public function testCache()
+ {
+ $mustache = new Mustache_Engine(array(
+ 'template_class_prefix' => '__whot__',
+ 'cache' => self::$tempDir,
+ ));
+
+ $source = '{{ foo }}';
+ $template = $mustache->loadTemplate($source);
+ $className = $mustache->getTemplateClassName($source);
+
+ $this->assertInstanceOf($className, $template);
+ }
+
+ 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());
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testEmptyTemplatePrefixThrowsException()
+ {
+ new Mustache_Engine(array(
+ 'template_class_prefix' => '',
+ ));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ * @dataProvider getBadEscapers
+ */
+ public function testNonCallableEscapeThrowsException($escape)
+ {
+ new Mustache_Engine(array('escape' => $escape));
+ }
+
+ public function getBadEscapers()
+ {
+ return array(
+ array('nothing'),
+ array('foo', 'bar'),
+ );
+ }
+
+ /**
+ * @expectedException Mustache_Exception_RuntimeException
+ */
+ public function testImmutablePartialsLoadersThrowException()
+ {
+ $mustache = new Mustache_Engine(array(
+ 'partials_loader' => new Mustache_Loader_StringLoader(),
+ ));
+
+ $mustache->setPartials(array('foo' => '{{ foo }}'));
+ }
+
+ public function testMissingPartialsTreatedAsEmptyString()
+ {
+ $mustache = new Mustache_Engine(array(
+ 'partials_loader' => new Mustache_Loader_ArrayLoader(array(
+ 'foo' => 'FOO',
+ 'baz' => 'BAZ',
+ )),
+ ));
+
+ $this->assertEquals('FOOBAZ', $mustache->render('{{>foo}}{{>bar}}{{>baz}}', array()));
+ }
+
+ public function testHelpers()
+ {
+ $foo = array($this, 'getFoo');
+ $bar = 'BAR';
+ $mustache = new Mustache_Engine(array('helpers' => array(
+ 'foo' => $foo,
+ 'bar' => $bar,
+ )));
+
+ $helpers = $mustache->getHelpers();
+ $this->assertTrue($mustache->hasHelper('foo'));
+ $this->assertTrue($mustache->hasHelper('bar'));
+ $this->assertTrue($helpers->has('foo'));
+ $this->assertTrue($helpers->has('bar'));
+ $this->assertSame($foo, $mustache->getHelper('foo'));
+ $this->assertSame($bar, $mustache->getHelper('bar'));
+
+ $mustache->removeHelper('bar');
+ $this->assertFalse($mustache->hasHelper('bar'));
+ $mustache->addHelper('bar', $bar);
+ $this->assertSame($bar, $mustache->getHelper('bar'));
+
+ $baz = array($this, 'wrapWithUnderscores');
+ $this->assertFalse($mustache->hasHelper('baz'));
+ $this->assertFalse($helpers->has('baz'));
+
+ $mustache->addHelper('baz', $baz);
+ $this->assertTrue($mustache->hasHelper('baz'));
+ $this->assertTrue($helpers->has('baz'));
+
+ // ... and a functional test
+ $tpl = $mustache->loadTemplate('{{foo}} - {{bar}} - {{#baz}}qux{{/baz}}');
+ $this->assertEquals('foo - BAR - __qux__', $tpl->render());
+ $this->assertEquals('foo - BAR - __qux__', $tpl->render(array('qux' => "won't mess things up")));
+ }
+
+ public static function wrapWithUnderscores($text)
+ {
+ return '__' . $text . '__';
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testSetHelpersThrowsExceptions()
+ {
+ $mustache = new Mustache_Engine();
+ $mustache->setHelpers('monkeymonkeymonkey');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testSetLoggerThrowsExceptions()
+ {
+ $mustache = new Mustache_Engine();
+ $mustache->setLogger(new StdClass());
+ }
+
+ public function testLoadPartialCascading()
+ {
+ $loader = new Mustache_Loader_ArrayLoader(array(
+ 'foo' => 'FOO',
+ ));
+
+ $mustache = new Mustache_Engine(array('loader' => $loader));
+
+ $tpl = $mustache->loadTemplate('foo');
+
+ $this->assertSame($tpl, $mustache->loadPartial('foo'));
+
+ $mustache->setPartials(array(
+ 'foo' => 'f00',
+ ));
+
+ // setting partials overrides the default template loading fallback.
+ $this->assertNotSame($tpl, $mustache->loadPartial('foo'));
+
+ // but it didn't overwrite the original template loader templates.
+ $this->assertSame($tpl, $mustache->loadTemplate('foo'));
+ }
+
+ public function testPartialLoadFailLogging()
+ {
+ $name = tempnam(sys_get_temp_dir(), 'mustache-test');
+ $mustache = new Mustache_Engine(array(
+ 'logger' => new Mustache_Logger_StreamLogger($name, Mustache_Logger::WARNING),
+ 'partials' => array(
+ 'foo' => 'FOO',
+ 'bar' => 'BAR',
+ ),
+ ));
+
+ $result = $mustache->render('{{> foo }}{{> bar }}{{> baz }}', array());
+ $this->assertEquals('FOOBAR', $result);
+
+ $this->assertContains('WARNING: Partial not found: "baz"', file_get_contents($name));
+ }
+
+ public function testCacheWarningLogging()
+ {
+ 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()
+ {
+ list($name, $mustache) = $this->getLoggedMustache();
+ $mustache->render('{{ foo }}{{> bar }}', array('foo' => 'FOO'));
+ $this->assertEmpty(file_get_contents($name));
+ }
+
+ public function testVerboseLoggingIsVerbose()
+ {
+ 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('WARNING: Partial not found: "bar"', $log);
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testUnknownPragmaThrowsException()
+ {
+ new Mustache_Engine(array(
+ 'pragmas' => array('UNKNOWN'),
+ ));
+ }
+
+ public function testCompileFromMustacheSourceInstance()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../fixtures/templates');
+ $mustache = new Mustache_Engine(array(
+ 'loader' => new Mustache_Loader_ProductionFilesystemLoader($baseDir),
+ ));
+ $this->assertEquals('one contents', $mustache->render('one'));
+ }
+
+ private function getLoggedMustache($level = Mustache_Logger::ERROR)
+ {
+ $name = tempnam(sys_get_temp_dir(), 'mustache-test');
+ $mustache = new Mustache_Engine(array(
+ 'logger' => new Mustache_Logger_StreamLogger($name, $level),
+ ));
+
+ return array($name, $mustache);
+ }
+
+ public function testCustomDelimiters()
+ {
+ $mustache = new Mustache_Engine(array(
+ 'delimiters' => '[[ ]]',
+ 'partials' => array(
+ 'one' => '[[> two ]]',
+ 'two' => '[[ a ]]',
+ ),
+ ));
+
+ $tpl = $mustache->loadTemplate('[[# a ]][[ b ]][[/a ]]');
+ $this->assertEquals('c', $tpl->render(array('a' => true, 'b' => 'c')));
+
+ $tpl = $mustache->loadTemplate('[[> one ]]');
+ $this->assertEquals('b', $tpl->render(array('a' => 'b')));
+ }
+}
+
+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();
+ }
+}
diff --git a/mustache/test/Mustache/Test/Exception/SyntaxExceptionTest.php b/mustache/test/Mustache/Test/Exception/SyntaxExceptionTest.php
new file mode 100755
index 0000000..074dafc
--- /dev/null
+++ b/mustache/test/Mustache/Test/Exception/SyntaxExceptionTest.php
@@ -0,0 +1,39 @@
+ 'this'));
+ $this->assertTrue($e instanceof LogicException);
+ $this->assertTrue($e instanceof Mustache_Exception);
+ }
+
+ public function testGetToken()
+ {
+ $token = array(Mustache_Tokenizer::TYPE => 'whatever');
+ $e = new Mustache_Exception_SyntaxException('ignore this', $token);
+ $this->assertEquals($token, $e->getToken());
+ }
+
+ public function testPrevious()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $this->markTestSkipped('Exception chaining requires at least PHP 5.3');
+ }
+
+ $previous = new Exception();
+ $e = new Mustache_Exception_SyntaxException('foo', array(), $previous);
+
+ $this->assertSame($previous, $e->getPrevious());
+ }
+}
diff --git a/mustache/test/Mustache/Test/Exception/UnknownFilterExceptionTest.php b/mustache/test/Mustache/Test/Exception/UnknownFilterExceptionTest.php
new file mode 100755
index 0000000..6dbe25e
--- /dev/null
+++ b/mustache/test/Mustache/Test/Exception/UnknownFilterExceptionTest.php
@@ -0,0 +1,44 @@
+assertTrue($e instanceof UnexpectedValueException);
+ $this->assertTrue($e instanceof Mustache_Exception);
+ }
+
+ public function testMessage()
+ {
+ $e = new Mustache_Exception_UnknownFilterException('sausage');
+ $this->assertEquals('Unknown filter: sausage', $e->getMessage());
+ }
+
+ public function testGetFilterName()
+ {
+ $e = new Mustache_Exception_UnknownFilterException('eggs');
+ $this->assertEquals('eggs', $e->getFilterName());
+ }
+
+ public function testPrevious()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $this->markTestSkipped('Exception chaining requires at least PHP 5.3');
+ }
+
+ $previous = new Exception();
+ $e = new Mustache_Exception_UnknownFilterException('foo', $previous);
+
+ $this->assertSame($previous, $e->getPrevious());
+ }
+}
diff --git a/mustache/test/Mustache/Test/Exception/UnknownHelperExceptionTest.php b/mustache/test/Mustache/Test/Exception/UnknownHelperExceptionTest.php
new file mode 100755
index 0000000..5b1ccbb
--- /dev/null
+++ b/mustache/test/Mustache/Test/Exception/UnknownHelperExceptionTest.php
@@ -0,0 +1,43 @@
+assertTrue($e instanceof InvalidArgumentException);
+ $this->assertTrue($e instanceof Mustache_Exception);
+ }
+
+ public function testMessage()
+ {
+ $e = new Mustache_Exception_UnknownHelperException('beta');
+ $this->assertEquals('Unknown helper: beta', $e->getMessage());
+ }
+
+ public function testGetHelperName()
+ {
+ $e = new Mustache_Exception_UnknownHelperException('gamma');
+ $this->assertEquals('gamma', $e->getHelperName());
+ }
+
+ public function testPrevious()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $this->markTestSkipped('Exception chaining requires at least PHP 5.3');
+ }
+
+ $previous = new Exception();
+ $e = new Mustache_Exception_UnknownHelperException('foo', $previous);
+ $this->assertSame($previous, $e->getPrevious());
+ }
+}
diff --git a/mustache/test/Mustache/Test/Exception/UnknownTemplateExceptionTest.php b/mustache/test/Mustache/Test/Exception/UnknownTemplateExceptionTest.php
new file mode 100755
index 0000000..b993bfa
--- /dev/null
+++ b/mustache/test/Mustache/Test/Exception/UnknownTemplateExceptionTest.php
@@ -0,0 +1,43 @@
+assertTrue($e instanceof InvalidArgumentException);
+ $this->assertTrue($e instanceof Mustache_Exception);
+ }
+
+ public function testMessage()
+ {
+ $e = new Mustache_Exception_UnknownTemplateException('luigi');
+ $this->assertEquals('Unknown template: luigi', $e->getMessage());
+ }
+
+ public function testGetTemplateName()
+ {
+ $e = new Mustache_Exception_UnknownTemplateException('yoshi');
+ $this->assertEquals('yoshi', $e->getTemplateName());
+ }
+
+ public function testPrevious()
+ {
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
+ $this->markTestSkipped('Exception chaining requires at least PHP 5.3');
+ }
+
+ $previous = new Exception();
+ $e = new Mustache_Exception_UnknownTemplateException('foo', $previous);
+ $this->assertSame($previous, $e->getPrevious());
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php
new file mode 100755
index 0000000..cc575a9
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/ClosureQuirksTest.php
@@ -0,0 +1,32 @@
+mustache = new Mustache_Engine();
+ }
+
+ public function testClosuresDontLikeItWhenYouTouchTheirProperties()
+ {
+ $tpl = $this->mustache->loadTemplate('{{ foo.bar }}');
+ $this->assertEquals('', $tpl->render(array('foo' => function () {
+ return 'FOO';
+ })));
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/EngineTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/EngineTest.php
new file mode 100755
index 0000000..406473e
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/EngineTest.php
@@ -0,0 +1,50 @@
+ $pragmas,
+ 'helpers' => $helpers,
+ ));
+
+ $this->assertEquals($expect, $mustache->render($tpl, $data));
+ }
+
+ public function pragmaData()
+ {
+ $helpers = array(
+ 'longdate' => function (\DateTime $value) {
+ return $value->format('Y-m-d h:m:s');
+ },
+ );
+
+ $data = array(
+ 'date' => new DateTime('1/1/2000', new DateTimeZone('UTC')),
+ );
+
+ $tpl = '{{ date | longdate }}';
+
+ return array(
+ array(array(Mustache_Engine::PRAGMA_FILTERS), $helpers, $data, $tpl, '2000-01-01 12:01:00'),
+ array(array(), $helpers, $data, $tpl, ''),
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/FiltersTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/FiltersTest.php
new file mode 100755
index 0000000..16dec60
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/FiltersTest.php
@@ -0,0 +1,187 @@
+mustache = new Mustache_Engine();
+ }
+
+ /**
+ * @dataProvider singleFilterData
+ */
+ public function testSingleFilter($tpl, $helpers, $data, $expect)
+ {
+ $this->mustache->setHelpers($helpers);
+ $this->assertEquals($expect, $this->mustache->render($tpl, $data));
+ }
+
+ 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);
+ },
+ );
+
+ return array(
+ array(
+ '{{% FILTERS }}{{ date | longdate }}',
+ $helpers,
+ (object) array('date' => new DateTime('1/1/2000', new DateTimeZone('UTC'))),
+ '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) {
+ return $value->format('Y-m-d h:m:s');
+ });
+
+ $this->mustache->addHelper('withbrackets', function ($value) {
+ return sprintf('[[%s]]', $value);
+ });
+
+ $foo = new \StdClass();
+ $foo->date = new DateTime('1/1/2000', new DateTimeZone('UTC'));
+
+ $this->assertEquals('[[2000-01-01 12:01:00]]', $tpl->render($foo));
+ }
+
+ 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(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) {
+ 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 brokenPipeData
+ */
+ public function testThrowsExceptionForBrokenPipes($tpl, $data)
+ {
+ $this->mustache->render($tpl, $data);
+ }
+
+ public function brokenPipeData()
+ {
+ return array(
+ 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';
+ })),
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php
new file mode 100755
index 0000000..eb05150
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/HigherOrderSectionsTest.php
@@ -0,0 +1,77 @@
+mustache = new Mustache_Engine();
+ }
+
+ public function testAnonymousFunctionSectionCallback()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#wrapper}}{{name}}{{/wrapper}}');
+
+ $foo = new Mustache_Test_FiveThree_Functional_Foo();
+ $foo->name = 'Mario';
+ $foo->wrapper = function ($text) {
+ return sprintf('%s
', $text);
+ };
+
+ $this->assertEquals(sprintf('%s
', $foo->name), $tpl->render($foo));
+ }
+
+ public function testSectionCallback()
+ {
+ $one = $this->mustache->loadTemplate('{{name}}');
+ $two = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
+
+ $foo = new Mustache_Test_FiveThree_Functional_Foo();
+ $foo->name = 'Luigi';
+
+ $this->assertEquals($foo->name, $one->render($foo));
+ $this->assertEquals(sprintf('%s ', $foo->name), $two->render($foo));
+ }
+
+ public function testViewArrayAnonymousSectionCallback()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
+
+ $data = array(
+ 'name' => 'Bob',
+ 'wrap' => function ($text) {
+ return sprintf('[[%s]]', $text);
+ },
+ );
+
+ $this->assertEquals(sprintf('[[%s]]', $data['name']), $tpl->render($data));
+ }
+}
+
+class Mustache_Test_FiveThree_Functional_Foo
+{
+ public $name = 'Justin';
+ public $lorem = 'Lorem ipsum dolor sit amet,';
+ public $wrap;
+
+ public function __construct()
+ {
+ $this->wrap = function ($text) {
+ return sprintf('%s ', $text);
+ };
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php
new file mode 100755
index 0000000..6fc5d40
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/LambdaHelperTest.php
@@ -0,0 +1,67 @@
+mustache = new Mustache_Engine();
+ }
+
+ public function testSectionLambdaHelper()
+ {
+ $one = $this->mustache->loadTemplate('{{name}}');
+ $two = $this->mustache->loadTemplate('{{#lambda}}{{name}}{{/lambda}}');
+
+ $foo = new StdClass();
+ $foo->name = 'Mario';
+ $foo->lambda = function ($text, $mustache) {
+ return strtoupper($mustache->render($text));
+ };
+
+ $this->assertEquals('Mario', $one->render($foo));
+ $this->assertEquals('MARIO', $two->render($foo));
+ }
+
+ public function testSectionLambdaHelperRespectsDelimiterChanges()
+ {
+ $tpl = $this->mustache->loadTemplate("{{=<% %>=}}\n<%# bang %><% value %><%/ bang %>");
+
+ $data = new StdClass();
+ $data->value = 'hello world';
+ $data->bang = function ($text, $mustache) {
+ return $mustache->render($text) . '!';
+ };
+
+ $this->assertEquals('hello world!', $tpl->render($data));
+ }
+
+ public function testLambdaHelperIsInvokable()
+ {
+ $one = $this->mustache->loadTemplate('{{name}}');
+ $two = $this->mustache->loadTemplate('{{#lambda}}{{name}}{{/lambda}}');
+
+ $foo = new StdClass();
+ $foo->name = 'Mario';
+ $foo->lambda = function ($text, $render) {
+ return strtoupper($render($text));
+ };
+
+ $this->assertEquals('Mario', $one->render($foo));
+ $this->assertEquals('MARIO', $two->render($foo));
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php
new file mode 100755
index 0000000..af3f982
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/MustacheSpecTest.php
@@ -0,0 +1,68 @@
+markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"');
+ }
+ }
+
+ /**
+ * @group lambdas
+ * @dataProvider loadLambdasSpec
+ */
+ public function testLambdasSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template($this->prepareLambdasSpec($data)), $desc);
+ }
+
+ public function loadLambdasSpec()
+ {
+ return $this->loadSpec('~lambdas');
+ }
+
+ /**
+ * Extract and lambdafy any 'lambda' values found in the $data array.
+ */
+ private function prepareLambdasSpec($data)
+ {
+ foreach ($data as $key => $val) {
+ if ($key === 'lambda') {
+ if (!isset($val['php'])) {
+ $this->markTestSkipped(sprintf('PHP lambda test not implemented for this test.'));
+ }
+
+ $func = $val['php'];
+ $data[$key] = function ($text = null) use ($func) {
+ return eval($func);
+ };
+ } elseif (is_array($val)) {
+ $data[$key] = $this->prepareLambdasSpec($val);
+ }
+ }
+
+ return $data;
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php
new file mode 100755
index 0000000..1f9af7c
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/PartialLambdaIndentTest.php
@@ -0,0 +1,94 @@
+
+ {{> input }}
+
+
+EOS;
+ $partial = <<<'EOS'
+
+
+EOS;
+
+ $expected = <<<'EOS'
+
+
+
+
+EOS;
+
+ $m = new Mustache_Engine(array(
+ 'partials' => array('input' => $partial),
+ ));
+
+ $tpl = $m->loadTemplate($src);
+
+ $data = new Mustache_Test_FiveThree_Functional_ClassWithLambda();
+ $this->assertEquals($expected, $tpl->render($data));
+ }
+
+ public function testLambdaInterpolationsInsidePartialsAreIndentedProperly()
+ {
+ $src = <<<'EOS'
+
+ {{> input }}
+
+
+EOS;
+ $partial = <<<'EOS'
+
+
+EOS;
+
+ $expected = <<<'EOS'
+
+
+
+
+EOS;
+
+ $m = new Mustache_Engine(array(
+ 'partials' => array('input' => $partial),
+ ));
+
+ $tpl = $m->loadTemplate($src);
+
+ $data = new Mustache_Test_FiveThree_Functional_ClassWithLambda();
+ $this->assertEquals($expected, $tpl->render($data));
+ }
+}
+
+class Mustache_Test_FiveThree_Functional_ClassWithLambda
+{
+ public function _t()
+ {
+ return function ($val) {
+ return strtoupper($val);
+ };
+ }
+
+ public function placeholder()
+ {
+ return function () {
+ return 'Enter your name';
+ };
+ }
+}
diff --git a/mustache/test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php b/mustache/test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php
new file mode 100755
index 0000000..2c01169
--- /dev/null
+++ b/mustache/test/Mustache/Test/FiveThree/Functional/StrictCallablesTest.php
@@ -0,0 +1,135 @@
+ $strict));
+ $tpl = $mustache->loadTemplate('{{# section }}{{ name }}{{/ section }}');
+
+ $data = new StdClass();
+ $data->name = $name;
+ $data->section = $section;
+
+ $this->assertEquals($expected, $tpl->render($data));
+ }
+
+ public function callables()
+ {
+ $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(
+ 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',
+ ),
+
+ // Strict interpolation lambdas
+ array(
+ true,
+ function () {
+ return 'Yoshi';
+ },
+ $lambda,
+ 'YOSHI',
+ ),
+
+ // Strict section lambdas
+ array(
+ true,
+ 'Yoshi',
+ array($this, 'instanceCallable'),
+ 'YoshiYoshi',
+ ),
+ array(
+ true,
+ 'Yoshi',
+ array(__CLASS__, 'staticCallable'),
+ 'YoshiYoshi',
+ ),
+ array(
+ true,
+ 'Yoshi',
+ function ($tpl, $mustache) {
+ return strtoupper($mustache->render($tpl));
+ },
+ 'YOSHI',
+ ),
+ );
+ }
+
+ public function instanceCallable($tpl, $mustache)
+ {
+ return strtoupper($mustache->render($tpl));
+ }
+
+ public static function staticCallable($tpl, $mustache)
+ {
+ return strtoupper($mustache->render($tpl));
+ }
+
+ public function instanceName()
+ {
+ return 'Yoshi';
+ }
+
+ public static function staticName()
+ {
+ return 'Yoshi';
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/CallTest.php b/mustache/test/Mustache/Test/Functional/CallTest.php
new file mode 100755
index 0000000..681ec1b
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/CallTest.php
@@ -0,0 +1,40 @@
+loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}');
+
+ $foo = new Mustache_Test_Functional_ClassWithCall();
+ $foo->name = 'Bob';
+
+ $data = array('label' => 'name', 'foo' => $foo);
+
+ $this->assertEquals('name: Bob', $tpl->render($data));
+ }
+}
+
+class Mustache_Test_Functional_ClassWithCall
+{
+ public $name;
+
+ public function __call($method, $args)
+ {
+ return 'unknown value';
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/ExamplesTest.php b/mustache/test/Mustache/Test/Functional/ExamplesTest.php
new file mode 100755
index 0000000..ac883ea
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/ExamplesTest.php
@@ -0,0 +1,142 @@
+ $partials,
+ ));
+ $this->assertEquals($expected, $mustache->loadTemplate($source)->render($context));
+ }
+
+ /**
+ * Data provider for testExamples method.
+ *
+ * Loads examples from the test fixtures directory.
+ *
+ * This examples directory should contain any number of subdirectories, each of which contains
+ * three files: one Mustache class (.php), one Mustache template (.mustache), and one output file
+ * (.txt). Optionally, the directory may contain a folder full of partials.
+ *
+ * @return array
+ */
+ public function getExamples()
+ {
+ $path = realpath(dirname(__FILE__) . '/../../../fixtures/examples');
+ $examples = array();
+
+ $handle = opendir($path);
+ while (($file = readdir($handle)) !== false) {
+ if ($file === '.' || $file === '..') {
+ continue;
+ }
+
+ $fullpath = $path . '/' . $file;
+ if (is_dir($fullpath)) {
+ $examples[$file] = $this->loadExample($fullpath);
+ }
+ }
+ closedir($handle);
+
+ return $examples;
+ }
+
+ /**
+ * Helper method to load an example given the full path.
+ *
+ * @param string $path
+ *
+ * @return array arguments for testExamples
+ */
+ private function loadExample($path)
+ {
+ $context = null;
+ $source = null;
+ $partials = array();
+ $expected = null;
+
+ $handle = opendir($path);
+ while (($file = readdir($handle)) !== false) {
+ $fullpath = $path . '/' . $file;
+ $info = pathinfo($fullpath);
+
+ if (is_dir($fullpath) && $info['basename'] === 'partials') {
+ // load partials
+ $partials = $this->loadPartials($fullpath);
+ } elseif (is_file($fullpath)) {
+ // load other files
+ switch ($info['extension']) {
+ case 'php':
+ require_once $fullpath;
+ $className = $info['filename'];
+ $context = new $className();
+ break;
+
+ case 'mustache':
+ $source = file_get_contents($fullpath);
+ break;
+
+ case 'txt':
+ $expected = file_get_contents($fullpath);
+ break;
+ }
+ }
+ }
+ closedir($handle);
+
+ return array($context, $source, $partials, $expected);
+ }
+
+ /**
+ * Helper method to load partials given an example directory.
+ *
+ * @param string $path
+ *
+ * @return array $partials
+ */
+ private function loadPartials($path)
+ {
+ $partials = array();
+
+ $handle = opendir($path);
+ while (($file = readdir($handle)) !== false) {
+ if ($file === '.' || $file === '..') {
+ continue;
+ }
+
+ $fullpath = $path . '/' . $file;
+ $info = pathinfo($fullpath);
+
+ if ($info['extension'] === 'mustache') {
+ $partials[$info['filename']] = file_get_contents($fullpath);
+ }
+ }
+ closedir($handle);
+
+ return $partials;
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/HigherOrderSectionsTest.php b/mustache/test/Mustache/Test/Functional/HigherOrderSectionsTest.php
new file mode 100755
index 0000000..14774cd
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/HigherOrderSectionsTest.php
@@ -0,0 +1,177 @@
+mustache = new Mustache_Engine();
+ }
+
+ /**
+ * @dataProvider sectionCallbackData
+ */
+ public function testSectionCallback($data, $tpl, $expect)
+ {
+ $this->assertEquals($expect, $this->mustache->render($tpl, $data));
+ }
+
+ public function sectionCallbackData()
+ {
+ $foo = new Mustache_Test_Functional_Foo();
+ $foo->doublewrap = array($foo, 'wrapWithBoth');
+
+ $bar = new Mustache_Test_Functional_Foo();
+ $bar->trimmer = array(get_class($bar), 'staticTrim');
+
+ return array(
+ array($foo, '{{#doublewrap}}{{name}}{{/doublewrap}}', sprintf('%s ', $foo->name)),
+ array($bar, '{{#trimmer}} {{name}} {{/trimmer}}', $bar->name),
+ );
+ }
+
+ public function testViewArraySectionCallback()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#trim}} {{name}} {{/trim}}');
+
+ $foo = new Mustache_Test_Functional_Foo();
+
+ $data = array(
+ 'name' => 'Bob',
+ 'trim' => array(get_class($foo), 'staticTrim'),
+ );
+
+ $this->assertEquals($data['name'], $tpl->render($data));
+ }
+
+ public function testMonsters()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#title}}{{title}} {{/title}}{{name}}');
+
+ $frank = new Mustache_Test_Functional_Monster();
+ $frank->title = 'Dr.';
+ $frank->name = 'Frankenstein';
+ $this->assertEquals('Dr. Frankenstein', $tpl->render($frank));
+
+ $dracula = new Mustache_Test_Functional_Monster();
+ $dracula->title = 'Count';
+ $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('NAME ', $tpl->render($foo));
+ }
+
+ public function testWithoutPassthroughOptimization()
+ {
+ $mustache = $this->getMock('Mustache_Engine', array('loadLambda'));
+ $mustache->expects($this->once())
+ ->method('loadLambda')
+ ->will($this->returnValue($mustache->loadTemplate('{{ name }} ')));
+
+ $tpl = $mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
+
+ $foo = new Mustache_Test_Functional_Foo();
+ $foo->wrap = array($foo, 'wrapWithEm');
+
+ $this->assertEquals('' . $foo->name . ' ', $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('' . $foo->name . ' ', $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
+{
+ public $name = 'Justin';
+ public $lorem = 'Lorem ipsum dolor sit amet,';
+
+ public function wrapWithEm($text)
+ {
+ return sprintf('%s ', $text);
+ }
+
+ /**
+ * @param string $text
+ */
+ public function wrapWithStrong($text)
+ {
+ return sprintf('%s ', $text);
+ }
+
+ public function wrapWithBoth($text)
+ {
+ return self::wrapWithStrong(self::wrapWithEm($text));
+ }
+
+ public static function staticTrim($text)
+ {
+ return trim($text);
+ }
+}
+
+class Mustache_Test_Functional_Monster
+{
+ public $title;
+ public $name;
+}
diff --git a/mustache/test/Mustache/Test/Functional/InheritanceTest.php b/mustache/test/Mustache/Test/Functional/InheritanceTest.php
new file mode 100755
index 0000000..6243704
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/InheritanceTest.php
@@ -0,0 +1,543 @@
+mustache = new Mustache_Engine(array(
+ 'pragmas' => array(Mustache_Engine::PRAGMA_BLOCKS),
+ ));
+ }
+
+ public function getIllegalInheritanceExamples()
+ {
+ return array(
+ array(
+ array(
+ 'foo' => '{{$baz}}default content{{/baz}}',
+ ),
+ array(
+ 'bar' => 'set by user',
+ ),
+ '{{< foo }}{{# bar }}{{$ baz }}{{/ baz }}{{/ bar }}{{/ foo }}',
+ ),
+ array(
+ array(
+ 'foo' => '{{$baz}}default content{{/baz}}',
+ ),
+ array(
+ ),
+ '{{ '{{$baz}}default content{{/baz}}',
+ 'qux' => 'I am a partial',
+ ),
+ array(
+ ),
+ '{{qux}}{{$baz}}set by template{{/baz}}{{/foo}}',
+ ),
+ array(
+ array(
+ 'foo' => '{{$baz}}default content{{/baz}}',
+ ),
+ array(),
+ '{{=}}<%={{ }}=%>{{/foo}}',
+ ),
+ );
+ }
+
+ public function getLegalInheritanceExamples()
+ {
+ return array(
+ array(
+ array(
+ 'foo' => '{{$baz}}default content{{/baz}}',
+ ),
+ array(
+ 'bar' => 'set by user',
+ ),
+ '{{ '{{$baz}}default content{{/baz}}',
+ ),
+ array(
+ ),
+ '{{ '{{$baz}}defualt content{{/baz}}',
+ ),
+ array(),
+ '{{ '{{$a}}FAIL!{{/a}}',
+ 'bar' => 'WIN!!',
+ ),
+ array(),
+ '{{mustache->loadTemplate('{{$title}}Default title{{/title}}');
+
+ $data = array();
+
+ $this->assertEquals('Default title', $tpl->render($data));
+ }
+
+ public function testDefaultContentRendersVariables()
+ {
+ $tpl = $this->mustache->loadTemplate('{{$foo}}default {{bar}} content{{/foo}}');
+
+ $data = array(
+ 'bar' => 'baz',
+ );
+
+ $this->assertEquals('default baz content', $tpl->render($data));
+ }
+
+ public function testDefaultContentRendersTripleMustacheVariables()
+ {
+ $tpl = $this->mustache->loadTemplate('{{$foo}}default {{{bar}}} content{{/foo}}');
+
+ $data = array(
+ 'bar' => '',
+ );
+
+ $this->assertEquals('default content', $tpl->render($data));
+ }
+
+ public function testDefaultContentRendersSections()
+ {
+ $tpl = $this->mustache->loadTemplate(
+ '{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}'
+ );
+
+ $data = array(
+ 'bar' => array('baz' => 'qux'),
+ );
+
+ $this->assertEquals('default qux content', $tpl->render($data));
+ }
+
+ public function testDefaultContentRendersNegativeSections()
+ {
+ $tpl = $this->mustache->loadTemplate(
+ '{{$foo}}default {{^bar}}{{baz}}{{/bar}} content{{/foo}}'
+ );
+
+ $data = array(
+ 'foo' => array('bar' => 'qux'),
+ 'baz' => 'three',
+ );
+
+ $this->assertEquals('default three content', $tpl->render($data));
+ }
+
+ public function testMustacheInjectionInDefaultContent()
+ {
+ $tpl = $this->mustache->loadTemplate(
+ '{{$foo}}default {{#bar}}{{baz}}{{/bar}} content{{/foo}}'
+ );
+
+ $data = array(
+ 'bar' => array('baz' => '{{qux}}'),
+ );
+
+ $this->assertEquals('default {{qux}} content', $tpl->render($data));
+ }
+
+ public function testDefaultContentRenderedInsideIncludedTemplates()
+ {
+ $partials = array(
+ 'include' => '{{$foo}}default content{{/foo}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('default content', $tpl->render($data));
+ }
+
+ public function testOverriddenContent()
+ {
+ $partials = array(
+ 'super' => '...{{$title}}Default title{{/title}}...',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('...sub template title...', $tpl->render($data));
+ }
+
+ public function testOverriddenPartial()
+ {
+ $partials = array(
+ 'partial' => '|{{$stuff}}...{{/stuff}}{{$default}} default{{/default}}|',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ 'test {{assertEquals('test |override1 default| |override2 default|', $tpl->render($data));
+ }
+
+ public function testBlocksDoNotLeakBetweenPartials()
+ {
+ $partials = array(
+ 'partial' => '|{{$a}}A{{/a}} {{$b}}B{{/b}}|',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ 'test {{assertEquals('test |C B| |A D|', $tpl->render($data));
+ }
+
+ public function testDataDoesNotOverrideBlock()
+ {
+ $partials = array(
+ 'include' => '{{$var}}var in include{{/var}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{ 'var in data',
+ );
+
+ $this->assertEquals('var in template', $tpl->render($data));
+ }
+
+ public function testDataDoesNotOverrideDefaultBlockValue()
+ {
+ $partials = array(
+ 'include' => '{{$var}}var in include{{/var}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{ 'var in data',
+ );
+
+ $this->assertEquals('var in include', $tpl->render($data));
+ }
+
+ public function testOverridePartialWithNewlines()
+ {
+ $partials = array(
+ 'partial' => '{{$ballmer}}peaking{{/ballmer}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ "{{assertEquals("peaked\n\n:(\n", $tpl->render($data));
+ }
+
+ public function testInheritIndentationWhenOverridingAPartial()
+ {
+ $partials = array(
+ 'partial' => 'stop:
+ {{$nineties}}collaborate and listen{{/nineties}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals(
+ 'stop:
+ hammer time',
+ $tpl->render($data)
+ );
+ }
+
+ public function testInheritSpacingWhenOverridingAPartial()
+ {
+ $partials = array(
+ 'parent' => 'collaborate_and{{$id}}{{/id}}',
+ 'child' => '{{mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ 'stop:
+ {{>child}}'
+ );
+
+ $data = array();
+
+ $this->assertEquals(
+ 'stop:
+ collaborate_and_listen',
+ $tpl->render($data)
+ );
+ }
+
+ public function testOverrideOneSubstitutionButNotTheOther()
+ {
+ $partials = array(
+ 'partial' => '{{$stuff}}default one{{/stuff}}, {{$stuff2}}default two{{/stuff2}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('default one, override two', $tpl->render($data));
+ }
+
+ public function testSuperTemplatesWithNoParameters()
+ {
+ $partials = array(
+ 'include' => '{{$foo}}default content{{/foo}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{>include}}|{{assertEquals('default content|default content', $tpl->render($data));
+ }
+
+ public function testRecursionInInheritedTemplates()
+ {
+ $partials = array(
+ 'include' => '{{$foo}}default content{{/foo}} {{$bar}}{{ '{{$foo}}include2 default content{{/foo}} {{mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('override override override don\'t recurse', $tpl->render($data));
+ }
+
+ public function testTopLevelSubstitutionsTakePrecedenceInMultilevelInheritance()
+ {
+ $partials = array(
+ 'parent' => '{{ '{{ '{{$a}}g{{/a}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('c', $tpl->render($data));
+ }
+
+ public function testMultiLevelInheritanceNoSubChild()
+ {
+ $partials = array(
+ 'parent' => '{{ '{{ '{{$a}}g{{/a}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('p', $tpl->render($data));
+ }
+
+ public function testIgnoreTextInsideSuperTemplatesButParseArgs()
+ {
+ $partials = array(
+ 'include' => '{{$foo}}default content{{/foo}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('hmm', $tpl->render($data));
+ }
+
+ public function testIgnoreTextInsideSuperTemplates()
+ {
+ $partials = array(
+ 'include' => '{{$foo}}default content{{/foo}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{assertEquals('default content', $tpl->render($data));
+ }
+
+ public function testInheritanceWithLazyEvaluation()
+ {
+ $partials = array(
+ 'parent' => '{{#items}}{{$value}}ignored{{/value}}{{/items}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{{{/value}}{{/parent}}'
+ );
+
+ $data = array('items' => array(1, 2, 3));
+
+ $this->assertEquals('<1><2><3>', $tpl->render($data));
+ }
+
+ public function testInheritanceWithLazyEvaluationWhitespaceIgnored()
+ {
+ $partials = array(
+ 'parent' => '{{#items}}{{$value}}\n\nignored\n\n{{/value}}{{/items}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{{{/value}}\n\n{{/parent}}'
+ );
+
+ $data = array('items' => array(1, 2, 3));
+
+ $this->assertEquals('<1><2><3>', $tpl->render($data));
+ }
+
+ public function testInheritanceWithLazyEvaluationAndSections()
+ {
+ $partials = array(
+ 'parent' => '{{#items}}{{$value}}\n\nignored {{.}} {{#more}} there is more {{/more}}\n\n{{/value}}{{/items}}',
+ );
+
+ $this->mustache->setPartials($partials);
+
+ $tpl = $this->mustache->loadTemplate(
+ '{{{{#more}} there is less {{/more}}{{/value}}\n\n{{/parent}}'
+ );
+
+ $data = array('items' => array(1, 2, 3), 'more' => 'stuff');
+
+ $this->assertEquals('<1> there is less <2> there is less <3> there is less ', $tpl->render($data));
+ }
+
+ /**
+ * @dataProvider getIllegalInheritanceExamples
+ * @expectedException Mustache_Exception_SyntaxException
+ * @expectedExceptionMessage Illegal content in < parent tag
+ */
+ public function testIllegalInheritanceExamples($partials, $data, $template)
+ {
+ $this->mustache->setPartials($partials);
+ $tpl = $this->mustache->loadTemplate($template);
+ $tpl->render($data);
+ }
+
+ /**
+ * @dataProvider getLegalInheritanceExamples
+ */
+ public function testLegalInheritanceExamples($partials, $data, $template, $expect)
+ {
+ $this->mustache->setPartials($partials);
+ $tpl = $this->mustache->loadTemplate($template);
+ $this->assertSame($expect, $tpl->render($data));
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/MustacheInjectionTest.php b/mustache/test/Mustache/Test/Functional/MustacheInjectionTest.php
new file mode 100755
index 0000000..7a9d6ac
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/MustacheInjectionTest.php
@@ -0,0 +1,83 @@
+mustache = new Mustache_Engine();
+ }
+
+ /**
+ * @dataProvider injectionData
+ */
+ public function testInjection($tpl, $data, $partials, $expect)
+ {
+ $this->mustache->setPartials($partials);
+ $this->assertEquals($expect, $this->mustache->render($tpl, $data));
+ }
+
+ public function injectionData()
+ {
+ $interpolationData = array(
+ 'a' => '{{ b }}',
+ 'b' => 'FAIL',
+ );
+
+ $sectionData = array(
+ 'a' => true,
+ 'b' => '{{ c }}',
+ 'c' => 'FAIL',
+ );
+
+ $lambdaInterpolationData = array(
+ 'a' => array($this, 'lambdaInterpolationCallback'),
+ 'b' => '{{ c }}',
+ 'c' => 'FAIL',
+ );
+
+ $lambdaSectionData = array(
+ 'a' => array($this, 'lambdaSectionCallback'),
+ 'b' => '{{ c }}',
+ 'c' => 'FAIL',
+ );
+
+ return array(
+ array('{{ a }}', $interpolationData, array(), '{{ b }}'),
+ array('{{{ a }}}', $interpolationData, array(), '{{ b }}'),
+
+ array('{{# a }}{{ b }}{{/ a }}', $sectionData, array(), '{{ c }}'),
+ array('{{# a }}{{{ b }}}{{/ a }}', $sectionData, array(), '{{ c }}'),
+
+ array('{{> partial }}', $interpolationData, array('partial' => '{{ a }}'), '{{ b }}'),
+ array('{{> partial }}', $interpolationData, array('partial' => '{{{ a }}}'), '{{ b }}'),
+
+ array('{{ a }}', $lambdaInterpolationData, array(), '{{ c }}'),
+ array('{{# a }}b{{/ a }}', $lambdaSectionData, array(), '{{ c }}'),
+ );
+ }
+
+ public static function lambdaInterpolationCallback()
+ {
+ return '{{ b }}';
+ }
+
+ public static function lambdaSectionCallback($text)
+ {
+ return '{{ ' . $text . ' }}';
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/MustacheSpecTest.php b/mustache/test/Mustache/Test/Functional/MustacheSpecTest.php
new file mode 100755
index 0000000..6cde602
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/MustacheSpecTest.php
@@ -0,0 +1,121 @@
+markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"');
+ }
+ }
+
+ /**
+ * @group comments
+ * @dataProvider loadCommentSpec
+ */
+ public function testCommentSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadCommentSpec()
+ {
+ return $this->loadSpec('comments');
+ }
+
+ /**
+ * @group delimiters
+ * @dataProvider loadDelimitersSpec
+ */
+ public function testDelimitersSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadDelimitersSpec()
+ {
+ return $this->loadSpec('delimiters');
+ }
+
+ /**
+ * @group interpolation
+ * @dataProvider loadInterpolationSpec
+ */
+ public function testInterpolationSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadInterpolationSpec()
+ {
+ return $this->loadSpec('interpolation');
+ }
+
+ /**
+ * @group inverted
+ * @group inverted-sections
+ * @dataProvider loadInvertedSpec
+ */
+ public function testInvertedSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadInvertedSpec()
+ {
+ return $this->loadSpec('inverted');
+ }
+
+ /**
+ * @group partials
+ * @dataProvider loadPartialsSpec
+ */
+ public function testPartialsSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadPartialsSpec()
+ {
+ return $this->loadSpec('partials');
+ }
+
+ /**
+ * @group sections
+ * @dataProvider loadSectionsSpec
+ */
+ public function testSectionsSpec($desc, $source, $partials, $data, $expected)
+ {
+ $template = self::loadTemplate($source, $partials);
+ $this->assertEquals($expected, $template->render($data), $desc);
+ }
+
+ public function loadSectionsSpec()
+ {
+ return $this->loadSpec('sections');
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/NestedPartialIndentTest.php b/mustache/test/Mustache/Test/Functional/NestedPartialIndentTest.php
new file mode 100755
index 0000000..90af4d9
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/NestedPartialIndentTest.php
@@ -0,0 +1,45 @@
+ $partials,
+ ));
+ $tpl = $m->loadTemplate($src);
+ $this->assertEquals($expected, $tpl->render());
+ }
+
+ public function partialsAndStuff()
+ {
+ $partials = array(
+ 'a' => ' {{> b }}',
+ 'b' => ' {{> d }}',
+ 'c' => ' {{> d }}{{> d }}',
+ 'd' => 'D!',
+ );
+
+ return array(
+ array(' {{> a }}', $partials, ' D!'),
+ array(' {{> b }}', $partials, ' D!'),
+ array(' {{> c }}', $partials, ' D!D!'),
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/Functional/ObjectSectionTest.php b/mustache/test/Mustache/Test/Functional/ObjectSectionTest.php
new file mode 100755
index 0000000..3cf01e1
--- /dev/null
+++ b/mustache/test/Mustache/Test/Functional/ObjectSectionTest.php
@@ -0,0 +1,110 @@
+mustache = new Mustache_Engine();
+ }
+
+ public function testBasicObject()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
+ $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Alpha()));
+ }
+
+ /**
+ * @group magic_methods
+ */
+ public function testObjectWithGet()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
+ $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Beta()));
+ }
+
+ /**
+ * @group magic_methods
+ */
+ public function testSectionObjectWithGet()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}');
+ $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Gamma()));
+ }
+
+ public function testSectionObjectWithFunction()
+ {
+ $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
+ $alpha = new Mustache_Test_Functional_Alpha();
+ $alpha->foo = new Mustache_Test_Functional_Delta();
+ $this->assertEquals('Foo', $tpl->render($alpha));
+ }
+}
+
+class Mustache_Test_Functional_Alpha
+{
+ public $foo;
+
+ public function __construct()
+ {
+ $this->foo = new StdClass();
+ $this->foo->name = 'Foo';
+ $this->foo->number = 1;
+ }
+}
+
+class Mustache_Test_Functional_Beta
+{
+ protected $_data = array();
+
+ public function __construct()
+ {
+ $this->_data['foo'] = new StdClass();
+ $this->_data['foo']->name = 'Foo';
+ $this->_data['foo']->number = 1;
+ }
+
+ public function __isset($name)
+ {
+ return array_key_exists($name, $this->_data);
+ }
+
+ public function __get($name)
+ {
+ return $this->_data[$name];
+ }
+}
+
+class Mustache_Test_Functional_Gamma
+{
+ public $bar;
+
+ public function __construct()
+ {
+ $this->bar = new Mustache_Test_Functional_Beta();
+ }
+}
+
+class Mustache_Test_Functional_Delta
+{
+ protected $_name = 'Foo';
+
+ public function name()
+ {
+ return $this->_name;
+ }
+}
diff --git a/mustache/test/Mustache/Test/FunctionalTestCase.php b/mustache/test/Mustache/Test/FunctionalTestCase.php
new file mode 100755
index 0000000..213b045
--- /dev/null
+++ b/mustache/test/Mustache/Test/FunctionalTestCase.php
@@ -0,0 +1,47 @@
+ $foo,
+ 'bar' => $bar,
+ ));
+
+ $this->assertSame($foo, $helpers->get('foo'));
+ $this->assertSame($bar, $helpers->get('bar'));
+ }
+
+ public static function getFoo()
+ {
+ echo 'foo';
+ }
+
+ public function testAccessorsAndMutators()
+ {
+ $foo = array($this, 'getFoo');
+ $bar = 'BAR';
+
+ $helpers = new Mustache_HelperCollection();
+ $this->assertTrue($helpers->isEmpty());
+ $this->assertFalse($helpers->has('foo'));
+ $this->assertFalse($helpers->has('bar'));
+
+ $helpers->add('foo', $foo);
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertTrue($helpers->has('foo'));
+ $this->assertFalse($helpers->has('bar'));
+
+ $helpers->add('bar', $bar);
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertTrue($helpers->has('foo'));
+ $this->assertTrue($helpers->has('bar'));
+
+ $helpers->remove('foo');
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertFalse($helpers->has('foo'));
+ $this->assertTrue($helpers->has('bar'));
+ }
+
+ public function testMagicMethods()
+ {
+ $foo = array($this, 'getFoo');
+ $bar = 'BAR';
+
+ $helpers = new Mustache_HelperCollection();
+ $this->assertTrue($helpers->isEmpty());
+ $this->assertFalse($helpers->has('foo'));
+ $this->assertFalse($helpers->has('bar'));
+ $this->assertFalse(isset($helpers->foo));
+ $this->assertFalse(isset($helpers->bar));
+
+ $helpers->foo = $foo;
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertTrue($helpers->has('foo'));
+ $this->assertFalse($helpers->has('bar'));
+ $this->assertTrue(isset($helpers->foo));
+ $this->assertFalse(isset($helpers->bar));
+
+ $helpers->bar = $bar;
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertTrue($helpers->has('foo'));
+ $this->assertTrue($helpers->has('bar'));
+ $this->assertTrue(isset($helpers->foo));
+ $this->assertTrue(isset($helpers->bar));
+
+ unset($helpers->foo);
+ $this->assertFalse($helpers->isEmpty());
+ $this->assertFalse($helpers->has('foo'));
+ $this->assertTrue($helpers->has('bar'));
+ $this->assertFalse(isset($helpers->foo));
+ $this->assertTrue(isset($helpers->bar));
+ }
+
+ /**
+ * @dataProvider getInvalidHelperArguments
+ */
+ public function testHelperCollectionIsntAfraidToThrowExceptions($helpers = array(), $actions = array(), $exception = null)
+ {
+ if ($exception) {
+ $this->setExpectedException($exception);
+ }
+
+ $helpers = new Mustache_HelperCollection($helpers);
+
+ foreach ($actions as $method => $args) {
+ call_user_func_array(array($helpers, $method), $args);
+ }
+ }
+
+ public function getInvalidHelperArguments()
+ {
+ return array(
+ array(
+ 'not helpers',
+ array(),
+ 'InvalidArgumentException',
+ ),
+ array(
+ array(),
+ array('get' => array('foo')),
+ 'InvalidArgumentException',
+ ),
+ array(
+ array('foo' => 'FOO'),
+ array('get' => array('foo')),
+ null,
+ ),
+ array(
+ array('foo' => 'FOO'),
+ array('get' => array('bar')),
+ 'InvalidArgumentException',
+ ),
+ array(
+ array('foo' => 'FOO'),
+ array(
+ 'add' => array('bar', 'BAR'),
+ 'get' => array('bar'),
+ ),
+ null,
+ ),
+ array(
+ array('foo' => 'FOO'),
+ array(
+ 'get' => array('foo'),
+ 'remove' => array('foo'),
+ ),
+ null,
+ ),
+ array(
+ array('foo' => 'FOO'),
+ array(
+ 'remove' => array('foo'),
+ 'get' => array('foo'),
+ ),
+ 'InvalidArgumentException',
+ ),
+ array(
+ array(),
+ array('remove' => array('foo')),
+ 'InvalidArgumentException',
+ ),
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/Loader/ArrayLoaderTest.php b/mustache/test/Mustache/Test/Loader/ArrayLoaderTest.php
new file mode 100755
index 0000000..1f30987
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/ArrayLoaderTest.php
@@ -0,0 +1,52 @@
+ 'bar',
+ ));
+
+ $this->assertEquals('bar', $loader->load('foo'));
+ }
+
+ public function testSetAndLoadTemplates()
+ {
+ $loader = new Mustache_Loader_ArrayLoader(array(
+ 'foo' => 'bar',
+ ));
+ $this->assertEquals('bar', $loader->load('foo'));
+
+ $loader->setTemplate('baz', 'qux');
+ $this->assertEquals('qux', $loader->load('baz'));
+
+ $loader->setTemplates(array(
+ 'foo' => 'FOO',
+ 'baz' => 'BAZ',
+ ));
+ $this->assertEquals('FOO', $loader->load('foo'));
+ $this->assertEquals('BAZ', $loader->load('baz'));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_UnknownTemplateException
+ */
+ public function testMissingTemplatesThrowExceptions()
+ {
+ $loader = new Mustache_Loader_ArrayLoader();
+ $loader->load('not_a_real_template');
+ }
+}
diff --git a/mustache/test/Mustache/Test/Loader/CascadingLoaderTest.php b/mustache/test/Mustache/Test/Loader/CascadingLoaderTest.php
new file mode 100755
index 0000000..ecf80d6
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/CascadingLoaderTest.php
@@ -0,0 +1,40 @@
+ '{{ foo }}')),
+ new Mustache_Loader_ArrayLoader(array('bar' => '{{#bar}}BAR{{/bar}}')),
+ ));
+
+ $this->assertEquals('{{ foo }}', $loader->load('foo'));
+ $this->assertEquals('{{#bar}}BAR{{/bar}}', $loader->load('bar'));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_UnknownTemplateException
+ */
+ public function testMissingTemplatesThrowExceptions()
+ {
+ $loader = new Mustache_Loader_CascadingLoader(array(
+ new Mustache_Loader_ArrayLoader(array('foo' => '{{ foo }}')),
+ new Mustache_Loader_ArrayLoader(array('bar' => '{{#bar}}BAR{{/bar}}')),
+ ));
+
+ $loader->load('not_a_real_template');
+ }
+}
diff --git a/mustache/test/Mustache/Test/Loader/FilesystemLoaderTest.php b/mustache/test/Mustache/Test/Loader/FilesystemLoaderTest.php
new file mode 100755
index 0000000..8c06e76
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/FilesystemLoaderTest.php
@@ -0,0 +1,80 @@
+ '.ms'));
+ $this->assertEquals('alpha contents', $loader->load('alpha'));
+ $this->assertEquals('beta contents', $loader->load('beta.ms'));
+ }
+
+ public function testTrailingSlashes()
+ {
+ $baseDir = dirname(__FILE__) . '/../../../fixtures/templates/';
+ $loader = new Mustache_Loader_FilesystemLoader($baseDir);
+ $this->assertEquals('one contents', $loader->load('one'));
+ }
+
+ public function testConstructorWithProtocol()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+
+ $loader = new Mustache_Loader_FilesystemLoader('test://' . $baseDir, array('extension' => '.ms'));
+ $this->assertEquals('alpha contents', $loader->load('alpha'));
+ $this->assertEquals('beta contents', $loader->load('beta.ms'));
+ }
+
+ public function testLoadTemplates()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+ $loader = new Mustache_Loader_FilesystemLoader($baseDir);
+ $this->assertEquals('one contents', $loader->load('one'));
+ $this->assertEquals('two contents', $loader->load('two.mustache'));
+ }
+
+ public function testEmptyExtensionString()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+
+ $loader = new Mustache_Loader_FilesystemLoader($baseDir, array('extension' => ''));
+ $this->assertEquals('one contents', $loader->load('one.mustache'));
+ $this->assertEquals('alpha contents', $loader->load('alpha.ms'));
+
+ $loader = new Mustache_Loader_FilesystemLoader($baseDir, array('extension' => null));
+ $this->assertEquals('two contents', $loader->load('two.mustache'));
+ $this->assertEquals('beta contents', $loader->load('beta.ms'));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_RuntimeException
+ */
+ public function testMissingBaseDirThrowsException()
+ {
+ new Mustache_Loader_FilesystemLoader(dirname(__FILE__) . '/not_a_directory');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_UnknownTemplateException
+ */
+ public function testMissingTemplateThrowsException()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+ $loader = new Mustache_Loader_FilesystemLoader($baseDir);
+
+ $loader->load('fake');
+ }
+}
diff --git a/mustache/test/Mustache/Test/Loader/InlineLoaderTest.php b/mustache/test/Mustache/Test/Loader/InlineLoaderTest.php
new file mode 100755
index 0000000..24f2e0b
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/InlineLoaderTest.php
@@ -0,0 +1,56 @@
+assertEquals('{{ foo }}', $loader->load('foo'));
+ $this->assertEquals('{{#bar}}BAR{{/bar}}', $loader->load('bar'));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_UnknownTemplateException
+ */
+ public function testMissingTemplatesThrowExceptions()
+ {
+ $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
+ $loader->load('not_a_real_template');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testInvalidOffsetThrowsException()
+ {
+ new Mustache_Loader_InlineLoader(__FILE__, 'notanumber');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testInvalidFileThrowsException()
+ {
+ new Mustache_Loader_InlineLoader('notarealfile', __COMPILER_HALT_OFFSET__);
+ }
+}
+
+__halt_compiler();
+
+@@ foo
+{{ foo }}
+
+@@ bar
+{{#bar}}BAR{{/bar}}
diff --git a/mustache/test/Mustache/Test/Loader/ProductionFilesystemLoaderTest.php b/mustache/test/Mustache/Test/Loader/ProductionFilesystemLoaderTest.php
new file mode 100755
index 0000000..0c7c7e3
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/ProductionFilesystemLoaderTest.php
@@ -0,0 +1,103 @@
+ '.ms'));
+ $this->assertInstanceOf('Mustache_Source', $loader->load('alpha'));
+ $this->assertEquals('alpha contents', $loader->load('alpha')->getSource());
+ $this->assertInstanceOf('Mustache_Source', $loader->load('beta.ms'));
+ $this->assertEquals('beta contents', $loader->load('beta.ms')->getSource());
+ }
+
+ public function testTrailingSlashes()
+ {
+ $baseDir = dirname(__FILE__) . '/../../../fixtures/templates/';
+ $loader = new Mustache_Loader_ProductionFilesystemLoader($baseDir);
+ $this->assertEquals('one contents', $loader->load('one')->getSource());
+ }
+
+ public function testConstructorWithProtocol()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+
+ $loader = new Mustache_Loader_ProductionFilesystemLoader('file://' . $baseDir, array('extension' => '.ms'));
+ $this->assertEquals('alpha contents', $loader->load('alpha')->getSource());
+ $this->assertEquals('beta contents', $loader->load('beta.ms')->getSource());
+ }
+
+ public function testLoadTemplates()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+ $loader = new Mustache_Loader_ProductionFilesystemLoader($baseDir);
+ $this->assertEquals('one contents', $loader->load('one')->getSource());
+ $this->assertEquals('two contents', $loader->load('two.mustache')->getSource());
+ }
+
+ public function testEmptyExtensionString()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+
+ $loader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('extension' => ''));
+ $this->assertEquals('one contents', $loader->load('one.mustache')->getSource());
+ $this->assertEquals('alpha contents', $loader->load('alpha.ms')->getSource());
+
+ $loader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('extension' => null));
+ $this->assertEquals('two contents', $loader->load('two.mustache')->getSource());
+ $this->assertEquals('beta contents', $loader->load('beta.ms')->getSource());
+ }
+
+ /**
+ * @expectedException Mustache_Exception_RuntimeException
+ */
+ public function testMissingBaseDirThrowsException()
+ {
+ new Mustache_Loader_ProductionFilesystemLoader(dirname(__FILE__) . '/not_a_directory');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_UnknownTemplateException
+ */
+ public function testMissingTemplateThrowsException()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+ $loader = new Mustache_Loader_ProductionFilesystemLoader($baseDir);
+
+ $loader->load('fake');
+ }
+
+ public function testLoadWithDifferentStatProps()
+ {
+ $baseDir = realpath(dirname(__FILE__) . '/../../../fixtures/templates');
+ $noStatLoader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('stat_props' => null));
+ $mtimeLoader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('stat_props' => array('mtime')));
+ $sizeLoader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('stat_props' => array('size')));
+ $bothLoader = new Mustache_Loader_ProductionFilesystemLoader($baseDir, array('stat_props' => array('mtime', 'size')));
+
+ $noStatKey = $noStatLoader->load('one.mustache')->getKey();
+ $mtimeKey = $mtimeLoader->load('one.mustache')->getKey();
+ $sizeKey = $sizeLoader->load('one.mustache')->getKey();
+ $bothKey = $bothLoader->load('one.mustache')->getKey();
+
+ $this->assertNotEquals($noStatKey, $mtimeKey);
+ $this->assertNotEquals($noStatKey, $sizeKey);
+ $this->assertNotEquals($noStatKey, $bothKey);
+ $this->assertNotEquals($mtimeKey, $sizeKey);
+ $this->assertNotEquals($mtimeKey, $bothKey);
+ $this->assertNotEquals($sizeKey, $bothKey);
+ }
+}
diff --git a/mustache/test/Mustache/Test/Loader/StringLoaderTest.php b/mustache/test/Mustache/Test/Loader/StringLoaderTest.php
new file mode 100755
index 0000000..5896f00
--- /dev/null
+++ b/mustache/test/Mustache/Test/Loader/StringLoaderTest.php
@@ -0,0 +1,25 @@
+assertEquals('foo', $loader->load('foo'));
+ $this->assertEquals('{{ bar }}', $loader->load('{{ bar }}'));
+ $this->assertEquals("\n{{! comment }}\n", $loader->load("\n{{! comment }}\n"));
+ }
+}
diff --git a/mustache/test/Mustache/Test/Logger/AbstractLoggerTest.php b/mustache/test/Mustache/Test/Logger/AbstractLoggerTest.php
new file mode 100755
index 0000000..19dc27c
--- /dev/null
+++ b/mustache/test/Mustache/Test/Logger/AbstractLoggerTest.php
@@ -0,0 +1,60 @@
+emergency('emergency message');
+ $logger->alert('alert message');
+ $logger->critical('critical message');
+ $logger->error('error message');
+ $logger->warning('warning message');
+ $logger->notice('notice message');
+ $logger->info('info message');
+ $logger->debug('debug message');
+
+ $expected = array(
+ array(Mustache_Logger::EMERGENCY, 'emergency message', array()),
+ array(Mustache_Logger::ALERT, 'alert message', array()),
+ array(Mustache_Logger::CRITICAL, 'critical message', array()),
+ array(Mustache_Logger::ERROR, 'error message', array()),
+ array(Mustache_Logger::WARNING, 'warning message', array()),
+ array(Mustache_Logger::NOTICE, 'notice message', array()),
+ array(Mustache_Logger::INFO, 'info message', array()),
+ array(Mustache_Logger::DEBUG, 'debug message', array()),
+ );
+
+ $this->assertEquals($expected, $logger->log);
+ }
+}
+
+class Mustache_Test_Logger_TestLogger extends Mustache_Logger_AbstractLogger
+{
+ public $log = array();
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ */
+ public function log($level, $message, array $context = array())
+ {
+ $this->log[] = array($level, $message, $context);
+ }
+}
diff --git a/mustache/test/Mustache/Test/Logger/StreamLoggerTest.php b/mustache/test/Mustache/Test/Logger/StreamLoggerTest.php
new file mode 100755
index 0000000..fc1a06f
--- /dev/null
+++ b/mustache/test/Mustache/Test/Logger/StreamLoggerTest.php
@@ -0,0 +1,209 @@
+log(Mustache_Logger::CRITICAL, 'message');
+
+ $this->assertEquals("CRITICAL: message\n", file_get_contents($name));
+ }
+
+ public function acceptsStreamData()
+ {
+ $one = tempnam(sys_get_temp_dir(), 'mustache-test');
+ $two = tempnam(sys_get_temp_dir(), 'mustache-test');
+
+ return array(
+ array($one, $one),
+ array($two, fopen($two, 'a')),
+ );
+ }
+
+ /**
+ * @expectedException Mustache_Exception_LogicException
+ */
+ public function testPrematurelyClosedStreamThrowsException()
+ {
+ $stream = tmpfile();
+ $logger = new Mustache_Logger_StreamLogger($stream);
+ fclose($stream);
+
+ $logger->log(Mustache_Logger::CRITICAL, 'message');
+ }
+
+ /**
+ * @dataProvider getLevels
+ */
+ public function testLoggingThresholds($logLevel, $level, $shouldLog)
+ {
+ $stream = tmpfile();
+ $logger = new Mustache_Logger_StreamLogger($stream, $logLevel);
+ $logger->log($level, 'logged');
+
+ rewind($stream);
+ $result = fread($stream, 1024);
+
+ if ($shouldLog) {
+ $this->assertContains('logged', $result);
+ } else {
+ $this->assertEmpty($result);
+ }
+ }
+
+ public function getLevels()
+ {
+ // $logLevel, $level, $shouldLog
+ return array(
+ // identities
+ array(Mustache_Logger::EMERGENCY, Mustache_Logger::EMERGENCY, true),
+ array(Mustache_Logger::ALERT, Mustache_Logger::ALERT, true),
+ array(Mustache_Logger::CRITICAL, Mustache_Logger::CRITICAL, true),
+ array(Mustache_Logger::ERROR, Mustache_Logger::ERROR, true),
+ array(Mustache_Logger::WARNING, Mustache_Logger::WARNING, true),
+ array(Mustache_Logger::NOTICE, Mustache_Logger::NOTICE, true),
+ array(Mustache_Logger::INFO, Mustache_Logger::INFO, true),
+ array(Mustache_Logger::DEBUG, Mustache_Logger::DEBUG, true),
+
+ // one above
+ array(Mustache_Logger::ALERT, Mustache_Logger::EMERGENCY, true),
+ array(Mustache_Logger::CRITICAL, Mustache_Logger::ALERT, true),
+ array(Mustache_Logger::ERROR, Mustache_Logger::CRITICAL, true),
+ array(Mustache_Logger::WARNING, Mustache_Logger::ERROR, true),
+ array(Mustache_Logger::NOTICE, Mustache_Logger::WARNING, true),
+ array(Mustache_Logger::INFO, Mustache_Logger::NOTICE, true),
+ array(Mustache_Logger::DEBUG, Mustache_Logger::INFO, true),
+
+ // one below
+ array(Mustache_Logger::EMERGENCY, Mustache_Logger::ALERT, false),
+ array(Mustache_Logger::ALERT, Mustache_Logger::CRITICAL, false),
+ array(Mustache_Logger::CRITICAL, Mustache_Logger::ERROR, false),
+ array(Mustache_Logger::ERROR, Mustache_Logger::WARNING, false),
+ array(Mustache_Logger::WARNING, Mustache_Logger::NOTICE, false),
+ array(Mustache_Logger::NOTICE, Mustache_Logger::INFO, false),
+ array(Mustache_Logger::INFO, Mustache_Logger::DEBUG, false),
+ );
+ }
+
+ /**
+ * @dataProvider getLogMessages
+ */
+ public function testLogging($level, $message, $context, $expected)
+ {
+ $stream = tmpfile();
+ $logger = new Mustache_Logger_StreamLogger($stream, Mustache_Logger::DEBUG);
+ $logger->log($level, $message, $context);
+
+ rewind($stream);
+ $result = fread($stream, 1024);
+
+ $this->assertEquals($expected, $result);
+ }
+
+ public function getLogMessages()
+ {
+ // $level, $message, $context, $expected
+ return array(
+ array(Mustache_Logger::DEBUG, 'debug message', array(), "DEBUG: debug message\n"),
+ array(Mustache_Logger::INFO, 'info message', array(), "INFO: info message\n"),
+ array(Mustache_Logger::NOTICE, 'notice message', array(), "NOTICE: notice message\n"),
+ array(Mustache_Logger::WARNING, 'warning message', array(), "WARNING: warning message\n"),
+ array(Mustache_Logger::ERROR, 'error message', array(), "ERROR: error message\n"),
+ array(Mustache_Logger::CRITICAL, 'critical message', array(), "CRITICAL: critical message\n"),
+ array(Mustache_Logger::ALERT, 'alert message', array(), "ALERT: alert message\n"),
+ array(Mustache_Logger::EMERGENCY, 'emergency message', array(), "EMERGENCY: emergency message\n"),
+
+ // with context
+ array(
+ Mustache_Logger::ERROR,
+ 'error message',
+ array('name' => 'foo', 'number' => 42),
+ "ERROR: error message\n",
+ ),
+
+ // with interpolation
+ array(
+ Mustache_Logger::ERROR,
+ 'error {name}-{number}',
+ array('name' => 'foo', 'number' => 42),
+ "ERROR: error foo-42\n",
+ ),
+
+ // with iterpolation false positive
+ array(
+ Mustache_Logger::ERROR,
+ 'error {nothing}',
+ array('name' => 'foo', 'number' => 42),
+ "ERROR: error {nothing}\n",
+ ),
+
+ // with interpolation injection
+ array(
+ Mustache_Logger::ERROR,
+ '{foo}',
+ array('foo' => '{bar}', 'bar' => 'FAIL'),
+ "ERROR: {bar}\n",
+ ),
+ );
+ }
+
+ public function testChangeLoggingLevels()
+ {
+ $stream = tmpfile();
+ $logger = new Mustache_Logger_StreamLogger($stream);
+
+ $logger->setLevel(Mustache_Logger::ERROR);
+ $this->assertEquals(Mustache_Logger::ERROR, $logger->getLevel());
+
+ $logger->log(Mustache_Logger::WARNING, 'ignore this');
+
+ $logger->setLevel(Mustache_Logger::INFO);
+ $this->assertEquals(Mustache_Logger::INFO, $logger->getLevel());
+
+ $logger->log(Mustache_Logger::WARNING, 'log this');
+
+ $logger->setLevel(Mustache_Logger::CRITICAL);
+ $this->assertEquals(Mustache_Logger::CRITICAL, $logger->getLevel());
+
+ $logger->log(Mustache_Logger::ERROR, 'ignore this');
+
+ rewind($stream);
+ $result = fread($stream, 1024);
+
+ $this->assertEquals("WARNING: log this\n", $result);
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testThrowsInvalidArgumentExceptionWhenSettingUnknownLevels()
+ {
+ $logger = new Mustache_Logger_StreamLogger(tmpfile());
+ $logger->setLevel('bacon');
+ }
+
+ /**
+ * @expectedException Mustache_Exception_InvalidArgumentException
+ */
+ public function testThrowsInvalidArgumentExceptionWhenLoggingUnknownLevels()
+ {
+ $logger = new Mustache_Logger_StreamLogger(tmpfile());
+ $logger->log('bacon', 'CODE BACON ERROR!');
+ }
+}
diff --git a/mustache/test/Mustache/Test/ParserTest.php b/mustache/test/Mustache/Test/ParserTest.php
new file mode 100755
index 0000000..d8458f7
--- /dev/null
+++ b/mustache/test/Mustache/Test/ParserTest.php
@@ -0,0 +1,425 @@
+assertEquals($expected, $parser->parse($tokens));
+ }
+
+ public function getTokenSets()
+ {
+ return array(
+ array(
+ array(),
+ array(),
+ ),
+
+ array(
+ array(array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'text',
+ )),
+ array(array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'text',
+ )),
+ ),
+
+ array(
+ array(array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::NAME => 'name',
+ )),
+ array(array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::NAME => 'name',
+ )),
+ ),
+
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'foo',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_INVERTED,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ Mustache_Tokenizer::NAME => 'parent',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 456,
+ Mustache_Tokenizer::NAME => 'parent',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ ),
+
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'foo',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_INVERTED,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ Mustache_Tokenizer::END => 456,
+ Mustache_Tokenizer::NODES => array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::NAME => 'name',
+ ),
+ ),
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ ),
+ ),
+
+ // This *would* be an invalid inheritance parse tree, but that pragma
+ // isn't enabled so it'll thunk it back into an "escaped" token:
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ ),
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => '$foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ ),
+ ),
+
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => ' ',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_DELIM_CHANGE,
+ Mustache_Tokenizer::LINE => 0,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => " \n",
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '[[',
+ Mustache_Tokenizer::CTAG => ']]',
+ Mustache_Tokenizer::LINE => 1,
+ ),
+ ),
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '[[',
+ Mustache_Tokenizer::CTAG => ']]',
+ Mustache_Tokenizer::LINE => 1,
+ ),
+ ),
+ ),
+
+ );
+ }
+
+ /**
+ * @dataProvider getInheritanceTokenSets
+ */
+ public function testParseWithInheritance($tokens, $expected)
+ {
+ $parser = new Mustache_Parser();
+ $parser->setPragmas(array(Mustache_Engine::PRAGMA_BLOCKS));
+ $this->assertEquals($expected, $parser->parse($tokens));
+ }
+
+ public function getInheritanceTokenSets()
+ {
+ return array(
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_PARENT,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 8,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'bar',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 16,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'baz',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'bar',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 19,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 27,
+ ),
+ ),
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_PARENT,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 8,
+ Mustache_Tokenizer::END => 27,
+ Mustache_Tokenizer::NODES => array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_ARG,
+ Mustache_Tokenizer::NAME => 'bar',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 16,
+ Mustache_Tokenizer::END => 19,
+ Mustache_Tokenizer::NODES => array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'baz',
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 11,
+ ),
+ ),
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::END => 11,
+ Mustache_Tokenizer::NODES => array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider getBadParseTrees
+ * @expectedException Mustache_Exception_SyntaxException
+ */
+ public function testParserThrowsExceptions($tokens)
+ {
+ $parser = new Mustache_Parser();
+ $parser->parse($tokens);
+ }
+
+ public function getBadParseTrees()
+ {
+ return array(
+ // no close
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_SECTION,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ ),
+ ),
+
+ // no close inverted
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_INVERTED,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ ),
+ ),
+
+ // no opening inverted
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ ),
+ ),
+
+ // weird nesting
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_SECTION,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_SECTION,
+ Mustache_Tokenizer::NAME => 'child',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'parent',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'child',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 123,
+ ),
+ ),
+ ),
+
+ // This *would* be a valid inheritance parse tree, but that pragma
+ // isn't enabled here so it's going to fail :)
+ array(
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'bar',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'foo',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 11,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/mustache/test/Mustache/Test/Source/FilesystemSourceTest.php b/mustache/test/Mustache/Test/Source/FilesystemSourceTest.php
new file mode 100755
index 0000000..a636ffe
--- /dev/null
+++ b/mustache/test/Mustache/Test/Source/FilesystemSourceTest.php
@@ -0,0 +1,25 @@
+getKey();
+ }
+}
diff --git a/mustache/test/Mustache/Test/SpecTestCase.php b/mustache/test/Mustache/Test/SpecTestCase.php
new file mode 100755
index 0000000..db188de
--- /dev/null
+++ b/mustache/test/Mustache/Test/SpecTestCase.php
@@ -0,0 +1,67 @@
+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;
+ }
+}
diff --git a/mustache/test/Mustache/Test/TemplateTest.php b/mustache/test/Mustache/Test/TemplateTest.php
new file mode 100755
index 0000000..60065aa
--- /dev/null
+++ b/mustache/test/Mustache/Test/TemplateTest.php
@@ -0,0 +1,55 @@
+assertSame($mustache, $template->getMustache());
+ }
+
+ public function testRendering()
+ {
+ $rendered = '<< wheee >>';
+ $mustache = new Mustache_Engine();
+ $template = new Mustache_Test_TemplateStub($mustache);
+ $template->rendered = $rendered;
+ $context = new Mustache_Context();
+
+ if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+ $this->assertEquals($rendered, $template());
+ }
+
+ $this->assertEquals($rendered, $template->render());
+ $this->assertEquals($rendered, $template->renderInternal($context));
+ $this->assertEquals($rendered, $template->render(array('foo' => 'bar')));
+ }
+}
+
+class Mustache_Test_TemplateStub extends Mustache_Template
+{
+ public $rendered;
+
+ public function getMustache()
+ {
+ return $this->mustache;
+ }
+
+ public function renderInternal(Mustache_Context $context, $indent = '', $escape = false)
+ {
+ return $this->rendered;
+ }
+}
diff --git a/mustache/test/Mustache/Test/TokenizerTest.php b/mustache/test/Mustache/Test/TokenizerTest.php
new file mode 100755
index 0000000..38f16dc
--- /dev/null
+++ b/mustache/test/Mustache/Test/TokenizerTest.php
@@ -0,0 +1,306 @@
+assertSame($expected, $tokenizer->scan($text, $delimiters));
+ }
+
+ /**
+ * @expectedException Mustache_Exception_SyntaxException
+ */
+ public function testUnevenBracesThrowExceptions()
+ {
+ $tokenizer = new Mustache_Tokenizer();
+
+ $text = '{{{ name }}';
+ $tokenizer->scan($text, null);
+ }
+
+ /**
+ * @expectedException Mustache_Exception_SyntaxException
+ */
+ public function testUnevenBracesWithCustomDelimiterThrowExceptions()
+ {
+ $tokenizer = new Mustache_Tokenizer();
+
+ $text = '<%{ name %>';
+ $tokenizer->scan($text, '<% %>');
+ }
+
+ public function getTokens()
+ {
+ return array(
+ array(
+ 'text',
+ null,
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'text',
+ ),
+ ),
+ ),
+
+ array(
+ 'text',
+ '<<< >>>',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'text',
+ ),
+ ),
+ ),
+
+ array(
+ '{{ name }}',
+ null,
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 10,
+ ),
+ ),
+ ),
+
+ array(
+ '{{ name }}',
+ '<<< >>>',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => '{{ name }}',
+ ),
+ ),
+ ),
+
+ array(
+ '<<< name >>>',
+ '<<< >>>',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'name',
+ Mustache_Tokenizer::OTAG => '<<<',
+ Mustache_Tokenizer::CTAG => '>>>',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 12,
+ ),
+ ),
+ ),
+
+ array(
+ "{{{ a }}}\n{{# b }} \n{{= | | =}}| c ||/ b |\n|{ d }|",
+ null,
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_UNESCAPED,
+ Mustache_Tokenizer::NAME => 'a',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 8,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => "\n",
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_SECTION,
+ Mustache_Tokenizer::NAME => 'b',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 1,
+ Mustache_Tokenizer::INDEX => 18,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 1,
+ Mustache_Tokenizer::VALUE => " \n",
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_DELIM_CHANGE,
+ Mustache_Tokenizer::LINE => 2,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'c',
+ Mustache_Tokenizer::OTAG => '|',
+ Mustache_Tokenizer::CTAG => '|',
+ Mustache_Tokenizer::LINE => 2,
+ Mustache_Tokenizer::INDEX => 37,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'b',
+ Mustache_Tokenizer::OTAG => '|',
+ Mustache_Tokenizer::CTAG => '|',
+ Mustache_Tokenizer::LINE => 2,
+ Mustache_Tokenizer::INDEX => 37,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 2,
+ Mustache_Tokenizer::VALUE => "\n",
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_UNESCAPED,
+ Mustache_Tokenizer::NAME => 'd',
+ Mustache_Tokenizer::OTAG => '|',
+ Mustache_Tokenizer::CTAG => '|',
+ Mustache_Tokenizer::LINE => 3,
+ Mustache_Tokenizer::INDEX => 51,
+ ),
+
+ ),
+ ),
+
+ // See https://github.com/bobthecow/mustache.php/issues/183
+ array(
+ '{{# a }}0{{/ a }}',
+ null,
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_SECTION,
+ Mustache_Tokenizer::NAME => 'a',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 8,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => '0',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'a',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 9,
+ ),
+ ),
+ ),
+
+ // custom delimiters don't swallow the next character, even if it is a }, }}}, or the same delimiter
+ array(
+ '<% a %>} <% b %>%> <% c %>}}}',
+ '<% %>',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'a',
+ Mustache_Tokenizer::OTAG => '<%',
+ Mustache_Tokenizer::CTAG => '%>',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 7,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => '} ',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'b',
+ Mustache_Tokenizer::OTAG => '<%',
+ Mustache_Tokenizer::CTAG => '%>',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 16,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => '%> ',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+ Mustache_Tokenizer::NAME => 'c',
+ Mustache_Tokenizer::OTAG => '<%',
+ Mustache_Tokenizer::CTAG => '%>',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 26,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => '}}}',
+ ),
+ ),
+ ),
+
+ // unescaped custom delimiters are properly parsed
+ array(
+ '<%{ a }%>',
+ '<% %>',
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_UNESCAPED,
+ Mustache_Tokenizer::NAME => 'a',
+ Mustache_Tokenizer::OTAG => '<%',
+ Mustache_Tokenizer::CTAG => '%>',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 9,
+ ),
+ ),
+ ),
+
+ // Ensure that $arg token is not picked up during tokenization
+ array(
+ '{{$arg}}default{{/arg}}',
+ null,
+ array(
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_BLOCK_VAR,
+ Mustache_Tokenizer::NAME => 'arg',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 8,
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT,
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::VALUE => 'default',
+ ),
+ array(
+ Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_END_SECTION,
+ Mustache_Tokenizer::NAME => 'arg',
+ Mustache_Tokenizer::OTAG => '{{',
+ Mustache_Tokenizer::CTAG => '}}',
+ Mustache_Tokenizer::LINE => 0,
+ Mustache_Tokenizer::INDEX => 15,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/mustache/test/bootstrap.php b/mustache/test/bootstrap.php
new file mode 100755
index 0000000..b3d0a19
--- /dev/null
+++ b/mustache/test/bootstrap.php
@@ -0,0 +1,91 @@
+filehandle = fopen($path, $mode);
+
+ return $this->filehandle !== false;
+ }
+
+ /**
+ * @return array
+ */
+ public function stream_stat()
+ {
+ return array();
+ }
+
+ /**
+ * @param int $count
+ *
+ * @return string
+ */
+ public function stream_read($count)
+ {
+ return fgets($this->filehandle, $count);
+ }
+
+ /**
+ * @return bool
+ */
+ public function stream_eof()
+ {
+ return feof($this->filehandle);
+ }
+
+ /**
+ * @return bool
+ */
+ public function stream_close()
+ {
+ return fclose($this->filehandle);
+ }
+}
+
+if (!stream_wrapper_register('test', 'TestStream')) {
+ die('Failed to register protocol');
+}
diff --git a/mustache/test/fixtures/autoloader/Mustache/Bar.php b/mustache/test/fixtures/autoloader/Mustache/Bar.php
new file mode 100755
index 0000000..35c2668
--- /dev/null
+++ b/mustache/test/fixtures/autoloader/Mustache/Bar.php
@@ -0,0 +1,15 @@
+ 'Punk',
+ 'subgenres' => array(
+ array(
+ 'name' => 'Hardcore',
+ 'subgenres' => array(
+ array(
+ 'name' => 'First wave of black metal',
+ 'subgenres' => array(
+ array('name' => 'Norwegian black metal'),
+ array(
+ 'name' => 'Death metal',
+ 'subgenres' => array(
+ array(
+ 'name' => 'Swedish death metal',
+ 'subgenres' => array(
+ array('name' => 'New wave of American metal'),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ array(
+ 'name' => 'Thrash metal',
+ 'subgenres' => array(
+ array('name' => 'Grindcore'),
+ array(
+ 'name' => 'Metalcore',
+ 'subgenres' => array(
+ array('name' => 'Nu metal'),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+}
diff --git a/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.mustache b/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.mustache
new file mode 100755
index 0000000..9494fb1
--- /dev/null
+++ b/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.mustache
@@ -0,0 +1,4 @@
+{{% ANCHORED-DOT }}
+{{# genres }}
+{{> genre }}
+{{/ genres }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.txt b/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.txt
new file mode 100755
index 0000000..fa56efc
--- /dev/null
+++ b/mustache/test/fixtures/examples/anchored_dot_notation/anchored_dot_notation.txt
@@ -0,0 +1,11 @@
+ - Punk
+ - Hardcore
+ - First wave of black metal
+ - Norwegian black metal
+ - Death metal
+ - Swedish death metal
+ - New wave of American metal
+ - Thrash metal
+ - Grindcore
+ - Metalcore
+ - Nu metal
diff --git a/mustache/test/fixtures/examples/anchored_dot_notation/partials/genre.mustache b/mustache/test/fixtures/examples/anchored_dot_notation/partials/genre.mustache
new file mode 100755
index 0000000..f51c503
--- /dev/null
+++ b/mustache/test/fixtures/examples/anchored_dot_notation/partials/genre.mustache
@@ -0,0 +1,5 @@
+{{% ANCHORED-DOT }}
+ - {{ name }}
+{{# .subgenres }}
+ {{> genre }}
+{{/ .subgenres }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/blocks/Blocks.php b/mustache/test/fixtures/examples/blocks/Blocks.php
new file mode 100755
index 0000000..6362aca
--- /dev/null
+++ b/mustache/test/fixtures/examples/blocks/Blocks.php
@@ -0,0 +1,15 @@
+ 'child works',
+ );
+
+ public $grandparent = array(
+ 'parent' => array(
+ 'child' => 'grandchild works',
+ ),
+ );
+}
diff --git a/mustache/test/fixtures/examples/child_context/child_context.mustache b/mustache/test/fixtures/examples/child_context/child_context.mustache
new file mode 100755
index 0000000..e1f2ebc
--- /dev/null
+++ b/mustache/test/fixtures/examples/child_context/child_context.mustache
@@ -0,0 +1,2 @@
+{{#parent}}{{child}}{{/parent}}
+{{#grandparent}}{{#parent}}{{child}}{{/parent}}{{/grandparent}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/child_context/child_context.txt b/mustache/test/fixtures/examples/child_context/child_context.txt
new file mode 100755
index 0000000..cfb76bf
--- /dev/null
+++ b/mustache/test/fixtures/examples/child_context/child_context.txt
@@ -0,0 +1,2 @@
+child works
+grandchild works
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/comments/Comments.php b/mustache/test/fixtures/examples/comments/Comments.php
new file mode 100755
index 0000000..88fe821
--- /dev/null
+++ b/mustache/test/fixtures/examples/comments/Comments.php
@@ -0,0 +1,18 @@
+{{title}}{{! just something interesting... #or ^not... }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/comments/comments.txt b/mustache/test/fixtures/examples/comments/comments.txt
new file mode 100755
index 0000000..9f40e77
--- /dev/null
+++ b/mustache/test/fixtures/examples/comments/comments.txt
@@ -0,0 +1 @@
+A Comedy of Errors
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/complex/complex.mustache b/mustache/test/fixtures/examples/complex/complex.mustache
new file mode 100755
index 0000000..807c201
--- /dev/null
+++ b/mustache/test/fixtures/examples/complex/complex.mustache
@@ -0,0 +1,16 @@
+{{header}}
+{{#notEmpty}}
+
+{{#item}}
+{{#current}}
+ {{name}}
+{{/current}}
+{{^current}}
+ {{name}}
+{{/current}}
+{{/item}}
+
+{{/notEmpty}}
+{{#isEmpty}}
+The list is empty.
+{{/isEmpty}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/complex/complex.php b/mustache/test/fixtures/examples/complex/complex.php
new file mode 100755
index 0000000..7c69615
--- /dev/null
+++ b/mustache/test/fixtures/examples/complex/complex.php
@@ -0,0 +1,31 @@
+ 'red', 'current' => true, 'url' => '#Red'),
+ array('name' => 'green', 'current' => false, 'url' => '#Green'),
+ array('name' => 'blue', 'current' => false, 'url' => '#Blue'),
+ );
+
+ public function notEmpty()
+ {
+ return !($this->isEmpty());
+ }
+
+ public function isEmpty()
+ {
+ return count($this->item) === 0;
+ }
+}
diff --git a/mustache/test/fixtures/examples/complex/complex.txt b/mustache/test/fixtures/examples/complex/complex.txt
new file mode 100755
index 0000000..facee6d
--- /dev/null
+++ b/mustache/test/fixtures/examples/complex/complex.txt
@@ -0,0 +1,6 @@
+Colors
+
diff --git a/mustache/test/fixtures/examples/delimiters/Delimiters.php b/mustache/test/fixtures/examples/delimiters/Delimiters.php
new file mode 100755
index 0000000..235a088
--- /dev/null
+++ b/mustache/test/fixtures/examples/delimiters/Delimiters.php
@@ -0,0 +1,25 @@
+ 'And it worked the second time.'),
+ array('item' => 'As well as the third.'),
+ );
+ }
+
+ public $final = 'Then, surprisingly, it worked the final time.';
+}
diff --git a/mustache/test/fixtures/examples/delimiters/delimiters.mustache b/mustache/test/fixtures/examples/delimiters/delimiters.mustache
new file mode 100755
index 0000000..e9b0332
--- /dev/null
+++ b/mustache/test/fixtures/examples/delimiters/delimiters.mustache
@@ -0,0 +1,8 @@
+{{=<% %>=}}
+* <% start %>
+<%=| |=%>
+|# middle |
+* | item |
+|/ middle |
+|={{ }}=|
+* {{ final }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/delimiters/delimiters.txt b/mustache/test/fixtures/examples/delimiters/delimiters.txt
new file mode 100755
index 0000000..e6b2d7a
--- /dev/null
+++ b/mustache/test/fixtures/examples/delimiters/delimiters.txt
@@ -0,0 +1,4 @@
+* It worked the first time.
+* And it worked the second time.
+* As well as the third.
+* Then, surprisingly, it worked the final time.
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/dot_notation/DotNotation.php b/mustache/test/fixtures/examples/dot_notation/DotNotation.php
new file mode 100755
index 0000000..eadbf61
--- /dev/null
+++ b/mustache/test/fixtures/examples/dot_notation/DotNotation.php
@@ -0,0 +1,24 @@
+ array('first' => 'Chris', 'last' => 'Firescythe'),
+ 'age' => 24,
+ 'hometown' => array(
+ 'city' => 'Cincinnati',
+ 'state' => 'OH',
+ ),
+ );
+
+ public $normal = 'Normal';
+}
diff --git a/mustache/test/fixtures/examples/dot_notation/dot_notation.mustache b/mustache/test/fixtures/examples/dot_notation/dot_notation.mustache
new file mode 100755
index 0000000..0566867
--- /dev/null
+++ b/mustache/test/fixtures/examples/dot_notation/dot_notation.mustache
@@ -0,0 +1,4 @@
+* {{person.name.first}} {{person.name.last}}
+* {{person.age}}
+* {{person.hometown.city}}, {{person.hometown.state}}
+* {{normal}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/dot_notation/dot_notation.txt b/mustache/test/fixtures/examples/dot_notation/dot_notation.txt
new file mode 100755
index 0000000..f8cf1fa
--- /dev/null
+++ b/mustache/test/fixtures/examples/dot_notation/dot_notation.txt
@@ -0,0 +1,4 @@
+* Chris Firescythe
+* 24
+* Cincinnati, OH
+* Normal
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/double_section/DoubleSection.php b/mustache/test/fixtures/examples/double_section/DoubleSection.php
new file mode 100755
index 0000000..1b3f6b6
--- /dev/null
+++ b/mustache/test/fixtures/examples/double_section/DoubleSection.php
@@ -0,0 +1,20 @@
+ "Shark"';
+}
diff --git a/mustache/test/fixtures/examples/escaped/escaped.mustache b/mustache/test/fixtures/examples/escaped/escaped.mustache
new file mode 100755
index 0000000..8be4ccb
--- /dev/null
+++ b/mustache/test/fixtures/examples/escaped/escaped.mustache
@@ -0,0 +1 @@
+{{title}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/escaped/escaped.txt b/mustache/test/fixtures/examples/escaped/escaped.txt
new file mode 100755
index 0000000..6ba3657
--- /dev/null
+++ b/mustache/test/fixtures/examples/escaped/escaped.txt
@@ -0,0 +1 @@
+"Bear" > "Shark"
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/filters/Filters.php b/mustache/test/fixtures/examples/filters/Filters.php
new file mode 100755
index 0000000..eb6e3b7
--- /dev/null
+++ b/mustache/test/fixtures/examples/filters/Filters.php
@@ -0,0 +1,97 @@
+ 'Alabama',
+ 'ak' => 'Alaska',
+ 'az' => 'Arizona',
+ 'ar' => 'Arkansas',
+ 'ca' => 'California',
+ 'co' => 'Colorado',
+ 'ct' => 'Connecticut',
+ 'de' => 'Delaware',
+ 'fl' => 'Florida',
+ 'ga' => 'Georgia',
+ 'hi' => 'Hawaii',
+ 'id' => 'Idaho',
+ 'il' => 'Illinois',
+ 'in' => 'Indiana',
+ 'ia' => 'Iowa',
+ 'ks' => 'Kansas',
+ 'ky' => 'Kentucky',
+ 'la' => 'Louisiana',
+ 'me' => 'Maine',
+ 'md' => 'Maryland',
+ 'ma' => 'Massachusetts',
+ 'mi' => 'Michigan',
+ 'mn' => 'Minnesota',
+ 'ms' => 'Mississippi',
+ 'mo' => 'Missouri',
+ 'mt' => 'Montana',
+ 'ne' => 'Nebraska',
+ 'nv' => 'Nevada',
+ 'nh' => 'New Hampshire',
+ 'nj' => 'New Jersey',
+ 'nm' => 'New Mexico',
+ 'ny' => 'New York',
+ 'nc' => 'North Carolina',
+ 'nd' => 'North Dakota',
+ 'oh' => 'Ohio',
+ 'ok' => 'Oklahoma',
+ 'or' => 'Oregon',
+ 'pa' => 'Pennsylvania',
+ 'ri' => 'Rhode Island',
+ 'sc' => 'South Carolina',
+ 'sd' => 'South Dakota',
+ 'tn' => 'Tennessee',
+ 'tx' => 'Texas',
+ 'ut' => 'Utah',
+ 'vt' => 'Vermont',
+ 'va' => 'Virginia',
+ 'wa' => 'Washington',
+ 'wv' => 'West Virginia',
+ 'wi' => 'Wisconsin',
+ 'wy' => 'Wyoming',
+ );
+
+ // The next few functions are ugly, because they have to work in PHP 5.2...
+ // for everyone who doesn't have to support 5.2, please, for the love, make
+ // your ViewModel return closures rather than `array($this, '...')`
+ //
+ // :)
+
+ public function upcase()
+ {
+ return array($this, '_upcase');
+ }
+
+ public function _upcase($val)
+ {
+ return strtoupper($val);
+ }
+
+ public function eachPair()
+ {
+ return array($this, '_eachPair');
+ }
+
+ public function _eachPair($val)
+ {
+ $ret = array();
+ foreach ($val as $key => $value) {
+ array_push($ret, compact('key', 'value'));
+ }
+
+ return $ret;
+ }
+}
diff --git a/mustache/test/fixtures/examples/filters/filters.mustache b/mustache/test/fixtures/examples/filters/filters.mustache
new file mode 100755
index 0000000..50f1465
--- /dev/null
+++ b/mustache/test/fixtures/examples/filters/filters.mustache
@@ -0,0 +1,4 @@
+{{%FILTERS}}
+{{# states | eachPair }}
+{{ key | upcase }}: {{ value }}
+{{/ states }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/filters/filters.txt b/mustache/test/fixtures/examples/filters/filters.txt
new file mode 100755
index 0000000..67466fd
--- /dev/null
+++ b/mustache/test/fixtures/examples/filters/filters.txt
@@ -0,0 +1,50 @@
+AL: Alabama
+AK: Alaska
+AZ: Arizona
+AR: Arkansas
+CA: California
+CO: Colorado
+CT: Connecticut
+DE: Delaware
+FL: Florida
+GA: Georgia
+HI: Hawaii
+ID: Idaho
+IL: Illinois
+IN: Indiana
+IA: Iowa
+KS: Kansas
+KY: Kentucky
+LA: Louisiana
+ME: Maine
+MD: Maryland
+MA: Massachusetts
+MI: Michigan
+MN: Minnesota
+MS: Mississippi
+MO: Missouri
+MT: Montana
+NE: Nebraska
+NV: Nevada
+NH: New Hampshire
+NJ: New Jersey
+NM: New Mexico
+NY: New York
+NC: North Carolina
+ND: North Dakota
+OH: Ohio
+OK: Oklahoma
+OR: Oregon
+PA: Pennsylvania
+RI: Rhode Island
+SC: South Carolina
+SD: South Dakota
+TN: Tennessee
+TX: Texas
+UT: Utah
+VT: Vermont
+VA: Virginia
+WA: Washington
+WV: West Virginia
+WI: Wisconsin
+WY: Wyoming
diff --git a/mustache/test/fixtures/examples/grand_parent_context/GrandParentContext.php b/mustache/test/fixtures/examples/grand_parent_context/GrandParentContext.php
new file mode 100755
index 0000000..2ee0e42
--- /dev/null
+++ b/mustache/test/fixtures/examples/grand_parent_context/GrandParentContext.php
@@ -0,0 +1,33 @@
+parent_contexts[] = array('parent_id' => 'parent1', 'child_contexts' => array(
+ array('child_id' => 'parent1-child1'),
+ array('child_id' => 'parent1-child2'),
+ ));
+
+ $parent2 = new stdClass();
+ $parent2->parent_id = 'parent2';
+ $parent2->child_contexts = array(
+ array('child_id' => 'parent2-child1'),
+ array('child_id' => 'parent2-child2'),
+ );
+
+ $this->parent_contexts[] = $parent2;
+ }
+}
diff --git a/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.mustache b/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.mustache
new file mode 100755
index 0000000..6d03ddf
--- /dev/null
+++ b/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.mustache
@@ -0,0 +1,7 @@
+{{grand_parent_id}}
+{{#parent_contexts}}
+ {{parent_id}} ({{grand_parent_id}})
+ {{#child_contexts}}
+ {{child_id}} ({{parent_id}} << {{grand_parent_id}})
+ {{/child_contexts}}
+{{/parent_contexts}}
diff --git a/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.txt b/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.txt
new file mode 100755
index 0000000..2687f84
--- /dev/null
+++ b/mustache/test/fixtures/examples/grand_parent_context/grand_parent_context.txt
@@ -0,0 +1,7 @@
+grand_parent1
+ parent1 (grand_parent1)
+ parent1-child1 (parent1 << grand_parent1)
+ parent1-child2 (parent1 << grand_parent1)
+ parent2 (grand_parent1)
+ parent2-child1 (parent2 << grand_parent1)
+ parent2-child2 (parent2 << grand_parent1)
diff --git a/mustache/test/fixtures/examples/i18n/I18n.php b/mustache/test/fixtures/examples/i18n/I18n.php
new file mode 100755
index 0000000..c36bf70
--- /dev/null
+++ b/mustache/test/fixtures/examples/i18n/I18n.php
@@ -0,0 +1,30 @@
+ 'Hola.',
+ 'My name is {{ name }}.' => 'Me llamo {{ name }}.',
+ );
+
+ public static function __trans($text)
+ {
+ return isset(self::$dictionary[$text]) ? self::$dictionary[$text] : $text;
+ }
+}
diff --git a/mustache/test/fixtures/examples/i18n/i18n.mustache b/mustache/test/fixtures/examples/i18n/i18n.mustache
new file mode 100755
index 0000000..eef169b
--- /dev/null
+++ b/mustache/test/fixtures/examples/i18n/i18n.mustache
@@ -0,0 +1 @@
+{{#__}}Hello.{{/__}} {{#__}}My name is {{ name }}.{{/__}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/i18n/i18n.txt b/mustache/test/fixtures/examples/i18n/i18n.txt
new file mode 100755
index 0000000..650a71a
--- /dev/null
+++ b/mustache/test/fixtures/examples/i18n/i18n.txt
@@ -0,0 +1 @@
+Hola. Me llamo Bob.
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/implicit_iterator/ImplicitIterator.php b/mustache/test/fixtures/examples/implicit_iterator/ImplicitIterator.php
new file mode 100755
index 0000000..074f8f4
--- /dev/null
+++ b/mustache/test/fixtures/examples/implicit_iterator/ImplicitIterator.php
@@ -0,0 +1,15 @@
+{{name}}
+{{/repo}}
+{{^repo}}
+ No repos :(
+{{/repo}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/inverted_section/inverted_section.txt b/mustache/test/fixtures/examples/inverted_section/inverted_section.txt
new file mode 100755
index 0000000..6ba5a99
--- /dev/null
+++ b/mustache/test/fixtures/examples/inverted_section/inverted_section.txt
@@ -0,0 +1 @@
+ No repos :(
diff --git a/mustache/test/fixtures/examples/nested_partials/NestedPartials.php b/mustache/test/fixtures/examples/nested_partials/NestedPartials.php
new file mode 100755
index 0000000..a2a2bb1
--- /dev/null
+++ b/mustache/test/fixtures/examples/nested_partials/NestedPartials.php
@@ -0,0 +1,15 @@
+
+ {{> second }}
+
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/nested_partials/nested_partials.txt b/mustache/test/fixtures/examples/nested_partials/nested_partials.txt
new file mode 100755
index 0000000..62776f9
--- /dev/null
+++ b/mustache/test/fixtures/examples/nested_partials/nested_partials.txt
@@ -0,0 +1,7 @@
+
+
+
+ FOURTH!
+
+
+
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/nested_partials/partials/fourth.mustache b/mustache/test/fixtures/examples/nested_partials/partials/fourth.mustache
new file mode 100755
index 0000000..727676f
--- /dev/null
+++ b/mustache/test/fixtures/examples/nested_partials/partials/fourth.mustache
@@ -0,0 +1 @@
+{{ val }}
diff --git a/mustache/test/fixtures/examples/nested_partials/partials/second.mustache b/mustache/test/fixtures/examples/nested_partials/partials/second.mustache
new file mode 100755
index 0000000..83f33cf
--- /dev/null
+++ b/mustache/test/fixtures/examples/nested_partials/partials/second.mustache
@@ -0,0 +1,3 @@
+
+ {{> third }}
+
diff --git a/mustache/test/fixtures/examples/nested_partials/partials/third.mustache b/mustache/test/fixtures/examples/nested_partials/partials/third.mustache
new file mode 100755
index 0000000..f33301a
--- /dev/null
+++ b/mustache/test/fixtures/examples/nested_partials/partials/third.mustache
@@ -0,0 +1,3 @@
+
+ {{> fourth }}
+
diff --git a/mustache/test/fixtures/examples/partials/Partials.php b/mustache/test/fixtures/examples/partials/Partials.php
new file mode 100755
index 0000000..e91dc68
--- /dev/null
+++ b/mustache/test/fixtures/examples/partials/Partials.php
@@ -0,0 +1,19 @@
+ 'Page Title',
+ 'subtitle' => 'Page Subtitle',
+ 'content' => 'Lorem ipsum dolor sit amet.',
+ );
+}
diff --git a/mustache/test/fixtures/examples/partials/partials.mustache b/mustache/test/fixtures/examples/partials/partials.mustache
new file mode 100755
index 0000000..54cf1c5
--- /dev/null
+++ b/mustache/test/fixtures/examples/partials/partials.mustache
@@ -0,0 +1,7 @@
+
+ {{> header }}
+
+
+ {{ page.content }}
+
+
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/partials/partials.txt b/mustache/test/fixtures/examples/partials/partials.txt
new file mode 100755
index 0000000..f8e45ce
--- /dev/null
+++ b/mustache/test/fixtures/examples/partials/partials.txt
@@ -0,0 +1,8 @@
+
+
Page Title
+
Page Subtitle
+
+
+ Lorem ipsum dolor sit amet.
+
+
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/partials/partials/header.mustache b/mustache/test/fixtures/examples/partials/partials/header.mustache
new file mode 100755
index 0000000..88d567b
--- /dev/null
+++ b/mustache/test/fixtures/examples/partials/partials/header.mustache
@@ -0,0 +1,4 @@
+{{# page }}
+{{ title }}
+{{ subtitle }}
+{{/ page }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/recursive_partials/RecursivePartials.php b/mustache/test/fixtures/examples/recursive_partials/RecursivePartials.php
new file mode 100755
index 0000000..15b6973
--- /dev/null
+++ b/mustache/test/fixtures/examples/recursive_partials/RecursivePartials.php
@@ -0,0 +1,22 @@
+ 'Dan',
+ 'child' => array(
+ 'name' => 'Justin',
+ 'child' => false,
+ ),
+ );
+}
diff --git a/mustache/test/fixtures/examples/recursive_partials/partials/child.mustache b/mustache/test/fixtures/examples/recursive_partials/partials/child.mustache
new file mode 100755
index 0000000..1282941
--- /dev/null
+++ b/mustache/test/fixtures/examples/recursive_partials/partials/child.mustache
@@ -0,0 +1 @@
+ > {{ name }}{{#child}}{{>child}}{{/child}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/recursive_partials/recursive_partials.mustache b/mustache/test/fixtures/examples/recursive_partials/recursive_partials.mustache
new file mode 100755
index 0000000..0bc5d03
--- /dev/null
+++ b/mustache/test/fixtures/examples/recursive_partials/recursive_partials.mustache
@@ -0,0 +1 @@
+{{name}}{{#child}}{{>child}}{{/child}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/recursive_partials/recursive_partials.txt b/mustache/test/fixtures/examples/recursive_partials/recursive_partials.txt
new file mode 100755
index 0000000..681cdef
--- /dev/null
+++ b/mustache/test/fixtures/examples/recursive_partials/recursive_partials.txt
@@ -0,0 +1 @@
+George > Dan > Justin
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/section_iterator_objects/SectionIteratorObjects.php b/mustache/test/fixtures/examples/section_iterator_objects/SectionIteratorObjects.php
new file mode 100755
index 0000000..0dc793c
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_iterator_objects/SectionIteratorObjects.php
@@ -0,0 +1,27 @@
+ 'And it worked the second time.'),
+ array('item' => 'As well as the third.'),
+ );
+
+ public function middle()
+ {
+ return new ArrayIterator($this->_data);
+ }
+
+ public $final = 'Then, surprisingly, it worked the final time.';
+}
diff --git a/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.mustache b/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.mustache
new file mode 100755
index 0000000..44dfce4
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.mustache
@@ -0,0 +1,5 @@
+* {{ start }}
+{{# middle }}
+* {{ item }}
+{{/ middle }}
+* {{ final }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.txt b/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.txt
new file mode 100755
index 0000000..e6b2d7a
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_iterator_objects/section_iterator_objects.txt
@@ -0,0 +1,4 @@
+* It worked the first time.
+* And it worked the second time.
+* As well as the third.
+* Then, surprisingly, it worked the final time.
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/section_magic_objects/SectionMagicObjects.php b/mustache/test/fixtures/examples/section_magic_objects/SectionMagicObjects.php
new file mode 100755
index 0000000..0fcb3b4
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_magic_objects/SectionMagicObjects.php
@@ -0,0 +1,40 @@
+ 'And it worked the second time.',
+ 'bar' => 'As well as the third.',
+ );
+
+ public function __get($key)
+ {
+ return isset($this->_data[$key]) ? $this->_data[$key] : null;
+ }
+
+ public function __isset($key)
+ {
+ return isset($this->_data[$key]);
+ }
+}
diff --git a/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.mustache b/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.mustache
new file mode 100755
index 0000000..9119608
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.mustache
@@ -0,0 +1,6 @@
+* {{ start }}
+{{# middle }}
+* {{ foo }}
+* {{ bar }}
+{{/ middle }}
+* {{ final }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.txt b/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.txt
new file mode 100755
index 0000000..e6b2d7a
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_magic_objects/section_magic_objects.txt
@@ -0,0 +1,4 @@
+* It worked the first time.
+* And it worked the second time.
+* As well as the third.
+* Then, surprisingly, it worked the final time.
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/section_objects/SectionObjects.php b/mustache/test/fixtures/examples/section_objects/SectionObjects.php
new file mode 100755
index 0000000..913c3eb
--- /dev/null
+++ b/mustache/test/fixtures/examples/section_objects/SectionObjects.php
@@ -0,0 +1,28 @@
+ 'And it worked the second time.'),
+ array('item' => 'As well as the third.'),
+ );
+ }
+
+ public $final = 'Then, surprisingly, it worked the final time.';
+}
diff --git a/mustache/test/fixtures/examples/sections/sections.mustache b/mustache/test/fixtures/examples/sections/sections.mustache
new file mode 100755
index 0000000..44dfce4
--- /dev/null
+++ b/mustache/test/fixtures/examples/sections/sections.mustache
@@ -0,0 +1,5 @@
+* {{ start }}
+{{# middle }}
+* {{ item }}
+{{/ middle }}
+* {{ final }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/sections/sections.txt b/mustache/test/fixtures/examples/sections/sections.txt
new file mode 100755
index 0000000..e6b2d7a
--- /dev/null
+++ b/mustache/test/fixtures/examples/sections/sections.txt
@@ -0,0 +1,4 @@
+* It worked the first time.
+* And it worked the second time.
+* As well as the third.
+* Then, surprisingly, it worked the final time.
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/sections_nested/SectionsNested.php b/mustache/test/fixtures/examples/sections_nested/SectionsNested.php
new file mode 100755
index 0000000..43657c3
--- /dev/null
+++ b/mustache/test/fixtures/examples/sections_nested/SectionsNested.php
@@ -0,0 +1,44 @@
+ 'Von Kaiser',
+ 'enemies' => array(
+ array('name' => 'Super Macho Man'),
+ array('name' => 'Piston Honda'),
+ array('name' => 'Mr. Sandman'),
+ ),
+ ),
+ array(
+ 'name' => 'Mike Tyson',
+ 'enemies' => array(
+ array('name' => 'Soda Popinski'),
+ array('name' => 'King Hippo'),
+ array('name' => 'Great Tiger'),
+ array('name' => 'Glass Joe'),
+ ),
+ ),
+ array(
+ 'name' => 'Don Flamenco',
+ 'enemies' => array(
+ array('name' => 'Bald Bull'),
+ ),
+ ),
+ );
+ }
+}
diff --git a/mustache/test/fixtures/examples/sections_nested/sections_nested.mustache b/mustache/test/fixtures/examples/sections_nested/sections_nested.mustache
new file mode 100755
index 0000000..9f8007d
--- /dev/null
+++ b/mustache/test/fixtures/examples/sections_nested/sections_nested.mustache
@@ -0,0 +1,7 @@
+Enemies of {{ name }}:
+{{# enemies }}
+{{ name }} ... who also has enemies:
+{{# enemies }}
+--> {{ name }}
+{{/ enemies }}
+{{/ enemies }}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/sections_nested/sections_nested.txt b/mustache/test/fixtures/examples/sections_nested/sections_nested.txt
new file mode 100755
index 0000000..72c44d0
--- /dev/null
+++ b/mustache/test/fixtures/examples/sections_nested/sections_nested.txt
@@ -0,0 +1,12 @@
+Enemies of Little Mac:
+Von Kaiser ... who also has enemies:
+--> Super Macho Man
+--> Piston Honda
+--> Mr. Sandman
+Mike Tyson ... who also has enemies:
+--> Soda Popinski
+--> King Hippo
+--> Great Tiger
+--> Glass Joe
+Don Flamenco ... who also has enemies:
+--> Bald Bull
diff --git a/mustache/test/fixtures/examples/simple/Simple.php b/mustache/test/fixtures/examples/simple/Simple.php
new file mode 100755
index 0000000..a5492e8
--- /dev/null
+++ b/mustache/test/fixtures/examples/simple/Simple.php
@@ -0,0 +1,23 @@
+value - ($this->value * 0.4);
+ }
+
+ public $in_ca = true;
+}
diff --git a/mustache/test/fixtures/examples/simple/simple.mustache b/mustache/test/fixtures/examples/simple/simple.mustache
new file mode 100755
index 0000000..03df206
--- /dev/null
+++ b/mustache/test/fixtures/examples/simple/simple.mustache
@@ -0,0 +1,5 @@
+Hello {{name}}
+You have just won ${{value}}!
+{{#in_ca}}
+Well, ${{ taxed_value }}, after taxes.
+{{/in_ca}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/simple/simple.txt b/mustache/test/fixtures/examples/simple/simple.txt
new file mode 100755
index 0000000..5d75d65
--- /dev/null
+++ b/mustache/test/fixtures/examples/simple/simple.txt
@@ -0,0 +1,3 @@
+Hello Chris
+You have just won $10000!
+Well, $6000, after taxes.
diff --git a/mustache/test/fixtures/examples/unescaped/Unescaped.php b/mustache/test/fixtures/examples/unescaped/Unescaped.php
new file mode 100755
index 0000000..ea85f88
--- /dev/null
+++ b/mustache/test/fixtures/examples/unescaped/Unescaped.php
@@ -0,0 +1,15 @@
+ Shark';
+}
diff --git a/mustache/test/fixtures/examples/unescaped/unescaped.mustache b/mustache/test/fixtures/examples/unescaped/unescaped.mustache
new file mode 100755
index 0000000..9982708
--- /dev/null
+++ b/mustache/test/fixtures/examples/unescaped/unescaped.mustache
@@ -0,0 +1 @@
+{{{title}}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/unescaped/unescaped.txt b/mustache/test/fixtures/examples/unescaped/unescaped.txt
new file mode 100755
index 0000000..01fa404
--- /dev/null
+++ b/mustache/test/fixtures/examples/unescaped/unescaped.txt
@@ -0,0 +1 @@
+Bear > Shark
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/utf8/UTF8.php b/mustache/test/fixtures/examples/utf8/UTF8.php
new file mode 100755
index 0000000..c53fec9
--- /dev/null
+++ b/mustache/test/fixtures/examples/utf8/UTF8.php
@@ -0,0 +1,15 @@
+中文 {{test}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/utf8/utf8.txt b/mustache/test/fixtures/examples/utf8/utf8.txt
new file mode 100755
index 0000000..bf17971
--- /dev/null
+++ b/mustache/test/fixtures/examples/utf8/utf8.txt
@@ -0,0 +1 @@
+中文 中文又来啦
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/utf8_unescaped/UTF8Unescaped.php b/mustache/test/fixtures/examples/utf8_unescaped/UTF8Unescaped.php
new file mode 100755
index 0000000..67f9366
--- /dev/null
+++ b/mustache/test/fixtures/examples/utf8_unescaped/UTF8Unescaped.php
@@ -0,0 +1,15 @@
+中文 {{{test}}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/utf8_unescaped/utf8_unescaped.txt b/mustache/test/fixtures/examples/utf8_unescaped/utf8_unescaped.txt
new file mode 100755
index 0000000..bf17971
--- /dev/null
+++ b/mustache/test/fixtures/examples/utf8_unescaped/utf8_unescaped.txt
@@ -0,0 +1 @@
+中文 中文又来啦
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/whitespace/Whitespace.php b/mustache/test/fixtures/examples/whitespace/Whitespace.php
new file mode 100755
index 0000000..57a3267
--- /dev/null
+++ b/mustache/test/fixtures/examples/whitespace/Whitespace.php
@@ -0,0 +1,43 @@
+ tag }}` and `{{> tag}}` and `{{>tag}}` should all be equivalent.
+ */
+class Whitespace
+{
+ public $foo = 'alpha';
+
+ public $bar = 'beta';
+
+ public function baz()
+ {
+ return 'gamma';
+ }
+
+ public function qux()
+ {
+ return array(
+ array('key with space' => 'A'),
+ array('key with space' => 'B'),
+ array('key with space' => 'C'),
+ array('key with space' => 'D'),
+ array('key with space' => 'E'),
+ array('key with space' => 'F'),
+ array('key with space' => 'G'),
+ );
+ }
+}
diff --git a/mustache/test/fixtures/examples/whitespace/partials/alphabet.mustache b/mustache/test/fixtures/examples/whitespace/partials/alphabet.mustache
new file mode 100755
index 0000000..d281c41
--- /dev/null
+++ b/mustache/test/fixtures/examples/whitespace/partials/alphabet.mustache
@@ -0,0 +1 @@
+ * {{.}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/whitespace/whitespace.mustache b/mustache/test/fixtures/examples/whitespace/whitespace.mustache
new file mode 100755
index 0000000..0b3ba00
--- /dev/null
+++ b/mustache/test/fixtures/examples/whitespace/whitespace.mustache
@@ -0,0 +1,10 @@
+{{^ inverted section test }}
+These are some things:
+{{/inverted section test }}
+* {{ foo }}
+* {{ bar}}
+* {{ baz }}
+{{# qux }}
+* {{ key with space }}
+{{/ qux }}
+{{#qux}}.{{/qux}}
\ No newline at end of file
diff --git a/mustache/test/fixtures/examples/whitespace/whitespace.txt b/mustache/test/fixtures/examples/whitespace/whitespace.txt
new file mode 100755
index 0000000..5226c69
--- /dev/null
+++ b/mustache/test/fixtures/examples/whitespace/whitespace.txt
@@ -0,0 +1,12 @@
+These are some things:
+* alpha
+* beta
+* gamma
+* A
+* B
+* C
+* D
+* E
+* F
+* G
+.......
\ No newline at end of file
diff --git a/mustache/test/fixtures/templates/alpha.ms b/mustache/test/fixtures/templates/alpha.ms
new file mode 100755
index 0000000..3845830
--- /dev/null
+++ b/mustache/test/fixtures/templates/alpha.ms
@@ -0,0 +1 @@
+alpha contents
\ No newline at end of file
diff --git a/mustache/test/fixtures/templates/beta.ms b/mustache/test/fixtures/templates/beta.ms
new file mode 100755
index 0000000..a083dfe
--- /dev/null
+++ b/mustache/test/fixtures/templates/beta.ms
@@ -0,0 +1 @@
+beta contents
\ No newline at end of file
diff --git a/mustache/test/fixtures/templates/one.mustache b/mustache/test/fixtures/templates/one.mustache
new file mode 100755
index 0000000..f83ad09
--- /dev/null
+++ b/mustache/test/fixtures/templates/one.mustache
@@ -0,0 +1 @@
+one contents
\ No newline at end of file
diff --git a/mustache/test/fixtures/templates/two.mustache b/mustache/test/fixtures/templates/two.mustache
new file mode 100755
index 0000000..dacc40e
--- /dev/null
+++ b/mustache/test/fixtures/templates/two.mustache
@@ -0,0 +1 @@
+two contents
\ No newline at end of file