Initial Mustache.php 2.0 commit.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache output Buffer class.
|
||||
*
|
||||
* Buffer instances are used by Mustache Templates for collecting output during rendering
|
||||
*/
|
||||
class Buffer {
|
||||
private $buffer = '';
|
||||
private $indent = '';
|
||||
private $charset = 'UTF-8';
|
||||
|
||||
/**
|
||||
* Mustache Buffer constructor.
|
||||
*
|
||||
* @param string $indent Initial indent level for all lines of this buffer (default: '')
|
||||
* @param string $charset Override the character set used by `htmlspecialchars()` (default: 'UTF-8')
|
||||
*/
|
||||
public function __construct($indent = null, $charset = null) {
|
||||
if ($indent !== null) {
|
||||
$this->setIndent($indent);
|
||||
}
|
||||
|
||||
if ($charset !== null) {
|
||||
$this->charset = $charset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current indent level.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIndent() {
|
||||
return $this->indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the buffer indent level.
|
||||
*
|
||||
* Each line output by this buffer will be prefixed by this whitespace. This is used when rendering
|
||||
* partials and Lambda sections.
|
||||
*
|
||||
* @param string $indent
|
||||
*/
|
||||
public function setIndent($indent) {
|
||||
$this->indent = $indent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the character set used when escaping values.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCharset() {
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a newline to the Buffer.
|
||||
*/
|
||||
public function writeLine() {
|
||||
$this->buffer .= "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Output text to the Buffer.
|
||||
*
|
||||
* @see \Mustache\Buffer::write
|
||||
*
|
||||
* @param string $text
|
||||
* @param bool $escape Escape this text with `htmlspecialchars()`? (default: false)
|
||||
*/
|
||||
public function writeText($text, $escape = false) {
|
||||
$this->write($text, true, $escape);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add output to the Buffer.
|
||||
*
|
||||
* @param string $text
|
||||
* @param bool $indent Indent this line? (default: false)
|
||||
* @param bool $escape Escape this text with `htmlspecialchars()`? (default: false)
|
||||
*/
|
||||
public function write($text, $indent = false, $escape = false) {
|
||||
$text = (string) $text;
|
||||
|
||||
if ($escape) {
|
||||
$text = $this->escape($text);
|
||||
}
|
||||
|
||||
if ($indent) {
|
||||
$this->buffer .= $this->indent . $text;
|
||||
} else {
|
||||
$this->buffer .= $text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush the contents of the Buffer.
|
||||
*
|
||||
* Resets the buffer and returns the current contents.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function flush() {
|
||||
$buffer = $this->buffer;
|
||||
$this->buffer = '';
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to escape text.
|
||||
*
|
||||
* Uses the Buffer's character set (default 'UTF-8', passed as the second argument to `__construct`).
|
||||
*
|
||||
* @see htmlspecialchars
|
||||
*
|
||||
* @param string $text
|
||||
*
|
||||
* @return string Escaped text
|
||||
*/
|
||||
private function escape($text) {
|
||||
return htmlspecialchars($text, ENT_COMPAT, $this->charset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache Compiler class.
|
||||
*
|
||||
* This class is responsible for turning a Mustache token parse tree into normal PHP source code.
|
||||
*/
|
||||
class Compiler {
|
||||
|
||||
/**
|
||||
* Compile a Mustache token parse tree into PHP source code.
|
||||
*
|
||||
* @param string $source Mustache Template source code
|
||||
* @param string $tree Parse tree of Mustache tokens
|
||||
* @param string $name Mustache Template class name
|
||||
*
|
||||
* @return string Generated PHP source code
|
||||
*/
|
||||
public function compile($source, array $tree, $name) {
|
||||
$this->source = $source;
|
||||
|
||||
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 (is_string($node) ? 'text' : $node[Tokenizer::TAG]) {
|
||||
case '#':
|
||||
$code .= $this->section(
|
||||
$node[Tokenizer::NODES],
|
||||
$node[Tokenizer::NAME],
|
||||
$node[Tokenizer::INDEX],
|
||||
$node[Tokenizer::END],
|
||||
$node[Tokenizer::OTAG],
|
||||
$node[Tokenizer::CTAG],
|
||||
$level
|
||||
);
|
||||
break;
|
||||
|
||||
case '^':
|
||||
$code .= $this->invertedSection(
|
||||
$node[Tokenizer::NODES],
|
||||
$node[Tokenizer::NAME],
|
||||
$level
|
||||
);
|
||||
break;
|
||||
|
||||
case '<':
|
||||
case '>':
|
||||
$code .= $this->partial(
|
||||
$node[Tokenizer::NAME],
|
||||
isset($node[Tokenizer::INDENT]) ? $node[Tokenizer::INDENT] : '',
|
||||
$level
|
||||
);
|
||||
break;
|
||||
|
||||
case '{':
|
||||
case '&':
|
||||
$code .= $this->variable($node[Tokenizer::NAME], false, $level);
|
||||
break;
|
||||
|
||||
case '!':
|
||||
break;
|
||||
|
||||
case '_v':
|
||||
$code .= $this->variable($node[Tokenizer::NAME], true, $level);
|
||||
break;
|
||||
|
||||
|
||||
case 'text':
|
||||
$code .= $this->text($node, $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 = \'\') {
|
||||
$mustache = $this->mustache;
|
||||
$buffer = new \Mustache\Buffer($indent, $mustache->getCharset());
|
||||
%s
|
||||
|
||||
return $buffer->flush();
|
||||
}
|
||||
}';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return sprintf($this->prepare(self::KLASS, 0, false), $name, $this->walk($tree), $name);
|
||||
}
|
||||
|
||||
const SECTION = '
|
||||
// %s section
|
||||
$value = $context->%s(%s);
|
||||
if ($context->isCallable($value)) {
|
||||
$source = %s;
|
||||
$buffer->write(
|
||||
$mustache
|
||||
->loadLambda((string) call_user_func($value, $source)%s)
|
||||
->renderInternal($context, $buffer->getIndent())
|
||||
);
|
||||
} elseif ($context->isTruthy($value)) {
|
||||
$values = $context->isIterable($value) ? $value : array($value);
|
||||
foreach ($values as $value) {
|
||||
$context->push($value);%s
|
||||
$context->pop();
|
||||
}
|
||||
}';
|
||||
|
||||
/**
|
||||
* 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 = '';
|
||||
}
|
||||
|
||||
return sprintf($this->prepare(self::SECTION, $level), $id, $method, $id, $source, $delims, $this->walk($nodes, $level + 1));
|
||||
}
|
||||
|
||||
const INVERTED_SECTION = '
|
||||
// %s inverted section
|
||||
if (!$context->isTruthy($context->%s(%s))) {
|
||||
%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 = '$buffer->write($mustache->loadPartial(%s)->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 ($context->isCallable($value)) {
|
||||
$value = $mustache
|
||||
->loadLambda((string) call_user_func($value))
|
||||
->renderInternal($context, $buffer->getIndent());
|
||||
}
|
||||
$buffer->writeText($value, %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) : '';
|
||||
$escape = $escape ? 'true' : 'false';
|
||||
|
||||
return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $escape);
|
||||
}
|
||||
|
||||
const LINE = '$buffer->writeLine();';
|
||||
const TEXT = '$buffer->writeText(%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") {
|
||||
return $this->prepare(self::LINE, $level);
|
||||
} else {
|
||||
return sprintf($this->prepare(self::TEXT, $level), var_export($text, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare PHP source code snippet for output.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $bonus Additional indent level (default: 0)
|
||||
* @param boolean $prependNewline Prepend a newline to the snippet? (default: true)
|
||||
*
|
||||
* @return string PHP source code snippet
|
||||
*/
|
||||
private function prepare($text, $bonus = 0, $prependNewline = true) {
|
||||
$text = ($prependNewline ? "\n" : '').trim($text);
|
||||
if ($prependNewline) {
|
||||
$bonus++;
|
||||
}
|
||||
|
||||
return preg_replace("/\n(\t\t)?/", "\n".str_repeat("\t", $bonus), $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache Template rendering Context.
|
||||
*/
|
||||
class 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to test whether a value is 'truthy'.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return boolean True if the value is 'truthy'
|
||||
*/
|
||||
public function isTruthy($value) {
|
||||
return !empty($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Higher order sections helper: tests whether a value 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, e.g. `array($someObject, 'methodName')`
|
||||
* 3. A class name and the name of a public static function, e.g. `array('SomeClass', 'methodName')`
|
||||
*
|
||||
* Note that this specifically excludes strings, which PHP would normally consider 'callable'.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return boolean True if the value is 'callable'
|
||||
*/
|
||||
public function isCallable($value) {
|
||||
return !is_string($value) && is_callable($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'
|
||||
*/
|
||||
public function isIterable($value) {
|
||||
if (is_object($value)) {
|
||||
return $value instanceof \Traversable;
|
||||
} elseif (is_array($value)) {
|
||||
return !array_diff_key($value, array_keys(array_keys($value)));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,18 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache Template Loader interface.
|
||||
*/
|
||||
interface Loader {
|
||||
|
||||
/**
|
||||
* Load a Template by name.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string Mustache Template source
|
||||
*/
|
||||
function load($name);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache\Loader;
|
||||
|
||||
use Mustache\Loader;
|
||||
use Mustache\Loader\MutableLoader;
|
||||
|
||||
/**
|
||||
* 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\Mustache 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 ArrayLoader implements 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,108 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache\Loader;
|
||||
|
||||
use Mustache\Loader;
|
||||
|
||||
/**
|
||||
* Mustache Template filesystem Loader implementation.
|
||||
*
|
||||
* An ArrayLoader instance loads Mustache Template source from the filesystem by name:
|
||||
*
|
||||
* $loader = new FilesystemLoader(__DIR__.'/views');
|
||||
* $tpl = $loader->load('foo'); // equivalent to `file_get_contents(__DIR__.'/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(__DIR__.'/views'),
|
||||
* 'partials_loader' => new FilesystemLoader(__DIR__.'/views/partials'),
|
||||
* ));
|
||||
*
|
||||
* @implements Loader
|
||||
*/
|
||||
class FilesystemLoader implements 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(__DIR__.'/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,24 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache\Loader;
|
||||
|
||||
/**
|
||||
* Mustache Template mutable Loader interface.
|
||||
*/
|
||||
interface MutableLoader {
|
||||
|
||||
/**
|
||||
* Set an associative array of Template sources for this loader.
|
||||
*
|
||||
* @param array $templates
|
||||
*/
|
||||
function setTemplates(array $templates);
|
||||
|
||||
/**
|
||||
* Set a Template source by name.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $template Mustache Template source
|
||||
*/
|
||||
function setTemplate($name, $template);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache\Loader;
|
||||
|
||||
use Mustache\Loader;
|
||||
|
||||
/**
|
||||
* 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 StringLoader implements Loader {
|
||||
|
||||
/**
|
||||
* Load a Template by source.
|
||||
*
|
||||
* @param string $name Mustache Template source
|
||||
*
|
||||
* @return string Mustache Template source
|
||||
*/
|
||||
public function load($name) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
+297
-807
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache Parser class.
|
||||
*
|
||||
* This class is responsible for turning a set of Mustache tokens into a parse tree.
|
||||
*/
|
||||
class Parser {
|
||||
|
||||
/**
|
||||
* Process an array of Mustache tokens and convert them into a parse tree.
|
||||
*
|
||||
* @param array $tree Set of Mustache tokens
|
||||
*
|
||||
* @return array Mustache token parse tree
|
||||
*/
|
||||
public function parse(array $tokens = array()) {
|
||||
return $this->buildTree(new \ArrayIterator($tokens));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for recursively building a parse tree.
|
||||
*
|
||||
* @throws \LogicException when nesting errors or mismatched section tags are encountered.
|
||||
*
|
||||
* @param \ArrayIterator $tokens Stream of Mustache tokens
|
||||
* @param array $parent Parent token (default: null)
|
||||
*
|
||||
* @return array Mustache Token parse tree
|
||||
*/
|
||||
private function buildTree(\ArrayIterator $tokens, array $parent = null) {
|
||||
$nodes = array();
|
||||
|
||||
do {
|
||||
$token = $tokens->current();
|
||||
$tokens->next();
|
||||
|
||||
if ($token === null) {
|
||||
continue;
|
||||
} elseif (is_array($token)) {
|
||||
switch ($token[Tokenizer::TAG]) {
|
||||
case '#':
|
||||
case '^':
|
||||
$nodes[] = $this->buildTree($tokens, $token);
|
||||
break;
|
||||
|
||||
case '/':
|
||||
if (!isset($parent)) {
|
||||
throw new \LogicException('Unexpected closing tag: /'. $token[Tokenizer::NAME]);
|
||||
}
|
||||
|
||||
if ($token[Tokenizer::NAME] !== $parent[Tokenizer::NAME]) {
|
||||
throw new \LogicException('Nesting error: ' . $parent[Tokenizer::NAME] . ' vs. ' . $token[Tokenizer::NAME]);
|
||||
}
|
||||
|
||||
$parent[Tokenizer::END] = $token[Tokenizer::INDEX];
|
||||
$parent[Tokenizer::NODES] = $nodes;
|
||||
|
||||
return $parent;
|
||||
break;
|
||||
|
||||
default:
|
||||
$nodes[] = $token;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$nodes[] = $token;
|
||||
}
|
||||
|
||||
} while($tokens->valid());
|
||||
|
||||
if (isset($parent)) {
|
||||
throw new \LogicException('Missing closing tag: ' . $parent[Tokenizer::NAME]);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Abstract Mustache Template class.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
abstract class Template {
|
||||
|
||||
/**
|
||||
* @var \Mustache\Mustache
|
||||
*/
|
||||
protected $mustache;
|
||||
|
||||
/**
|
||||
* Mustache Template constructor.
|
||||
*
|
||||
* @param \Mustache\Mustache $mustache
|
||||
*/
|
||||
public function __construct(Mustache $mustache) {
|
||||
$this->mustache = $mustache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mustache Template instances can be treated as a function and rendered by simply calling them:
|
||||
*
|
||||
* $m = new Mustache;
|
||||
* $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(new Context($context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal rendering method implemented by Mustache Template concrete subclasses.
|
||||
*
|
||||
* This is where the magic happens :)
|
||||
*
|
||||
* @abstract
|
||||
*
|
||||
* @param \Mustache\Context $context
|
||||
*
|
||||
* @return string Rendered template
|
||||
*/
|
||||
abstract public function renderInternal(Context $context);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace Mustache;
|
||||
|
||||
/**
|
||||
* Mustache Tokenizer class.
|
||||
*
|
||||
* This class is responsible for turning raw template source into a set of Mustache tokens.
|
||||
*/
|
||||
class Tokenizer {
|
||||
|
||||
// Finite state machine states
|
||||
const IN_TEXT = 0;
|
||||
const IN_TAG_TYPE = 1;
|
||||
const IN_TAG = 2;
|
||||
|
||||
// Token types
|
||||
const T_SECTION = 1;
|
||||
const T_INVERTED = 2;
|
||||
const T_END_SECTION = 3;
|
||||
const T_COMMENT = 4;
|
||||
const T_PARTIAL = 5;
|
||||
const T_PARTIAL_2 = 6;
|
||||
const T_DELIM_CHANGE = 7;
|
||||
const T_ESCAPED = 8;
|
||||
const T_UNESCAPED = 9;
|
||||
const T_UNESCAPED_2 = 10;
|
||||
|
||||
// Token types map
|
||||
private static $tagTypes = array(
|
||||
'#' => self::T_SECTION,
|
||||
'^' => self::T_INVERTED,
|
||||
'/' => self::T_END_SECTION,
|
||||
'!' => self::T_COMMENT,
|
||||
'>' => self::T_PARTIAL,
|
||||
'<' => self::T_PARTIAL_2,
|
||||
'=' => self::T_DELIM_CHANGE,
|
||||
'_v' => self::T_ESCAPED,
|
||||
'{' => self::T_UNESCAPED,
|
||||
'&' => self::T_UNESCAPED_2,
|
||||
);
|
||||
|
||||
// Token properties
|
||||
const NODES = 'nodes';
|
||||
const TAG = 'tag';
|
||||
const NAME = 'name';
|
||||
const OTAG = 'otag';
|
||||
const CTAG = 'ctag';
|
||||
const INDEX = 'index';
|
||||
const END = 'end';
|
||||
const INDENT = 'indent';
|
||||
|
||||
private $state;
|
||||
private $tagType;
|
||||
private $tag;
|
||||
private $buf;
|
||||
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;
|
||||
$tag = isset(self::$tagTypes[$text[$i + 1]]) ? self::$tagTypes[$text[$i + 1]] : null;
|
||||
$this->tagType = $tag ? $text[$i + 1] : '_v';
|
||||
if ($this->tagType === '=') {
|
||||
$i = $this->changeDelimiters($text, $i);
|
||||
$this->state = self::IN_TEXT;
|
||||
} else {
|
||||
if ($tag) {
|
||||
$i++;
|
||||
}
|
||||
$this->state = self::IN_TAG;
|
||||
}
|
||||
$this->seenTag = $i;
|
||||
break;
|
||||
|
||||
default:
|
||||
if ($this->tagChange($this->ctag, $text, $i)) {
|
||||
$this->tokens[] = array(
|
||||
self::TAG => $this->tagType,
|
||||
self::NAME => trim($this->buffer),
|
||||
self::OTAG => $this->otag,
|
||||
self::CTAG => $this->ctag,
|
||||
self::INDEX => ($this->tagType == '/') ? $this->seenTag - strlen($this->otag) : $i + strlen($this->ctag)
|
||||
);
|
||||
|
||||
$this->buffer = '';
|
||||
$i += strlen($this->ctag) - 1;
|
||||
$this->state = self::IN_TEXT;
|
||||
if ($this->tagType == '{') {
|
||||
if ($this->ctag == '}}') {
|
||||
$i++;
|
||||
} else {
|
||||
$this->cleanTripleStache($this->tokens[count($this->tokens) - 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[] = $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 (is_array($token) && isset(self::$tagTypes[$token[self::TAG]])) {
|
||||
if (self::$tagTypes[$token[self::TAG]] >= self::T_ESCAPED) {
|
||||
return false;
|
||||
}
|
||||
} elseif (is_string($token)) {
|
||||
if (preg_match('/\S/', $token)) {
|
||||
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 (!is_array($this->tokens[$j])) {
|
||||
if (isset($this->tokens[$j+1]) && is_array($this->tokens[$j+1]) && $this->tokens[$j+1][self::TAG] == '>') {
|
||||
$this->tokens[$j+1][self::INDENT] = (string) $this->tokens[$j];
|
||||
}
|
||||
|
||||
$this->tokens[$j] = null;
|
||||
}
|
||||
}
|
||||
} elseif (!$noNewLine) {
|
||||
$this->tokens[] = "\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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up `{{{ tripleStache }}}` style tokens.
|
||||
*
|
||||
* @param array &$token
|
||||
*/
|
||||
private function cleanTripleStache(&$token) {
|
||||
if (substr($token[self::NAME], -1) === '}') {
|
||||
$token[self::NAME] = trim(substr($token[self::NAME], 0, -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,16 @@
|
||||
<?php
|
||||
|
||||
require __DIR__.'/../src/Mustache/Buffer.php';
|
||||
require __DIR__.'/../src/Mustache/Compiler.php';
|
||||
require __DIR__.'/../src/Mustache/Context.php';
|
||||
require __DIR__.'/../src/Mustache/Loader.php';
|
||||
require __DIR__.'/../src/Mustache/Loader/MutableLoader.php';
|
||||
require __DIR__.'/../src/Mustache/Loader/ArrayLoader.php';
|
||||
require __DIR__.'/../src/Mustache/Loader/StringLoader.php';
|
||||
require __DIR__.'/../src/Mustache/Loader/FilesystemLoader.php';
|
||||
require __DIR__.'/../src/Mustache/Mustache.php';
|
||||
require __DIR__.'/../src/Mustache/Parser.php';
|
||||
require __DIR__.'/../src/Mustache/Template.php';
|
||||
require __DIR__.'/../src/Mustache/Tokenizer.php';
|
||||
|
||||
require __DIR__.'/lib/yaml/lib/sfYamlParser.php';
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
alpha contents
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
beta contents
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
one contents
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
two contents
|
||||
Reference in New Issue
Block a user