Selaa lähdekoodia

Update to proposed PSR-1 coding standard.

 * 4 space (no tab) indent
 * K&R-style braces
Justin Hileman 13 vuotta sitten
vanhempi
sitoutus
2448992cc8
57 muutettua tiedostoa jossa 3508 lisäystä ja 3234 poistoa
  1. 33 33
      bin/create_example.php
  2. 47 43
      src/Mustache/Autoloader.php
  3. 366 351
      src/Mustache/Compiler.php
  4. 123 115
      src/Mustache/Context.php
  5. 563 531
      src/Mustache/Engine.php
  6. 153 141
      src/Mustache/HelperCollection.php
  7. 10 9
      src/Mustache/Loader.php
  8. 44 39
      src/Mustache/Loader/ArrayLoader.php
  9. 79 74
      src/Mustache/Loader/FilesystemLoader.php
  10. 15 14
      src/Mustache/Loader/MutableLoader.php
  11. 13 11
      src/Mustache/Loader/StringLoader.php
  12. 59 56
      src/Mustache/Parser.php
  13. 121 115
      src/Mustache/Template.php
  14. 240 233
      src/Mustache/Tokenizer.php
  15. 17 14
      test/Mustache/Test/AutoloaderTest.php
  16. 80 75
      test/Mustache/Test/CompilerTest.php
  17. 99 91
      test/Mustache/Test/ContextTest.php
  18. 238 222
      test/Mustache/Test/EngineTest.php
  19. 18 14
      test/Mustache/Test/Functional/CallTest.php
  20. 126 121
      test/Mustache/Test/Functional/ExamplesTest.php
  21. 71 59
      test/Mustache/Test/Functional/HigherOrderSectionsTest.php
  22. 101 89
      test/Mustache/Test/Functional/MustacheInjectionTest.php
  23. 157 140
      test/Mustache/Test/Functional/MustacheSpecTest.php
  24. 83 67
      test/Mustache/Test/Functional/ObjectSectionTest.php
  25. 25 18
      test/Mustache/Test/HelperCollectionTest.php
  26. 32 28
      test/Mustache/Test/Loader/ArrayLoaderTest.php
  27. 32 27
      test/Mustache/Test/Loader/FilesystemLoaderTest.php
  28. 9 7
      test/Mustache/Test/Loader/StringLoaderTest.php
  29. 156 153
      test/Mustache/Test/ParserTest.php
  30. 36 30
      test/Mustache/Test/TemplateTest.php
  31. 121 118
      test/Mustache/Test/TokenizerTest.php
  32. 3 2
      test/fixtures/autoloader/Mustache/Bar.php
  33. 3 2
      test/fixtures/autoloader/Mustache/Foo.php
  34. 3 2
      test/fixtures/autoloader/NonMustacheClass.php
  35. 10 9
      test/fixtures/examples/child_context/ChildContext.php
  36. 6 4
      test/fixtures/examples/comments/Comments.php
  37. 16 13
      test/fixtures/examples/complex/complex.php
  38. 11 9
      test/fixtures/examples/delimiters/Delimiters.php
  39. 11 10
      test/fixtures/examples/dot_notation/DotNotation.php
  40. 7 5
      test/fixtures/examples/double_section/DoubleSection.php
  41. 3 2
      test/fixtures/examples/escaped/Escaped.php
  42. 18 16
      test/fixtures/examples/grand_parent_context/GrandParentContext.php
  43. 4 2
      test/fixtures/examples/i18n/I18n.php
  44. 3 2
      test/fixtures/examples/implicit_iterator/ImplicitIterator.php
  45. 4 3
      test/fixtures/examples/inverted_double_section/InvertedDoubleSection.php
  46. 3 2
      test/fixtures/examples/inverted_section/InvertedSection.php
  47. 10 9
      test/fixtures/examples/recursive_partials/RecursivePartials.php
  48. 12 10
      test/fixtures/examples/section_iterator_objects/SectionIteratorObjects.php
  49. 22 17
      test/fixtures/examples/section_magic_objects/SectionMagicObjects.php
  50. 12 9
      test/fixtures/examples/section_objects/SectionObjects.php
  51. 11 9
      test/fixtures/examples/sections/Sections.php
  52. 31 29
      test/fixtures/examples/sections_nested/SectionsNested.php
  53. 9 7
      test/fixtures/examples/simple/Simple.php
  54. 3 2
      test/fixtures/examples/unescaped/Unescaped.php
  55. 3 2
      test/fixtures/examples/utf8/UTF8.php
  56. 3 2
      test/fixtures/examples/utf8_unescaped/UTF8Unescaped.php
  57. 20 17
      test/fixtures/examples/whitespace/Whitespace.php

+ 33 - 33
bin/create_example.php

@@ -38,10 +38,10 @@ define('EXAMPLE_PATH', realpath(dirname(__FILE__) . '/../test/fixtures/examples'
  * @return string
  */
 function getLowerCaseName($name) {
-	return preg_replace_callback("/([A-Z])/", create_function (
-		'$match',
-		'return "_" . strtolower($match[1]);'
-	), lcfirst($name));
+    return preg_replace_callback("/([A-Z])/", create_function (
+        '$match',
+        'return "_" . strtolower($match[1]);'
+    ), lcfirst($name));
 }
 
 /**
@@ -57,10 +57,10 @@ function getLowerCaseName($name) {
  * @return string
  */
 function getUpperCaseName($name) {
-	return preg_replace_callback("/_([a-z])/", create_function (
-		'$match',
-		'return strtoupper($match{1});'
-	), ucfirst($name));
+    return preg_replace_callback("/_([a-z])/", create_function (
+        '$match',
+        'return strtoupper($match{1});'
+    ), ucfirst($name));
 }
 
 
@@ -72,8 +72,8 @@ function getUpperCaseName($name) {
  * @return mixed
  */
 function out($value) {
-	echo $value . "\n";
-	return $value;
+    echo $value . "\n";
+    return $value;
 }
 
 /**
@@ -89,8 +89,8 @@ function out($value) {
  * @return string
  */
 function buildPath($directory, $filename = null,  $extension = null) {
-	return out(EXAMPLE_PATH . '/' . $directory.
-					($extension !== null && $filename !== null ? '/' . $filename. "." . $extension : ""));
+    return out(EXAMPLE_PATH . '/' . $directory.
+                    ($extension !== null && $filename !== null ? '/' . $filename. "." . $extension : ""));
 }
 
 /**
@@ -102,9 +102,9 @@ function buildPath($directory, $filename = null,  $extension = null) {
  * @return void
  */
 function createDirectory($directory) {
-	if(!@mkdir(buildPath($directory))) {
-		die("FAILED to create directory\n");
-	}
+    if(!@mkdir(buildPath($directory))) {
+        die("FAILED to create directory\n");
+    }
 }
 
 /**
@@ -119,13 +119,13 @@ function createDirectory($directory) {
  * @return void
  */
 function createFile($directory, $filename, $extension, $content = "") {
-	$handle = @fopen(buildPath($directory, $filename, $extension), "w");
-	if($handle) {
-		fwrite($handle, $content);
-		fclose($handle);
-	} else {
-		die("FAILED to create file\n");
-	}
+    $handle = @fopen(buildPath($directory, $filename, $extension), "w");
+    if($handle) {
+        fwrite($handle, $content);
+        fclose($handle);
+    } else {
+        die("FAILED to create file\n");
+    }
 }
 
 
@@ -143,12 +143,12 @@ function createFile($directory, $filename, $extension, $content = "") {
  * @return void
  */
 function main($example_name) {
-	$lowercase = getLowerCaseName($example_name);
-	$uppercase = getUpperCaseName($example_name);
-	createDirectory($lowercase);
-	createFile($lowercase, $lowercase, "mustache");
-	createFile($lowercase, $lowercase, "txt");
-	createFile($lowercase, $uppercase, "php", <<<CONTENT
+    $lowercase = getLowerCaseName($example_name);
+    $uppercase = getUpperCaseName($example_name);
+    createDirectory($lowercase);
+    createFile($lowercase, $lowercase, "mustache");
+    createFile($lowercase, $lowercase, "txt");
+    createFile($lowercase, $uppercase, "php", <<<CONTENT
 <?php
 
 class {$uppercase} {
@@ -156,16 +156,16 @@ class {$uppercase} {
 }
 
 CONTENT
-	);
+    );
 }
 
 // check if enougth arguments are given
 if(count($argv) > 1) {
-	// get the name of the example
-	$example_name = $argv[1];
+    // get the name of the example
+    $example_name = $argv[1];
 
-	main($example_name);
+    main($example_name);
 
 } else {
-	echo USAGE;
+    echo USAGE;
 }

+ 47 - 43
src/Mustache/Autoloader.php

@@ -12,54 +12,58 @@
 /**
  * Mustache class autoloader.
  */
-class Mustache_Autoloader {
+class Mustache_Autoloader
+{
 
-	private $baseDir;
+    private $baseDir;
 
-	/**
-	 * Autoloader constructor.
-	 *
-	 * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
-	 */
-	public function __construct($baseDir = null) {
-		if ($baseDir === null) {
-			$this->baseDir = dirname(__FILE__).'/..';
-		} else {
-			$this->baseDir = rtrim($baseDir, '/');
-		}
-	}
+    /**
+     * Autoloader constructor.
+     *
+     * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
+     */
+    public function __construct($baseDir = null)
+    {
+        if ($baseDir === null) {
+            $this->baseDir = dirname(__FILE__).'/..';
+        } else {
+            $this->baseDir = rtrim($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
-	 */
-	static public function register($baseDir = null) {
-		$loader = new self($baseDir);
-		spl_autoload_register(array($loader, 'autoload'));
+    /**
+     * Register a new instance as an SPL autoloader.
+     *
+     * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
+     *
+     * @return Mustache_Autoloader Registered Autoloader instance
+     */
+    static public function register($baseDir = null)
+    {
+        $loader = new self($baseDir);
+        spl_autoload_register(array($loader, 'autoload'));
 
-		return $loader;
-	}
+        return $loader;
+    }
 
-	/**
-	 * Autoload Mustache classes.
-	 *
-	 * @param string $class
-	 */
-	public function autoload($class) {
-		if ($class[0] === '\\') {
-			$class = substr($class, 1);
-		}
+    /**
+     * Autoload Mustache classes.
+     *
+     * @param string $class
+     */
+    public function autoload($class)
+    {
+        if ($class[0] === '\\') {
+            $class = substr($class, 1);
+        }
 
-		if (strpos($class, 'Mustache') !== 0) {
-			return;
-		}
+        if (strpos($class, 'Mustache') !== 0) {
+            return;
+        }
 
-		$file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
-		if (is_file($file)) {
-			require $file;
-		}
-	}
+        $file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
+        if (is_file($file)) {
+            require $file;
+        }
+    }
 }

+ 366 - 351
src/Mustache/Compiler.php

@@ -14,355 +14,370 @@
  *
  * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
  */
-class Mustache_Compiler {
-
-	private $sections;
-	private $source;
-	private $indentNextLine;
-	private $customEscape;
-	private $charset;
-
-	/**
-	 * Compile a Mustache token parse tree into PHP source code.
-	 *
-	 * @param string $source Mustache Template source code
-	 * @param string $tree   Parse tree of Mustache tokens
-	 * @param string $name   Mustache Template class name
-	 *
-	 * @return string Generated PHP source code
-	 */
-	public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8') {
-		$this->sections       = array();
-		$this->source         = $source;
-		$this->indentNextLine = true;
-		$this->customEscape   = $customEscape;
-		$this->charset        = $charset;
-
-		return $this->writeCode($tree, $name);
-	}
-
-	/**
-	 * Helper function for walking the Mustache token parse tree.
-	 *
-	 * @throws InvalidArgumentException 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_SECTION:
-					$code .= $this->section(
-						$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_INVERTED:
-					$code .= $this->invertedSection(
-						$node[Mustache_Tokenizer::NODES],
-						$node[Mustache_Tokenizer::NAME],
-						$level
-					);
-					break;
-
-				case Mustache_Tokenizer::T_PARTIAL:
-				case Mustache_Tokenizer::T_PARTIAL_2:
-					$code .= $this->partial(
-						$node[Mustache_Tokenizer::NAME],
-						isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
-						$level
-					);
-					break;
-
-				case Mustache_Tokenizer::T_UNESCAPED:
-				case Mustache_Tokenizer::T_UNESCAPED_2:
-					$code .= $this->variable($node[Mustache_Tokenizer::NAME], false, $level);
-					break;
-
-				case Mustache_Tokenizer::T_COMMENT:
-					break;
-
-				case Mustache_Tokenizer::T_ESCAPED:
-					$code .= $this->variable($node[Mustache_Tokenizer::NAME], true, $level);
-					break;
-
-
-				case Mustache_Tokenizer::T_TEXT:
-					$code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
-					break;
-
-				default:
-					throw new InvalidArgumentException('Unknown node type: '.json_encode($node));
-			}
-		}
-
-		return $code;
-	}
-
-	const KLASS = '<?php
-
-		class %s extends Mustache_Template {
-			public function renderInternal(Mustache_Context $context, $indent = \'\', $escape = false) {
-				$buffer = \'\';
-		%s
-
-				if ($escape) {
-					return %s;
-				} else {
-					return $buffer;
-				}
-			}
-		%s
-		}';
-
-	/**
-	 * Generate Mustache Template class PHP source.
-	 *
-	 * @param array  $tree Parse tree of Mustache tokens
-	 * @param string $name Mustache Template class name
-	 *
-	 * @return string Generated PHP source code
-	 */
-	private function writeCode($tree, $name) {
-		$code     = $this->walk($tree);
-		$sections = implode("\n", $this->sections);
-
-		return sprintf($this->prepare(self::KLASS, 0, false), $name, $code, $this->getEscape('$buffer'), $sections);
-	}
-
-	const SECTION_CALL = '
-		// %s section
-		$buffer .= $this->section%s($context, $indent, $context->%s(%s));
-	';
-
-	const SECTION = '
-		private function section%s(Mustache_Context $context, $indent, $value) {
-			$buffer = \'\';
-			if (!is_string($value) && is_callable($value)) {
-				$source = %s;
-				$buffer .= $this->mustache
-					->loadLambda((string) call_user_func($value, $source)%s)
-					->renderInternal($context, $indent);
-			} 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 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, $start, $end, $otag, $ctag, $level) {
-		$method = $this->getFindMethod($id);
-		$id     = var_export($id, true);
-		$source = var_export(substr($this->source, $start, $end - $start), true);
-
-		if ($otag !== '{{' || $ctag !== '}}') {
-			$delims = ', '.var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
-		} else {
-			$delims = '';
-		}
-
-		$key    = ucfirst(md5($delims."\n".$source));
-
-		if (!isset($this->sections[$key])) {
-			$this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $source, $delims, $this->walk($nodes, 2));
-		}
-
-		return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $key, $method, $id);
-	}
-
-	const INVERTED_SECTION = '
-		// %s inverted section
-		$value = $context->%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 int    $level
-	 *
-	 * @return string Generated inverted section PHP source code
-	 */
-	private function invertedSection($nodes, $id, $level) {
-		$method = $this->getFindMethod($id);
-		$id     = var_export($id, true);
-
-		return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level));
-	}
-
-	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) {
-		return sprintf(
-			$this->prepare(self::PARTIAL, $level),
-			var_export($id, true),
-			var_export($indent, true)
-		);
-	}
-
-	const VARIABLE = '
-		$value = $context->%s(%s);
-		if (!is_string($value) && is_callable($value)) {
-			$value = $this->mustache
-				->loadLambda((string) call_user_func($value))
-				->renderInternal($context, $indent);
-		}
-		$buffer .= %s%s;
-	';
-
-	/**
-	 * Generate Mustache Template variable interpolation PHP source.
-	 *
-	 * @param string  $id     Variable name
-	 * @param boolean $escape Escape the variable value for output?
-	 * @param int     $level
-	 *
-	 * @return string Generated variable interpolation PHP source
-	 */
-	private function variable($id, $escape, $level) {
-		$method = $this->getFindMethod($id);
-		$id     = ($method !== 'last') ? var_export($id, true) : '';
-		$value  = $escape ? $this->getEscape() : '$value';
-
-		return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $this->flushIndent(), $value);
-	}
-
-	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) {
-		if ($text === "\n") {
-			$this->indentNextLine = true;
-
-			return $this->prepare(self::LINE, $level);
-		} else {
-			return sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
-		}
-	}
-
-	/**
-	 * Prepare PHP source code snippet for output.
-	 *
-	 * @param string  $text
-	 * @param int     $bonus          Additional indent level (default: 0)
-	 * @param boolean $prependNewline Prepend a newline to the snippet? (default: true)
-	 *
-	 * @return string PHP source code snippet
-	 */
-	private function prepare($text, $bonus = 0, $prependNewline = true) {
-		$text = ($prependNewline ? "\n" : '').trim($text);
-		if ($prependNewline) {
-			$bonus++;
-		}
-
-		return preg_replace("/\n(\t\t)?/", "\n".str_repeat("\t", $bonus), $text);
-	}
-
-	const DEFAULT_ESCAPE = 'htmlspecialchars(%s, ENT_COMPAT, %s)';
-	const CUSTOM_ESCAPE  = 'call_user_func($this->mustache->getEscape(), %s)';
-
-	/**
-	 * Get the current escaper.
-	 *
-	 * @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);
-		} else {
-			return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->charset, true));
-		}
-	}
-
-	/**
-	 * Select the appropriate Context `find` method for a given $id.
-	 *
-	 * The return value will be one of `find`, `findDot` 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';
-		} elseif (strpos($id, '.') === false) {
-			return 'find';
-		} else {
-			return 'findDot';
-		}
-	}
-
-	const LINE_INDENT = '$indent . ';
-
-	/**
-	 * Get the current $indent prefix to write to the buffer.
-	 *
-	 * @return string "$indent . " or ""
-	 */
-	private function flushIndent() {
-		if ($this->indentNextLine) {
-			$this->indentNextLine = false;
-
-			return self::LINE_INDENT;
-		} else {
-			return '';
-		}
-	}
+class Mustache_Compiler
+{
+
+    private $sections;
+    private $source;
+    private $indentNextLine;
+    private $customEscape;
+    private $charset;
+
+    /**
+     * Compile a Mustache token parse tree into PHP source code.
+     *
+     * @param string $source Mustache Template source code
+     * @param string $tree   Parse tree of Mustache tokens
+     * @param string $name   Mustache Template class name
+     *
+     * @return string Generated PHP source code
+     */
+    public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8')
+    {
+        $this->sections       = array();
+        $this->source         = $source;
+        $this->indentNextLine = true;
+        $this->customEscape   = $customEscape;
+        $this->charset        = $charset;
+
+        return $this->writeCode($tree, $name);
+    }
+
+    /**
+     * Helper function for walking the Mustache token parse tree.
+     *
+     * @throws InvalidArgumentException 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_SECTION:
+                    $code .= $this->section(
+                        $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_INVERTED:
+                    $code .= $this->invertedSection(
+                        $node[Mustache_Tokenizer::NODES],
+                        $node[Mustache_Tokenizer::NAME],
+                        $level
+                    );
+                    break;
+
+                case Mustache_Tokenizer::T_PARTIAL:
+                case Mustache_Tokenizer::T_PARTIAL_2:
+                    $code .= $this->partial(
+                        $node[Mustache_Tokenizer::NAME],
+                        isset($node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
+                        $level
+                    );
+                    break;
+
+                case Mustache_Tokenizer::T_UNESCAPED:
+                case Mustache_Tokenizer::T_UNESCAPED_2:
+                    $code .= $this->variable($node[Mustache_Tokenizer::NAME], false, $level);
+                    break;
+
+                case Mustache_Tokenizer::T_COMMENT:
+                    break;
+
+                case Mustache_Tokenizer::T_ESCAPED:
+                    $code .= $this->variable($node[Mustache_Tokenizer::NAME], true, $level);
+                    break;
+
+
+                case Mustache_Tokenizer::T_TEXT:
+                    $code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
+                    break;
+
+                default:
+                    throw new InvalidArgumentException('Unknown node type: '.json_encode($node));
+            }
+        }
+
+        return $code;
+    }
+
+    const KLASS = '<?php
+
+        class %s extends Mustache_Template
+        {
+            public function renderInternal(Mustache_Context $context, $indent = \'\', $escape = false)
+            {
+                $buffer = \'\';
+        %s
+
+                if ($escape) {
+                    return %s;
+                } else {
+                    return $buffer;
+                }
+            }
+        %s
+        }';
+
+    /**
+     * Generate Mustache Template class PHP source.
+     *
+     * @param array  $tree Parse tree of Mustache tokens
+     * @param string $name Mustache Template class name
+     *
+     * @return string Generated PHP source code
+     */
+    private function writeCode($tree, $name)
+    {
+        $code     = $this->walk($tree);
+        $sections = implode("\n", $this->sections);
+
+        return sprintf($this->prepare(self::KLASS, 0, false), $name, $code, $this->getEscape('$buffer'), $sections);
+    }
+
+    const SECTION_CALL = '
+        // %s section
+        $buffer .= $this->section%s($context, $indent, $context->%s(%s));
+    ';
+
+    const SECTION = '
+        private function section%s(Mustache_Context $context, $indent, $value) {
+            $buffer = \'\';
+            if (!is_string($value) && is_callable($value)) {
+                $source = %s;
+                $buffer .= $this->mustache
+                    ->loadLambda((string) call_user_func($value, $source)%s)
+                    ->renderInternal($context, $indent);
+            } 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 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, $start, $end, $otag, $ctag, $level)
+    {
+        $method = $this->getFindMethod($id);
+        $id     = var_export($id, true);
+        $source = var_export(substr($this->source, $start, $end - $start), true);
+
+        if ($otag !== '{{' || $ctag !== '}}') {
+            $delims = ', '.var_export(sprintf('{{= %s %s =}}', $otag, $ctag), true);
+        } else {
+            $delims = '';
+        }
+
+        $key    = ucfirst(md5($delims."\n".$source));
+
+        if (!isset($this->sections[$key])) {
+            $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $source, $delims, $this->walk($nodes, 2));
+        }
+
+        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $key, $method, $id);
+    }
+
+    const INVERTED_SECTION = '
+        // %s inverted section
+        $value = $context->%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 int    $level
+     *
+     * @return string Generated inverted section PHP source code
+     */
+    private function invertedSection($nodes, $id, $level)
+    {
+        $method = $this->getFindMethod($id);
+        $id     = var_export($id, true);
+
+        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $method, $id, $this->walk($nodes, $level));
+    }
+
+    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)
+    {
+        return sprintf(
+            $this->prepare(self::PARTIAL, $level),
+            var_export($id, true),
+            var_export($indent, true)
+        );
+    }
+
+    const VARIABLE = '
+        $value = $context->%s(%s);
+        if (!is_string($value) && is_callable($value)) {
+            $value = $this->mustache
+                ->loadLambda((string) call_user_func($value))
+                ->renderInternal($context, $indent);
+        }
+        $buffer .= %s%s;
+    ';
+
+    /**
+     * Generate Mustache Template variable interpolation PHP source.
+     *
+     * @param string  $id     Variable name
+     * @param boolean $escape Escape the variable value for output?
+     * @param int     $level
+     *
+     * @return string Generated variable interpolation PHP source
+     */
+    private function variable($id, $escape, $level)
+    {
+        $method = $this->getFindMethod($id);
+        $id     = ($method !== 'last') ? var_export($id, true) : '';
+        $value  = $escape ? $this->getEscape() : '$value';
+
+        return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $this->flushIndent(), $value);
+    }
+
+    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)
+    {
+        if ($text === "\n") {
+            $this->indentNextLine = true;
+
+            return $this->prepare(self::LINE, $level);
+        } else {
+            return sprintf($this->prepare(self::TEXT, $level), $this->flushIndent(), var_export($text, true));
+        }
+    }
+
+    /**
+     * Prepare PHP source code snippet for output.
+     *
+     * @param string  $text
+     * @param int     $bonus          Additional indent level (default: 0)
+     * @param boolean $prependNewline Prepend a newline to the snippet? (default: true)
+     *
+     * @return string PHP source code snippet
+     */
+    private function prepare($text, $bonus = 0, $prependNewline = true)
+    {
+        $text = ($prependNewline ? "\n" : '').trim($text);
+        if ($prependNewline) {
+            $bonus++;
+        }
+
+        return preg_replace("/\n( {8})?/", "\n".str_repeat(" ", $bonus * 4), $text);
+    }
+
+    const DEFAULT_ESCAPE = 'htmlspecialchars(%s, ENT_COMPAT, %s)';
+    const CUSTOM_ESCAPE  = 'call_user_func($this->mustache->getEscape(), %s)';
+
+    /**
+     * Get the current escaper.
+     *
+     * @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);
+        } else {
+            return sprintf(self::DEFAULT_ESCAPE, $value, var_export($this->charset, true));
+        }
+    }
+
+    /**
+     * Select the appropriate Context `find` method for a given $id.
+     *
+     * The return value will be one of `find`, `findDot` 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';
+        } elseif (strpos($id, '.') === false) {
+            return 'find';
+        } else {
+            return 'findDot';
+        }
+    }
+
+    const LINE_INDENT = '$indent . ';
+
+    /**
+     * Get the current $indent prefix to write to the buffer.
+     *
+     * @return string "$indent . " or ""
+     */
+    private function flushIndent()
+    {
+        if ($this->indentNextLine) {
+            $this->indentNextLine = false;
+
+            return self::LINE_INDENT;
+        } else {
+            return '';
+        }
+    }
 }

