Merge branch 'release/2.0.0'
Conflicts: .gitmodules Mustache.php test/fixtures/examples/i18n/I18n.php vendor/spec
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
composer.lock
|
||||||
|
vendor
|
||||||
+5
-2
@@ -1,3 +1,6 @@
|
|||||||
[submodule "test/spec"]
|
[submodule "vendor/spec"]
|
||||||
path = test/spec
|
path = vendor/spec
|
||||||
url = git://github.com/mustache/spec.git
|
url = git://github.com/mustache/spec.git
|
||||||
|
[submodule "vendor/yaml"]
|
||||||
|
path = vendor/yaml
|
||||||
|
url = git://github.com/fabpot/yaml.git
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
language: php
|
||||||
|
php:
|
||||||
|
- 5.2
|
||||||
|
- 5.3
|
||||||
|
- 5.4
|
||||||
-931
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+10
-45
@@ -1,8 +1,9 @@
|
|||||||
Mustache.php
|
Mustache.php
|
||||||
============
|
============
|
||||||
|
|
||||||
A [Mustache](http://defunkt.github.com/mustache/) implementation in PHP.
|
A [Mustache](http://mustache.github.com/) implementation in PHP.
|
||||||
|
|
||||||
|
[](http://travis-ci.org/bobthecow/mustache.php)
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
@@ -11,16 +12,14 @@ A quick example:
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
include('Mustache.php');
|
$m = new Mustache_Engine;
|
||||||
$m = new Mustache;
|
echo $m->render('Hello {{planet}}', array('planet' => 'World!')); // "Hello World!"
|
||||||
echo $m->render('Hello {{planet}}', array('planet' => 'World!'));
|
|
||||||
// "Hello World!"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
And a more in-depth example--this is the canonical Mustache template:
|
And a more in-depth example -- this is the canonical Mustache template:
|
||||||
|
|
||||||
```
|
```html+jinja
|
||||||
Hello {{name}}
|
Hello {{name}}
|
||||||
You have just won ${{value}}!
|
You have just won ${{value}}!
|
||||||
{{#in_ca}}
|
{{#in_ca}}
|
||||||
@@ -29,35 +28,7 @@ Well, ${{taxed_value}}, after taxes.
|
|||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Along with the associated Mustache class:
|
Create a view "context" object -- which could also be an associative array, but those don't do functions quite as well:
|
||||||
|
|
||||||
```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:
|
|
||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
@@ -78,21 +49,15 @@ And render it:
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
$m = new Mustache_Engine;
|
||||||
$chris = new Chris;
|
$chris = new Chris;
|
||||||
$m = new Mustache;
|
|
||||||
echo $m->render($template, $chris);
|
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
|
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).
|
* [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.
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ This creates a new example and the corresponding files in the examples/ director
|
|||||||
USAGE
|
USAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
define('DS', DIRECTORY_SEPARATOR);
|
define('EXAMPLE_PATH', realpath(dirname(__FILE__) . '/../test/fixtures/examples'));
|
||||||
define('EXAMPLE_PATH', realpath(dirname(__FILE__) . DS . ".." . DS . "examples"));
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,8 +89,8 @@ function out($value) {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function buildPath($directory, $filename = null, $extension = null) {
|
function buildPath($directory, $filename = null, $extension = null) {
|
||||||
return out(EXAMPLE_PATH . DS . $directory.
|
return out(EXAMPLE_PATH . '/' . $directory.
|
||||||
($extension !== null && $filename !== null ? DS . $filename. "." . $extension : ""));
|
($extension !== null && $filename !== null ? '/' . $filename. "." . $extension : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,7 +151,7 @@ function main($example_name) {
|
|||||||
createFile($lowercase, $uppercase, "php", <<<CONTENT
|
createFile($lowercase, $uppercase, "php", <<<CONTENT
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
class {$uppercase} extends Mustache {
|
class {$uppercase} {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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/" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class ChildContext extends Mustache {
|
|
||||||
public $parent = array(
|
|
||||||
'child' => 'child works',
|
|
||||||
);
|
|
||||||
|
|
||||||
public $grandparent = array(
|
|
||||||
'parent' => array(
|
|
||||||
'child' => 'grandchild works',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Comments extends Mustache {
|
|
||||||
public function title() {
|
|
||||||
return 'A Comedy of Errors';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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.";
|
|
||||||
}
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
@@ -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 :(
|
|
||||||
@@ -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}}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
Children of {{name}}:
|
|
||||||
{{>children}}
|
|
||||||
@@ -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 > 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!! >
|
|
||||||
< RAWR!! >
|
|
||||||
@@ -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.';
|
|
||||||
}
|
|
||||||
@@ -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'),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Unescaped extends Mustache {
|
|
||||||
public $title = "Bear > Shark";
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class UTF8Unescaped extends Mustache {
|
|
||||||
public $test = '中文又来啦';
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class UTF8 extends Mustache {
|
|
||||||
public $test = '中文又来啦';
|
|
||||||
}
|
|
||||||
@@ -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",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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 . ' }}';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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) {}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 > Shark', $m->render('{{title}}'));
|
|
||||||
$this->assertEquals('Bear > Shark', $m->render('{{%UNESCAPED}}{{{title}}}'));
|
|
||||||
$this->assertEquals('Bear > Shark', $m->render('{{{title}}}'));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||||
|
}
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
{{# truthy }}{{ bar }}{{/ truthy }}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ChildContext
|
||||||
|
{
|
||||||
|
public $parent = array(
|
||||||
|
'child' => 'child works',
|
||||||
|
);
|
||||||
|
|
||||||
|
public $grandparent = array(
|
||||||
|
'parent' => array(
|
||||||
|
'child' => 'grandchild works',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
Vendored
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class Comments
|
||||||
|
{
|
||||||
|
public function title()
|
||||||
|
{
|
||||||
|
return 'A Comedy of Errors';
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -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
Reference in New Issue
Block a user