Merge branch 'release/2.0.0'

Conflicts:
	.gitmodules
	Mustache.php
	test/fixtures/examples/i18n/I18n.php
	vendor/spec
This commit is contained in:
Justin Hileman
2012-07-09 14:55:26 -07:00
186 changed files with 4815 additions and 4847 deletions
+2
View File
@@ -0,0 +1,2 @@
composer.lock
vendor
+5 -2
View File
@@ -1,3 +1,6 @@
[submodule "test/spec"]
path = test/spec
[submodule "vendor/spec"]
path = vendor/spec
url = git://github.com/mustache/spec.git
[submodule "vendor/yaml"]
path = vendor/yaml
url = git://github.com/fabpot/yaml.git
+5
View File
@@ -0,0 +1,5 @@
language: php
php:
- 5.2
- 5.3
- 5.4
-931
View File
@@ -1,931 +0,0 @@
<?php
/**
* A Mustache implementation in PHP.
*
* {@link http://defunkt.github.com/mustache}
*
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
* logic from template files. In fact, it is not even possible to embed logic in the template.
*
* This is very, very rad.
*
* @author Justin Hileman {@link http://justinhileman.com}
*/
class Mustache {
const VERSION = '1.1.0';
const SPEC_VERSION = '1.1.2';
/**
* Should this Mustache throw exceptions when it finds unexpected tags?
*
* @see self::_throwsException()
*/
protected $_throwsExceptions = array(
MustacheException::UNKNOWN_VARIABLE => false,
MustacheException::UNCLOSED_SECTION => true,
MustacheException::UNEXPECTED_CLOSE_SECTION => true,
MustacheException::UNKNOWN_PARTIAL => false,
MustacheException::UNKNOWN_PRAGMA => true,
);
// Override the escaper function. Defaults to `htmlspecialchars`.
protected $_escape;
// Override charset passed to htmlentities() and htmlspecialchars(). Defaults to UTF-8.
protected $_charset = 'UTF-8';
/**
* Pragmas are macro-like directives that, when invoked, change the behavior or
* syntax of Mustache.
*
* They should be considered extremely experimental. Most likely their implementation
* will change in the future.
*/
/**
* The {{%UNESCAPED}} pragma swaps the meaning of the {{normal}} and {{{unescaped}}}
* Mustache tags. That is, once this pragma is activated the {{normal}} tag will not be
* escaped while the {{{unescaped}}} tag will be escaped.
*
* Pragmas apply only to the current template. Partials, even those included after the
* {{%UNESCAPED}} call, will need their own pragma declaration.
*
* This may be useful in non-HTML Mustache situations.
*/
const PRAGMA_UNESCAPED = 'UNESCAPED';
/**
* Constants used for section and tag RegEx
*/
const SECTION_TYPES = '\^#\/';
const TAG_TYPES = '#\^\/=!<>\\{&';
protected $_otag = '{{';
protected $_ctag = '}}';
protected $_tagRegEx;
protected $_template = '';
protected $_context = array();
protected $_partials = array();
protected $_pragmas = array();
protected $_pragmasImplemented = array(
self::PRAGMA_UNESCAPED
);
protected $_localPragmas = array();
/**
* Mustache class constructor.
*
* This method accepts a $template string and a $view object. Optionally, pass an associative
* array of partials as well.
*
* Passing an $options array allows overriding certain Mustache options during instantiation:
*
* $options = array(
* // `escape` -- custom escaper callback; must be callable.
* 'escape' => function($text) {
* return htmlspecialchars($text, ENT_COMPAT, 'UTF-8');
* },
*
* // `charset` -- must be supported by `htmlspecialentities()`. defaults to 'UTF-8'
* 'charset' => 'ISO-8859-1',
*
* // opening and closing delimiters, as an array or a space-separated string
* 'delimiters' => '<% %>',
*
* // an array of pragmas to enable/disable
* 'pragmas' => array(
* Mustache::PRAGMA_UNESCAPED => true
* ),
*
* // an array of thrown exceptions to enable/disable
* 'throws_exceptions' => array(
* MustacheException::UNKNOWN_VARIABLE => false,
* MustacheException::UNCLOSED_SECTION => true,
* MustacheException::UNEXPECTED_CLOSE_SECTION => true,
* MustacheException::UNKNOWN_PARTIAL => false,
* MustacheException::UNKNOWN_PRAGMA => true,
* ),
* );
*
* @access public
* @param string $template (default: null)
* @param mixed $view (default: null)
* @param array $partials (default: null)
* @param array $options (default: array())
* @return void
*/
public function __construct($template = null, $view = null, $partials = null, array $options = null) {
if ($template !== null) $this->_template = $template;
if ($partials !== null) $this->_partials = $partials;
if ($view !== null) $this->_context = array($view);
if ($options !== null) $this->_setOptions($options);
}
/**
* Helper function for setting options from constructor args.
*
* @access protected
* @param array $options
* @return void
*/
protected function _setOptions(array $options) {
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'];
}
if (isset($options['delimiters'])) {
$delims = $options['delimiters'];
if (!is_array($delims)) {
$delims = array_map('trim', explode(' ', $delims, 2));
}
$this->_otag = $delims[0];
$this->_ctag = $delims[1];
}
if (isset($options['pragmas'])) {
foreach ($options['pragmas'] as $pragma_name => $pragma_value) {
if (!in_array($pragma_name, $this->_pragmasImplemented, true)) {
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
}
}
$this->_pragmas = $options['pragmas'];
}
if (isset($options['throws_exceptions'])) {
foreach ($options['throws_exceptions'] as $exception => $value) {
$this->_throwsExceptions[$exception] = $value;
}
}
}
/**
* Mustache class clone method.
*
* A cloned Mustache instance should have pragmas, delimeters and root context
* reset to default values.
*
* @access public
* @return void
*/
public function __clone() {
$this->_otag = '{{';
$this->_ctag = '}}';
$this->_localPragmas = array();
if ($keys = array_keys($this->_context)) {
$last = array_pop($keys);
if ($this->_context[$last] instanceof Mustache) {
$this->_context[$last] =& $this;
}
}
}
/**
* Render the given template and view object.
*
* Defaults to the template and view passed to the class constructor unless a new one is provided.
* Optionally, pass an associative array of partials as well.
*
* @access public
* @param string $template (default: null)
* @param mixed $view (default: null)
* @param array $partials (default: null)
* @return string Rendered Mustache template.
*/
public function render($template = null, $view = null, $partials = null) {
if ($template === null) $template = $this->_template;
if ($partials !== null) $this->_partials = $partials;
$otag_orig = $this->_otag;
$ctag_orig = $this->_ctag;
if ($view) {
$this->_context = array($view);
} else if (empty($this->_context)) {
$this->_context = array($this);
}
$template = $this->_renderPragmas($template);
$template = $this->_renderTemplate($template);
$this->_otag = $otag_orig;
$this->_ctag = $ctag_orig;
return $template;
}
/**
* Wrap the render() function for string conversion.
*
* @access public
* @return string
*/
public function __toString() {
// PHP doesn't like exceptions in __toString.
// catch any exceptions and convert them to strings.
try {
$result = $this->render();
return $result;
} catch (Exception $e) {
return "Error rendering mustache: " . $e->getMessage();
}
}
/**
* Internal render function, used for recursive calls.
*
* @access protected
* @param string $template
* @return string Rendered Mustache template.
*/
protected function _renderTemplate($template) {
if ($section = $this->_findSection($template)) {
list($before, $type, $tag_name, $content, $after) = $section;
$rendered_before = $this->_renderTags($before);
$rendered_content = '';
$val = $this->_getVariable($tag_name);
switch($type) {
// inverted section
case '^':
if (empty($val)) {
$rendered_content = $this->_renderTemplate($content);
}
break;
// regular section
case '#':
// higher order sections
if ($this->_varIsCallable($val)) {
$rendered_content = $this->_renderTemplate(call_user_func($val, $content));
} else if ($this->_varIsIterable($val)) {
foreach ($val as $local_context) {
$this->_pushContext($local_context);
$rendered_content .= $this->_renderTemplate($content);
$this->_popContext();
}
} else if ($val) {
if (is_array($val) || is_object($val)) {
$this->_pushContext($val);
$rendered_content = $this->_renderTemplate($content);
$this->_popContext();
} else {
$rendered_content = $this->_renderTemplate($content);
}
}
break;
}
return $rendered_before . $rendered_content . $this->_renderTemplate($after);
}
return $this->_renderTags($template);
}
/**
* Prepare a section RegEx string for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _prepareSectionRegEx($otag, $ctag) {
return sprintf(
'/(?:(?<=\\n)[ \\t]*)?%s(?:(?P<type>[%s])(?P<tag_name>.+?)|=(?P<delims>.*?)=)%s\\n?/s',
preg_quote($otag, '/'),
self::SECTION_TYPES,
preg_quote($ctag, '/')
);
}
/**
* Extract the first section from $template.
*
* @access protected
* @param string $template
* @return array $before, $type, $tag_name, $content and $after
*/
protected function _findSection($template) {
$regEx = $this->_prepareSectionRegEx($this->_otag, $this->_ctag);
$section_start = null;
$section_type = null;
$content_start = null;
$search_offset = 0;
$section_stack = array();
$matches = array();
while (preg_match($regEx, $template, $matches, PREG_OFFSET_CAPTURE, $search_offset)) {
if (isset($matches['delims'][0])) {
list($otag, $ctag) = explode(' ', $matches['delims'][0]);
$regEx = $this->_prepareSectionRegEx($otag, $ctag);
$search_offset = $matches[0][1] + strlen($matches[0][0]);
continue;
}
$match = $matches[0][0];
$offset = $matches[0][1];
$type = $matches['type'][0];
$tag_name = trim($matches['tag_name'][0]);
$search_offset = $offset + strlen($match);
switch ($type) {
case '^':
case '#':
if (empty($section_stack)) {
$section_start = $offset;
$section_type = $type;
$content_start = $search_offset;
}
array_push($section_stack, $tag_name);
break;
case '/':
if (empty($section_stack) || ($tag_name !== array_pop($section_stack))) {
if ($this->_throwsException(MustacheException::UNEXPECTED_CLOSE_SECTION)) {
throw new MustacheException('Unexpected close section: ' . $tag_name, MustacheException::UNEXPECTED_CLOSE_SECTION);
}
}
if (empty($section_stack)) {
// $before, $type, $tag_name, $content, $after
return array(
substr($template, 0, $section_start),
$section_type,
$tag_name,
substr($template, $content_start, $offset - $content_start),
substr($template, $search_offset),
);
}
break;
}
}
if (!empty($section_stack)) {
if ($this->_throwsException(MustacheException::UNCLOSED_SECTION)) {
throw new MustacheException('Unclosed section: ' . $section_stack[0], MustacheException::UNCLOSED_SECTION);
}
}
}
/**
* Prepare a pragma RegEx for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _preparePragmaRegEx($otag, $ctag) {
return sprintf(
'/%s%%\\s*(?P<pragma_name>[\\w_-]+)(?P<options_string>(?: [\\w]+=[\\w]+)*)\\s*%s\\n?/s',
preg_quote($otag, '/'),
preg_quote($ctag, '/')
);
}
/**
* Initialize pragmas and remove all pragma tags.
*
* @access protected
* @param string $template
* @return string
*/
protected function _renderPragmas($template) {
$this->_localPragmas = $this->_pragmas;
// no pragmas
if (strpos($template, $this->_otag . '%') === false) {
return $template;
}
$regEx = $this->_preparePragmaRegEx($this->_otag, $this->_ctag);
return preg_replace_callback($regEx, array($this, '_renderPragma'), $template);
}
/**
* A preg_replace helper to remove {{%PRAGMA}} tags and enable requested pragma.
*
* @access protected
* @param mixed $matches
* @return void
* @throws MustacheException unknown pragma
*/
protected function _renderPragma($matches) {
$pragma = $matches[0];
$pragma_name = $matches['pragma_name'];
$options_string = $matches['options_string'];
if (!in_array($pragma_name, $this->_pragmasImplemented)) {
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
} else {
return '';
}
}
$options = array();
foreach (explode(' ', trim($options_string)) as $o) {
if ($p = trim($o)) {
$p = explode('=', $p);
$options[$p[0]] = $p[1];
}
}
if (empty($options)) {
$this->_localPragmas[$pragma_name] = true;
} else {
$this->_localPragmas[$pragma_name] = $options;
}
return '';
}
/**
* Check whether this Mustache has a specific pragma.
*
* @access protected
* @param string $pragma_name
* @return bool
*/
protected function _hasPragma($pragma_name) {
if (array_key_exists($pragma_name, $this->_localPragmas) && $this->_localPragmas[$pragma_name]) {
return true;
} else {
return false;
}
}
/**
* Return pragma options, if any.
*
* @access protected
* @param string $pragma_name
* @return mixed
* @throws MustacheException Unknown pragma
*/
protected function _getPragmaOptions($pragma_name) {
if (!$this->_hasPragma($pragma_name)) {
if ($this->_throwsException(MustacheException::UNKNOWN_PRAGMA)) {
throw new MustacheException('Unknown pragma: ' . $pragma_name, MustacheException::UNKNOWN_PRAGMA);
}
}
return (is_array($this->_localPragmas[$pragma_name])) ? $this->_localPragmas[$pragma_name] : array();
}
/**
* Check whether this Mustache instance throws a given exception.
*
* Expects exceptions to be MustacheException error codes (i.e. class constants).
*
* @access protected
* @param mixed $exception
* @return void
*/
protected function _throwsException($exception) {
return (isset($this->_throwsExceptions[$exception]) && $this->_throwsExceptions[$exception]);
}
/**
* Prepare a tag RegEx for the given opening/closing tags.
*
* @access protected
* @param string $otag
* @param string $ctag
* @return string
*/
protected function _prepareTagRegEx($otag, $ctag, $first = false) {
return sprintf(
'/(?P<leading>(?:%s\\r?\\n)[ \\t]*)?%s(?P<type>[%s]?)(?P<tag_name>.+?)(?:\\2|})?%s(?P<trailing>\\s*(?:\\r?\\n|\\Z))?/s',
($first ? '\\A|' : ''),
preg_quote($otag, '/'),
self::TAG_TYPES,
preg_quote($ctag, '/')
);
}
/**
* Loop through and render individual Mustache tags.
*
* @access protected
* @param string $template
* @return void
*/
protected function _renderTags($template) {
if (strpos($template, $this->_otag) === false) {
return $template;
}
$first = true;
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag, true);
$html = '';
$matches = array();
while (preg_match($this->_tagRegEx, $template, $matches, PREG_OFFSET_CAPTURE)) {
$tag = $matches[0][0];
$offset = $matches[0][1];
$modifier = $matches['type'][0];
$tag_name = trim($matches['tag_name'][0]);
if (isset($matches['leading']) && $matches['leading'][1] > -1) {
$leading = $matches['leading'][0];
} else {
$leading = null;
}
if (isset($matches['trailing']) && $matches['trailing'][1] > -1) {
$trailing = $matches['trailing'][0];
} else {
$trailing = null;
}
$html .= substr($template, 0, $offset);
$next_offset = $offset + strlen($tag);
if ((substr($html, -1) == "\n") && (substr($template, $next_offset, 1) == "\n")) {
$next_offset++;
}
$template = substr($template, $next_offset);
$html .= $this->_renderTag($modifier, $tag_name, $leading, $trailing);
if ($first == true) {
$first = false;
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
}
}
return $html . $template;
}
/**
* Render the named tag, given the specified modifier.
*
* Accepted modifiers are `=` (change delimiter), `!` (comment), `>` (partial)
* `{` or `&` (don't escape output), or none (render escaped output).
*
* @access protected
* @param string $modifier
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @throws MustacheException Unmatched section tag encountered.
* @return string
*/
protected function _renderTag($modifier, $tag_name, $leading, $trailing) {
switch ($modifier) {
case '=':
return $this->_changeDelimiter($tag_name, $leading, $trailing);
break;
case '!':
return $this->_renderComment($tag_name, $leading, $trailing);
break;
case '>':
case '<':
return $this->_renderPartial($tag_name, $leading, $trailing);
break;
case '{':
// strip the trailing } ...
if ($tag_name[(strlen($tag_name) - 1)] == '}') {
$tag_name = substr($tag_name, 0, -1);
}
case '&':
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
return $this->_renderEscaped($tag_name, $leading, $trailing);
} else {
return $this->_renderUnescaped($tag_name, $leading, $trailing);
}
break;
case '#':
case '^':
case '/':
// remove any leftover section tags
return $leading . $trailing;
break;
default:
if ($this->_hasPragma(self::PRAGMA_UNESCAPED)) {
return $this->_renderUnescaped($modifier . $tag_name, $leading, $trailing);
} else {
return $this->_renderEscaped($modifier . $tag_name, $leading, $trailing);
}
break;
}
}
/**
* Returns true if any of its args contains the "\r" character.
*
* @access protected
* @param string $str
* @return boolean
*/
protected function _stringHasR($str) {
foreach (func_get_args() as $arg) {
if (strpos($arg, "\r") !== false) {
return true;
}
}
return false;
}
/**
* Escape and return the requested tag.
*
* @access protected
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @return string
*/
protected function _renderEscaped($tag_name, $leading, $trailing) {
$value = $this->_renderUnescaped($tag_name, '', '');
if (isset($this->_escape)) {
$rendered = call_user_func($this->_escape, $value);
} else {
$rendered = htmlentities($value, ENT_COMPAT, $this->_charset);
}
return $leading . $rendered . $trailing;
}
/**
* Render a comment (i.e. return an empty string).
*
* @access protected
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @return string
*/
protected function _renderComment($tag_name, $leading, $trailing) {
if ($leading !== null && $trailing !== null) {
if (strpos($leading, "\n") === false) {
return '';
}
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
}
return $leading . $trailing;
}
/**
* Return the requested tag unescaped.
*
* @access protected
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @return string
*/
protected function _renderUnescaped($tag_name, $leading, $trailing) {
$val = $this->_getVariable($tag_name);
if ($this->_varIsCallable($val)) {
$val = $this->_renderTemplate(call_user_func($val));
}
return $leading . $val . $trailing;
}
/**
* Render the requested partial.
*
* @access protected
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @return string
*/
protected function _renderPartial($tag_name, $leading, $trailing) {
$partial = $this->_getPartial($tag_name);
if ($leading !== null && $trailing !== null) {
$whitespace = trim($leading, "\r\n");
$partial = preg_replace('/(\\r?\\n)(?!$)/s', "\\1" . $whitespace, $partial);
}
$view = clone($this);
if ($leading !== null && $trailing !== null) {
return $leading . $view->render($partial);
} else {
return $leading . $view->render($partial) . $trailing;
}
}
/**
* Change the Mustache tag delimiter. This method also replaces this object's current
* tag RegEx with one using the new delimiters.
*
* @access protected
* @param string $tag_name
* @param string $leading Whitespace
* @param string $trailing Whitespace
* @return string
*/
protected function _changeDelimiter($tag_name, $leading, $trailing) {
list($otag, $ctag) = explode(' ', $tag_name);
$this->_otag = $otag;
$this->_ctag = $ctag;
$this->_tagRegEx = $this->_prepareTagRegEx($this->_otag, $this->_ctag);
if ($leading !== null && $trailing !== null) {
if (strpos($leading, "\n") === false) {
return '';
}
return $this->_stringHasR($leading, $trailing) ? "\r\n" : "\n";
}
return $leading . $trailing;
}
/**
* Push a local context onto the stack.
*
* @access protected
* @param array &$local_context
* @return void
*/
protected function _pushContext(&$local_context) {
$new = array();
$new[] =& $local_context;
foreach (array_keys($this->_context) as $key) {
$new[] =& $this->_context[$key];
}
$this->_context = $new;
}
/**
* Remove the latest context from the stack.
*
* @access protected
* @return void
*/
protected function _popContext() {
$new = array();
$keys = array_keys($this->_context);
array_shift($keys);
foreach ($keys as $key) {
$new[] =& $this->_context[$key];
}
$this->_context = $new;
}
/**
* Get a variable from the context array.
*
* If the view is an array, returns the value with array key $tag_name.
* If the view is an object, this will check for a public member variable
* named $tag_name. If none is available, this method will execute and return
* any class method named $tag_name. Failing all of the above, this method will
* return an empty string.
*
* @access protected
* @param string $tag_name
* @throws MustacheException Unknown variable name.
* @return string
*/
protected function _getVariable($tag_name) {
if ($tag_name === '.') {
return $this->_context[0];
} else if (strpos($tag_name, '.') !== false) {
$chunks = explode('.', $tag_name);
$first = array_shift($chunks);
$ret = $this->_findVariableInContext($first, $this->_context);
foreach ($chunks as $next) {
// Slice off a chunk of context for dot notation traversal.
$c = array($ret);
$ret = $this->_findVariableInContext($next, $c);
}
return $ret;
} else {
return $this->_findVariableInContext($tag_name, $this->_context);
}
}
/**
* Get a variable from the context array. Internal helper used by getVariable() to abstract
* variable traversal for dot notation.
*
* @access protected
* @param string $tag_name
* @param array $context
* @throws MustacheException Unknown variable name.
* @return string
*/
protected function _findVariableInContext($tag_name, $context) {
foreach ($context as $view) {
if (is_object($view)) {
if (method_exists($view, $tag_name)) {
return $view->$tag_name();
} else if (isset($view->$tag_name)) {
return $view->$tag_name;
}
} else if (is_array($view) && array_key_exists($tag_name, $view)) {
return $view[$tag_name];
}
}
if ($this->_throwsException(MustacheException::UNKNOWN_VARIABLE)) {
throw new MustacheException("Unknown variable: " . $tag_name, MustacheException::UNKNOWN_VARIABLE);
} else {
return '';
}
}
/**
* Retrieve the partial corresponding to the requested tag name.
*
* Silently fails (i.e. returns '') when the requested partial is not found.
*
* @access protected
* @param string $tag_name
* @throws MustacheException Unknown partial name.
* @return string
*/
protected function _getPartial($tag_name) {
if ((is_array($this->_partials) || $this->_partials instanceof ArrayAccess) && isset($this->_partials[$tag_name])) {
return $this->_partials[$tag_name];
}
if ($this->_throwsException(MustacheException::UNKNOWN_PARTIAL)) {
throw new MustacheException('Unknown partial: ' . $tag_name, MustacheException::UNKNOWN_PARTIAL);
} else {
return '';
}
}
/**
* Check whether the given $var should be iterated (i.e. in a section context).
*
* @access protected
* @param mixed $var
* @return bool
*/
protected function _varIsIterable($var) {
return $var instanceof Traversable || (is_array($var) && !array_diff_key($var, array_keys(array_keys($var))));
}
/**
* Higher order sections helper: tests whether the section $var is a valid callback.
*
* In Mustache.php, a variable is considered 'callable' if the variable is:
*
* 1. an anonymous function.
* 2. an object and the name of a public function, i.e. `array($SomeObject, 'methodName')`
* 3. a class name and the name of a public static function, i.e. `array('SomeClass', 'methodName')`
*
* @access protected
* @param mixed $var
* @return bool
*/
protected function _varIsCallable($var) {
return !is_string($var) && is_callable($var);
}
}
/**
* MustacheException class.
*
* @extends Exception
*/
class MustacheException extends Exception {
// An UNKNOWN_VARIABLE exception is thrown when a {{variable}} is not found
// in the current context.
const UNKNOWN_VARIABLE = 0;
// An UNCLOSED_SECTION exception is thrown when a {{#section}} is not closed.
const UNCLOSED_SECTION = 1;
// An UNEXPECTED_CLOSE_SECTION exception is thrown when {{/section}} appears
// without a corresponding {{#section}} or {{^section}}.
const UNEXPECTED_CLOSE_SECTION = 2;
// An UNKNOWN_PARTIAL exception is thrown whenever a {{>partial}} tag appears
// with no associated partial.
const UNKNOWN_PARTIAL = 3;
// An UNKNOWN_PRAGMA exception is thrown whenever a {{%PRAGMA}} tag appears
// which can't be handled by this Mustache instance.
const UNKNOWN_PRAGMA = 4;
}
-85
View File
@@ -1,85 +0,0 @@
<?php
/**
* A Mustache Partial filesystem loader.
*
* @author Justin Hileman {@link http://justinhileman.com}
*/
class MustacheLoader implements ArrayAccess {
protected $baseDir;
protected $partialsCache = array();
protected $extension;
/**
* MustacheLoader constructor.
*
* @access public
* @param string $baseDir Base template directory.
* @param string $extension File extension for Mustache files (default: 'mustache')
* @return void
*/
public function __construct($baseDir, $extension = 'mustache') {
if (!is_dir($baseDir)) {
throw new InvalidArgumentException('$baseDir must be a valid directory, ' . $baseDir . ' given.');
}
$this->baseDir = $baseDir;
$this->extension = $extension;
}
/**
* @param string $offset Name of partial
* @return boolean
*/
public function offsetExists($offset) {
return (isset($this->partialsCache[$offset]) || file_exists($this->pathName($offset)));
}
/**
* @throws InvalidArgumentException if the given partial doesn't exist
* @param string $offset Name of partial
* @return string Partial template contents
*/
public function offsetGet($offset) {
if (!$this->offsetExists($offset)) {
throw new InvalidArgumentException('Partial does not exist: ' . $offset);
}
if (!isset($this->partialsCache[$offset])) {
$this->partialsCache[$offset] = file_get_contents($this->pathName($offset));
}
return $this->partialsCache[$offset];
}
/**
* MustacheLoader is an immutable filesystem loader. offsetSet throws a LogicException if called.
*
* @throws LogicException
* @return void
*/
public function offsetSet($offset, $value) {
throw new LogicException('Unable to set offset: MustacheLoader is an immutable ArrayAccess object.');
}
/**
* MustacheLoader is an immutable filesystem loader. offsetUnset throws a LogicException if called.
*
* @throws LogicException
* @return void
*/
public function offsetUnset($offset) {
throw new LogicException('Unable to unset offset: MustacheLoader is an immutable ArrayAccess object.');
}
/**
* An internal helper for generating path names.
*
* @param string $file Partial name
* @return string File path
*/
protected function pathName($file) {
return $this->baseDir . '/' . $file . '.' . $this->extension;
}
}
+9 -44
View File
@@ -1,8 +1,9 @@
Mustache.php
============
A [Mustache](http://defunkt.github.com/mustache/) implementation in PHP.
A [Mustache](http://mustache.github.com/) implementation in PHP.
[![Build Status](https://secure.travis-ci.org/bobthecow/mustache.php.png?branch=dev)](http://travis-ci.org/bobthecow/mustache.php)
Usage
-----
@@ -11,16 +12,14 @@ A quick example:
```php
<?php
include('Mustache.php');
$m = new Mustache;
echo $m->render('Hello {{planet}}', array('planet' => 'World!'));
// "Hello World!"
$m = new Mustache_Engine;
echo $m->render('Hello {{planet}}', array('planet' => 'World!')); // "Hello World!"
```
And a more in-depth example -- this is the canonical Mustache template:
```
```html+jinja
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
@@ -29,35 +28,7 @@ Well, ${{taxed_value}}, after taxes.
```
Along with the associated Mustache class:
```php
<?php
class Chris extends Mustache {
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
}
```
Render it like so:
```php
<?php
$chris = new Chris;
echo $chris->render($template);
```
Here's the same thing, a different way:
Create a view object--which could also be an associative array, but those don't do functions quite as well:
Create a view "context" object -- which could also be an associative array, but those don't do functions quite as well:
```php
<?php
@@ -78,21 +49,15 @@ And render it:
```php
<?php
$m = new Mustache_Engine;
$chris = new Chris;
$m = new Mustache;
echo $m->render($template, $chris);
```
Known Issues
------------
* As of Mustache spec v1.1.2, there are a couple of whitespace bugs around section tags... Despite these failing tests, this
version is actually *closer* to correct than previous releases.
See Also
--------
* [Mustache.php wiki](https://github.com/bobthecow/mustache.php/wiki/Home).
* [Readme for the Ruby Mustache implementation](http://github.com/defunkt/mustache/blob/master/README.md).
* [mustache(1)](http://mustache.github.com/mustache.1.html) and [mustache(5)](http://mustache.github.com/mustache.5.html) man pages.
* [mustache(5)](http://mustache.github.com/mustache.5.html) man page.
+4 -5
View File
@@ -22,8 +22,7 @@ This creates a new example and the corresponding files in the examples/ director
USAGE
);
define('DS', DIRECTORY_SEPARATOR);
define('EXAMPLE_PATH', realpath(dirname(__FILE__) . DS . ".." . DS . "examples"));
define('EXAMPLE_PATH', realpath(dirname(__FILE__) . '/../test/fixtures/examples'));
/**
@@ -90,8 +89,8 @@ function out($value) {
* @return string
*/
function buildPath($directory, $filename = null, $extension = null) {
return out(EXAMPLE_PATH . DS . $directory.
($extension !== null && $filename !== null ? DS . $filename. "." . $extension : ""));
return out(EXAMPLE_PATH . '/' . $directory.
($extension !== null && $filename !== null ? '/' . $filename. "." . $extension : ""));
}
/**
@@ -152,7 +151,7 @@ function main($example_name) {
createFile($lowercase, $uppercase, "php", <<<CONTENT
<?php
class {$uppercase} extends Mustache {
class {$uppercase} {
}
+21
View File
@@ -0,0 +1,21 @@
{
"name": "mustache/mustache",
"description": "A Mustache implementation in PHP.",
"keywords": ["templating", "mustache"],
"homepage": "https://github.com/bobthecow/mustache.php",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Justin Hileman",
"email": "justin@justinhileman.info",
"homepage": "http://justinhileman.com"
}
],
"require": {
"php": ">=5.2.4"
},
"autoload": {
"psr-0": { "Mustache": "src/" }
}
}
-13
View File
@@ -1,13 +0,0 @@
<?php
class ChildContext extends Mustache {
public $parent = array(
'child' => 'child works',
);
public $grandparent = array(
'parent' => array(
'child' => 'grandchild works',
),
);
}
-7
View File
@@ -1,7 +0,0 @@
<?php
class Comments extends Mustache {
public function title() {
return 'A Comedy of Errors';
}
}
-19
View File
@@ -1,19 +0,0 @@
<?php
class Complex extends Mustache {
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 function notEmpty() {
return !($this->isEmpty());
}
public function isEmpty() {
return count($this->item) === 0;
}
}
-14
View File
@@ -1,14 +0,0 @@
<?php
class Delimiters extends Mustache {
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 $final = "Then, surprisingly, it worked the final time.";
}
-20
View File
@@ -1,20 +0,0 @@
<?php
/**
* DotNotation example class. Uses DOT_NOTATION pragma.
*
* @extends Mustache
*/
class DotNotation extends Mustache {
public $person = array(
'name' => array('first' => 'Chris', 'last' => 'Firescythe'),
'age' => 24,
'hobbies' => array('Cycling', 'Fishing'),
'hometown' => array(
'city' => 'Cincinnati',
'state' => 'OH',
),
);
public $normal = 'Normal';
}
@@ -1,9 +0,0 @@
<?php
class DoubleSection extends Mustache {
public function t() {
return true;
}
public $two = "second";
}
-5
View File
@@ -1,5 +0,0 @@
<?php
class Escaped extends Mustache {
public $title = '"Bear" > "Shark"';
}
@@ -1,24 +0,0 @@
<?php
class GrandParentContext extends Mustache {
public $grand_parent_id = 'grand_parent1';
public $parent_contexts = array();
public function __construct() {
parent::__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')
);
$this->parent_contexts[] = $parent2;
}
}
@@ -1,5 +0,0 @@
<?php
class ImplicitIterator extends Mustache {
protected $data = array('Donkey Kong', 'Luigi', 'Mario', 'Peach', 'Yoshi');
}
@@ -1,6 +0,0 @@
<?php
class InvertedDoubleSection extends Mustache {
public $t = false;
public $two = 'second';
}
@@ -1,5 +0,0 @@
<?php
class InvertedSection extends Mustache {
public $repo = array();
}
@@ -1,2 +0,0 @@
{{#repo}}<b>{{name}}</b>{{/repo}}
{{^repo}}No repos :({{/repo}}
@@ -1 +0,0 @@
No repos :(
-13
View File
@@ -1,13 +0,0 @@
<?php
class Partials extends Mustache {
public $name = 'ilmich';
public $data = array(
array('name' => 'federica', 'age' => 27, 'gender' => 'female'),
array('name' => 'marco', 'age' => 32, 'gender' => 'male'),
);
protected $_partials = array(
'children' => "{{#data}}{{name}} - {{age}} - {{gender}}\n{{/data}}",
);
}
-2
View File
@@ -1,2 +0,0 @@
Children of {{name}}:
{{>children}}
-3
View File
@@ -1,3 +0,0 @@
Children of ilmich:
federica - 27 - female
marco - 32 - male
@@ -1,19 +0,0 @@
<?php
class PartialsWithViewClass extends Mustache {
public function __construct($template = null, $view = null, $partials = null) {
// Use an object of an arbitrary class as a View for this Mustache instance:
$view = new StdClass();
$view->name = 'ilmich';
$view->data = array(
array('name' => 'federica', 'age' => 27, 'gender' => 'female'),
array('name' => 'marco', 'age' => 32, 'gender' => 'male'),
);
$partials = array(
'children' => "{{#data}}{{name}} - {{age}} - {{gender}}\n{{/data}}",
);
parent::__construct($template, $view, $partials);
}
}
@@ -1,2 +0,0 @@
Children of {{name}}:
{{>children}}
@@ -1,3 +0,0 @@
Children of ilmich:
federica - 27 - female
marco - 32 - male
@@ -1,5 +0,0 @@
<?php
class PragmaUnescaped extends Mustache {
public $vs = 'Bear > Shark';
}
@@ -1,3 +0,0 @@
{{%UNESCAPED}}
{{vs}}
{{{vs}}}
@@ -1,2 +0,0 @@
Bear > Shark
Bear &gt; Shark
@@ -1,8 +0,0 @@
<?php
class PragmasInPartials extends Mustache {
public $say = '< RAWR!! >';
protected $_partials = array(
'dinosaur' => '{{say}}'
);
}
@@ -1,3 +0,0 @@
{{%UNESCAPED}}
{{say}}
{{>dinosaur}}
@@ -1,2 +0,0 @@
< RAWR!! >
&lt; RAWR!! &gt;
@@ -1,16 +0,0 @@
<?php
class RecursivePartials extends Mustache {
protected $_partials = array(
'child' => " > {{ name }}{{#child}}{{>child}}{{/child}}",
);
public $name = 'George';
public $child = array(
'name' => 'Dan',
'child' => array(
'name' => 'Justin',
'child' => false,
)
);
}
@@ -1,16 +0,0 @@
<?php
class SectionIteratorObjects extends Mustache {
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.'),
);
public function middle() {
return new ArrayIterator($this->_data);
}
public $final = "Then, surprisingly, it worked the final time.";
}
@@ -1,26 +0,0 @@
<?php
class SectionMagicObjects extends Mustache {
public $start = "It worked the first time.";
public function middle() {
return new MagicObject();
}
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.'
);
public function __get($key) {
return isset($this->_data[$key]) ? $this->_data[$key] : NULL;
}
public function __isset($key) {
return isset($this->_data[$key]);
}
}
@@ -1,16 +0,0 @@
<?php
class SectionObjects extends Mustache {
public $start = "It worked the first time.";
public function middle() {
return new SectionObject;
}
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.';
}
-14
View File
@@ -1,14 +0,0 @@
<?php
class Sections extends Mustache {
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 $final = "Then, surprisingly, it worked the final time.";
}
@@ -1,33 +0,0 @@
<?php
class SectionsNested extends Mustache {
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'),
)
),
);
}
}
-12
View File
@@ -1,12 +0,0 @@
<?php
class Simple extends Mustache {
public $name = "Chris";
public $value = 10000;
public function taxed_value() {
return $this->value - ($this->value * 0.4);
}
public $in_ca = true;
};
-5
View File
@@ -1,5 +0,0 @@
<?php
class Unescaped extends Mustache {
public $title = "Bear > Shark";
}
-5
View File
@@ -1,5 +0,0 @@
<?php
class UTF8Unescaped extends Mustache {
public $test = '中文又来啦';
}
@@ -1,5 +0,0 @@
<?php
class UTF8 extends Mustache {
public $test = '中文又来啦';
}
-37
View File
@@ -1,37 +0,0 @@
<?php
/**
* Whitespace test for tag names.
*
* Per http://github.com/janl/mustache.js/issues/issue/34/#comment_244396
* tags should strip leading and trailing whitespace in key names.
*
* `{{> tag }}` and `{{> tag}}` and `{{>tag}}` should all be equivalent.
*
* @extends Mustache
*/
class Whitespace extends Mustache {
public $foo = 'alpha';
public $bar = 'beta';
public function baz() {
return 'gamma';
}
public function qux() {
return array(
array('key with space' => 'A'),
array('key with space' => 'B'),
array('key with space' => 'C'),
array('key with space' => 'D'),
array('key with space' => 'E'),
array('key with space' => 'F'),
array('key with space' => 'G'),
);
}
protected $_partials = array(
'alphabet' => " * {{.}}\n",
);
}
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false" colors="true" bootstrap="./test/bootstrap.php">
<testsuite name="Mustache">
<directory suffix="Test.php">./test</directory>
<exclude>./test/Mustache/Test/FiveThree</exclude>
</testsuite>
<testsuite name="Mustache FiveThree">
<directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">./test/Mustache/Test/FiveThree</directory>
</testsuite>
<filter>
<whitelist>
<directory suffix=".php">./src/Mustache</directory>
</whitelist>
</filter>
</phpunit>
+69
View File
@@ -0,0 +1,69 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache class autoloader.
*/
class Mustache_Autoloader
{
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, '/');
}
}
/**
* Register a new instance as an SPL autoloader.
*
* @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
*
* @return Mustache_Autoloader Registered Autoloader instance
*/
public static function register($baseDir = null)
{
$loader = new self($baseDir);
spl_autoload_register(array($loader, 'autoload'));
return $loader;
}
/**
* Autoload Mustache classes.
*
* @param string $class
*/
public function autoload($class)
{
if ($class[0] === '\\') {
$class = substr($class, 1);
}
if (strpos($class, 'Mustache') !== 0) {
return;
}
$file = sprintf('%s/%s.php', $this->baseDir, str_replace('_', '/', $class));
if (is_file($file)) {
require $file;
}
}
}
+386
View File
@@ -0,0 +1,386 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Compiler class.
*
* 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
* @param bool $customEscape (default: false)
* @param string $charset (default: 'UTF-8')
*
* @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.
*
* @param string $value (default: '$value')
*
* @return string Either a custom callback, or an inline call to `htmlspecialchars`
*/
private function getEscape($value = '$value')
{
if ($this->customEscape) {
return sprintf(self::CUSTOM_ESCAPE, $value);
} 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 '';
}
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template rendering Context.
*/
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);
}
}
/**
* 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);
}
/**
* Get the last Context frame.
*
* @return mixed Last Context frame (object or array)
*/
public function last()
{
return end($this->stack);
}
/**
* Find a variable in the Context stack.
*
* Starting with the last Context frame (the context of the innermost section), and working back to the top-level
* rendering context, look for a variable with the given name:
*
* * If the Context frame is an associative array which contains the key $id, returns the value of that element.
* * If the Context frame is an object, this will check first for a public method, then a public property named
* $id. Failing both of these, it will try `__isset` and `__get` magic methods.
* * If a value named $id is not found in any Context frame, returns an empty string.
*
* @param string $id Variable name
*
* @return mixed Variable value, or '' if not found
*/
public function find($id)
{
return $this->findVariableInStack($id, $this->stack);
}
/**
* Find a 'dot notation' variable in the Context stack.
*
* Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
* the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
* result. For example, given the following context stack:
*
* $data = array(
* 'name' => 'Fred',
* 'child' => array(
* 'name' => 'Bob'
* ),
* );
*
* ... and the Mustache following template:
*
* {{ child.name }}
*
* ... the `name` value is only searched for within the `child` value of the global Context, not within parent
* Context frames.
*
* @param string $id Dotted variable selector
*
* @return mixed Variable value, or '' if not found
*/
public function findDot($id)
{
$chunks = explode('.', $id);
$first = array_shift($chunks);
$value = $this->findVariableInStack($first, $this->stack);
foreach ($chunks as $chunk) {
if ($value === '') {
return $value;
}
$value = $this->findVariableInStack($chunk, array($value));
}
return $value;
}
/**
* 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 '';
}
}
+589
View File
@@ -0,0 +1,589 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A Mustache implementation in PHP.
*
* {@link http://defunkt.github.com/mustache}
*
* Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
* logic from template files. In fact, it is not even possible to embed logic in the template.
*
* This is very, very rad.
*
* @author Justin Hileman {@link http://justinhileman.com}
*/
class Mustache_Engine
{
const VERSION = '2.0.0';
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
*
* @codeCoverageIgnore
*/
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));
}
}
+168
View File
@@ -0,0 +1,168 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* 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);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template Loader interface.
*/
interface Mustache_Loader
{
/**
* Load a Template by name.
*
* @param string $name
*
* @return string Mustache Template source
*/
public function load($name);
}
+79
View File
@@ -0,0 +1,79 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template array Loader implementation.
*
* An ArrayLoader instance loads Mustache Template source by name from an initial array:
*
* $loader = new ArrayLoader(
* 'foo' => '{{ bar }}',
* 'baz' => 'Hey {{ qux }}!'
* );
*
* $tpl = $loader->load('foo'); // '{{ bar }}'
*
* The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials
* is set. It can also be used as a quick-and-dirty Template loader.
*
* @implements Loader
* @implements 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;
}
/**
* 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];
}
/**
* 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;
}
}
+118
View File
@@ -0,0 +1,118 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template filesystem Loader implementation.
*
* An ArrayLoader instance loads Mustache Template source from the filesystem by name:
*
* $loader = new FilesystemLoader(dirname(__FILE__).'/views');
* $tpl = $loader->load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache');
*
* This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
*
* $m = new Mustache(array(
* 'loader' => new FilesystemLoader(dirname(__FILE__).'/views'),
* 'partials_loader' => new FilesystemLoader(dirname(__FILE__).'/views/partials'),
* ));
*
* @implements Loader
*/
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), '/');
if (!is_dir($this->baseDir)) {
throw new RuntimeException('FilesystemLoader baseDir must be a directory: '.$baseDir);
}
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);
}
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);
if (!file_exists($fileName)) {
throw new InvalidArgumentException('Template '.$name.' not found.');
}
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;
}
return $fileName;
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template mutable Loader interface.
*/
interface Mustache_Loader_MutableLoader
{
/**
* 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
*/
public function setTemplate($name, $template);
}
+42
View File
@@ -0,0 +1,42 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Template string Loader implementation.
*
* A StringLoader instance is essentially a noop. It simply passes the 'name' argument straight through:
*
* $loader = new StringLoader;
* $tpl = $loader->load('{{ foo }}'); // '{{ foo }}'
*
* This is the default Template Loader instance used by Mustache:
*
* $m = new Mustache;
* $tpl = $m->loadTemplate('{{ foo }}');
* echo $tpl->render(array('foo' => 'bar')); // "bar"
*
* @implements 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;
}
}
+88
View File
@@ -0,0 +1,88 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Parser class.
*
* This class is responsible for turning a set of Mustache tokens into a parse tree.
*/
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));
}
/**
* Helper method for recursively building a parse tree.
*
* @param ArrayIterator $tokens Stream of Mustache tokens
* @param array $parent Parent token (default: null)
*
* @return array Mustache Token parse tree
*
* @throws LogicException when nesting errors or mismatched section tags are encountered.
*/
private function buildTree(ArrayIterator $tokens, array $parent = null)
{
$nodes = array();
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;
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]);
}
$parent[Mustache_Tokenizer::END] = $token[Mustache_Tokenizer::INDEX];
$parent[Mustache_Tokenizer::NODES] = $nodes;
return $parent;
break;
default:
$nodes[] = $token;
break;
}
}
} while ($tokens->valid());
if (isset($parent)) {
throw new LogicException('Missing closing tag: ' . $parent[Mustache_Tokenizer::NAME]);
}
return $nodes;
}
}
+149
View File
@@ -0,0 +1,149 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Abstract Mustache Template class.
*
* @abstract
*/
abstract class Mustache_Template
{
/**
* @var Mustache_Engine
*/
protected $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);
}
/**
* 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 :)
*
* @param Mustache_Context $context
* @param string $indent (default: '')
* @param bool $escape (default: false)
*
* @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;
}
}
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;
$helpers = $this->mustache->getHelpers();
if (!$helpers->isEmpty()) {
$stack->push($helpers);
}
if (!empty($context)) {
$stack->push($context);
}
return $stack;
}
}
+286
View File
@@ -0,0 +1,286 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Mustache Tokenizer class.
*
* This class is responsible for turning raw template source into a set of Mustache tokens.
*/
class Mustache_Tokenizer
{
// 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';
// 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,
);
// 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;
/**
* 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;
}
$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:
$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;
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->filterLine(true);
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 = '}}';
}
/**
* 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;
}
}
}
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];
}
$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);
}
/**
* 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;
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;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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')));
}
public function testAutoloader()
{
$loader = new Mustache_Autoloader(dirname(__FILE__).'/../../fixtures/autoloader');
$this->assertNull($loader->autoload('NonMustacheClass'));
$this->assertFalse(class_exists('NonMustacheClass'));
$loader->autoload('Mustache_Foo');
$this->assertTrue(class_exists('Mustache_Foo'));
$loader->autoload('\Mustache_Bar');
$this->assertTrue(class_exists('Mustache_Bar'));
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group unit
*/
class Mustache_Test_CompilerTest extends PHPUnit_Framework_TestCase
{
/**
* @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);
}
}
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', 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;',
)
),
);
}
/**
* @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,
);
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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_TestDummy
{
public $name = 'dummy';
public function __invoke()
{
// nothing
}
public static function foo()
{
return '<foo>';
}
public function bar()
{
return '<bar>';
}
}
+256
View File
@@ -0,0 +1,256 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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 MustacheStub extends Mustache_Engine {
public $source;
public $template;
public function loadTemplate($source)
{
$this->source = $source;
return $this->template;
}
}
@@ -0,0 +1,71 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group lambdas
* @group functional
*/
class Mustache_Test_FiveThree_Functional_HigherOrderSectionsTest extends PHPUnit_Framework_TestCase {
private $mustache;
public function setUp() {
$this->mustache = new Mustache_Engine;
}
public function testAnonymousFunctionSectionCallback() {
$tpl = $this->mustache->loadTemplate('{{#wrapper}}{{name}}{{/wrapper}}');
$foo = new Mustache_Test_FiveThree_Functional_Foo;
$foo->name = 'Mario';
$foo->wrapper = function($text) {
return sprintf('<div class="anonymous">%s</div>', $text);
};
$this->assertEquals(sprintf('<div class="anonymous">%s</div>', $foo->name), $tpl->render($foo));
}
public function testSectionCallback() {
$one = $this->mustache->loadTemplate('{{name}}');
$two = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
$foo = new Mustache_Test_FiveThree_Functional_Foo;
$foo->name = 'Luigi';
$this->assertEquals($foo->name, $one->render($foo));
$this->assertEquals(sprintf('<em>%s</em>', $foo->name), $two->render($foo));
}
public function testViewArrayAnonymousSectionCallback() {
$tpl = $this->mustache->loadTemplate('{{#wrap}}{{name}}{{/wrap}}');
$data = array(
'name' => 'Bob',
'wrap' => function($text) {
return sprintf('[[%s]]', $text);
}
);
$this->assertEquals(sprintf('[[%s]]', $data['name']), $tpl->render($data));
}
}
class Mustache_Test_FiveThree_Functional_Foo {
public $name = 'Justin';
public $lorem = 'Lorem ipsum dolor sit amet,';
public $wrap;
public function __construct() {
$this->wrap = function($text) {
return sprintf('<em>%s</em>', $text);
};
}
}
@@ -0,0 +1,114 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A PHPUnit test case wrapping the Mustache Spec
*
* @group mustache-spec
* @group functional
*/
class Mustache_Test_FiveThree_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 lambdas
* @dataProvider loadLambdasSpec
*/
public function testLambdasSpec($desc, $source, $partials, $data, $expected) {
$template = self::loadTemplate($source, $partials);
$this->assertEquals($expected, $template($this->prepareLambdasSpec($data)), $desc);
}
public function loadLambdasSpec() {
return $this->loadSpec('~lambdas');
}
/**
* Extract and lambdafy any 'lambda' values found in the $data array.
*/
private function prepareLambdasSpec($data) {
foreach ($data as $key => $val) {
if ($key === 'lambda') {
if (!isset($val['php'])) {
$this->markTestSkipped(sprintf('PHP lambda test not implemented for this test.'));
}
$func = $val['php'];
$data[$key] = function($text = null) use ($func) {
return eval($func);
};
} else if (is_array($val)) {
$data[$key] = $this->prepareLambdasSpec($val);
}
}
return $data;
}
/**
* Data provider for the mustache spec test.
*
* Loads YAML files from the spec and converts them to PHPisms.
*
* @access public
* @return array
*/
private function loadSpec($name) {
$filename = dirname(__FILE__) . '/../../../../../vendor/spec/specs/' . $name . '.yml';
if (!file_exists($filename)) {
return array();
}
$data = array();
$yaml = new sfYamlParser;
$file = file_get_contents($filename);
// @hack: pre-process the 'lambdas' spec so the Symfony YAML parser doesn't complain.
if ($name === '~lambdas') {
$file = str_replace(" !code\n", "\n", $file);
}
$spec = $yaml->parse($file);
foreach ($spec['tests'] as $test) {
$data[] = array(
$test['name'] . ': ' . $test['desc'],
$test['template'],
isset($test['partials']) ? $test['partials'] : array(),
$test['data'],
$test['expected'],
);
}
return $data;
}
private static function loadTemplate($source, $partials) {
self::$mustache->setPartials($partials);
return self::$mustache->loadTemplate($source);
}
}
@@ -0,0 +1,40 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group magic_methods
* @group functional
*/
class Mustache_Test_Functional_CallTest extends PHPUnit_Framework_TestCase
{
public function testCallEatsContext()
{
$m = new Mustache_Engine;
$tpl = $m->loadTemplate('{{# foo }}{{ label }}: {{ name }}{{/ foo }}');
$foo = new Mustache_Test_Functional_ClassWithCall();
$foo->name = 'Bob';
$data = array('label' => 'name', 'foo' => $foo);
$this->assertEquals('name: Bob', $tpl->render($data));
}
}
class Mustache_Test_Functional_ClassWithCall
{
public $name;
public function __call($method, $args)
{
return 'unknown value';
}
}
@@ -0,0 +1,142 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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;
}
}
@@ -0,0 +1,106 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group lambdas
* @group functional
*/
class Mustache_Test_Functional_HigherOrderSectionsTest extends PHPUnit_Framework_TestCase
{
private $mustache;
public function setUp()
{
$this->mustache = new Mustache_Engine;
}
public function testRuntimeSectionCallback()
{
$tpl = $this->mustache->loadTemplate('{{#doublewrap}}{{name}}{{/doublewrap}}');
$foo = new Mustache_Test_Functional_Foo;
$foo->doublewrap = array($foo, 'wrapWithBoth');
$this->assertEquals(sprintf('<strong><em>%s</em></strong>', $foo->name), $tpl->render($foo));
}
public function testStaticSectionCallback()
{
$tpl = $this->mustache->loadTemplate('{{#trimmer}} {{name}} {{/trimmer}}');
$foo = new Mustache_Test_Functional_Foo;
$foo->trimmer = array(get_class($foo), 'staticTrim');
$this->assertEquals($foo->name, $tpl->render($foo));
}
public function testViewArraySectionCallback()
{
$tpl = $this->mustache->loadTemplate('{{#trim}} {{name}} {{/trim}}');
$foo = new Mustache_Test_Functional_Foo;
$data = array(
'name' => 'Bob',
'trim' => array(get_class($foo), 'staticTrim'),
);
$this->assertEquals($data['name'], $tpl->render($data));
}
public function testMonsters()
{
$tpl = $this->mustache->loadTemplate('{{#title}}{{title}} {{/title}}{{name}}');
$frank = new Mustache_Test_Functional_Monster();
$frank->title = 'Dr.';
$frank->name = 'Frankenstein';
$this->assertEquals('Dr. Frankenstein', $tpl->render($frank));
$dracula = new Mustache_Test_Functional_Monster();
$dracula->title = 'Count';
$dracula->name = 'Dracula';
$this->assertEquals('Count Dracula', $tpl->render($dracula));
}
}
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;
}
@@ -0,0 +1,152 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group mustache_injection
* @group functional
*/
class Mustache_Test_Functional_MustacheInjectionTest extends PHPUnit_Framework_TestCase
{
private $mustache;
public function setUp()
{
$this->mustache = new Mustache_Engine;
}
// interpolation
public function testInterpolationInjection()
{
$tpl = $this->mustache->loadTemplate('{{ a }}');
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$this->assertEquals('{{ b }}', $tpl->render($data));
}
public function testUnescapedInterpolationInjection()
{
$tpl = $this->mustache->loadTemplate('{{{ a }}}');
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$this->assertEquals('{{ b }}', $tpl->render($data));
}
// sections
public function testSectionInjection()
{
$tpl = $this->mustache->loadTemplate('{{# a }}{{ b }}{{/ a }}');
$data = array(
'a' => true,
'b' => '{{ c }}',
'c' => 'FAIL'
);
$this->assertEquals('{{ c }}', $tpl->render($data));
}
public function testUnescapedSectionInjection()
{
$tpl = $this->mustache->loadTemplate('{{# a }}{{{ b }}}{{/ a }}');
$data = array(
'a' => true,
'b' => '{{ c }}',
'c' => 'FAIL'
);
$this->assertEquals('{{ c }}', $tpl->render($data));
}
// partials
public function testPartialInjection()
{
$tpl = $this->mustache->loadTemplate('{{> partial }}');
$this->mustache->setPartials(array(
'partial' => '{{ a }}',
));
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$this->assertEquals('{{ b }}', $tpl->render($data));
}
public function testPartialUnescapedInjection()
{
$tpl = $this->mustache->loadTemplate('{{> partial }}');
$this->mustache->setPartials(array(
'partial' => '{{{ a }}}',
));
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$this->assertEquals('{{ b }}', $tpl->render($data));
}
// lambdas
public function testLambdaInterpolationInjection()
{
$tpl = $this->mustache->loadTemplate('{{ a }}');
$data = array(
'a' => array($this, 'lambdaInterpolationCallback'),
'b' => '{{ c }}',
'c' => 'FAIL'
);
$this->assertEquals('{{ c }}', $tpl->render($data));
}
public static function lambdaInterpolationCallback()
{
return '{{ b }}';
}
public function testLambdaSectionInjection()
{
$tpl = $this->mustache->loadTemplate('{{# a }}b{{/ a }}');
$data = array(
'a' => array($this, 'lambdaSectionCallback'),
'b' => '{{ c }}',
'c' => 'FAIL'
);
$this->assertEquals('{{ c }}', $tpl->render($data));
}
public static function lambdaSectionCallback($text)
{
return '{{ ' . $text . ' }}';
}
}
@@ -0,0 +1,175 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* A PHPUnit test case wrapping the Mustache Spec
*
* @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);
}
}
@@ -0,0 +1,110 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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_Alpha
{
public $foo;
public function __construct()
{
$this->foo = new StdClass();
$this->foo->name = 'Foo';
$this->foo->number = 1;
}
}
class Mustache_Test_Functional_Beta
{
protected $_data = array();
public function __construct()
{
$this->_data['foo'] = new StdClass();
$this->_data['foo']->name = 'Foo';
$this->_data['foo']->number = 1;
}
public function __isset($name)
{
return array_key_exists($name, $this->_data);
}
public function __get($name)
{
return $this->_data[$name];
}
}
class Mustache_Test_Functional_Gamma
{
public $bar;
public function __construct()
{
$this->bar = new Mustache_Test_Functional_Beta;
}
}
class Mustache_Test_Functional_Delta
{
protected $_name = 'Foo';
public function name()
{
return $this->_name;
}
}
+163
View File
@@ -0,0 +1,163 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* 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';
$helpers = new Mustache_HelperCollection(array(
'foo' => $foo,
'bar' => $bar,
));
$this->assertSame($foo, $helpers->get('foo'));
$this->assertSame($bar, $helpers->get('bar'));
}
public static function getFoo()
{
echo 'foo';
}
public function testAccessorsAndMutators()
{
$foo = array($this, 'getFoo');
$bar = 'BAR';
$helpers = new Mustache_HelperCollection;
$this->assertTrue($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$helpers->add('foo', $foo);
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$helpers->add('bar', $bar);
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$helpers->remove('foo');
$this->assertFalse($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
}
public function testMagicMethods()
{
$foo = array($this, 'getFoo');
$bar = 'BAR';
$helpers = new Mustache_HelperCollection;
$this->assertTrue($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$this->assertFalse(isset($helpers->foo));
$this->assertFalse(isset($helpers->bar));
$helpers->foo = $foo;
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertFalse($helpers->has('bar'));
$this->assertTrue(isset($helpers->foo));
$this->assertFalse(isset($helpers->bar));
$helpers->bar = $bar;
$this->assertFalse($helpers->isEmpty());
$this->assertTrue($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$this->assertTrue(isset($helpers->foo));
$this->assertTrue(isset($helpers->bar));
unset($helpers->foo);
$this->assertFalse($helpers->isEmpty());
$this->assertFalse($helpers->has('foo'));
$this->assertTrue($helpers->has('bar'));
$this->assertFalse(isset($helpers->foo));
$this->assertTrue(isset($helpers->bar));
}
/**
* @dataProvider getInvalidHelperArguments
*/
public function testHelperCollectionIsntAfraidToThrowExceptions($helpers = array(), $actions = array(), $exception = null)
{
if ($exception) {
$this->setExpectedException($exception);
}
$helpers = new Mustache_HelperCollection($helpers);
foreach ($actions as $method => $args) {
call_user_func_array(array($helpers, $method), $args);
}
}
public function getInvalidHelperArguments()
{
return array(
array(
'not helpers',
array(),
'InvalidArgumentException',
),
array(
array(),
array('get' => array('foo')),
'InvalidArgumentException',
),
array(
array('foo' => 'FOO'),
array('get' => array('foo')),
null,
),
array(
array('foo' => 'FOO'),
array('get' => array('bar')),
'InvalidArgumentException',
),
array(
array('foo' => 'FOO'),
array(
'add' => array('bar', 'BAR'),
'get' => array('bar'),
),
null,
),
array(
array('foo' => 'FOO'),
array(
'get' => array('foo'),
'remove' => array('foo'),
),
null,
),
array(
array('foo' => 'FOO'),
array(
'remove' => array('foo'),
'get' => array('foo'),
),
'InvalidArgumentException',
),
array(
array(),
array('remove' => array('foo')),
'InvalidArgumentException',
),
);
}
}
@@ -0,0 +1,52 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group unit
*/
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'));
}
public function testSetAndLoadTemplates()
{
$loader = new Mustache_Loader_ArrayLoader(array(
'foo' => 'bar'
));
$this->assertEquals('bar', $loader->load('foo'));
$loader->setTemplate('baz', 'qux');
$this->assertEquals('qux', $loader->load('baz'));
$loader->setTemplates(array(
'foo' => 'FOO',
'baz' => 'BAZ',
));
$this->assertEquals('FOO', $loader->load('foo'));
$this->assertEquals('BAZ', $loader->load('baz'));
}
/**
* @expectedException InvalidArgumentException
*/
public function testMissingTemplatesThrowExceptions()
{
$loader = new Mustache_Loader_ArrayLoader;
$loader->load('not_a_real_template');
}
}
@@ -0,0 +1,51 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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'));
}
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 InvalidArgumentException
*/
public function testMissingTemplateThrowsException()
{
$baseDir = realpath(dirname(__FILE__).'/../../../fixtures/templates');
$loader = new Mustache_Loader_FilesystemLoader($baseDir);
$loader->load('fake');
}
}
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group unit
*/
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"));
}
}
+182
View File
@@ -0,0 +1,182 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group unit
*/
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));
}
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_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'
),
),
),
);
}
/**
* @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,
),
),
),
// 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,
),
),
),
// 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,
),
),
),
);
}
}
+55
View File
@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @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_TemplateStub extends Mustache_Template
{
public $rendered;
public function getMustache()
{
return $this->mustache;
}
public function renderInternal(Mustache_Context $context, $indent = '', $escape = false)
{
return $this->rendered;
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* @group unit
*/
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));
}
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(
'{{ 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_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,
),
)
),
);
}
}
-24
View File
@@ -1,24 +0,0 @@
<?php
require_once '../Mustache.php';
class MustacheCallTest extends PHPUnit_Framework_TestCase {
public function testCallEatsContext() {
$foo = new ClassWithCall();
$foo->name = 'Bob';
$template = '{{# foo }}{{ label }}: {{ name }}{{/ foo }}';
$data = array('label' => 'name', 'foo' => $foo);
$m = new Mustache($template, $data);
$this->assertEquals('name: Bob', $m->render());
}
}
class ClassWithCall {
public $name;
public function __call($method, $args) {
return 'unknown value';
}
}
-152
View File
@@ -1,152 +0,0 @@
<?php
require_once '../Mustache.php';
class MustacheExceptionTest extends PHPUnit_Framework_TestCase {
const TEST_CLASS = 'Mustache';
protected $pickyMustache;
protected $slackerMustache;
public function setUp() {
$this->pickyMustache = new PickyMustache();
$this->slackerMustache = new SlackerMustache();
}
/**
* @group interpolation
* @expectedException MustacheException
*/
public function testThrowsUnknownVariableException() {
$this->pickyMustache->render('{{not_a_variable}}');
}
/**
* @group sections
* @expectedException MustacheException
*/
public function testThrowsUnclosedSectionException() {
$this->pickyMustache->render('{{#unclosed}}');
}
/**
* @group sections
* @expectedException MustacheException
*/
public function testThrowsUnclosedInvertedSectionException() {
$this->pickyMustache->render('{{^unclosed}}');
}
/**
* @group sections
* @expectedException MustacheException
*/
public function testThrowsUnexpectedCloseSectionException() {
$this->pickyMustache->render('{{/unopened}}');
}
/**
* @group partials
* @expectedException MustacheException
*/
public function testThrowsUnknownPartialException() {
$this->pickyMustache->render('{{>impartial}}');
}
/**
* @group pragmas
* @expectedException MustacheException
*/
public function testThrowsUnknownPragmaException() {
$this->pickyMustache->render('{{%SWEET-MUSTACHE-BRO}}');
}
/**
* @group sections
*/
public function testDoesntThrowUnclosedSectionException() {
$this->assertEquals('', $this->slackerMustache->render('{{#unclosed}}'));
}
/**
* @group sections
*/
public function testDoesntThrowUnexpectedCloseSectionException() {
$this->assertEquals('', $this->slackerMustache->render('{{/unopened}}'));
}
/**
* @group partials
*/
public function testDoesntThrowUnknownPartialException() {
$this->assertEquals('', $this->slackerMustache->render('{{>impartial}}'));
}
/**
* @group pragmas
* @expectedException MustacheException
*/
public function testGetPragmaOptionsThrowsExceptionsIfItThinksYouHaveAPragmaButItTurnsOutYouDont() {
$mustache = new TestableMustache();
$mustache->testableGetPragmaOptions('PRAGMATIC');
}
public function testOverrideThrownExceptionsViaConstructorOptions() {
$exceptions = array(
MustacheException::UNKNOWN_VARIABLE,
MustacheException::UNCLOSED_SECTION,
MustacheException::UNEXPECTED_CLOSE_SECTION,
MustacheException::UNKNOWN_PARTIAL,
MustacheException::UNKNOWN_PRAGMA,
);
$one = new TestableMustache(null, null, null, array(
'throws_exceptions' => array_fill_keys($exceptions, true)
));
$thrownExceptions = $one->getThrownExceptions();
foreach ($exceptions as $exception) {
$this->assertTrue($thrownExceptions[$exception]);
}
$two = new TestableMustache(null, null, null, array(
'throws_exceptions' => array_fill_keys($exceptions, false)
));
$thrownExceptions = $two->getThrownExceptions();
foreach ($exceptions as $exception) {
$this->assertFalse($thrownExceptions[$exception]);
}
}
}
class PickyMustache extends Mustache {
protected $_throwsExceptions = array(
MustacheException::UNKNOWN_VARIABLE => true,
MustacheException::UNCLOSED_SECTION => true,
MustacheException::UNEXPECTED_CLOSE_SECTION => true,
MustacheException::UNKNOWN_PARTIAL => true,
MustacheException::UNKNOWN_PRAGMA => true,
);
}
class SlackerMustache extends Mustache {
protected $_throwsExceptions = array(
MustacheException::UNKNOWN_VARIABLE => false,
MustacheException::UNCLOSED_SECTION => false,
MustacheException::UNEXPECTED_CLOSE_SECTION => false,
MustacheException::UNKNOWN_PARTIAL => false,
MustacheException::UNKNOWN_PRAGMA => false,
);
}
class TestableMustache extends Mustache {
public function testableGetPragmaOptions($pragma_name) {
return $this->_getPragmaOptions($pragma_name);
}
public function getThrownExceptions() {
return $this->_throwsExceptions;
}
}
-114
View File
@@ -1,114 +0,0 @@
<?php
require_once '../Mustache.php';
class MustacheHigherOrderSectionsTest extends PHPUnit_Framework_TestCase {
public function setUp() {
$this->foo = new Foo();
}
public function testAnonymousFunctionSectionCallback() {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3');
return;
}
$this->foo->wrapper = function($text) {
return sprintf('<div class="anonymous">%s</div>', $text);
};
$this->assertEquals(
sprintf('<div class="anonymous">%s</div>', $this->foo->name),
$this->foo->render('{{#wrapper}}{{name}}{{/wrapper}}')
);
}
public function testSectionCallback() {
$this->assertEquals(sprintf('%s', $this->foo->name), $this->foo->render('{{name}}'));
$this->assertEquals(sprintf('<em>%s</em>', $this->foo->name), $this->foo->render('{{#wrap}}{{name}}{{/wrap}}'));
}
public function testRuntimeSectionCallback() {
$this->foo->double_wrap = array($this->foo, 'wrapWithBoth');
$this->assertEquals(
sprintf('<strong><em>%s</em></strong>', $this->foo->name),
$this->foo->render('{{#double_wrap}}{{name}}{{/double_wrap}}')
);
}
public function testStaticSectionCallback() {
$this->foo->trimmer = array(get_class($this->foo), 'staticTrim');
$this->assertEquals($this->foo->name, $this->foo->render('{{#trimmer}} {{name}} {{/trimmer}}'));
}
public function testViewArraySectionCallback() {
$data = array(
'name' => 'Bob',
'trim' => array(get_class($this->foo), 'staticTrim'),
);
$this->assertEquals($data['name'], $this->foo->render('{{#trim}} {{name}} {{/trim}}', $data));
}
public function testViewArrayAnonymousSectionCallback() {
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
$this->markTestSkipped('Unable to test anonymous function section callbacks in PHP < 5.3');
return;
}
$data = array(
'name' => 'Bob',
'wrap' => function($text) {
return sprintf('[[%s]]', $text);
}
);
$this->assertEquals(
sprintf('[[%s]]', $data['name']),
$this->foo->render('{{#wrap}}{{name}}{{/wrap}}', $data)
);
}
public function testMonsters() {
$frank = new Monster();
$frank->title = 'Dr.';
$frank->name = 'Frankenstein';
$this->assertEquals('Dr. Frankenstein', $frank->render());
$dracula = new Monster();
$dracula->title = 'Count';
$dracula->name = 'Dracula';
$this->assertEquals('Count Dracula', $dracula->render());
}
}
class Foo extends Mustache {
public $name = 'Justin';
public $lorem = 'Lorem ipsum dolor sit amet,';
public $wrap;
public function __construct($template = null, $view = null, $partials = null) {
$this->wrap = array($this, 'wrapWithEm');
parent::__construct($template, $view, $partials);
}
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 Monster extends Mustache {
public $_template = '{{#title}}{{title}} {{/title}}{{name}}';
public $title;
public $name;
}
-127
View File
@@ -1,127 +0,0 @@
<?php
require_once '../Mustache.php';
/**
* @group mustache_injection
*/
class MustacheInjectionSectionTest extends PHPUnit_Framework_TestCase {
// interpolation
public function testInterpolationInjection() {
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$template = '{{ a }}';
$output = '{{ b }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
public function testUnescapedInterpolationInjection() {
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$template = '{{{ a }}}';
$output = '{{ b }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
// sections
public function testSectionInjection() {
$data = array(
'a' => true,
'b' => '{{ c }}',
'c' => 'FAIL'
);
$template = '{{# a }}{{ b }}{{/ a }}';
$output = '{{ c }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
public function testUnescapedSectionInjection() {
$data = array(
'a' => true,
'b' => '{{ c }}',
'c' => 'FAIL'
);
$template = '{{# a }}{{{ b }}}{{/ a }}';
$output = '{{ c }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
// partials
public function testPartialInjection() {
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$template = '{{> partial }}';
$partials = array(
'partial' => '{{ a }}',
);
$output = '{{ b }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data, $partials));
}
public function testPartialUnescapedInjection() {
$data = array(
'a' => '{{ b }}',
'b' => 'FAIL'
);
$template = '{{> partial }}';
$partials = array(
'partial' => '{{{ a }}}',
);
$output = '{{ b }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data, $partials));
}
// lambdas
public function testLambdaInterpolationInjection() {
$data = array(
'a' => array($this, 'interpolationLambda'),
'b' => '{{ c }}',
'c' => 'FAIL'
);
$template = '{{ a }}';
$output = '{{ c }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
public function interpolationLambda() {
return '{{ b }}';
}
public function testLambdaSectionInjection() {
$data = array(
'a' => array($this, 'sectionLambda'),
'b' => '{{ c }}',
'c' => 'FAIL'
);
$template = '{{# a }}b{{/ a }}';
$output = '{{ c }}';
$m = new Mustache();
$this->assertEquals($output, $m->render($template, $data));
}
public function sectionLambda($content) {
return '{{ ' . $content . ' }}';
}
}
-60
View File
@@ -1,60 +0,0 @@
<?php
require_once '../Mustache.php';
require_once '../MustacheLoader.php';
/**
* @group loader
*/
class MustacheLoaderTest extends PHPUnit_Framework_TestCase {
public function testTheActualFilesystemLoader() {
$loader = new MustacheLoader(dirname(__FILE__).'/fixtures');
$this->assertEquals(file_get_contents(dirname(__FILE__).'/fixtures/foo.mustache'), $loader['foo']);
$this->assertEquals(file_get_contents(dirname(__FILE__).'/fixtures/bar.mustache'), $loader['bar']);
}
public function testMustacheUsesFilesystemLoader() {
$template = '{{> foo }} {{> bar }}';
$data = array(
'truthy' => true,
'foo' => 'FOO',
'bar' => 'BAR',
);
$output = 'FOO BAR';
$m = new Mustache();
$partials = new MustacheLoader(dirname(__FILE__).'/fixtures');
$this->assertEquals($output, $m->render($template, $data, $partials));
}
public function testMustacheUsesDifferentLoadersToo() {
$template = '{{> foo }} {{> bar }}';
$data = array(
'truthy' => true,
'foo' => 'FOO',
'bar' => 'BAR',
);
$output = 'FOO BAR';
$m = new Mustache();
$partials = new DifferentMustacheLoader();
$this->assertEquals($output, $m->render($template, $data, $partials));
}
}
class DifferentMustacheLoader implements ArrayAccess {
protected $partials = array(
'foo' => '{{ foo }}',
'bar' => '{{# truthy }}{{ bar }}{{/ truthy }}',
);
public function offsetExists($offset) {
return isset($this->partials[$offset]);
}
public function offsetGet($offset) {
return $this->partials[$offset];
}
public function offsetSet($offset, $value) {}
public function offsetUnset($offset) {}
}
-73
View File
@@ -1,73 +0,0 @@
<?php
require_once '../Mustache.php';
/**
* @group sections
*/
class MustacheObjectSectionTest extends PHPUnit_Framework_TestCase {
public function testBasicObject() {
$alpha = new Alpha();
$this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}'));
}
public function testObjectWithGet() {
$beta = new Beta();
$this->assertEquals('Foo', $beta->render('{{#foo}}{{name}}{{/foo}}'));
}
public function testSectionObjectWithGet() {
$gamma = new Gamma();
$this->assertEquals('Foo', $gamma->render('{{#bar}}{{#foo}}{{name}}{{/foo}}{{/bar}}'));
}
public function testSectionObjectWithFunction() {
$alpha = new Alpha();
$alpha->foo = new Delta();
$this->assertEquals('Foo', $alpha->render('{{#foo}}{{name}}{{/foo}}'));
}
}
class Alpha extends Mustache {
public $foo;
public function __construct() {
$this->foo = new StdClass();
$this->foo->name = 'Foo';
$this->foo->number = 1;
}
}
class Beta extends Mustache {
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 Gamma extends Mustache {
public $bar;
public function __construct() {
$this->bar = new Beta();
}
}
class Delta extends Mustache {
protected $_name = 'Foo';
public function name() {
return $this->_name;
}
}
-74
View File
@@ -1,74 +0,0 @@
<?php
require_once '../Mustache.php';
/**
* @group pragmas
*/
class MustachePragmaTest extends PHPUnit_Framework_TestCase {
public function testUnknownPragmaException() {
$m = new Mustache();
try {
$m->render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}');
} catch (MustacheException $e) {
$this->assertEquals(MustacheException::UNKNOWN_PRAGMA, $e->getCode(), 'Caught exception code was not MustacheException::UNKNOWN_PRAGMA');
return;
}
$this->fail('Mustache should have thrown an unknown pragma exception');
}
public function testSuppressUnknownPragmaException() {
$m = new LessWhinyMustache();
try {
$this->assertEquals('', $m->render('{{%I-HAVE-THE-GREATEST-MUSTACHE}}'));
} catch (MustacheException $e) {
if ($e->getCode() == MustacheException::UNKNOWN_PRAGMA) {
$this->fail('Mustache should have thrown an unknown pragma exception');
} else {
throw $e;
}
}
}
public function testPragmaReplace() {
$m = new Mustache();
$this->assertEquals('', $m->render('{{%UNESCAPED}}'), 'Pragma tag not removed');
}
public function testPragmaReplaceMultiple() {
$m = new Mustache();
$this->assertEquals('', $m->render('{{% UNESCAPED }}'), 'Pragmas should allow whitespace');
$this->assertEquals('', $m->render('{{% UNESCAPED foo=bar }}'), 'Pragmas should allow whitespace');
$this->assertEquals('', $m->render("{{%UNESCAPED}}\n{{%UNESCAPED}}"), 'Multiple pragma tags not removed');
$this->assertEquals(' ', $m->render('{{%UNESCAPED}} {{%UNESCAPED}}'), 'Multiple pragma tags not removed');
}
public function testPragmaReplaceNewline() {
$m = new Mustache();
$this->assertEquals('', $m->render("{{%UNESCAPED}}\n"), 'Trailing newline after pragma tag not removed');
$this->assertEquals("\n", $m->render("\n{{%UNESCAPED}}\n"), 'Too many newlines removed with pragma tag');
$this->assertEquals("1\n23", $m->render("1\n2{{%UNESCAPED}}\n3"), 'Wrong newline removed with pragma tag');
}
public function testPragmaReset() {
$m = new Mustache('', array('symbol' => '>>>'));
$this->assertEquals('>>>', $m->render('{{{symbol}}}'));
$this->assertEquals('>>>', $m->render('{{%UNESCAPED}}{{symbol}}'));
$this->assertEquals('>>>', $m->render('{{{symbol}}}'));
}
}
class LessWhinyMustache extends Mustache {
protected $_throwsExceptions = array(
MustacheException::UNKNOWN_VARIABLE => false,
MustacheException::UNCLOSED_SECTION => true,
MustacheException::UNEXPECTED_CLOSE_SECTION => true,
MustacheException::UNKNOWN_PARTIAL => false,
MustacheException::UNKNOWN_PRAGMA => false,
);
}
-19
View File
@@ -1,19 +0,0 @@
<?php
require_once '../Mustache.php';
/**
* @group pragmas
*/
class MustachePragmaUnescapedTest extends PHPUnit_Framework_TestCase {
public function testPragmaUnescaped() {
$m = new Mustache(null, array('title' => 'Bear > Shark'));
$this->assertEquals('Bear > Shark', $m->render('{{%UNESCAPED}}{{title}}'));
$this->assertEquals('Bear &gt; Shark', $m->render('{{title}}'));
$this->assertEquals('Bear &gt; Shark', $m->render('{{%UNESCAPED}}{{{title}}}'));
$this->assertEquals('Bear > Shark', $m->render('{{{title}}}'));
}
}
-174
View File
@@ -1,174 +0,0 @@
<?php
require_once '../Mustache.php';
require_once './lib/yaml/lib/sfYamlParser.php';
/**
* A PHPUnit test case wrapping the Mustache Spec
*
* @group mustache-spec
*/
class MustacheSpecTest extends PHPUnit_Framework_TestCase {
/**
* 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() {
$spec_dir = dirname(__FILE__) . '/spec/specs/';
if (!file_exists($spec_dir)) {
$this->markTestSkipped('Mustache spec submodule not initialized: run "git submodule update --init"');
}
}
/**
* @group comments
* @dataProvider loadCommentSpec
*/
public function testCommentSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* @group delimiters
* @dataProvider loadDelimitersSpec
*/
public function testDelimitersSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* @group interpolation
* @dataProvider loadInterpolationSpec
*/
public function testInterpolationSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* @group inverted-sections
* @dataProvider loadInvertedSpec
*/
public function testInvertedSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* @group lambdas
* @dataProvider loadLambdasSpec
*/
public function testLambdasSpec($desc, $template, $data, $partials, $expected) {
if (!version_compare(PHP_VERSION, '5.3.0', '>=')) {
$this->markTestSkipped('Unable to test Lambdas spec with PHP < 5.3.');
}
$data = $this->prepareLambdasSpec($data);
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* Extract and lambdafy any 'lambda' values found in the $data array.
*/
protected function prepareLambdasSpec($data) {
foreach ($data as $key => $val) {
if ($key === 'lambda') {
if (!isset($val['php'])) {
$this->markTestSkipped(sprintf('PHP lambda test not implemented for this test.'));
}
$func = $val['php'];
$data[$key] = function($text = null) use ($func) { return eval($func); };
} else if (is_array($val)) {
$data[$key] = $this->prepareLambdasSpec($val);
}
}
return $data;
}
/**
* @group partials
* @dataProvider loadPartialsSpec
*/
public function testPartialsSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
/**
* @group sections
* @dataProvider loadSectionsSpec
*/
public function testSectionsSpec($desc, $template, $data, $partials, $expected) {
$m = new Mustache($template, $data, $partials);
$this->assertEquals($expected, $m->render(), $desc);
}
public function loadCommentSpec() {
return $this->loadSpec('comments');
}
public function loadDelimitersSpec() {
return $this->loadSpec('delimiters');
}
public function loadInterpolationSpec() {
return $this->loadSpec('interpolation');
}
public function loadInvertedSpec() {
return $this->loadSpec('inverted');
}
public function loadLambdasSpec() {
return $this->loadSpec('~lambdas');
}
public function loadPartialsSpec() {
return $this->loadSpec('partials');
}
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
*/
protected function loadSpec($name) {
$filename = dirname(__FILE__) . '/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'],
$test['data'],
isset($test['partials']) ? $test['partials'] : array(),
$test['expected'],
);
}
return $data;
}
}
-464
View File
@@ -1,464 +0,0 @@
<?php
require_once '../Mustache.php';
/**
* A PHPUnit test case for Mustache.php.
*
* This is a very basic, very rudimentary unit test case. It's probably more important to have tests
* than to have elegant tests, so let's bear with it for a bit.
*
* This class assumes an example directory exists at `../examples` with the following structure:
*
* @code
* examples
* foo
* Foo.php
* foo.mustache
* foo.txt
* bar
* Bar.php
* bar.mustache
* bar.txt
* @endcode
*
* To use this test:
*
* 1. {@link http://www.phpunit.de/manual/current/en/installation.html Install PHPUnit}
* 2. run phpunit from the `test` directory:
* `phpunit MustacheTest`
* 3. Fix bugs. Lather, rinse, repeat.
*
* @extends PHPUnit_Framework_TestCase
*/
class MustacheTest extends PHPUnit_Framework_TestCase {
const TEST_CLASS = 'Mustache';
protected $knownIssues = array(
// Just the whitespace ones...
);
/**
* Test Mustache constructor.
*
* @access public
* @return void
*/
public function test__construct() {
$template = '{{#mustaches}}{{#last}}and {{/last}}{{type}}{{^last}}, {{/last}}{{/mustaches}}';
$data = array(
'mustaches' => array(
array('type' => 'Natural'),
array('type' => 'Hungarian'),
array('type' => 'Dali'),
array('type' => 'English'),
array('type' => 'Imperial'),
array('type' => 'Freestyle', 'last' => 'true'),
)
);
$output = 'Natural, Hungarian, Dali, English, Imperial, and Freestyle';
$m1 = new Mustache();
$this->assertEquals($output, $m1->render($template, $data));
$m2 = new Mustache($template);
$this->assertEquals($output, $m2->render(null, $data));
$m3 = new Mustache($template, $data);
$this->assertEquals($output, $m3->render());
$m4 = new Mustache(null, $data);
$this->assertEquals($output, $m4->render($template));
}
/**
* @dataProvider constructorOptions
*/
public function testConstructorOptions($options, $charset, $delimiters, $pragmas) {
$mustache = new MustacheExposedOptionsStub(null, null, null, $options);
$this->assertEquals($charset, $mustache->getCharset());
$this->assertEquals($delimiters, $mustache->getDelimiters());
$this->assertEquals($pragmas, $mustache->getPragmas());
}
public function constructorOptions() {
return array(
array(
array(),
'UTF-8',
array('{{', '}}'),
array(),
),
array(
array(
'charset' => 'UTF-8',
'delimiters' => '<< >>',
'pragmas' => array(Mustache::PRAGMA_UNESCAPED => true)
),
'UTF-8',
array('<<', '>>'),
array(Mustache::PRAGMA_UNESCAPED => true),
),
array(
array(
'charset' => 'cp866',
'delimiters' => array('[[[[', ']]]]'),
'pragmas' => array(Mustache::PRAGMA_UNESCAPED => true)
),
'cp866',
array('[[[[', ']]]]'),
array(Mustache::PRAGMA_UNESCAPED => true),
),
);
}
/**
* @expectedException MustacheException
*/
public function testConstructorInvalidPragmaOptionsThrowExceptions() {
$mustache = new Mustache(null, null, null, array('pragmas' => array('banana phone' => true)));
}
/**
* Test __toString() function.
*
* @access public
* @return void
*/
public function test__toString() {
$m = new Mustache('{{first_name}} {{last_name}}', array('first_name' => 'Karl', 'last_name' => 'Marx'));
$this->assertEquals('Karl Marx', $m->__toString());
$this->assertEquals('Karl Marx', (string) $m);
$m2 = $this->getMock(self::TEST_CLASS, array('render'), array());
$m2->expects($this->once())
->method('render')
->will($this->returnValue('foo'));
$this->assertEquals('foo', $m2->render());
}
public function test__toStringException() {
$m = $this->getMock(self::TEST_CLASS, array('render'), array());
$m->expects($this->once())
->method('render')
->will($this->throwException(new Exception));
try {
$out = (string) $m;
} catch (Exception $e) {
$this->fail('__toString should catch all exceptions');
}
}
/**
* Test render().
*
* @access public
* @return void
*/
public function testRender() {
$m = new Mustache();
$this->assertEquals('', $m->render(''));
$this->assertEquals('foo', $m->render('foo'));
$this->assertEquals('', $m->render(null));
$m2 = new Mustache('foo');
$this->assertEquals('foo', $m2->render());
$m3 = new Mustache('');
$this->assertEquals('', $m3->render());
$m3 = new Mustache();
$this->assertEquals('', $m3->render(null));
}
/**
* Test render() with data.
*
* @group interpolation
*/
public function testRenderWithData() {
$m = new Mustache('{{first_name}} {{last_name}}');
$this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin')));
$this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa')));
}
/**
* @group partials
*/
public function testRenderWithPartials() {
$m = new Mustache('{{>stache}}', null, array('stache' => '{{first_name}} {{last_name}}'));
$this->assertEquals('Charlie Chaplin', $m->render(null, array('first_name' => 'Charlie', 'last_name' => 'Chaplin')));
$this->assertEquals('Zappa, Frank', $m->render('{{last_name}}, {{first_name}}', array('first_name' => 'Frank', 'last_name' => 'Zappa')));
}
/**
* @group interpolation
* @dataProvider interpolationData
*/
public function testDoubleRenderMustacheTags($template, $context, $expected) {
$m = new Mustache($template, $context);
$this->assertEquals($expected, $m->render());
}
public function interpolationData() {
return array(
array(
'{{#a}}{{=<% %>=}}{{b}} c<%={{ }}=%>{{/a}}',
array('a' => array(array('b' => 'Do Not Render'))),
'{{b}} c'
),
array(
'{{#a}}{{b}}{{/a}}',
array('a' => array('b' => '{{c}}'), 'c' => 'FAIL'),
'{{c}}'
),
);
}
/**
* Mustache should allow newlines (and other whitespace) in comments and all other tags.
*
* @group comments
*/
public function testNewlinesInComments() {
$m = new Mustache("{{! comment \n \t still a comment... }}");
$this->assertEquals('', $m->render());
}
/**
* Mustache should return the same thing when invoked multiple times.
*/
public function testMultipleInvocations() {
$m = new Mustache('x');
$first = $m->render();
$second = $m->render();
$this->assertEquals('x', $first);
$this->assertEquals($first, $second);
}
/**
* Mustache should return the same thing when invoked multiple times.
*
* @group interpolation
*/
public function testMultipleInvocationsWithTags() {
$m = new Mustache('{{one}} {{two}}', array('one' => 'foo', 'two' => 'bar'));
$first = $m->render();
$second = $m->render();
$this->assertEquals('foo bar', $first);
$this->assertEquals($first, $second);
}
/**
* Mustache should not use templates passed to the render() method for subsequent invocations.
*/
public function testResetTemplateForMultipleInvocations() {
$m = new Mustache('Sirve.');
$this->assertEquals('No sirve.', $m->render('No sirve.'));
$this->assertEquals('Sirve.', $m->render());
$m2 = new Mustache();
$this->assertEquals('No sirve.', $m2->render('No sirve.'));
$this->assertEquals('', $m2->render());
}
/**
* Test the __clone() magic function.
*
* @group examples
* @dataProvider getExamples
*
* @param string $class
* @param string $template
* @param string $output
*/
public function test__clone($class, $template, $output) {
if (isset($this->knownIssues[$class])) {
return $this->markTestSkipped($this->knownIssues[$class]);
}
$m = new $class;
$n = clone $m;
$n_output = $n->render($template);
$o = clone $n;
$this->assertEquals($m->render($template), $n_output);
$this->assertEquals($n_output, $o->render($template));
$this->assertNotSame($m, $n);
$this->assertNotSame($n, $o);
$this->assertNotSame($m, $o);
}
/**
* Test everything in the `examples` directory.
*
* @group examples
* @dataProvider getExamples
*
* @param string $class
* @param string $template
* @param string $output
*/
public function testExamples($class, $template, $output) {
if (isset($this->knownIssues[$class])) {
return $this->markTestSkipped($this->knownIssues[$class]);
}
$m = new $class;
$this->assertEquals($output, $m->render($template));
}
/**
* Data provider for testExamples method.
*
* Assumes that an `examples` directory exists inside parent 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).
*
* This whole mess will be refined later to be more intuitive and less prescriptive, but it'll
* do for now. Especially since it means we can have unit tests :)
*
* @return array
*/
public function getExamples() {
$basedir = dirname(__FILE__) . '/../examples/';
$ret = array();
$files = new RecursiveDirectoryIterator($basedir);
while ($files->valid()) {
if ($files->hasChildren() && $children = $files->getChildren()) {
$example = $files->getSubPathname();
$class = null;
$template = null;
$output = null;
foreach ($children as $file) {
if (!$file->isFile()) continue;
$filename = $file->getPathname();
$info = pathinfo($filename);
if (isset($info['extension'])) {
switch($info['extension']) {
case 'php':
$class = $info['filename'];
include_once($filename);
break;
case 'mustache':
$template = file_get_contents($filename);
break;
case 'txt':
$output = file_get_contents($filename);
break;
}
}
}
if (!empty($class)) {
$ret[$example] = array($class, $template, $output);
}
}
$files->next();
}
return $ret;
}
/**
* @group delimiters
*/
public function testCrazyDelimiters() {
$m = new Mustache(null, array('result' => 'success'));
$this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]'));
$this->assertEquals('success', $m->render('{{=(( ))=}}(( result ))'));
$this->assertEquals('success', $m->render('{{={$ $}=}}{$ result $}'));
$this->assertEquals('success', $m->render('{{=<.. ..>=}}<.. result ..>'));
$this->assertEquals('success', $m->render('{{=^^ ^^}}^^ result ^^'));
$this->assertEquals('success', $m->render('{{=// \\\\}}// result \\\\'));
}
/**
* @group delimiters
*/
public function testResetDelimiters() {
$m = new Mustache(null, array('result' => 'success'));
$this->assertEquals('success', $m->render('{{=[[ ]]=}}[[ result ]]'));
$this->assertEquals('success', $m->render('{{=<< >>=}}<< result >>'));
$this->assertEquals('success', $m->render('{{=<% %>=}}<% result %>'));
}
/**
* @group delimiters
*/
public function testStickyDelimiters() {
$m = new Mustache(null, array('result' => 'FAIL'));
$this->assertEquals('{{ result }}', $m->render('{{=[[ ]]=}}{{ result }}[[={{ }}=]]'));
$this->assertEquals('{{#result}}{{/result}}', $m->render('{{=[[ ]]=}}{{#result}}{{/result}}[[={{ }}=]]'));
$this->assertEquals('{{ result }}', $m->render('{{=[[ ]]=}}[[#result]]{{ result }}[[/result]][[={{ }}=]]'));
$this->assertEquals('{{ result }}', $m->render('{{#result}}{{=[[ ]]=}}{{ result }}[[/result]][[^result]][[={{ }}=]][[ result ]]{{/result}}'));
}
/**
* @group sections
* @dataProvider poorlyNestedSections
* @expectedException MustacheException
*/
public function testPoorlyNestedSections($template) {
$m = new Mustache($template);
$m->render();
}
public function poorlyNestedSections() {
return array(
array('{{#foo}}'),
array('{{#foo}}{{/bar}}'),
array('{{#foo}}{{#bar}}{{/foo}}'),
array('{{#foo}}{{#bar}}{{/foo}}{{/bar}}'),
array('{{#foo}}{{/bar}}{{/foo}}'),
);
}
/**
* Ensure that Mustache doesn't double-render sections (allowing mustache injection).
*
* @group sections
*/
public function testMustacheInjection() {
$template = '{{#foo}}{{bar}}{{/foo}}';
$view = array(
'foo' => true,
'bar' => '{{win}}',
'win' => 'FAIL',
);
$m = new Mustache($template, $view);
$this->assertEquals('{{win}}', $m->render());
}
}
class MustacheExposedOptionsStub extends Mustache {
public function getPragmas() {
return $this->_pragmas;
}
public function getCharset() {
return $this->_charset;
}
public function getDelimiters() {
return array($this->_otag, $this->_ctag);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
require dirname(__FILE__).'/../src/Mustache/Autoloader.php';
Mustache_Autoloader::register();
require dirname(__FILE__).'/../vendor/yaml/lib/sfYamlParser.php';
+15
View File
@@ -0,0 +1,15 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Mustache_Bar
{
// nada
}
+15
View File
@@ -0,0 +1,15 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class Mustache_Foo
{
// nada
}
+15
View File
@@ -0,0 +1,15 @@
<?php
/*
* This file is part of Mustache.php.
*
* (c) 2012 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class NonMustacheClass
{
// noop
}
-1
View File
@@ -1 +0,0 @@
{{# truthy }}{{ bar }}{{/ truthy }}
+14
View File
@@ -0,0 +1,14 @@
<?php
class ChildContext
{
public $parent = array(
'child' => 'child works',
);
public $grandparent = array(
'parent' => array(
'child' => 'grandchild works',
),
);
}
+9
View File
@@ -0,0 +1,9 @@
<?php
class Comments
{
public function title()
{
return 'A Comedy of Errors';
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
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 function notEmpty()
{
return !($this->isEmpty());
}
public function isEmpty()
{
return count($this->item) === 0;
}
}

Some files were not shown because too many files have changed in this diff Show More