+ 123 - 115
src/Mustache/Context.php

@@ -12,130 +12,138 @@
 /**
  * Mustache Template rendering Context.
  */
-class Mustache_Context {
-	private $stack = array();
+class Mustache_Context
+{
+    private $stack = array();
 
-	/**
-	 * Mustache rendering Context constructor.
-	 *
-	 * @param mixed $context Default rendering context (default: null)
-	 */
-	public function __construct($context = null) {
-		if ($context !== null) {
-			$this->stack = array($context);
-		}
-	}
+    /**
+     * Mustache rendering Context constructor.
+     *
+     * @param mixed $context Default rendering context (default: null)
+     */
+    public function __construct($context = null)
+    {
+        if ($context !== null) {
+            $this->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 stack.
+     *
+     * @param mixed $value Object or array to use for context
+     */
+    public function push($value)
+    {
+        array_push($this->stack, $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 Context frame from the stack.
+     *
+     * @return mixed Last Context frame (object or array)
+     */
+    public function pop()
+    {
+        return array_pop($this->stack);
+    }
 
-	/**
-	 * Get the last Context frame.
-	 *
-	 * @return mixed Last Context frame (object or array)
-	 */
-	public function last() {
-		return end($this->stack);
-	}
+    /**
+     * 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 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);
+    /**
+     * 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;
-			}
+        foreach ($chunks as $chunk) {
+            if ($value === '') {
+                return $value;
+            }
 
-			$value = $this->findVariableInStack($chunk, array($value));
-		}
+            $value = $this->findVariableInStack($chunk, array($value));
+        }
 
-		return $value;
-	}
+        return $value;
+    }
 
-	/**
-	 * 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--) {
-			if (is_object($stack[$i])) {
-				if (method_exists($stack[$i], $id)) {
-					return $stack[$i]->$id();
-				} elseif (isset($stack[$i]->$id)) {
-					return $stack[$i]->$id;
-				}
-			} elseif (is_array($stack[$i]) && array_key_exists($id, $stack[$i])) {
-				return $stack[$i][$id];
-			}
-		}
+    /**
+     * 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--) {
+            if (is_object($stack[$i])) {
+                if (method_exists($stack[$i], $id)) {
+                    return $stack[$i]->$id();
+                } elseif (isset($stack[$i]->$id)) {
+                    return $stack[$i]->$id;
+                }
+            } elseif (is_array($stack[$i]) && array_key_exists($id, $stack[$i])) {
+                return $stack[$i][$id];
+            }
+        }
 
-		return '';
-	}
+        return '';
+    }
 }

+ 563 - 531
src/Mustache/Engine.php

@@ -21,535 +21,567 @@
  *
  * @author Justin Hileman {@link http://justinhileman.com}
  */
-class Mustache_Engine {
-	const VERSION      = '2.0.0-dev';
-	const SPEC_VERSION = '1.1.2';
-
-	// Template cache
-	private $templates = array();
-
-	// Environment
-	private $templateClassPrefix = '__Mustache_';
-	private $cache = null;
-	private $loader;
-	private $partialsLoader;
-	private $helpers;
-	private $escape;
-	private $charset = 'UTF-8';
-
-	/**
-	 * 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 cache directory for compiled templates. Mustache will not cache templates unless this is set
-	 *         'cache' => dirname(__FILE__).'/tmp/cache/mustache',
-	 *
-	 *         // 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');
-	 *         },
-	 *
-	 *         // character set for `htmlspecialchars`. Defaults to 'UTF-8'
-	 *         'charset' => 'ISO-8859-1',
-	 *     );
-	 *
-	 * @param array $options (default: array())
-	 */
-	public function __construct(array $options = array()) {
-		if (isset($options['template_class_prefix'])) {
-			$this->templateClassPrefix = $options['template_class_prefix'];
-		}
-
-		if (isset($options['cache'])) {
-			$this->cache = $options['cache'];
-		}
-
-		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 InvalidArgumentException('Mustache Constructor "escape" option must be callable');
-			}
-
-			$this->escape = $options['escape'];
-		}
-
-		if (isset($options['charset'])) {
-			$this->charset = $options['charset'];
-		}
-	}
-
-	/**
-	 * Shortcut 'render' invocation.
-	 *
-	 * Equivalent to calling `$mustache->loadTemplate($template)->render($data);`
-	 *
-	 * @see Mustache_Engine::loadTemplate
-	 * @see Mustache_Template::render
-	 *
-	 * @param string $template
-	 * @param mixed  $data
-	 *
-	 * @return string Rendered template
-	 */
-	public function render($template, $data) {
-		return $this->loadTemplate($template)->render($data);
-	}
-
-	/**
-	 * Get the current Mustache escape callback.
-	 *
-	 * @return mixed Callable or null
-	 */
-	public function getEscape() {
-		return $this->escape;
-	}
-
-	/**
-	 * Get the current Mustache character set.
-	 *
-	 * @return string
-	 */
-	public function getCharset() {
-		return $this->charset;
-	}
-
-	/**
-	 * 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 RuntimeException If the current Loader instance is immutable
-	 *
-	 * @param array $partials (default: array())
-	 */
-	public function setPartials(array $partials = array()) {
-		$loader = $this->getPartialsLoader();
-		if (!$loader instanceof Mustache_Loader_MutableLoader) {
-			throw new RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
-		}
-
-		$loader->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 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 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 boolean 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 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;
-	}
-
-	/**
-	 * Helper method to generate a Mustache template class.
-	 *
-	 * @param string $source
-	 *
-	 * @return string Mustache Template class name
-	 */
-	public function getTemplateClassName($source) {
-		return $this->templateClassPrefix . md5(sprintf(
-			'version:%s,escape:%s,charset:%s,source:%s',
-			self::VERSION,
-			isset($this->escape) ? 'custom' : 'default',
-			$this->charset,
-			$source
-		));
-	}
-
-	/**
-	 * 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 {
-			return $this->loadSource($this->getPartialsLoader()->load($name));
-		} catch (InvalidArgumentException $e) {
-			// If the named partial cannot be found, return null.
-		}
-	}
-
-	/**
-	 * 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);
-	}
-
-	/**
-	 * Instantiate and return a Mustache Template instance by source.
-	 *
-	 * @see Mustache_Engine::loadTemplate
-	 * @see Mustache_Engine::loadPartial
-	 * @see Mustache_Engine::loadLambda
-	 *
-	 * @param string $source
-	 *
-	 * @return Mustache_Template
-	 */
-	private function loadSource($source) {
-		$className = $this->getTemplateClassName($source);
-
-		if (!isset($this->templates[$className])) {
-			if (!class_exists($className, false)) {
-				if ($fileName = $this->getCacheFilename($source)) {
-					if (!is_file($fileName)) {
-						$this->writeCacheFile($fileName, $this->compile($source));
-					}
-
-					require_once $fileName;
-				} else {
-					eval('?>'.$this->compile($source));
-				}
-			}
-
-			$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);
-	}
-
-	/**
-	 * Helper method to parse a Mustache template.
-	 *
-	 * @see Mustache_Parser::parse
-	 *
-	 * @param string $source
-	 *
-	 * @return array Token tree
-	 */
-	private function parse($source) {
-		return $this->getParser()->parse($this->tokenize($source));
-	}
-
-	/**
-	 * Helper method to compile a Mustache template.
-	 *
-	 * @see Mustache_Compiler::compile
-	 *
-	 * @param string $source
-	 *
-	 * @return string generated Mustache template class code
-	 */
-	private function compile($source) {
-		$tree = $this->parse($source);
-		$name = $this->getTemplateClassName($source);
-
-		return $this->getCompiler()->compile($source, $tree, $name, isset($this->escape), $this->charset);
-	}
-
-	/**
-	 * Helper method to generate a Mustache Template class cache filename.
-	 *
-	 * @param string $source
-	 *
-	 * @return string Mustache Template class cache filename
-	 */
-	private function getCacheFilename($source) {
-		if ($this->cache) {
-			return sprintf('%s/%s.php', $this->cache, $this->getTemplateClassName($source));
-		}
-	}
-
-	/**
-	 * Helper method to dump a generated Mustache Template subclass to the file cache.
-	 *
-	 * @throws RuntimeException if unable to write to $fileName.
-	 *
-	 * @param string $fileName
-	 * @param string $source
-	 */
-	private function writeCacheFile($fileName, $source) {
-		if (!is_dir(dirname($fileName))) {
-			mkdir(dirname($fileName), 0777, true);
-		}
-
-		$tempFile = tempnam(dirname($fileName), basename($fileName));
-		if (false !== @file_put_contents($tempFile, $source)) {
-			if (@rename($tempFile, $fileName)) {
-				chmod($fileName, 0644);
-
-				return;
-			}
-		}
-
-		throw new RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
-	}
+class Mustache_Engine
+{
+    const VERSION      = '2.0.0-dev';
+    const SPEC_VERSION = '1.1.2';
+
+    // Template cache
+    private $templates = array();
+
+    // Environment
+    private $templateClassPrefix = '__Mustache_';
+    private $cache = null;
+    private $loader;
+    private $partialsLoader;
+    private $helpers;
+    private $escape;
+    private $charset = 'UTF-8';
+
+    /**
+     * 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 cache directory for compiled templates. Mustache will not cache templates unless this is set
+     *         'cache' => dirname(__FILE__).'/tmp/cache/mustache',
+     *
+     *         // 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');
+     *         },
+     *
+     *         // character set for `htmlspecialchars`. Defaults to 'UTF-8'
+     *         'charset' => 'ISO-8859-1',
+     *     );
+     *
+     * @param array $options (default: array())
+     */
+    public function __construct(array $options = array())
+    {
+        if (isset($options['template_class_prefix'])) {
+            $this->templateClassPrefix = $options['template_class_prefix'];
+        }
+
+        if (isset($options['cache'])) {
+            $this->cache = $options['cache'];
+        }
+
+        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 InvalidArgumentException('Mustache Constructor "escape" option must be callable');
+            }
+
+            $this->escape = $options['escape'];
+        }
+
+        if (isset($options['charset'])) {
+            $this->charset = $options['charset'];
+        }
+    }
+
+    /**
+     * Shortcut 'render' invocation.
+     *
+     * Equivalent to calling `$mustache->loadTemplate($template)->render($data);`
+     *
+     * @see Mustache_Engine::loadTemplate
+     * @see Mustache_Template::render
+     *
+     * @param string $template
+     * @param mixed  $data
+     *
+     * @return string Rendered template
+     */
+    public function render($template, $data)
+    {
+        return $this->loadTemplate($template)->render($data);
+    }
+
+    /**
+     * Get the current Mustache escape callback.
+     *
+     * @return mixed Callable or null
+     */
+    public function getEscape()
+    {
+        return $this->escape;
+    }
+
+    /**
+     * Get the current Mustache character set.
+     *
+     * @return string
+     */
+    public function getCharset()
+    {
+        return $this->charset;
+    }
+
+    /**
+     * 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 RuntimeException If the current Loader instance is immutable
+     *
+     * @param array $partials (default: array())
+     */
+    public function setPartials(array $partials = array())
+    {
+        $loader = $this->getPartialsLoader();
+        if (!$loader instanceof Mustache_Loader_MutableLoader) {
+            throw new RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
+        }
+
+        $loader->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 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 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 boolean 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 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;
+    }
+
+    /**
+     * Helper method to generate a Mustache template class.
+     *
+     * @param string $source
+     *
+     * @return string Mustache Template class name
+     */
+    public function getTemplateClassName($source)
+    {
+        return $this->templateClassPrefix . md5(sprintf(
+            'version:%s,escape:%s,charset:%s,source:%s',
+            self::VERSION,
+            isset($this->escape) ? 'custom' : 'default',
+            $this->charset,
+            $source
+        ));
+    }
+
+    /**
+     * 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 {
+            return $this->loadSource($this->getPartialsLoader()->load($name));
+        } catch (InvalidArgumentException $e) {
+            // If the named partial cannot be found, return null.
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * Instantiate and return a Mustache Template instance by source.
+     *
+     * @see Mustache_Engine::loadTemplate
+     * @see Mustache_Engine::loadPartial
+     * @see Mustache_Engine::loadLambda
+     *
+     * @param string $source
+     *
+     * @return Mustache_Template
+     */
+    private function loadSource($source)
+    {
+        $className = $this->getTemplateClassName($source);
+
+        if (!isset($this->templates[$className])) {
+            if (!class_exists($className, false)) {
+                if ($fileName = $this->getCacheFilename($source)) {
+                    if (!is_file($fileName)) {
+                        $this->writeCacheFile($fileName, $this->compile($source));
+                    }
+
+                    require_once $fileName;
+                } else {
+                    eval('?>'.$this->compile($source));
+                }
+            }
+
+            $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);
+    }
+
+    /**
+     * Helper method to parse a Mustache template.
+     *
+     * @see Mustache_Parser::parse
+     *
+     * @param string $source
+     *
+     * @return array Token tree
+     */
+    private function parse($source)
+    {
+        return $this->getParser()->parse($this->tokenize($source));
+    }
+
+    /**
+     * Helper method to compile a Mustache template.
+     *
+     * @see Mustache_Compiler::compile
+     *
+     * @param string $source
+     *
+     * @return string generated Mustache template class code
+     */
+    private function compile($source)
+    {
+        $tree = $this->parse($source);
+        $name = $this->getTemplateClassName($source);
+
+        return $this->getCompiler()->compile($source, $tree, $name, isset($this->escape), $this->charset);
+    }
+
+    /**
+     * Helper method to generate a Mustache Template class cache filename.
+     *
+     * @param string $source
+     *
+     * @return string Mustache Template class cache filename
+     */
+    private function getCacheFilename($source)
+    {
+        if ($this->cache) {
+            return sprintf('%s/%s.php', $this->cache, $this->getTemplateClassName($source));
+        }
+    }
+
+    /**
+     * Helper method to dump a generated Mustache Template subclass to the file cache.
+     *
+     * @throws RuntimeException if unable to write to $fileName.
+     *
+     * @param string $fileName
+     * @param string $source
+     */
+    private function writeCacheFile($fileName, $source)
+    {
+        if (!is_dir(dirname($fileName))) {
+            mkdir(dirname($fileName), 0777, true);
+        }
+
+        $tempFile = tempnam(dirname($fileName), basename($fileName));
+        if (false !== @file_put_contents($tempFile, $source)) {
+            if (@rename($tempFile, $fileName)) {
+                chmod($fileName, 0644);
+
+                return;
+            }
+        }
+
+        throw new RuntimeException(sprintf('Failed to write cache file "%s".', $fileName));
+    }
 }

+ 153 - 141
src/Mustache/HelperCollection.php

@@ -12,145 +12,157 @@
 /**
  * A collection of helpers for a Mustache instance.
  */
-class Mustache_HelperCollection {
-	private $helpers = array();
-
-	/**
-	 * Helper Collection constructor.
-	 *
-	 * Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
-	 *
-	 * @throws 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) {
-			if (!is_array($helpers) && !$helpers instanceof Traversable) {
-				throw new 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.
-	 *
-	 * @param string $name
-	 *
-	 * @return mixed Helper
-	 */
-	public function get($name) {
-		if (!$this->has($name)) {
-			throw new InvalidArgumentException('Unknown helper: '.$name);
-		}
-
-		return $this->helpers[$name];
-	}
-
-	/**
-	 * Magic isset().
-	 *
-	 * @see Mustache_HelperCollection::has
-	 *
-	 * @param string $name
-	 *
-	 * @return boolean 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 boolean 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 InvalidArgumentException if the requested helper is not present.
-	 *
-	 * @param string $name
-	 */
-	public function remove($name) {
-		if (!$this->has($name)) {
-			throw new InvalidArgumentException('Unknown helper: '.$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 boolean True if the collection is empty
-	 */
-	public function isEmpty() {
-		return empty($this->helpers);
-	}
+class Mustache_HelperCollection
+{
+    private $helpers = array();
+
+    /**
+     * Helper Collection constructor.
+     *
+     * Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
+     *
+     * @throws 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) {
+            if (!is_array($helpers) && !$helpers instanceof Traversable) {
+                throw new 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.
+     *
+     * @param string $name
+     *
+     * @return mixed Helper
+     */
+    public function get($name)
+    {
+        if (!$this->has($name)) {
+            throw new InvalidArgumentException('Unknown helper: '.$name);
+        }
+
+        return $this->helpers[$name];
+    }
+
+    /**
+     * Magic isset().
+     *
+     * @see Mustache_HelperCollection::has
+     *
+     * @param string $name
+     *
+     * @return boolean 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 boolean 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 InvalidArgumentException if the requested helper is not present.
+     *
+     * @param string $name
+     */
+    public function remove($name)
+    {
+        if (!$this->has($name)) {
+            throw new InvalidArgumentException('Unknown helper: '.$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 boolean True if the collection is empty
+     */
+    public function isEmpty()
+    {
+        return empty($this->helpers);
+    }
 }

+ 10 - 9
src/Mustache/Loader.php

@@ -12,14 +12,15 @@
 /**
  * Mustache Template Loader interface.
  */
-interface Mustache_Loader {
+interface Mustache_Loader
+{
 
-	/**
-	 * Load a Template by name.
-	 *
-	 * @param string $name
-	 *
-	 * @return string Mustache Template source
-	 */
-	function load($name);
+    /**
+     * Load a Template by name.
+     *
+     * @param string $name
+     *
+     * @return string Mustache Template source
+     */
+    public function load($name);
 }

+ 44 - 39
src/Mustache/Loader/ArrayLoader.php

@@ -27,48 +27,53 @@
  * @implements Loader
  * @implements MutableLoader
  */
-class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader {
+class Mustache_Loader_ArrayLoader implements Mustache_Loader, Mustache_Loader_MutableLoader
+{
 
-	/**
-	 * ArrayLoader constructor.
-	 *
-	 * @param array $templates Associative array of Template source (default: array())
-	 */
-	public function __construct(array $templates = array()) {
-		$this->templates = $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.
-	 *
-	 * @param string $name
-	 *
-	 * @return string Mustache Template source
-	 */
-	public function load($name) {
-		if (!isset($this->templates[$name])) {
-			throw new InvalidArgumentException('Template '.$name.' not found.');
-		}
+    /**
+     * Load a Template.
+     *
+     * @param string $name
+     *
+     * @return string Mustache Template source
+     */
+    public function load($name)
+    {
+        if (!isset($this->templates[$name])) {
+            throw new InvalidArgumentException('Template '.$name.' not found.');
+        }
 
-		return $this->templates[$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 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;
-	}
+    /**
+     * Set a Template source by name.
+     *
+     * @param string $name
+     * @param string $template Mustache Template source
+     */
+    public function setTemplate($name, $template)
+    {
+        $this->templates[$name] = $template;
+    }
 }

+ 79 - 74
src/Mustache/Loader/FilesystemLoader.php

@@ -26,88 +26,93 @@
  *
  * @implements Loader
  */
-class Mustache_Loader_FilesystemLoader implements Mustache_Loader {
-	private $baseDir;
-	private $extension = '.mustache';
-	private $templates = array();
+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 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 = rtrim(realpath($baseDir), '/');
+    /**
+     * 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 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 = rtrim(realpath($baseDir), '/');
 
-		if (!is_dir($this->baseDir)) {
-			throw new RuntimeException('FilesystemLoader baseDir must be a directory: '.$baseDir);
-		}
+        if (!is_dir($this->baseDir)) {
+            throw new RuntimeException('FilesystemLoader baseDir must be a directory: '.$baseDir);
+        }
 
-		if (isset($options['extension'])) {
-			$this->extension = '.' . ltrim($options['extension'], '.');
-		}
-	}
+        if (isset($options['extension'])) {
+            $this->extension = '.' . ltrim($options['extension'], '.');
+        }
+    }
 
-	/**
-	 * Load a Template by name.
-	 *
-	 *     $loader = new 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);
-		}
+    /**
+     * Load a Template by name.
+     *
+     *     $loader = new 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];
-	}
+        return $this->templates[$name];
+    }
 
-	/**
-	 * Helper function for loading a Mustache file by name.
-	 *
-	 * @throws InvalidArgumentException if a template file is not found.
-	 *
-	 * @param string $name
-	 *
-	 * @return string Mustache Template source
-	 */
-	private function loadFile($name) {
-		$fileName = $this->getFileName($name);
+    /**
+     * Helper function for loading a Mustache file by name.
+     *
+     * @throws InvalidArgumentException if a template file is not found.
+     *
+     * @param string $name
+     *
+     * @return string Mustache Template source
+     */
+    private function loadFile($name)
+    {
+        $fileName = $this->getFileName($name);
 
-		if (!file_exists($fileName)) {
-			throw new InvalidArgumentException('Template '.$name.' not found.');
-		}
+        if (!file_exists($fileName)) {
+            throw new InvalidArgumentException('Template '.$name.' not found.');
+        }
 
-		return file_get_contents($fileName);
-	}
+        return file_get_contents($fileName);
+    }
 
-	/**
-	 * Helper function for getting a Mustache template file name.
-	 *
-	 * @param string $name
-	 *
-	 * @return string Template file name
-	 */
-	private function getFileName($name) {
-		$fileName = $this->baseDir . '/' . $name;
-		if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
-			$fileName .= $this->extension;
-		}
+    /**
+     * Helper function for getting a Mustache template file name.
+     *
+     * @param string $name
+     *
+     * @return string Template file name
+     */
+    private function getFileName($name)
+    {
+        $fileName = $this->baseDir . '/' . $name;
+        if (substr($fileName, 0 - strlen($this->extension)) !== $this->extension) {
+            $fileName .= $this->extension;
+        }
 
-		return $fileName;
-	}
+        return $fileName;
+    }
 }

+ 15 - 14
src/Mustache/Loader/MutableLoader.php

@@ -12,20 +12,21 @@
 /**
  * Mustache Template mutable Loader interface.
  */
-interface Mustache_Loader_MutableLoader {
+interface Mustache_Loader_MutableLoader
+{
 
-	/**
-	 * Set an associative array of Template sources for this loader.
-	 *
-	 * @param array $templates
-	 */
-	function setTemplates(array $templates);
+    /**
+     * Set an associative array of Template sources for this loader.
+     *
+     * @param array $templates
+     */
+    public function setTemplates(array $templates);
 
-	/**
-	 * Set a Template source by name.
-	 *
-	 * @param string $name
-	 * @param string $template Mustache Template source
-	 */
-	function setTemplate($name, $template);
+    /**
+     * Set a Template source by name.
+     *
+     * @param string $name
+     * @param string $template Mustache Template source
+     */
+    public function setTemplate($name, $template);
 }

+ 13 - 11
src/Mustache/Loader/StringLoader.php

@@ -25,16 +25,18 @@
  *
  * @implements Loader
  */
-class Mustache_Loader_StringLoader implements Mustache_Loader {
+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;
-	}
+    /**
+     * Load a Template by source.
+     *
+     * @param string $name Mustache Template source
+     *
+     * @return string Mustache Template source
+     */
+    public function load($name)
+    {
+        return $name;
+    }
 }

+ 59 - 56
src/Mustache/Parser.php

@@ -14,72 +14,75 @@
  *
  * This class is responsible for turning a set of Mustache tokens into a parse tree.
  */
-class Mustache_Parser {
+class Mustache_Parser
+{
 
-	/**
-	 * Process an array of Mustache tokens and convert them into a parse tree.
-	 *
-	 * @param array $tokens Set of Mustache tokens
-	 *
-	 * @return array Mustache token parse tree
-	 */
-	public function parse(array $tokens = array()) {
-		return $this->buildTree(new ArrayIterator($tokens));
-	}
+    /**
+     * Process an array of Mustache tokens and convert them into a parse tree.
+     *
+     * @param array $tokens Set of Mustache tokens
+     *
+     * @return array Mustache token parse tree
+     */
+    public function parse(array $tokens = array())
+    {
+        return $this->buildTree(new ArrayIterator($tokens));
+    }
 
-	/**
-	 * Helper method for recursively building a parse tree.
-	 *
-	 * @throws LogicException when nesting errors or mismatched section tags are encountered.
-	 *
-	 * @param ArrayIterator $tokens Stream of Mustache tokens
-	 * @param array          $parent Parent token (default: null)
-	 *
-	 * @return array Mustache Token parse tree
-	 */
-	private function buildTree(ArrayIterator $tokens, array $parent = null) {
-		$nodes = array();
+    /**
+     * Helper method for recursively building a parse tree.
+     *
+     * @throws LogicException when nesting errors or mismatched section tags are encountered.
+     *
+     * @param ArrayIterator $tokens Stream of Mustache tokens
+     * @param array          $parent Parent token (default: null)
+     *
+     * @return array Mustache Token parse tree
+     */
+    private function buildTree(ArrayIterator $tokens, array $parent = null)
+    {
+        $nodes = array();
 
-		do {
-			$token = $tokens->current();
-			$tokens->next();
+        do {
+            $token = $tokens->current();
+            $tokens->next();
 
-			if ($token === null) {
-				continue;
-			} else {
-				switch ($token[Mustache_Tokenizer::TYPE]) {
-					case Mustache_Tokenizer::T_SECTION:
-					case Mustache_Tokenizer::T_INVERTED:
-						$nodes[] = $this->buildTree($tokens, $token);
-						break;
+            if ($token === null) {
+                continue;
+            } else {
+                switch ($token[Mustache_Tokenizer::TYPE]) {
+                    case Mustache_Tokenizer::T_SECTION:
+                    case Mustache_Tokenizer::T_INVERTED:
+                        $nodes[] = $this->buildTree($tokens, $token);
+                        break;
 
-					case Mustache_Tokenizer::T_END_SECTION:
-						if (!isset($parent)) {
-							throw new LogicException('Unexpected closing tag: /'. $token[Mustache_Tokenizer::NAME]);
-						}
+                    case Mustache_Tokenizer::T_END_SECTION:
+                        if (!isset($parent)) {
+                            throw new LogicException('Unexpected closing tag: /'. $token[Mustache_Tokenizer::NAME]);
+                        }
 
-						if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
-							throw new LogicException('Nesting error: ' . $parent[Mustache_Tokenizer::NAME] . ' vs. ' . $token[Mustache_Tokenizer::NAME]);
-						}
+                        if ($token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
+                            throw new LogicException('Nesting error: ' . $parent[Mustache_Tokenizer::NAME] . ' vs. ' . $token[Mustache_Tokenizer::NAME]);
+                        }
 
-						$parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
-						$parent[Mustache_Tokenizer::NODES] = $nodes;
+                        $parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
+                        $parent[Mustache_Tokenizer::NODES] = $nodes;
 
-						return $parent;
-						break;
+                        return $parent;
+                        break;
 
-					default:
-						$nodes[] = $token;
-						break;
-				}
-			}
+                    default:
+                        $nodes[] = $token;
+                        break;
+                }
+            }
 
-		} while ($tokens->valid());
+        } while ($tokens->valid());
 
-		if (isset($parent)) {
-			throw new LogicException('Missing closing tag: ' . $parent[Mustache_Tokenizer::NAME]);
-		}
+        if (isset($parent)) {
+            throw new LogicException('Missing closing tag: ' . $parent[Mustache_Tokenizer::NAME]);
+        }
 
-		return $nodes;
-	}
+        return $nodes;
+    }
 }

+ 121 - 115
src/Mustache/Template.php

@@ -14,130 +14,136 @@
  *
  * @abstract
  */
-abstract class Mustache_Template {
+abstract class Mustache_Template
+{
 
-	/**
-	 * @var Mustache_Engine
-	 */
-	protected $mustache;
+    /**
+     * @var Mustache_Engine
+     */
+    protected $mustache;
 
-	/**
-	 * Mustache Template constructor.
-	 *
-	 * @param Mustache_Engine $mustache
-	 */
-	public function __construct(Mustache_Engine $mustache) {
-		$this->mustache = $mustache;
-	}
+    /**
+     * Mustache Template constructor.
+     *
+     * @param Mustache_Engine $mustache
+     */
+    public function __construct(Mustache_Engine $mustache)
+    {
+        $this->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);
-	}
+    /**
+     * 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));
-	}
+    /**
+     * 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 :)
-	 *
-	 * @abstract
-	 *
-	 * @param Mustache_Context $context
-	 *
-	 * @return string Rendered template
-	 */
-	abstract public function renderInternal(Mustache_Context $context, $indent = '', $escape = false);
+    /**
+     * Internal rendering method implemented by Mustache Template concrete subclasses.
+     *
+     * This is where the magic happens :)
+     *
+     * @abstract
+     *
+     * @param Mustache_Context $context
+     *
+     * @return string Rendered template
+     */
+    abstract public function renderInternal(Mustache_Context $context, $indent = '', $escape = false);
 
-	/**
-	 * 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 boolean True if the value is 'iterable'
-	 */
-	protected function isIterable($value) {
-		if (is_object($value)) {
-			return $value instanceof Traversable;
-		} elseif (is_array($value)) {
-			$i = 0;
-			foreach ($value as $k => $v) {
-				if ($k !== $i++) {
-					return false;
-				}
-			}
+    /**
+     * 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 boolean True if the value is 'iterable'
+     */
+    protected function isIterable($value)
+    {
+        if (is_object($value)) {
+            return $value instanceof Traversable;
+        } elseif (is_array($value)) {
+            $i = 0;
+            foreach ($value as $k => $v) {
+                if ($k !== $i++) {
+                    return false;
+                }
+            }
 
-			return true;
-		} else {
-			return false;
-		}
-	}
+            return true;
+        } else {
+            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;
+    /**
+     * 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);
-		}
+        $helpers = $this->mustache->getHelpers();
+        if (!$helpers->isEmpty()) {
+            $stack->push($helpers);
+        }
 
-		if (!empty($context)) {
-			$stack->push($context);
-		}
+        if (!empty($context)) {
+            $stack->push($context);
+        }
 
-		return $stack;
-	}
+        return $stack;
+    }
 }

+ 240 - 233
src/Mustache/Tokenizer.php

@@ -16,263 +16,270 @@
  */
 class Mustache_Tokenizer {
 
-	// Finite state machine states
-	const IN_TEXT     = 0;
-	const IN_TAG_TYPE = 1;
-	const IN_TAG      = 2;
+    // Finite state machine states
+    const IN_TEXT     = 0;
+    const IN_TAG_TYPE = 1;
+    const IN_TAG      = 2;
 
-	// Token types
-	const T_SECTION      = '#';
-	const T_INVERTED     = '^';
-	const T_END_SECTION  = '/';
-	const T_COMMENT      = '!';
-	const T_PARTIAL      = '>';
-	const T_PARTIAL_2    = '<';
-	const T_DELIM_CHANGE = '=';
-	const T_ESCAPED      = '_v';
-	const T_UNESCAPED    = '{';
-	const T_UNESCAPED_2  = '&';
-	const T_TEXT         = '_t';
+    // Token types
+    const T_SECTION      = '#';
+    const T_INVERTED     = '^';
+    const T_END_SECTION  = '/';
+    const T_COMMENT      = '!';
+    const T_PARTIAL      = '>';
+    const T_PARTIAL_2    = '<';
+    const T_DELIM_CHANGE = '=';
+    const T_ESCAPED      = '_v';
+    const T_UNESCAPED    = '{';
+    const T_UNESCAPED_2  = '&';
+    const T_TEXT         = '_t';
 
-	// 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_PARTIAL_2    => true,
-		self::T_DELIM_CHANGE => true,
-		self::T_ESCAPED      => true,
-		self::T_UNESCAPED    => true,
-		self::T_UNESCAPED_2  => true,
-	);
+    // 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_PARTIAL_2    => true,
+        self::T_DELIM_CHANGE => true,
+        self::T_ESCAPED      => true,
+        self::T_UNESCAPED    => true,
+        self::T_UNESCAPED_2  => true,
+    );
 
-	// Interpolated tags
-	private static $interpolatedTags = array(
-		self::T_ESCAPED      => true,
-		self::T_UNESCAPED    => true,
-		self::T_UNESCAPED_2  => true,
-	);
+    // Interpolated tags
+    private static $interpolatedTags = array(
+        self::T_ESCAPED      => true,
+        self::T_UNESCAPED    => true,
+        self::T_UNESCAPED_2  => true,
+    );
 
-	// Token properties
-	const TYPE   = 'type';
-	const NAME   = 'name';
-	const OTAG   = 'otag';
-	const CTAG   = 'ctag';
-	const INDEX  = 'index';
-	const END    = 'end';
-	const INDENT = 'indent';
-	const NODES  = 'nodes';
-	const VALUE  = 'value';
+    // Token properties
+    const TYPE   = 'type';
+    const NAME   = 'name';
+    const OTAG   = 'otag';
+    const CTAG   = 'ctag';
+    const INDEX  = 'index';
+    const END    = 'end';
+    const INDENT = 'indent';
+    const NODES  = 'nodes';
+    const VALUE  = 'value';
 
-	private $state;
-	private $tagType;
-	private $tag;
-	private $buffer;
-	private $tokens;
-	private $seenTag;
-	private $lineStart;
-	private $otag;
-	private $ctag;
+    private $state;
+    private $tagType;
+    private $tag;
+    private $buffer;
+    private $tokens;
+    private $seenTag;
+    private $lineStart;
+    private $otag;
+    private $ctag;
 
-	/**
-	 * Scan and tokenize template source.
-	 *
-	 * @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) {
-		$this->reset();
+    /**
+     * Scan and tokenize template source.
+     *
+     * @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)
+    {
+        $this->reset();
 
-		if ($delimiters = trim($delimiters)) {
-			list($otag, $ctag) = explode(' ', $delimiters);
-			$this->otag = $otag;
-			$this->ctag = $ctag;
-		}
+        if ($delimiters = trim($delimiters)) {
+            list($otag, $ctag) = explode(' ', $delimiters);
+            $this->otag = $otag;
+            $this->ctag = $ctag;
+        }
 
-		$len = strlen($text);
-		for ($i = 0; $i < $len; $i++) {
-			switch ($this->state) {
-				case self::IN_TEXT:
-					if ($this->tagChange($this->otag, $text, $i)) {
-						$i--;
-						$this->flushBuffer();
-						$this->state = self::IN_TAG_TYPE;
-					} else {
-						if ($text[$i] == "\n") {
-							$this->filterLine();
-						} else {
-							$this->buffer .= $text[$i];
-						}
-					}
-					break;
+        $len = strlen($text);
+        for ($i = 0; $i < $len; $i++) {
+            switch ($this->state) {
+                case self::IN_TEXT:
+                    if ($this->tagChange($this->otag, $text, $i)) {
+                        $i--;
+                        $this->flushBuffer();
+                        $this->state = self::IN_TAG_TYPE;
+                    } else {
+                        if ($text[$i] == "\n") {
+                            $this->filterLine();
+                        } else {
+                            $this->buffer .= $text[$i];
+                        }
+                    }
+                    break;
 
-				case self::IN_TAG_TYPE:
+                case self::IN_TAG_TYPE:
 
-					$i += strlen($this->otag) - 1;
-					if (isset(self::$tagTypes[$text[$i + 1]])) {
-						$tag = $text[$i + 1];
-						$this->tagType = $tag;
-					} else {
-						$tag = null;
-						$this->tagType = self::T_ESCAPED;
-					}
+                    $i += strlen($this->otag) - 1;
+                    if (isset(self::$tagTypes[$text[$i + 1]])) {
+                        $tag = $text[$i + 1];
+                        $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;
-					} else {
-						if ($tag !== null) {
-							$i++;
-						}
-						$this->state = self::IN_TAG;
-					}
-					$this->seenTag = $i;
-					break;
+                    if ($this->tagType === self::T_DELIM_CHANGE) {
+                        $i = $this->changeDelimiters($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, $text, $i)) {
-						$this->tokens[] = array(
-							self::TYPE  => $this->tagType,
-							self::NAME  => trim($this->buffer),
-							self::OTAG  => $this->otag,
-							self::CTAG  => $this->ctag,
-							self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag)
-						);
+                default:
+                    if ($this->tagChange($this->ctag, $text, $i)) {
+                        $this->tokens[] = array(
+                            self::TYPE  => $this->tagType,
+                            self::NAME  => trim($this->buffer),
+                            self::OTAG  => $this->otag,
+                            self::CTAG  => $this->ctag,
+                            self::INDEX => ($this->tagType == self::T_END_SECTION) ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag)
+                        );
 
-						$this->buffer = '';
-						$i += strlen($this->ctag) - 1;
-						$this->state = self::IN_TEXT;
-						if ($this->tagType == self::T_UNESCAPED) {
-							if ($this->ctag == '}}') {
-								$i++;
-							} else {
-								// Clean up `{{{ tripleStache }}}` style tokens.
-								$lastName = $this->tokens[count($this->tokens) - 1][self::NAME];
-								if (substr($lastName, -1) === '}') {
-									$this->tokens[count($this->tokens) - 1][self::NAME] = trim(substr($lastName, 0, -1));
-								}
-							}
-						}
-					} else {
-						$this->buffer .= $text[$i];
-					}
-					break;
-			}
-		}
+                        $this->buffer = '';
+                        $i += strlen($this->ctag) - 1;
+                        $this->state = self::IN_TEXT;
+                        if ($this->tagType == self::T_UNESCAPED) {
+                            if ($this->ctag == '}}') {
+                                $i++;
+                            } else {
+                                // Clean up `{{{ tripleStache }}}` style tokens.
+                                $lastName = $this->tokens[count($this->tokens) - 1][self::NAME];
+                                if (substr($lastName, -1) === '}') {
+                                    $this->tokens[count($this->tokens) - 1][self::NAME] = trim(substr($lastName, 0, -1));
+                                }
+                            }
+                        }
+                    } else {
+                        $this->buffer .= $text[$i];
+                    }
+                    break;
+            }
+        }
 
-		$this->filterLine(true);
+        $this->filterLine(true);
 
-		return $this->tokens;
-	}
+        return $this->tokens;
+    }
 
-	/**
-	 * Helper function to reset tokenizer internal state.
-	 */
-	private function reset() {
-		$this->state     = self::IN_TEXT;
-		$this->tagType   = null;
-		$this->tag       = null;
-		$this->buffer    = '';
-		$this->tokens    = array();
-		$this->seenTag   = false;
-		$this->lineStart = 0;
-		$this->otag      = '{{';
-		$this->ctag      = '}}';
-	}
+    /**
+     * Helper function to reset tokenizer internal state.
+     */
+    private function reset()
+    {
+        $this->state     = self::IN_TEXT;
+        $this->tagType   = null;
+        $this->tag       = null;
+        $this->buffer    = '';
+        $this->tokens    = array();
+        $this->seenTag   = false;
+        $this->lineStart = 0;
+        $this->otag      = '{{';
+        $this->ctag      = '}}';
+    }
 
-	/**
-	 * Flush the current buffer to a token.
-	 */
-	private function flushBuffer() {
-		if (!empty($this->buffer)) {
-			$this->tokens[] = array(self::TYPE  => self::T_TEXT, self::VALUE => $this->buffer);
-			$this->buffer   = '';
-		}
-	}
+    /**
+     * Flush the current buffer to a token.
+     */
+    private function flushBuffer()
+    {
+        if (!empty($this->buffer)) {
+            $this->tokens[] = array(self::TYPE  => self::T_TEXT, self::VALUE => $this->buffer);
+            $this->buffer   = '';
+        }
+    }
 
-	/**
-	 * Test whether the current line is entirely made up of whitespace.
-	 *
-	 * @return boolean True if the current line is all whitespace
-	 */
-	private function lineIsWhitespace() {
-		$tokensCount = count($this->tokens);
-		for ($j = $this->lineStart; $j < $tokensCount; $j++) {
-			$token = $this->tokens[$j];
-			if (isset(self::$tagTypes[$token[self::TYPE]])) {
-				if (isset(self::$interpolatedTags[$token[self::TYPE]])) {
-					return false;
-				}
-			} elseif ($token[self::TYPE] == self::T_TEXT) {
-				if (preg_match('/\S/', $token[self::VALUE])) {
-					return false;
-				}
-			}
-		}
+    /**
+     * Test whether the current line is entirely made up of whitespace.
+     *
+     * @return boolean True if the current line is all whitespace
+     */
+    private function lineIsWhitespace()
+    {
+        $tokensCount = count($this->tokens);
+        for ($j = $this->lineStart; $j < $tokensCount; $j++) {
+            $token = $this->tokens[$j];
+            if (isset(self::$tagTypes[$token[self::TYPE]])) {
+                if (isset(self::$interpolatedTags[$token[self::TYPE]])) {
+                    return false;
+                }
+            } elseif ($token[self::TYPE] == self::T_TEXT) {
+                if (preg_match('/\S/', $token[self::VALUE])) {
+                    return false;
+                }
+            }
+        }
 
-		return true;
-	}
+        return true;
+    }
 
-	/**
-	 * Filter out whitespace-only lines and store indent levels for partials.
-	 *
-	 * @param bool $noNewLine Suppress the newline? (default: false)
-	 */
-	private function filterLine($noNewLine = false) {
-		$this->flushBuffer();
-		if ($this->seenTag && $this->lineIsWhitespace()) {
-			$tokensCount = count($this->tokens);
-			for ($j = $this->lineStart; $j < $tokensCount; $j++) {
-				if ($this->tokens[$j][self::TYPE] == self::T_TEXT) {
-					if (isset($this->tokens[$j+1]) && $this->tokens[$j+1][self::TYPE] == self::T_PARTIAL) {
-						$this->tokens[$j+1][self::INDENT] = $this->tokens[$j][self::VALUE];
-					}
+    /**
+     * Filter out whitespace-only lines and store indent levels for partials.
+     *
+     * @param bool $noNewLine Suppress the newline? (default: false)
+     */
+    private function filterLine($noNewLine = false)
+    {
+        $this->flushBuffer();
+        if ($this->seenTag && $this->lineIsWhitespace()) {
+            $tokensCount = count($this->tokens);
+            for ($j = $this->lineStart; $j < $tokensCount; $j++) {
+                if ($this->tokens[$j][self::TYPE] == self::T_TEXT) {
+                    if (isset($this->tokens[$j+1]) && $this->tokens[$j+1][self::TYPE] == self::T_PARTIAL) {
+                        $this->tokens[$j+1][self::INDENT] = $this->tokens[$j][self::VALUE];
+                    }
 
-					$this->tokens[$j] = null;
-				}
-			}
-		} elseif (!$noNewLine) {
-			$this->tokens[] = array(self::TYPE => self::T_TEXT, self::VALUE => "\n");
-		}
+                    $this->tokens[$j] = null;
+                }
+            }
+        } elseif (!$noNewLine) {
+            $this->tokens[] = array(self::TYPE => self::T_TEXT, self::VALUE => "\n");
+        }
 
-		$this->seenTag   = false;
-		$this->lineStart = count($this->tokens);
-	}
+        $this->seenTag   = false;
+        $this->lineStart = count($this->tokens);
+    }
 
-	/**
-	 * 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);
+    /**
+     * 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);
 
-		list($otag, $ctag) = explode(' ', trim(substr($text, $startIndex, $closeIndex - $startIndex)));
-		$this->otag = $otag;
-		$this->ctag = $ctag;
+        list($otag, $ctag) = explode(' ', trim(substr($text, $startIndex, $closeIndex - $startIndex)));
+        $this->otag = $otag;
+        $this->ctag = $ctag;
 
-		return $closeIndex + strlen($close) - 1;
-	}
+        return $closeIndex + strlen($close) - 1;
+    }
 
-	/**
-	 * Test whether it's time to change tags.
-	 *
-	 * @param string $tag   Current tag name
-	 * @param string $text  Mustache template source
-	 * @param int    $index Current tokenizer index
-	 *
-	 * @return boolean True if this is a closing section tag
-	 */
-	private function tagChange($tag, $text, $index) {
-		return substr($text, $index, strlen($tag)) === $tag;
-	}
+    /**
+     * Test whether it's time to change tags.
+     *
+     * @param string $tag   Current tag name
+     * @param string $text  Mustache template source
+     * @param int    $index Current tokenizer index
+     *
+     * @return boolean True if this is a closing section tag
+     */
+    private function tagChange($tag, $text, $index)
+    {
+        return substr($text, $index, strlen($tag)) === $tag;
+    }
 }

+ 17 - 14
test/Mustache/Test/AutoloaderTest.php

@@ -12,22 +12,25 @@
 /**
  * @group unit
  */
-class Mustache_Test_AutoloaderTest extends PHPUnit_Framework_TestCase {
-	public function testRegister() {
-		$loader = Mustache_Autoloader::register();
-		$this->assertTrue(spl_autoload_unregister(array($loader, 'autoload')));
-	}
+class Mustache_Test_AutoloaderTest extends PHPUnit_Framework_TestCase
+{
+    public function testRegister()
+    {
+        $loader = Mustache_Autoloader::register();
+        $this->assertTrue(spl_autoload_unregister(array($loader, 'autoload')));
+    }
 
-	public function testAutoloader() {
-		$loader = new Mustache_Autoloader(dirname(__FILE__).'/../../fixtures/autoloader');
+    public function testAutoloader()
+    {
+        $loader = new Mustache_Autoloader(dirname(__FILE__).'/../../fixtures/autoloader');
 
-		$this->assertNull($loader->autoload('NonMustacheClass'));
-		$this->assertFalse(class_exists('NonMustacheClass'));
+        $this->assertNull($loader->autoload('NonMustacheClass'));
+        $this->assertFalse(class_exists('NonMustacheClass'));
 
-		$loader->autoload('Mustache_Foo');
-		$this->assertTrue(class_exists('Mustache_Foo'));
+        $loader->autoload('Mustache_Foo');
+        $this->assertTrue(class_exists('Mustache_Foo'));
 
-		$loader->autoload('\Mustache_Bar');
-		$this->assertTrue(class_exists('Mustache_Bar'));
-	}
+        $loader->autoload('\Mustache_Bar');
+        $this->assertTrue(class_exists('Mustache_Bar'));
+    }
 }

+ 80 - 75
test/Mustache/Test/CompilerTest.php

@@ -12,87 +12,92 @@
 /**
  * @group unit
  */
-class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase
+{
 
-	/**
-	 * @dataProvider getCompileValues
-	 */
-	public function testCompile($source, array $tree, $name, $customEscaper, $charset, $expected) {
-		$compiler = new Mustache_Compiler;
+    /**
+     * @dataProvider getCompileValues
+     */
+    public function testCompile($source, array $tree, $name, $customEscaper, $charset, $expected)
+    {
+        $compiler = new Mustache_Compiler;
 
-		$compiled = $compiler->compile($source, $tree, $name, $customEscaper, $charset);
-		foreach ($expected as $contains) {
-			$this->assertContains($contains, $compiled);
-		}
-	}
+        $compiled = $compiler->compile($source, $tree, $name, $customEscaper, $charset);
+        foreach ($expected as $contains) {
+            $this->assertContains($contains, $compiled);
+        }
+    }
 
-	public function getCompileValues() {
-		return array(
-			array('', array(), 'Banana', false, 'ISO-8859-1', array(
-				"\nclass Banana extends Mustache_Template",
-				'return htmlspecialchars($buffer, ENT_COMPAT, \'ISO-8859-1\');',
-				'return $buffer;',
-			)),
+    public function getCompileValues()
+    {
+        return array(
+            array('', array(), 'Banana', false, 'ISO-8859-1', array(
+                "\nclass Banana extends Mustache_Template",
+                'return htmlspecialchars($buffer, ENT_COMPAT, \'ISO-8859-1\');',
+                'return $buffer;',
+            )),
 
-			array('', array($this->createTextToken('TEXT')), 'Monkey', false, 'UTF-8', array(
-				"\nclass Monkey extends Mustache_Template",
-				'return htmlspecialchars($buffer, ENT_COMPAT, \'UTF-8\');',
-				'$buffer .= $indent . \'TEXT\';',
-				'return $buffer;',
-			)),
+            array('', array($this->createTextToken('TEXT')), 'Monkey', false, 'UTF-8', array(
+                "\nclass Monkey extends Mustache_Template",
+                'return htmlspecialchars($buffer, ENT_COMPAT, \'UTF-8\');',
+                '$buffer .= $indent . \'TEXT\';',
+                'return $buffer;',
+            )),
 
-			array('', array($this->createTextToken('TEXT')), 'Monkey', true, 'ISO-8859-1', array(
-				"\nclass Monkey extends Mustache_Template",
-				'$buffer .= $indent . \'TEXT\';',
-				'return call_user_func($this->mustache->getEscape(), $buffer);',
-				'return $buffer;',
-			)),
+            array('', array($this->createTextToken('TEXT')), 'Monkey', true, 'ISO-8859-1', array(
+                "\nclass Monkey extends Mustache_Template",
+                '$buffer .= $indent . \'TEXT\';',
+                'return call_user_func($this->mustache->getEscape(), $buffer);',
+                'return $buffer;',
+            )),
 
-			array(
-				'',
-				array(
-					$this->createTextToken('foo'),
-					$this->createTextToken("\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,
-				'UTF-8',
-				array(
-					"\nclass Monkey extends Mustache_Template",
-					'$buffer .= $indent . \'foo\'',
-					'$buffer .= "\n"',
-					'$value = $context->find(\'name\');',
-					'$buffer .= htmlspecialchars($value, ENT_COMPAT, \'UTF-8\');',
-					'$value = $context->last();',
-					'$buffer .= \'\\\'bar\\\'\';',
-					'return htmlspecialchars($buffer, ENT_COMPAT, \'UTF-8\');',
-					'return $buffer;',
-				)
-			),
-		);
-	}
+            array(
+                '',
+                array(
+                    $this->createTextToken('foo'),
+                    $this->createTextToken("\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,
+                'UTF-8',
+                array(
+                    "\nclass Monkey extends Mustache_Template",
+                    '$buffer .= $indent . \'foo\'',
+                    '$buffer .= "\n"',
+                    '$value = $context->find(\'name\');',
+                    '$buffer .= htmlspecialchars($value, ENT_COMPAT, \'UTF-8\');',
+                    '$value = $context->last();',
+                    '$buffer .= \'\\\'bar\\\'\';',
+                    'return htmlspecialchars($buffer, ENT_COMPAT, \'UTF-8\');',
+                    'return $buffer;',
+                )
+            ),
+        );
+    }
 
-	/**
-	 * @expectedException InvalidArgumentException
-	 */
-	public function testCompilerThrowsUnknownNodeTypeException() {
-		$compiler = new Mustache_Compiler;
-		$compiler->compile('', array(array(Mustache_Tokenizer::TYPE => 'invalid')), 'SomeClass');
-	}
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testCompilerThrowsUnknownNodeTypeException()
+    {
+        $compiler = new Mustache_Compiler;
+        $compiler->compile('', array(array(Mustache_Tokenizer::TYPE => 'invalid')), 'SomeClass');
+    }
 
-	private function createTextToken($value) {
-		return array(
-			Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-			Mustache_Tokenizer::VALUE => $value,
-		);
-	}
+    private function createTextToken($value)
+    {
+        return array(
+            Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+            Mustache_Tokenizer::VALUE => $value,
+        );
+    }
 }

+ 99 - 91
test/Mustache/Test/ContextTest.php

@@ -12,100 +12,108 @@
 /**
  * @group unit
  */
-class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase {
-	public function testConstructor() {
-		$one = new Mustache_Context;
-		$this->assertSame('', $one->find('foo'));
-		$this->assertSame('', $one->find('bar'));
-
-		$two = new Mustache_Context(array(
-			'foo' => 'FOO',
-			'bar' => '<BAR>'
-		));
-		$this->assertEquals('FOO', $two->find('foo'));
-		$this->assertEquals('<BAR>', $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('<foo>', $context->find('foo'));
-		$this->assertEquals('<bar>', $context->findDot('bar'));
-	}
+class Mustache_Test_ContextTest extends PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $one = new Mustache_Context;
+        $this->assertSame('', $one->find('foo'));
+        $this->assertSame('', $one->find('bar'));
+
+        $two = new Mustache_Context(array(
+            'foo' => 'FOO',
+            'bar' => '<BAR>'
+        ));
+        $this->assertEquals('FOO', $two->find('foo'));
+        $this->assertEquals('<BAR>', $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('<foo>', $context->find('foo'));
+        $this->assertEquals('<bar>', $context->findDot('bar'));
+    }
 }
 
-class Mustache_Test_TestDummy {
-	public $name = 'dummy';
+class Mustache_Test_TestDummy
+{
+    public $name = 'dummy';
 
-	public function __invoke() {
-		// nothing
-	}
+    public function __invoke()
+    {
+        // nothing
+    }
 
-	public static function foo() {
-		return '<foo>';
-	}
+    public static function foo()
+    {
+        return '<foo>';
+    }
 
-	public function bar() {
-		return '<bar>';
-	}
+    public function bar()
+    {
+        return '<bar>';
+    }
 }

+ 238 - 222
test/Mustache/Test/EngineTest.php

@@ -12,229 +12,245 @@
 /**
  * @group unit
  */
-class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase {
-
-	private static $tempDir;
-
-	public static function setUpBeforeClass() {
-		self::$tempDir = sys_get_temp_dir() . '/mustache_test';
-		if (file_exists(self::$tempDir)) {
-			self::rmdir(self::$tempDir);
-		}
-	}
-
-	public function testConstructor() {
-		$loader         = new Mustache_Loader_StringLoader;
-		$partialsLoader = new Mustache_Loader_ArrayLoader;
-		$mustache       = new Mustache_Engine(array(
-			'template_class_prefix' => '__whot__',
-			'cache' => self::$tempDir,
-			'loader' => $loader,
-			'partials_loader' => $partialsLoader,
-			'partials' => array(
-				'foo' => '{{ foo }}',
-			),
-			'helpers' => array(
-				'foo' => array($this, 'getFoo'),
-				'bar' => 'BAR',
-			),
-			'escape'  => 'strtoupper',
-			'charset' => 'ISO-8859-1',
-		));
-
-		$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('ISO-8859-1', $mustache->getCharset());
-		$this->assertTrue($mustache->hasHelper('foo'));
-		$this->assertTrue($mustache->hasHelper('bar'));
-		$this->assertFalse($mustache->hasHelper('baz'));
-	}
-
-	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() {
-		$loader    = new Mustache_Loader_StringLoader;
-		$tokenizer = new Mustache_Tokenizer;
-		$parser    = new Mustache_Parser;
-		$compiler  = new Mustache_Compiler;
-		$mustache  = new Mustache_Engine;
-
-		$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());
-	}
-
-	/**
-	 * @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);
-		$fileName  = self::$tempDir . '/' . $className . '.php';
-		$this->assertInstanceOf($className, $template);
-		$this->assertFileExists($fileName);
-		$this->assertContains("\nclass $className extends Mustache_Template", file_get_contents($fileName));
-	}
-
-	/**
-	 * @expectedException InvalidArgumentException
-	 * @dataProvider getBadEscapers
-	 */
-	public function testNonCallableEscapeThrowsException($escape) {
-		new Mustache_Engine(array('escape' => $escape));
-	}
-
-	public function getBadEscapers() {
-		return array(
-			array('nothing'),
-			array('foo', 'bar'),
-		);
-	}
-
-	/**
-	 * @expectedException 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 InvalidArgumentException
-	 */
-	public function testSetHelpersThrowsExceptions() {
-		$mustache = new Mustache_Engine;
-		$mustache->setHelpers('monkeymonkeymonkey');
-	}
-
-	private static function rmdir($path) {
-		$path = rtrim($path, '/').'/';
-		$handle = opendir($path);
-		while (($file = readdir($handle)) !== false) {
-			if ($file == '.' || $file == '..') {
-				continue;
-			}
-
-			$fullpath = $path.$file;
-			if (is_dir($fullpath)) {
-				self::rmdir($fullpath);
-			} else {
-				unlink($fullpath);
-			}
-		}
-
-		closedir($handle);
-		rmdir($path);
-	}
+class Mustache_Test_EngineTest extends PHPUnit_Framework_TestCase
+{
+
+    private static $tempDir;
+
+    public static function setUpBeforeClass()
+    {
+        self::$tempDir = sys_get_temp_dir() . '/mustache_test';
+        if (file_exists(self::$tempDir)) {
+            self::rmdir(self::$tempDir);
+        }
+    }
+
+    public function testConstructor()
+    {
+        $loader         = new Mustache_Loader_StringLoader;
+        $partialsLoader = new Mustache_Loader_ArrayLoader;
+        $mustache       = new Mustache_Engine(array(
+            'template_class_prefix' => '__whot__',
+            'cache' => self::$tempDir,
+            'loader' => $loader,
+            'partials_loader' => $partialsLoader,
+            'partials' => array(
+                'foo' => '{{ foo }}',
+            ),
+            'helpers' => array(
+                'foo' => array($this, 'getFoo'),
+                'bar' => 'BAR',
+            ),
+            'escape'  => 'strtoupper',
+            'charset' => 'ISO-8859-1',
+        ));
+
+        $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('ISO-8859-1', $mustache->getCharset());
+        $this->assertTrue($mustache->hasHelper('foo'));
+        $this->assertTrue($mustache->hasHelper('bar'));
+        $this->assertFalse($mustache->hasHelper('baz'));
+    }
+
+    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()
+    {
+        $loader    = new Mustache_Loader_StringLoader;
+        $tokenizer = new Mustache_Tokenizer;
+        $parser    = new Mustache_Parser;
+        $compiler  = new Mustache_Compiler;
+        $mustache  = new Mustache_Engine;
+
+        $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());
+    }
+
+    /**
+     * @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);
+        $fileName  = self::$tempDir . '/' . $className . '.php';
+        $this->assertInstanceOf($className, $template);
+        $this->assertFileExists($fileName);
+        $this->assertContains("\nclass $className extends Mustache_Template", file_get_contents($fileName));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     * @dataProvider getBadEscapers
+     */
+    public function testNonCallableEscapeThrowsException($escape)
+    {
+        new Mustache_Engine(array('escape' => $escape));
+    }
+
+    public function getBadEscapers()
+    {
+        return array(
+            array('nothing'),
+            array('foo', 'bar'),
+        );
+    }
+
+    /**
+     * @expectedException 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 InvalidArgumentException
+     */
+    public function testSetHelpersThrowsExceptions()
+    {
+        $mustache = new Mustache_Engine;
+        $mustache->setHelpers('monkeymonkeymonkey');
+    }
+
+    private static function rmdir($path)
+    {
+        $path = rtrim($path, '/').'/';
+        $handle = opendir($path);
+        while (($file = readdir($handle)) !== false) {
+            if ($file == '.' || $file == '..') {
+                continue;
+            }
+
+            $fullpath = $path.$file;
+            if (is_dir($fullpath)) {
+                self::rmdir($fullpath);
+            } else {
+                unlink($fullpath);
+            }
+        }
+
+        closedir($handle);
+        rmdir($path);
+    }
 }
 
 class MustacheStub extends Mustache_Engine {
-	public $source;
-	public $template;
-	public function loadTemplate($source) {
-		$this->source = $source;
-
-		return $this->template;
-	}
+    public $source;
+    public $template;
+    public function loadTemplate($source)
+    {
+        $this->source = $source;
+
+        return $this->template;
+    }
 }

+ 18 - 14
test/Mustache/Test/Functional/CallTest.php

@@ -13,24 +13,28 @@
  * @group magic_methods
  * @group functional
  */
-class Mustache_Test_Functional_CallTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_Functional_CallTest extends PHPUnit_Framework_TestCase
+{
 
-	public function testCallEatsContext() {
-		$m = new Mustache_Engine;
-		$tpl = $m->loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}');
+    public function testCallEatsContext()
+    {
+        $m = new Mustache_Engine;
+        $tpl = $m->loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}');
 
-		$foo = new Mustache_Test_Functional_ClassWithCall();
-		$foo->name = 'Bob';
+        $foo = new Mustache_Test_Functional_ClassWithCall();
+        $foo->name = 'Bob';
 
-		$data = array('label' => 'name', 'foo' => $foo);
+        $data = array('label' => 'name', 'foo' => $foo);
 
-		$this->assertEquals('name: Bob', $tpl->render($data));
-	}
+        $this->assertEquals('name: Bob', $tpl->render($data));
+    }
 }
 
-class Mustache_Test_Functional_ClassWithCall {
-	public $name;
-	public function __call($method, $args) {
-		return 'unknown value';
-	}
+class Mustache_Test_Functional_ClassWithCall
+{
+    public $name;
+    public function __call($method, $args)
+    {
+        return 'unknown value';
+    }
 }

+ 126 - 121
test/Mustache/Test/Functional/ExamplesTest.php

@@ -13,125 +13,130 @@
  * @group examples
  * @group functional
  */
-class Mustache_Test_Functional_ExamplesTest extends PHPUnit_Framework_TestCase {
-
-	/**
-	 * Test everything in the `examples` directory.
-	 *
-	 * @dataProvider getExamples
-	 *
-	 * @param string $context
-	 * @param string $source
-	 * @param array  $partials
-	 * @param string $expected
-	 */
-	public function testExamples($context, $source, $partials, $expected) {
-		$mustache = new Mustache_Engine(array(
-			'partials' => $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);
-						$context = new $info['filename'];
-						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;
-	}
+class Mustache_Test_Functional_ExamplesTest extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Test everything in the `examples` directory.
+     *
+     * @dataProvider getExamples
+     *
+     * @param string $context
+     * @param string $source
+     * @param array  $partials
+     * @param string $expected
+     */
+    public function testExamples($context, $source, $partials, $expected)
+    {
+        $mustache = new Mustache_Engine(array(
+            'partials' => $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);
+                        $context = new $info['filename'];
+                        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;
+    }
 }

+ 71 - 59
test/Mustache/Test/Functional/HigherOrderSectionsTest.php

@@ -13,82 +13,94 @@
  * @group lambdas
  * @group functional
  */
-class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework_TestCase
+{
 
-	private $mustache;
+    private $mustache;
 
-	public function setUp() {
-		$this->mustache = new Mustache_Engine;
-	}
+    public function setUp()
+    {
+        $this->mustache = new Mustache_Engine;
+    }
 
-	public function testRuntimeSectionCallback() {
-		$tpl = $this->mustache->loadTemplate('{{#doublewrap}}{{name}}{{/doublewrap}}');
+    public function testRuntimeSectionCallback()
+    {
+        $tpl = $this->mustache->loadTemplate('{{#doublewrap}}{{name}}{{/doublewrap}}');
 
-		$foo = new Mustache_Test_Functional_Foo;
-		$foo->doublewrap = array($foo, 'wrapWithBoth');
+        $foo = new Mustache_Test_Functional_Foo;
+        $foo->doublewrap = array($foo, 'wrapWithBoth');
 
-		$this->assertEquals(sprintf('<strong><em>%s</em></strong>', $foo->name), $tpl->render($foo));
-	}
+        $this->assertEquals(sprintf('<strong><em>%s</em></strong>', $foo->name), $tpl->render($foo));
+    }
 
-	public function testStaticSectionCallback() {
-		$tpl = $this->mustache->loadTemplate('{{#trimmer}}    {{name}}    {{/trimmer}}');
+    public function testStaticSectionCallback()
+    {
+        $tpl = $this->mustache->loadTemplate('{{#trimmer}}    {{name}}    {{/trimmer}}');
 
-		$foo = new Mustache_Test_Functional_Foo;
-		$foo->trimmer = array(get_class($foo), 'staticTrim');
+        $foo = new Mustache_Test_Functional_Foo;
+        $foo->trimmer = array(get_class($foo), 'staticTrim');
 
-		$this->assertEquals($foo->name, $tpl->render($foo));
-	}
+        $this->assertEquals($foo->name, $tpl->render($foo));
+    }
 
-	public function testViewArraySectionCallback() {
-		$tpl = $this->mustache->loadTemplate('{{#trim}}    {{name}}    {{/trim}}');
+    public function testViewArraySectionCallback()
+    {
+        $tpl = $this->mustache->loadTemplate('{{#trim}}    {{name}}    {{/trim}}');
 
-		$foo = new Mustache_Test_Functional_Foo;
+        $foo = new Mustache_Test_Functional_Foo;
 
-		$data = array(
-			'name' => 'Bob',
-			'trim' => array(get_class($foo), 'staticTrim'),
-		);
+        $data = array(
+            'name' => 'Bob',
+            'trim' => array(get_class($foo), 'staticTrim'),
+        );
 
-		$this->assertEquals($data['name'], $tpl->render($data));
-	}
+        $this->assertEquals($data['name'], $tpl->render($data));
+    }
 
-	public function testMonsters() {
-		$tpl = $this->mustache->loadTemplate('{{#title}}{{title}} {{/title}}{{name}}');
+    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));
+        $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));
-	}
+        $dracula = new Mustache_Test_Functional_Monster();
+        $dracula->title = 'Count';
+        $dracula->name  = 'Dracula';
+        $this->assertEquals('Count Dracula', $tpl->render($dracula));
+    }
 }
 
-class Mustache_Test_Functional_Foo {
-	public $name = 'Justin';
-	public $lorem = 'Lorem ipsum dolor sit amet,';
-
-	public function wrapWithEm($text) {
-		return sprintf('<em>%s</em>', $text);
-	}
-
-	public function wrapWithStrong($text) {
-		return sprintf('<strong>%s</strong>', $text);
-	}
-
-	public function wrapWithBoth($text) {
-		return self::wrapWithStrong(self::wrapWithEm($text));
-	}
-
-	public static function staticTrim($text) {
-		return trim($text);
-	}
+class Mustache_Test_Functional_Foo
+{
+    public $name = 'Justin';
+    public $lorem = 'Lorem ipsum dolor sit amet,';
+
+    public function wrapWithEm($text)
+    {
+        return sprintf('<em>%s</em>', $text);
+    }
+
+    public function wrapWithStrong($text)
+    {
+        return sprintf('<strong>%s</strong>', $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;
+class Mustache_Test_Functional_Monster
+{
+    public $title;
+    public $name;
 }

+ 101 - 89
test/Mustache/Test/Functional/MustacheInjectionTest.php

@@ -13,128 +13,140 @@
  * @group mustache_injection
  * @group functional
  */
-class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_TestCase
+{
 
-	private $mustache;
+    private $mustache;
 
-	public function setUp() {
-		$this->mustache = new Mustache_Engine;
-	}
+    public function setUp()
+    {
+        $this->mustache = new Mustache_Engine;
+    }
 
-	// interpolation
+    // interpolation
 
-	public function testInterpolationInjection() {
-		$tpl = $this->mustache->loadTemplate('{{ a }}');
+    public function testInterpolationInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{ a }}');
 
-		$data = array(
-			'a' => '{{ b }}',
-			'b' => 'FAIL'
-		);
+        $data = array(
+            'a' => '{{ b }}',
+            'b' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ b }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ b }}', $tpl->render($data));
+    }
 
-	public function testUnescapedInterpolationInjection() {
-		$tpl = $this->mustache->loadTemplate('{{{ a }}}');
+    public function testUnescapedInterpolationInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{{ a }}}');
 
-		$data = array(
-			'a' => '{{ b }}',
-			'b' => 'FAIL'
-		);
+        $data = array(
+            'a' => '{{ b }}',
+            'b' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ b }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ b }}', $tpl->render($data));
+    }
 
 
-	// sections
+    // sections
 
-	public function testSectionInjection() {
-		$tpl = $this->mustache->loadTemplate('{{# a }}{{ b }}{{/ a }}');
+    public function testSectionInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{# a }}{{ b }}{{/ a }}');
 
-		$data = array(
-			'a' => true,
-			'b' => '{{ c }}',
-			'c' => 'FAIL'
-		);
+        $data = array(
+            'a' => true,
+            'b' => '{{ c }}',
+            'c' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ c }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ c }}', $tpl->render($data));
+    }
 
-	public function testUnescapedSectionInjection() {
-		$tpl = $this->mustache->loadTemplate('{{# a }}{{{ b }}}{{/ a }}');
+    public function testUnescapedSectionInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{# a }}{{{ b }}}{{/ a }}');
 
-		$data = array(
-			'a' => true,
-			'b' => '{{ c }}',
-			'c' => 'FAIL'
-		);
+        $data = array(
+            'a' => true,
+            'b' => '{{ c }}',
+            'c' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ c }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ c }}', $tpl->render($data));
+    }
 
 
-	// partials
+    // partials
 
-	public function testPartialInjection() {
-		$tpl = $this->mustache->loadTemplate('{{> partial }}');
-		$this->mustache->setPartials(array(
-			'partial' => '{{ a }}',
-		));
+    public function testPartialInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{> partial }}');
+        $this->mustache->setPartials(array(
+            'partial' => '{{ a }}',
+        ));
 
-		$data = array(
-			'a' => '{{ b }}',
-			'b' => 'FAIL'
-		);
+        $data = array(
+            'a' => '{{ b }}',
+            'b' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ b }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ b }}', $tpl->render($data));
+    }
 
-	public function testPartialUnescapedInjection() {
-		$tpl = $this->mustache->loadTemplate('{{> partial }}');
-		$this->mustache->setPartials(array(
-			'partial' => '{{{ a }}}',
-		));
+    public function testPartialUnescapedInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{> partial }}');
+        $this->mustache->setPartials(array(
+            'partial' => '{{{ a }}}',
+        ));
 
-		$data = array(
-			'a' => '{{ b }}',
-			'b' => 'FAIL'
-		);
+        $data = array(
+            'a' => '{{ b }}',
+            'b' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ b }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ b }}', $tpl->render($data));
+    }
 
 
-	// lambdas
+    // lambdas
 
-	public function testLambdaInterpolationInjection() {
-		$tpl = $this->mustache->loadTemplate('{{ a }}');
+    public function testLambdaInterpolationInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{ a }}');
 
-		$data = array(
-			'a' => array($this, 'lambdaInterpolationCallback'),
-			'b' => '{{ c }}',
-			'c' => 'FAIL'
-		);
+        $data = array(
+            'a' => array($this, 'lambdaInterpolationCallback'),
+            'b' => '{{ c }}',
+            'c' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ c }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ c }}', $tpl->render($data));
+    }
 
-	public static function lambdaInterpolationCallback() {
-		return '{{ b }}';
-	}
+    public static function lambdaInterpolationCallback()
+    {
+        return '{{ b }}';
+    }
 
-	public function testLambdaSectionInjection() {
-		$tpl = $this->mustache->loadTemplate('{{# a }}b{{/ a }}');
+    public function testLambdaSectionInjection()
+    {
+        $tpl = $this->mustache->loadTemplate('{{# a }}b{{/ a }}');
 
-		$data = array(
-			'a' => array($this, 'lambdaSectionCallback'),
-			'b' => '{{ c }}',
-			'c' => 'FAIL'
-		);
+        $data = array(
+            'a' => array($this, 'lambdaSectionCallback'),
+            'b' => '{{ c }}',
+            'c' => 'FAIL'
+        );
 
-		$this->assertEquals('{{ c }}', $tpl->render($data));
-	}
+        $this->assertEquals('{{ c }}', $tpl->render($data));
+    }
 
-	public static function lambdaSectionCallback($text) {
-		return '{{ ' . $text . ' }}';
-	}
+    public static function lambdaSectionCallback($text)
+    {
+        return '{{ ' . $text . ' }}';
+    }
 }

+ 157 - 140
test/Mustache/Test/Functional/MustacheSpecTest.php

@@ -15,144 +15,161 @@
  * @group mustache-spec
  * @group functional
  */
-class Mustache_Test_Functional_MustacheSpecTest extends PHPUnit_Framework_TestCase {
-
-	private static $mustache;
-
-	public static function setUpBeforeClass() {
-		self::$mustache = new Mustache_Engine;
-	}
-
-	/**
-	 * For some reason data providers can't mark tests skipped, so this test exists
-	 * simply to provide a 'skipped' test if the `spec` submodule isn't initialized.
-	 */
-	public function testSpecInitialized() {
-		if (!file_exists(dirname(__FILE__).'/../../../../vendor/spec/specs/')) {
-			$this->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');
-	}
-
-	/**
-	 * Data provider for the mustache spec test.
-	 *
-	 * Loads YAML files from the spec and converts them to PHPisms.
-	 *
-	 * @access public
-	 * @return array
-	 */
-	private function loadSpec($name) {
-		$filename = dirname(__FILE__) . '/../../../../vendor/spec/specs/' . $name . '.yml';
-		if (!file_exists($filename)) {
-			return array();
-		}
-
-		$data = array();
-		$yaml = new sfYamlParser;
-		$file = file_get_contents($filename);
-
-		// @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
-		if ($name === '~lambdas') {
-			$file = str_replace(" !code\n", "\n", $file);
-		}
-
-		$spec = $yaml->parse($file);
-
-		foreach ($spec['tests'] as $test) {
-			$data[] = array(
-				$test['name'] . ': ' . $test['desc'],
-				$test['template'],
-				isset($test['partials']) ? $test['partials'] : array(),
-				$test['data'],
-				$test['expected'],
-			);
-		}
-
-		return $data;
-	}
-
-	private static function loadTemplate($source, $partials) {
-		self::$mustache->setPartials($partials);
-
-		return self::$mustache->loadTemplate($source);
-	}
+class Mustache_Test_Functional_MustacheSpecTest extends PHPUnit_Framework_TestCase
+{
+
+    private static $mustache;
+
+    public static function setUpBeforeClass()
+    {
+        self::$mustache = new Mustache_Engine;
+    }
+
+    /**
+     * For some reason data providers can't mark tests skipped, so this test exists
+     * simply to provide a 'skipped' test if the `spec` submodule isn't initialized.
+     */
+    public function testSpecInitialized()
+    {
+        if (!file_exists(dirname(__FILE__).'/../../../../vendor/spec/specs/')) {
+            $this->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');
+    }
+
+    /**
+     * Data provider for the mustache spec test.
+     *
+     * Loads YAML files from the spec and converts them to PHPisms.
+     *
+     * @access public
+     * @return array
+     */
+    private function loadSpec($name)
+    {
+        $filename = dirname(__FILE__) . '/../../../../vendor/spec/specs/' . $name . '.yml';
+        if (!file_exists($filename)) {
+            return array();
+        }
+
+        $data = array();
+        $yaml = new sfYamlParser;
+        $file = file_get_contents($filename);
+
+        // @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
+        if ($name === '~lambdas') {
+            $file = str_replace(" !code\n", "\n", $file);
+        }
+
+        $spec = $yaml->parse($file);
+
+        foreach ($spec['tests'] as $test) {
+            $data[] = array(
+                $test['name'] . ': ' . $test['desc'],
+                $test['template'],
+                isset($test['partials']) ? $test['partials'] : array(),
+                $test['data'],
+                $test['expected'],
+            );
+        }
+
+        return $data;
+    }
+
+    private static function loadTemplate($source, $partials)
+    {
+        self::$mustache->setPartials($partials);
+
+        return self::$mustache->loadTemplate($source);
+    }
 }

+ 83 - 67
test/Mustache/Test/Functional/ObjectSectionTest.php

@@ -13,82 +13,98 @@
  * @group sections
  * @group functional
  */
-class Mustache_Test_Functional_ObjectSectionTest extends PHPUnit_Framework_TestCase {
-	private $mustache;
-
-	public function setUp() {
-		$this->mustache = new Mustache_Engine;
-	}
-
-	public function testBasicObject() {
-		$tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
-		$this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Alpha));
-	}
-
-	/**
-	 * @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_ObjectSectionTest extends PHPUnit_Framework_TestCase
+{
+    private $mustache;
+
+    public function setUp()
+    {
+        $this->mustache = new Mustache_Engine;
+    }
+
+    public function testBasicObject()
+    {
+        $tpl = $this->mustache->loadTemplate('{{#foo}}{{name}}{{/foo}}');
+        $this->assertEquals('Foo', $tpl->render(new Mustache_Test_Functional_Alpha));
+    }
+
+    /**
+     * @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;
+class Mustache_Test_Functional_Alpha
+{
+    public $foo;
 
-	public function __construct() {
-		$this->foo = new StdClass();
-		$this->foo->name = 'Foo';
-		$this->foo->number = 1;
-	}
+    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_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;
+class Mustache_Test_Functional_Gamma
+{
+    public $bar;
 
-	public function __construct() {
-		$this->bar = new Mustache_Test_Functional_Beta;
-	}
+    public function __construct()
+    {
+        $this->bar = new Mustache_Test_Functional_Beta;
+    }
 }
 
-class Mustache_Test_Functional_Delta {
-	protected $_name = 'Foo';
+class Mustache_Test_Functional_Delta
+{
+    protected $_name = 'Foo';
 
-	public function name() {
-		return $this->_name;
-	}
+    public function name()
+    {
+        return $this->_name;
+    }
 }

+ 25 - 18
test/Mustache/Test/HelperCollectionTest.php

@@ -9,27 +9,31 @@
  * file that was distributed with this source code.
  */
 
-class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase {
-	public function testConstructor() {
-		$foo = array($this, 'getFoo');
-		$bar = 'BAR';
+class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $foo = array($this, 'getFoo');
+        $bar = 'BAR';
 
-		$helpers = new Mustache_HelperCollection(array(
-			'foo' => $foo,
-			'bar' => $bar,
-		));
+        $helpers = new Mustache_HelperCollection(array(
+            'foo' => $foo,
+            'bar' => $bar,
+        ));
 
-		$this->assertSame($foo, $helpers->get('foo'));
-		$this->assertSame($bar, $helpers->get('bar'));
-	}
+        $this->assertSame($foo, $helpers->get('foo'));
+        $this->assertSame($bar, $helpers->get('bar'));
+    }
 
-    public static function getFoo() {
+    public static function getFoo()
+    {
         echo 'foo';
     }
 
-	public function testAccessorsAndMutators() {
-		$foo = array($this, 'getFoo');
-		$bar = 'BAR';
+    public function testAccessorsAndMutators()
+    {
+        $foo = array($this, 'getFoo');
+        $bar = 'BAR';
 
         $helpers = new Mustache_HelperCollection;
         $this->assertTrue($helpers->isEmpty());
@@ -52,7 +56,8 @@ class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase {
         $this->assertTrue($helpers->has('bar'));
     }
 
-    public function testMagicMethods() {
+    public function testMagicMethods()
+    {
         $foo = array($this, 'getFoo');
         $bar = 'BAR';
 
@@ -88,7 +93,8 @@ class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase {
     /**
      * @dataProvider getInvalidHelperArguments
      */
-    public function testHelperCollectionIsntAfraidToThrowExceptions($helpers = array(), $actions = array(), $exception = null) {
+    public function testHelperCollectionIsntAfraidToThrowExceptions($helpers = array(), $actions = array(), $exception = null)
+    {
         if ($exception) {
             $this->setExpectedException($exception);
         }
@@ -100,7 +106,8 @@ class Mustache_Test_HelperCollectionTest extends PHPUnit_Framework_TestCase {
         }
     }
 
-    public function getInvalidHelperArguments() {
+    public function getInvalidHelperArguments()
+    {
         return array(
             array(
                 'not helpers',

+ 32 - 28
test/Mustache/Test/Loader/ArrayLoaderTest.php

@@ -12,37 +12,41 @@
 /**
  * @group unit
  */
-class Mustache_Test_Loader_ArrayLoaderTest extends PHPUnit_Framework_TestCase {
-	public function testConstructor() {
-		$loader = new Mustache_Loader_ArrayLoader(array(
-			'foo' => 'bar'
-		));
+class Mustache_Test_Loader_ArrayLoaderTest extends PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $loader = new Mustache_Loader_ArrayLoader(array(
+            'foo' => 'bar'
+        ));
 
-		$this->assertEquals('bar', $loader->load('foo'));
-	}
+        $this->assertEquals('bar', $loader->load('foo'));
+    }
 
-	public function testSetAndLoadTemplates() {
-		$loader = new Mustache_Loader_ArrayLoader(array(
-			'foo' => '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->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'));
-	}
+        $loader->setTemplates(array(
+            'foo' => 'FOO',
+            'baz' => 'BAZ',
+        ));
+        $this->assertEquals('FOO', $loader->load('foo'));
+        $this->assertEquals('BAZ', $loader->load('baz'));
+    }
 
-	/**
-	 * @expectedException InvalidArgumentException
-	 */
-	public function testMissingTemplatesThrowExceptions() {
-		$loader = new Mustache_Loader_ArrayLoader;
-		$loader->load('not_a_real_template');
-	}
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testMissingTemplatesThrowExceptions()
+    {
+        $loader = new Mustache_Loader_ArrayLoader;
+        $loader->load('not_a_real_template');
+    }
 }

+ 32 - 27
test/Mustache/Test/Loader/FilesystemLoaderTest.php

@@ -12,35 +12,40 @@
 /**
  * @group unit
  */
-class Mustache_Test_Loader_FilesystemLoaderTest extends PHPUnit_Framework_TestCase {
-	public function testConstructor() {
-		$baseDir = realpath(dirname(__FILE__).'/../../../fixtures/templates');
-		$loader = new Mustache_Loader_FilesystemLoader($baseDir, array('extension' => '.ms'));
-		$this->assertEquals('alpha contents', $loader->load('alpha'));
-		$this->assertEquals('beta contents', $loader->load('beta.ms'));
-	}
+class Mustache_Test_Loader_FilesystemLoaderTest extends PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $baseDir = realpath(dirname(__FILE__).'/../../../fixtures/templates');
+        $loader = new Mustache_Loader_FilesystemLoader($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 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'));
+    }
 
-	/**
-	 * @expectedException RuntimeException
-	 */
-	public function testMissingBaseDirThrowsException() {
-		$loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/not_a_directory');
-	}
+    /**
+     * @expectedException RuntimeException
+     */
+    public function testMissingBaseDirThrowsException()
+    {
+        $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/not_a_directory');
+    }
 
-	/**
-	 * @expectedException InvalidArgumentException
-	 */
-	public function testMissingTemplateThrowsException() {
-		$baseDir = realpath(dirname(__FILE__).'/../../../fixtures/templates');
-		$loader = new Mustache_Loader_FilesystemLoader($baseDir);
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testMissingTemplateThrowsException()
+    {
+        $baseDir = realpath(dirname(__FILE__).'/../../../fixtures/templates');
+        $loader = new Mustache_Loader_FilesystemLoader($baseDir);
 
-		$loader->load('fake');
-	}
+        $loader->load('fake');
+    }
 }

+ 9 - 7
test/Mustache/Test/Loader/StringLoaderTest.php

@@ -12,12 +12,14 @@
 /**
  * @group unit
  */
-class Mustache_Test_Loader_StringLoaderTest extends PHPUnit_Framework_TestCase {
-	public function testLoadTemplates() {
-		$loader = new Mustache_Loader_StringLoader;
+class Mustache_Test_Loader_StringLoaderTest extends PHPUnit_Framework_TestCase
+{
+    public function testLoadTemplates()
+    {
+        $loader = new Mustache_Loader_StringLoader;
 
-		$this->assertEquals('foo', $loader->load('foo'));
-		$this->assertEquals('{{ bar }}', $loader->load('{{ bar }}'));
-		$this->assertEquals("\n{{! comment }}\n", $loader->load("\n{{! comment }}\n"));
-	}
+        $this->assertEquals('foo', $loader->load('foo'));
+        $this->assertEquals('{{ bar }}', $loader->load('{{ bar }}'));
+        $this->assertEquals("\n{{! comment }}\n", $loader->load("\n{{! comment }}\n"));
+    }
 }

+ 156 - 153
test/Mustache/Test/ParserTest.php

@@ -12,168 +12,171 @@
 /**
  * @group unit
  */
-class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_ParserTest extends PHPUnit_Framework_TestCase
+{
 
-	/**
-	 * @dataProvider getTokenSets
-	 */
-	public function testParse($tokens, $expected)
-	{
-		$parser = new Mustache_Parser;
-		$this->assertEquals($expected, $parser->parse($tokens));
-	}
+    /**
+     * @dataProvider getTokenSets
+     */
+    public function testParse($tokens, $expected)
+    {
+        $parser = new Mustache_Parser;
+        $this->assertEquals($expected, $parser->parse($tokens));
+    }
 
-	public function getTokenSets()
-	{
-		return array(
-			array(
-				array(),
-				array()
-			),
+    public function getTokenSets()
+    {
+        return array(
+            array(
+                array(),
+                array()
+            ),
 
-			array(
-				array(array(
-					Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-					Mustache_Tokenizer::VALUE => 'text'
-				)),
-				array(array(
-					Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-					Mustache_Tokenizer::VALUE => 'text'
-				)),
-			),
+            array(
+                array(array(
+                    Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                    Mustache_Tokenizer::VALUE => 'text'
+                )),
+                array(array(
+                    Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                    Mustache_Tokenizer::VALUE => 'text'
+                )),
+            ),
 
-			array(
-				array(array(
-					Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
-					Mustache_Tokenizer::NAME => 'name'
-				)),
-				array(array(
-					Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
-					Mustache_Tokenizer::NAME => 'name'
-				)),
-			),
+            array(
+                array(array(
+                    Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                    Mustache_Tokenizer::NAME => 'name'
+                )),
+                array(array(
+                    Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                    Mustache_Tokenizer::NAME => 'name'
+                )),
+            ),
 
-			array(
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'foo'
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
-						Mustache_Tokenizer::INDEX => 123,
-						Mustache_Tokenizer::NAME  => 'parent'
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
-						Mustache_Tokenizer::NAME  => 'name'
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
-						Mustache_Tokenizer::INDEX => 456,
-						Mustache_Tokenizer::NAME  => 'parent'
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'bar'
-					),
-				),
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'foo'
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
-						Mustache_Tokenizer::NAME  => 'parent',
-						Mustache_Tokenizer::INDEX => 123,
-						Mustache_Tokenizer::END   => 456,
-						Mustache_Tokenizer::NODES => array(
-							array(
-								Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
-								Mustache_Tokenizer::NAME => 'name'
-							),
-						),
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'bar'
-					),
-				),
-			),
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => 'foo'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
+                        Mustache_Tokenizer::INDEX => 123,
+                        Mustache_Tokenizer::NAME  => 'parent'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME  => 'name'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::INDEX => 456,
+                        Mustache_Tokenizer::NAME  => 'parent'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                ),
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => 'foo'
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                        Mustache_Tokenizer::END   => 456,
+                        Mustache_Tokenizer::NODES => array(
+                            array(
+                                Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED,
+                                Mustache_Tokenizer::NAME => 'name'
+                            ),
+                        ),
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => 'bar'
+                    ),
+                ),
+            ),
 
-		);
-	}
+        );
+    }
 
-	/**
-	 * @dataProvider getBadParseTrees
-	 * @expectedException LogicException
-	 */
-	public function testParserThrowsExceptions($tokens) {
-		$parser = new Mustache_Parser;
-		$parser->parse($tokens);
-	}
+    /**
+     * @dataProvider getBadParseTrees
+     * @expectedException LogicException
+     */
+    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::INDEX => 123,
-					),
-				),
-			),
+    public function getBadParseTrees()
+    {
+        return array(
+            // no close
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                ),
+            ),
 
-			// no close inverted
-			array(
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
-						Mustache_Tokenizer::NAME  => 'parent',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-				),
-			),
+            // no close inverted
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_INVERTED,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                ),
+            ),
 
-			// no opening inverted
-			array(
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
-						Mustache_Tokenizer::NAME  => 'parent',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-				),
-			),
+            // no opening inverted
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                ),
+            ),
 
-			// weird nesting
-			array(
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
-						Mustache_Tokenizer::NAME  => 'parent',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
-						Mustache_Tokenizer::NAME  => 'child',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
-						Mustache_Tokenizer::NAME  => 'parent',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
-						Mustache_Tokenizer::NAME  => 'child',
-						Mustache_Tokenizer::INDEX => 123,
-					),
-				),
-			),
-		);
-	}
+            // weird nesting
+            array(
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
+                        Mustache_Tokenizer::NAME  => 'child',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME  => 'parent',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_END_SECTION,
+                        Mustache_Tokenizer::NAME  => 'child',
+                        Mustache_Tokenizer::INDEX => 123,
+                    ),
+                ),
+            ),
+        );
+    }
 }

+ 36 - 30
test/Mustache/Test/TemplateTest.php

@@ -12,38 +12,44 @@
 /**
  * @group unit
  */
-class Mustache_Test_TemplateTest extends PHPUnit_Framework_TestCase {
-	public function testConstructor() {
-		$mustache = new Mustache_Engine;
-		$template = new Mustache_Test_TemplateStub($mustache);
-		$this->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_TemplateTest extends PHPUnit_Framework_TestCase
+{
+    public function testConstructor()
+    {
+        $mustache = new Mustache_Engine;
+        $template = new Mustache_Test_TemplateStub($mustache);
+        $this->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;
+class Mustache_Test_TemplateStub extends Mustache_Template
+{
+    public $rendered;
 
-	public function getMustache() {
-		return $this->mustache;
-	}
+    public function getMustache()
+    {
+        return $this->mustache;
+    }
 
-	public function renderInternal(Mustache_Context $context, $indent = '', $escape = false) {
-		return $this->rendered;
-	}
+    public function renderInternal(Mustache_Context $context, $indent = '', $escape = false)
+    {
+        return $this->rendered;
+    }
 }

+ 121 - 118
test/Mustache/Test/TokenizerTest.php

@@ -12,130 +12,133 @@
 /**
  * @group unit
  */
-class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase {
+class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase
+{
 
-	/**
-	 * @dataProvider getTokens
-	 */
-	public function testScan($text, $delimiters, $expected) {
-		$tokenizer = new Mustache_Tokenizer;
-		$this->assertSame($expected, $tokenizer->scan($text, $delimiters));
-	}
+    /**
+     * @dataProvider getTokens
+     */
+    public function testScan($text, $delimiters, $expected)
+    {
+        $tokenizer = new Mustache_Tokenizer;
+        $this->assertSame($expected, $tokenizer->scan($text, $delimiters));
+    }
 
-	public function getTokens() {
-		return array(
-			array(
-				'text',
-				null,
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'text',
-					),
-				),
-			),
+    public function getTokens()
+    {
+        return array(
+            array(
+                'text',
+                null,
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => 'text',
+                    ),
+                ),
+            ),
 
-			array(
-				'text',
-				'<<< >>>',
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => 'text',
-					),
-				),
-			),
+            array(
+                'text',
+                '<<< >>>',
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        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::INDEX => 10,
-					)
-				)
-			),
+            array(
+                '{{ name }}',
+                null,
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME  => 'name',
+                        Mustache_Tokenizer::OTAG  => '{{',
+                        Mustache_Tokenizer::CTAG  => '}}',
+                        Mustache_Tokenizer::INDEX => 10,
+                    )
+                )
+            ),
 
-			array(
-				'{{ name }}',
-				'<<< >>>',
-				array(
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => '{{ name }}',
-					),
-				),
-			),
+            array(
+                '{{ name }}',
+                '<<< >>>',
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        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::INDEX => 12,
-					)
-				)
-			),
+            array(
+                '<<< name >>>',
+                '<<< >>>',
+                array(
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME  => 'name',
+                        Mustache_Tokenizer::OTAG  => '<<<',
+                        Mustache_Tokenizer::CTAG  => '>>>',
+                        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::INDEX => 8,
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => "\n",
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
-						Mustache_Tokenizer::NAME  => 'b',
-						Mustache_Tokenizer::OTAG  => '{{',
-						Mustache_Tokenizer::CTAG  => '}}',
-						Mustache_Tokenizer::INDEX => 18,
-					),
-					null,
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
-						Mustache_Tokenizer::NAME  => 'c',
-						Mustache_Tokenizer::OTAG  => '|',
-						Mustache_Tokenizer::CTAG  => '|',
-						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::INDEX => 37,
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
-						Mustache_Tokenizer::VALUE => "\n",
-					),
-					array(
-						Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_UNESCAPED,
-						Mustache_Tokenizer::NAME  => 'd',
-						Mustache_Tokenizer::OTAG  => '|',
-						Mustache_Tokenizer::CTAG  => '|',
-						Mustache_Tokenizer::INDEX => 51,
-					),
+            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::INDEX => 8,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => "\n",
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_SECTION,
+                        Mustache_Tokenizer::NAME  => 'b',
+                        Mustache_Tokenizer::OTAG  => '{{',
+                        Mustache_Tokenizer::CTAG  => '}}',
+                        Mustache_Tokenizer::INDEX => 18,
+                    ),
+                    null,
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_ESCAPED,
+                        Mustache_Tokenizer::NAME  => 'c',
+                        Mustache_Tokenizer::OTAG  => '|',
+                        Mustache_Tokenizer::CTAG  => '|',
+                        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::INDEX => 37,
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_TEXT,
+                        Mustache_Tokenizer::VALUE => "\n",
+                    ),
+                    array(
+                        Mustache_Tokenizer::TYPE  => Mustache_Tokenizer::T_UNESCAPED,
+                        Mustache_Tokenizer::NAME  => 'd',
+                        Mustache_Tokenizer::OTAG  => '|',
+                        Mustache_Tokenizer::CTAG  => '|',
+                        Mustache_Tokenizer::INDEX => 51,
+                    ),
 
-				)
-			),
-		);
-	}
+                )
+            ),
+        );
+    }
 }

+ 3 - 2
test/fixtures/autoloader/Mustache/Bar.php

@@ -9,6 +9,7 @@
  * file that was distributed with this source code.
  */
 
-class Mustache_Bar {
-	// nada
+class Mustache_Bar
+{
+    // nada
 }

+ 3 - 2
test/fixtures/autoloader/Mustache/Foo.php

@@ -9,6 +9,7 @@
  * file that was distributed with this source code.
  */
 
-class Mustache_Foo {
-	// nada
+class Mustache_Foo
+{
+    // nada
 }

+ 3 - 2
test/fixtures/autoloader/NonMustacheClass.php

@@ -9,6 +9,7 @@
  * file that was distributed with this source code.
  */
 
-class NonMustacheClass {
-	// noop
+class NonMustacheClass
+{
+    // noop
 }

+ 10 - 9
test/fixtures/examples/child_context/ChildContext.php

@@ -1,13 +1,14 @@
 <?php
 
-class ChildContext {
-	public $parent = array(
-		'child' => 'child works',
-	);
+class ChildContext
+{
+    public $parent = array(
+        'child' => 'child works',
+    );
 
-	public $grandparent = array(
-		'parent' => array(
-			'child' => 'grandchild works',
-		),
-	);
+    public $grandparent = array(
+        'parent' => array(
+            'child' => 'grandchild works',
+        ),
+    );
 }

+ 6 - 4
test/fixtures/examples/comments/Comments.php

@@ -1,7 +1,9 @@
 <?php
 
-class Comments {
-	public function title() {
-		return 'A Comedy of Errors';
-	}
+class Comments
+{
+    public function title()
+    {
+        return 'A Comedy of Errors';
+    }
 }

+ 16 - 13
test/fixtures/examples/complex/complex.php

@@ -1,19 +1,22 @@
 <?php
 
-class Complex {
-	public $header = 'Colors';
+class Complex
+{
+    public $header = 'Colors';
 
-	public $item = array(
-		array('name' => 'red', 'current' => true, 'url' => '#Red'),
-		array('name' => 'green', 'current' => false, 'url' => '#Green'),
-		array('name' => 'blue', 'current' => false, 'url' => '#Blue'),
-	);
+    public $item = array(
+        array('name' => '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 notEmpty()
+    {
+        return !($this->isEmpty());
+    }
 
-	public function isEmpty() {
-		return count($this->item) === 0;
-	}
+    public function isEmpty()
+    {
+        return count($this->item) === 0;
+    }
 }

+ 11 - 9
test/fixtures/examples/delimiters/Delimiters.php

@@ -1,14 +1,16 @@
 <?php
 
-class Delimiters {
-	public $start = "It worked the first time.";
+class Delimiters
+{
+    public $start = "It worked the first time.";
 
-	public function middle() {
-		return array(
-			array('item' => "And it worked the second time."),
-			array('item' => "As well as the third."),
-		);
-	}
+    public function middle()
+    {
+        return array(
+            array('item' => "And it worked the second time."),
+            array('item' => "As well as the third."),
+        );
+    }
 
-	public $final = "Then, surprisingly, it worked the final time.";
+    public $final = "Then, surprisingly, it worked the final time.";
 }

+ 11 - 10
test/fixtures/examples/dot_notation/DotNotation.php

@@ -1,14 +1,15 @@
 <?php
 
-class DotNotation {
-	public $person = array(
-		'name' => array('first' => 'Chris', 'last' => 'Firescythe'),
-		'age' => 24,
-		'hometown' => array(
-			'city'  => 'Cincinnati',
-			'state' => 'OH',
-		)
-	);
+class DotNotation
+{
+    public $person = array(
+        'name' => array('first' => 'Chris', 'last' => 'Firescythe'),
+        'age' => 24,
+        'hometown' => array(
+            'city'  => 'Cincinnati',
+            'state' => 'OH',
+        )
+    );
 
-	public $normal = 'Normal';
+    public $normal = 'Normal';
 }

+ 7 - 5
test/fixtures/examples/double_section/DoubleSection.php

@@ -1,9 +1,11 @@
 <?php
 
-class DoubleSection {
-	public function t() {
-		return true;
-	}
+class DoubleSection
+{
+    public function t()
+    {
+        return true;
+    }
 
-	public $two = "second";
+    public $two = "second";
 }

+ 3 - 2
test/fixtures/examples/escaped/Escaped.php

@@ -1,5 +1,6 @@
 <?php
 
-class Escaped {
-	public $title = '"Bear" > "Shark"';
+class Escaped
+{
+    public $title = '"Bear" > "Shark"';
 }

+ 18 - 16
test/fixtures/examples/grand_parent_context/GrandParentContext.php

@@ -1,22 +1,24 @@
 <?php
 
-class GrandParentContext {
-	public $grand_parent_id = 'grand_parent1';
-	public $parent_contexts = array();
+class GrandParentContext
+{
+    public $grand_parent_id = 'grand_parent1';
+    public $parent_contexts = array();
 
-	public function __construct() {
-		$this->parent_contexts[] = array('parent_id' => 'parent1', 'child_contexts' => array(
-			array('child_id' => 'parent1-child1'),
-			array('child_id' => 'parent1-child2')
-		));
+    public function __construct()
+    {
+        $this->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')
-		);
+        $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;
-	}
+        $this->parent_contexts[] = $parent2;
+    }
 }

+ 4 - 2
test/fixtures/examples/i18n/I18n.php

@@ -1,6 +1,7 @@
 <?php
 
-class I18n {
+class I18n
+{
 
     // Variable to be interpolated
     public $name = 'Bob';
@@ -14,7 +15,8 @@ class I18n {
         'My name is {{ name }}.' => 'Me llamo {{ name }}.',
     );
 
-    public static function __trans($text) {
+    public static function __trans($text)
+    {
         return isset(self::$dictionary[$text]) ? self::$dictionary[$text] : $text;
     }
 }

+ 3 - 2
test/fixtures/examples/implicit_iterator/ImplicitIterator.php

@@ -1,5 +1,6 @@
 <?php
 
-class ImplicitIterator {
-	public $data = array('Donkey Kong', 'Luigi', 'Mario', 'Peach', 'Yoshi');
+class ImplicitIterator
+{
+    public $data = array('Donkey Kong', 'Luigi', 'Mario', 'Peach', 'Yoshi');
 }

+ 4 - 3
test/fixtures/examples/inverted_double_section/InvertedDoubleSection.php

@@ -1,6 +1,7 @@
 <?php
 
-class InvertedDoubleSection {
-	public $t = false;
-	public $two = 'second';
+class InvertedDoubleSection
+{
+    public $t = false;
+    public $two = 'second';
 }

+ 3 - 2
test/fixtures/examples/inverted_section/InvertedSection.php

@@ -1,5 +1,6 @@
 <?php
 
-class InvertedSection {
-	public $repo = array();
+class InvertedSection
+{
+    public $repo = array();
 }

+ 10 - 9
test/fixtures/examples/recursive_partials/RecursivePartials.php

@@ -1,12 +1,13 @@
 <?php
 
-class RecursivePartials {
-	public $name  = 'George';
-	public $child = array(
-		'name'  => 'Dan',
-		'child' => array(
-			'name'  => 'Justin',
-			'child' => false,
-		)
-	);
+class RecursivePartials
+{
+    public $name  = 'George';
+    public $child = array(
+        'name'  => 'Dan',
+        'child' => array(
+            'name'  => 'Justin',
+            'child' => false,
+        )
+    );
 }

+ 12 - 10
test/fixtures/examples/section_iterator_objects/SectionIteratorObjects.php

@@ -1,16 +1,18 @@
 <?php
 
-class SectionIteratorObjects {
-	public $start = "It worked the first time.";
+class SectionIteratorObjects
+{
+    public $start = "It worked the first time.";
 
-	protected $_data = array(
-		array('item' => 'And it worked the second time.'),
-		array('item' => 'As well as the third.'),
-	);
+    protected $_data = array(
+        array('item' => 'And it worked the second time.'),
+        array('item' => 'As well as the third.'),
+    );
 
-	public function middle() {
-		return new ArrayIterator($this->_data);
-	}
+    public function middle()
+    {
+        return new ArrayIterator($this->_data);
+    }
 
-	public $final = "Then, surprisingly, it worked the final time.";
+    public $final = "Then, surprisingly, it worked the final time.";
 }

+ 22 - 17
test/fixtures/examples/section_magic_objects/SectionMagicObjects.php

@@ -1,26 +1,31 @@
 <?php
 
-class SectionMagicObjects {
-	public $start = "It worked the first time.";
+class SectionMagicObjects
+{
+    public $start = "It worked the first time.";
 
-	public function middle() {
-		return new MagicObject();
-	}
+    public function middle()
+    {
+        return new MagicObject();
+    }
 
-	public $final = "Then, surprisingly, it worked the final time.";
+    public $final = "Then, surprisingly, it worked the final time.";
 }
 
-class MagicObject {
-	protected $_data = array(
-		'foo' => 'And it worked the second time.',
-		'bar' => 'As well as the third.'
-	);
+class MagicObject
+{
+    protected $_data = array(
+        'foo' => '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 __get($key)
+    {
+        return isset($this->_data[$key]) ? $this->_data[$key] : NULL;
+    }
 
-	public function __isset($key) {
-		return isset($this->_data[$key]);
-	}
+    public function __isset($key)
+    {
+        return isset($this->_data[$key]);
+    }
 }

+ 12 - 9
test/fixtures/examples/section_objects/SectionObjects.php

@@ -1,16 +1,19 @@
 <?php
 
-class SectionObjects {
-	public $start = "It worked the first time.";
+class SectionObjects
+{
+    public $start = "It worked the first time.";
 
-	public function middle() {
-		return new SectionObject;
-	}
+    public function middle()
+    {
+        return new SectionObject;
+    }
 
-	public $final = "Then, surprisingly, it worked the final time.";
+    public $final = "Then, surprisingly, it worked the final time.";
 }
 
-class SectionObject {
-	public $foo = 'And it worked the second time.';
-	public $bar = 'As well as the third.';
+class SectionObject
+{
+    public $foo = 'And it worked the second time.';
+    public $bar = 'As well as the third.';
 }

+ 11 - 9
test/fixtures/examples/sections/Sections.php

@@ -1,14 +1,16 @@
 <?php
 
-class Sections {
-	public $start = "It worked the first time.";
+class Sections
+{
+    public $start = "It worked the first time.";
 
-	public function middle() {
-		return array(
-			array('item' => "And it worked the second time."),
-			array('item' => "As well as the third."),
-		);
-	}
+    public function middle()
+    {
+        return array(
+            array('item' => "And it worked the second time."),
+            array('item' => "As well as the third."),
+        );
+    }
 
-	public $final = "Then, surprisingly, it worked the final time.";
+    public $final = "Then, surprisingly, it worked the final time.";
 }

+ 31 - 29
test/fixtures/examples/sections_nested/SectionsNested.php

@@ -1,33 +1,35 @@
 <?php
 
-class SectionsNested {
-	public $name = 'Little Mac';
+class SectionsNested
+{
+    public $name = 'Little Mac';
 
-	public function enemies() {
-		return array(
-			array(
-				'name' => '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'),
-				)
-			),
-		);
-	}
+    public function enemies()
+    {
+        return array(
+            array(
+                'name' => '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'),
+                )
+            ),
+        );
+    }
 }

+ 9 - 7
test/fixtures/examples/simple/Simple.php

@@ -1,12 +1,14 @@
 <?php
 
-class Simple {
-	public $name = "Chris";
-	public $value = 10000;
+class Simple
+{
+    public $name = "Chris";
+    public $value = 10000;
 
-	public function taxed_value() {
-		return $this->value - ($this->value * 0.4);
-	}
+    public function taxed_value()
+    {
+        return $this->value - ($this->value * 0.4);
+    }
 
-	public $in_ca = true;
+    public $in_ca = true;
 };

+ 3 - 2
test/fixtures/examples/unescaped/Unescaped.php

@@ -1,5 +1,6 @@
 <?php
 
-class Unescaped {
-	public $title = "Bear > Shark";
+class Unescaped
+{
+    public $title = "Bear > Shark";
 }

+ 3 - 2
test/fixtures/examples/utf8/UTF8.php

@@ -1,5 +1,6 @@
 <?php
 
-class UTF8 {
-	public $test = '中文又来啦';
+class UTF8
+{
+    public $test = '中文又来啦';
 }

+ 3 - 2
test/fixtures/examples/utf8_unescaped/UTF8Unescaped.php

@@ -1,5 +1,6 @@
 <?php
 
-class UTF8Unescaped {
-	public $test = '中文又来啦';
+class UTF8Unescaped
+{
+    public $test = '中文又来啦';
 }

+ 20 - 17
test/fixtures/examples/whitespace/Whitespace.php

@@ -8,24 +8,27 @@
  *
  * `{{> tag }}` and `{{> tag}}` and `{{>tag}}` should all be equivalent.
  */
-class Whitespace {
-	public $foo = 'alpha';
+class Whitespace
+{
+    public $foo = 'alpha';
 
-	public $bar = 'beta';
+    public $bar = 'beta';
 
-	public function baz() {
-		return 'gamma';
-	}
+    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'),
-		);
-	}
+    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'),
+        );
+    }
 }