initial commit

This commit is contained in:
fzerorubigd 2012-10-25 19:00:10 +03:30
commit 2af461c7f6
14 changed files with 1837 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
vendor

13
composer.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "xamin/handlebars.php",
"description": "Handlebars processor for php",
"authors": [
{
"name": "fzerorubigd",
"email": "fzerorubigd@gmail.com"
}
],
"require": {
}
}

60
src/Handlebars/Cache.php Normal file
View File

@ -0,0 +1,60 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Cache interface
* Base cache interface
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
interface Handlebars_Cache
{
/**
* Get cache for $name if exist.
*
* @param string $name Cache id
*
* @return data on hit, boolean false on cache not found
*/
public function get($name);
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value);
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name);
}

View File

@ -0,0 +1,73 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* A dummy array cache
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Cache_Dummy implements Handlebars_Cache
{
private $_cache = array();
/**
* Get cache for $name if exist.
*
* @param string $name Cache id
*
* @return data on hit, boolean false on cache not found
*/
public function get($name)
{
if (array_key_exists($name, $this->_cache)) {
return $this->_cache[$name];
}
return false;
}
/**
* Set a cache
*
* @param string $name cache id
* @param mixed $value data to store
*
* @return void
*/
public function set($name, $value)
{
$this->_cache[$name] = $value;
}
/**
* Remove cache
*
* @param string $name Cache id
*
* @return void
*/
public function remove($name)
{
unset($this->_cache[$name]);
}
}

163
src/Handlebars/Context.php Normal file
View File

@ -0,0 +1,163 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars context
* Context for a template
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Context
{
/**
* @var array stack for context only top stack is available
*/
protected $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
*
* @return void
*/
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);
}
/**
* Change the current context to one of current context members
*
* @param string $variableName name of variable or a callable on current context
*
* @return mixed actual value
*/
public function with($variableName)
{
$value = $this->get($variableName);
$this->push($value);
return $value;
}
/**
* Get a avariable from current context
* Supported types :
* variable , ../variable , variable.variable , .
*
* @param string $variableName variavle name to get from current context
*
* @return mixed
*/
public function get($variableName)
{
//Need to clean up
$variableName = trim($variableName);
$level = 0;
while (substr($variableName, 0, 3) == '../') {
$variableName = trim(substr($variableName, 3));
$level++;
}
if (count($this->stack) < $level) {
return '';
}
end($this->stack);
while ($level) {
prev($this->stack);
$level--;
}
$current = current($this->stack);
if (!$variableName) {
return '';
} elseif ($variableName == '.') {
return $current;
} else {
$chunks = explode('.', $variableName);
foreach ($chunks as $chunk) {
if ($current == '') {
return $current;
}
$current = $this->_findVariableInContext($current, $chunk);
}
}
return $current;
}
/**
* Check if $variable->$inside is available
*
* @param mixed $variable variable to check
* @param string $inside property/method to check
*
* @return boolean true if exist
*/
private function _findVariableInContext($variable, $inside)
{
$value = '';
if (is_array($variable)) {
if (isset($variable[$inside])) {
$value = $variable[$inside];
}
} elseif (is_object($variable)) {
if (isset($variable->$inside)) {
$value = $variable->$inside;
} elseif (is_callable(array($variable, $inside))) {
$value = call_user_func(array($variable, $inside));
}
} elseif ($inside === '.') {
$value = $variable;
}
return $value;
}
}

389
src/Handlebars/Engine.php Normal file
View File

@ -0,0 +1,389 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars parser (infact its a mustache parser)
* This class is responsible for turning raw template source into a set of Mustache tokens.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Engine
{
const VERSION = '1.0.0';
/**
* @var Handlebars_Tokenizer
*/
private $_tokenizer;
/**
* @var Handlebars_Parser
*/
private $_parser;
/**
* @var Handlebars_Helpers
*/
private $_helpers;
/**
* @var Handlebars_Loader
*/
private $_loader;
/**
* @var Handlebars_Loader
*/
private $_partialLoader;
/**
* @var Handlebars_Cache
*/
private $_cache;
/**
* @var callable escape function to use
*/
private $_escape = 'htmlspecialchars';
/**
* @var array parametes to pass to escape function, script prepend string to this array
*/
private $_escapeArgs = array (
ENT_COMPAT,
'UTF-8'
);
/**
* Handlebars engine constructor
* $options array can contain :
* helpers => Handlebars_Helpers object
* escape => a callable function to escape values
* escapeArgs => array to pass as extra parameter to escape function
* loader => Handlebars_Loader object
* partial_loader => Handlebars_Loader object
* cache => Handlebars_Cache object
*
* @param array $options array of options to set
*/
public function __construct(array $options = array())
{
if (isset($options['helpers'])) {
$this->setHelpers($options['helpers']);
}
if (isset($options['loader'])) {
$this->setLoader($options['loader']);
}
if (isset($options['partial_loader'])) {
$this->setPartialLoader($options['partial_loader']);
}
if (isset($options['cache'])) {
$this->setCache($options['cache']);
}
if (isset($options['escape'])) {
if (!is_callable($options['escape'])) {
throw new InvalidArgumentException('Handlebars Constructor "escape" option must be callable');
}
$this->_escape = $options['escape'];
}
if (isset($options['escapeArgs'])) {
if (!is_array($options['escapeArgs'])) {
$options['escapeArgs'] = array($options['escapeArgs']);
}
$this->_escapeArgs = $options['escapeArgs'];
}
}
/**
* Shortcut 'render' invocation.
*
* Equivalent to calling `$handlebars->loadTemplate($template)->render($data);`
*
* @param string $template template name
* @param mixed $data data to use as context
*
* @return string Rendered template
* @see Handlebars_Engine::loadTemplate
* @see Handlebars_Template::render
*/
public function render($template, $data)
{
return $this->loadTemplate($template)->render($data);
}
/**
* Set helpers for current enfine
*
* @param Handlebars_Helpers $helpers handlebars helper
*
* @return void
*/
public function setHelpers(Handlebars_Helpers $helpers)
{
$this->_helpers = $helpers;
}
/**
* Get helpers, or create new one if ther is no helper
*
* @return Handlebars_Helpers
*/
public function getHelpers()
{
if (!isset($this->_helpers)) {
$this->_helpers = new Handlebars_Helpers();
}
return $this->_helpers;
}
/**
* Set current loader
*
* @param Handlebars_Loader $loader handlebars loader
*
* @return void
*/
public function setLoader(Handlebars_Loader $loader)
{
$this->_loader = $loader;
}
/**
* get current loader
*
* @return Handlebars_Loader
*/
public function getLoader()
{
if (!isset($this->_loader)) {
$this->_loader = new Handlebars_Loader_StringLoader();
}
return $this->_loader;
}
/**
* Set current partial loader
*
* @param Handlebars_Loader $loader handlebars loader
*
* @return void
*/
public function setPartialLoader(Handlebars_Loader $loader)
{
$this->_partialLoader = $loader;
}
/**
* get current partial loader
*
* @return Handlebars_Loader
*/
public function getPartialLoader()
{
if (!isset($this->_partialLoader)) {
$this->_partialLoader = new Handlebars_Loader_StringLoader();
}
return $this->_partialLoader;
}
/**
* Set cache for current engine
*
* @param Handlebars_cache $cache handlebars cache
*
* @return void
*/
public function setCache(Handlebars_Cache $cache)
{
$this->_cache = $cache;
}
/**
* Get cache
*
* @return Handlebars_Cache
*/
public function getCache()
{
if (!isset($this->_cache)) {
$this->_cache = new Handlebars_Cache_Dummy();
}
return $this->_cache;
}
/**
* Get current escape function
*
* @return callable
*/
public function getEscape()
{
return $this->_escape;
}
/**
* Set current escpae function
*
* @param callable $escape function
*
* @return void
*/
public function setEscape($escape)
{
if (!is_callable($escape)) {
throw new InvalidArgumentException('Escape function must be a callable');
}
$this->_escape = $escape;
}
/**
* Get current escape function
*
* @return callable
*/
public function getEscapeArgs()
{
return $this->_escapeArgs;
}
/**
* Set current escpae function
*
* @param array $escapeArgs arguments to pass as extra arg to function
*
* @return void
*/
public function setEscapeArgs($escapeArgs)
{
if (!is_array($escapeArgs)) {
$escapeArgs = array($escapeArgs);
}
$this->_escapeArgs = $escapeArgs;
}
/**
* Set the Handlebars Tokenizer instance.
*
* @param Handlebars_Tokenizer $tokenizer tokenizer
*
* @return void
*/
public function setTokenizer(Handlebars_Tokenizer $tokenizer)
{
$this->_tokenizer = $tokenizer;
}
/**
* Get the current Handlebars Tokenizer instance.
*
* If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Handlebars_Tokenizer
*/
public function getTokenizer()
{
if (!isset($this->_tokenizer)) {
$this->_tokenizer = new Handlebars_Tokenizer();
}
return $this->_tokenizer;
}
/**
* Set the Handlebars Parser instance.
*
* @param Handlebars_Parser $parser parser object
*
* @return void
*/
public function setParser(Handlebars_Parser $parser)
{
$this->_parser = $parser;
}
/**
* Get the current Handlebars Parser instance.
*
* If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
*
* @return Handlebars_Parser
*/
public function getParser()
{
if (!isset($this->_parser)) {
$this->_parser = new Handlebars_Parser();
}
return $this->_parser;
}
/**
* Load a template by name with current template loader
*
* @param string $name template name
*
* @return Handlebars_Template
*/
public function loadTemplate($name)
{
$source = $this->getLoader()->load($name);
$tree = $this->_tokenize($source);
return new Handlebars_Template($this, $tree, $source);
}
/**
* Load a partial by name with current partial loader
*
* @param string $name partial name
*
* @return Handlebars_Template
*/
public function loadPartial($name)
{
$source = $this->getPartialLoader()->load($name);
$tree = $this->_tokenize($source);
return new Handlebars_Template($this, $tree, $source);
}
/**
* try to tokenize source, or get them from cache if available
*
* @param string $source handlebars source code
*
* @return array handlebars parsed data into array
*/
private function _tokenize($source)
{
$hash = md5(sprintf('version: %s, data : %s', self::VERSION, $source));
$tree = $this->getCache()->get($hash);
if ($tree === false) {
$tokens = $this->getTokenizer()->scan($source);
$tree = $this->getParser()->parse($tokens);
}
return $tree;
}
}

249
src/Handlebars/Helpers.php Normal file
View File

@ -0,0 +1,249 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars helpers
*
* a collection of helper function. normally a function like
* function ($sender, $name, $arguments) $arguments is unscaped arguments and is a string, not array
* TODO: Add support for an interface with an execute method
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Helpers
{
/**
* @var array array of helpers
*/
protected $helpers = array();
/**
* Create new helper container class
*
* @param array $helpers array of name=>$value helpers
* @param array $defaults add defaults helper (if, unless, each,with)
*
* @throw InvalidArgumentException when $helpers is not an array (or traversable) or helper is not a caallable
*/
public function __construct($helpers = null, $defaults = true)
{
if ($defaults) {
$this->addDefaultHelpers();
}
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, $helpers);
}
}
}
/**
* Add default helpers (if unless each with)
*
* @return void
*/
protected function addDefaultHelpers()
{
$this->add(
'if',
function ($template, $context, $args, $source)
{
$tmp = $context->get($args);
$buffer = '';
if ($tmp) {
$buffer = $template->render($context);
}
return $buffer;
}
);
$this->add(
'each',
function($template, $context, $args, $source)
{
$tmp = $context->get($args);
$buffer = '';
if (is_array($tmp) || $tmp instanceof Traversable) {
foreach ($tmp as $var) {
$context->push($var);
$buffer .= $template->render($context);
$context->pop();
}
}
return $buffer;
}
);
$this->add(
'unless',
function ($template, $context, $args, $source)
{
$tmp = $context->get($args);
$buffer = '';
if (!$tmp) {
$buffer = $template->render($context);
}
return $buffer;
}
);
$this->add(
'with',
function ($template, $context, $args, $source)
{
$tmp = $context->get($args);
$context->push($tmp);
$buffer = $template->render($context);
$context->pop();
return $buffer;
}
);
}
/**
* Add a new helper to helpers
*
* @param string $name helper name
* @param callable $helper a function as a helper
*
* @return void
* @throw InvalidArgumentException if $helper is not a callable
*/
public function add($name ,$helper)
{
if (!is_callable($helper)) {
throw new InvalidArgumentException("$name Helper is not a callable.");
}
$this->helpers[$name] = $helper;
}
/**
* Check if $name helper is available
*
* @param string $name helper name
*
* @return boolean
*/
public function has($name)
{
return array_key_exists($name, $this->helpers);
}
/**
* Get a helper. __magic__ method :)
*
* @param string $name helper name
*
* @return callable helper function
* @throw InvalidArgumentException if $name is not available
*/
public function __get($name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException('Unknow helper :' . $name);
}
return $this->helpers[$name];
}
/**
* Check if $name helper is available __magic__ method :)
*
* @param string $name helper name
*
* @return boolean
* @see Handlebras_Helpers::has
*/
public function __isset($name)
{
return $this->has($name);
}
/**
* Add a new helper to helpers __magic__ method :)
*
* @param string $name helper name
* @param callable $helper a function as a helper
*
* @return void
* @throw InvalidArgumentException if $helper is not a callable
*/
public function __set($name ,$helper)
{
$this->add($name, $helpers);
}
/**
* Unset a helper
*
* @param string $name helpername to remove
*
* @return void
*/
public function __unset($name)
{
unset($this->helpers[$name]);
}
/**
* Check whether a given helper is present in the collection.
*
* @param string $name helper name
*
* @return void
* @throws InvalidArgumentException if the requested helper is not present.
*/
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
*
* @return void
*/
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);
}
}

40
src/Handlebars/Loader.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars loader interface
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
interface Handlebars_Loader
{
/**
* Load a Template by name.
*
* @param string $name template name to load
*
* @return string Mustache Template source
*/
public function load($name);
}

View File

@ -0,0 +1,118 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars Template filesystem Loader implementation.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir *
* @implements Loader
*/
class Handlebars_Loader_FilesystemLoader implements Handlebars_Loader
{
private $_baseDir;
private $_extension = '.handlebars';
private $_templates = array();
/**
* Handlebars 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',
* );
*
* @param string $baseDir Base directory containing Mustache template files.
* @param array $options Array of Loader options (default: array())
*
* @throws RuntimeException if $baseDir does not exist.
*/
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 template name
*
* @return string Handkebars 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.
*
* @param string $name template name
*
* @return string Mustache Template source
* @throws InvalidArgumentException if a template file is not found.
*/
protected 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 template name
*
* @return string Template file name
*/
protected function getFileName($name)
{
$fileName = $this->_baseDir . '/' . $name;
if (substr($fileName, 0 - strlen($this->_extension)) !== $this->_extension) {
$fileName .= $this->_extension;
}
return $fileName;
}
}

View File

@ -0,0 +1,43 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars Template string Loader implementation.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir *
* @implements Loader
*/
class Handlebars_Loader_StringLoader implements Handlebars_Loader
{
/**
* Load a Template by source.
*
* @param string $name Handlebars Template source
*
* @return string Handlebars Template source
*/
public function load($name)
{
return $name;
}
}

98
src/Handlebars/Parser.php Normal file
View File

@ -0,0 +1,98 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
* re-write to use with handlebars
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars parser (infact its a mustache parser)
* This class is responsible for turning raw template source into a set of Mustache tokens.
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Parser
{
/**
* Process array of tokens and convert them into parse tree
*
* @param array $tokens Set of
*
* @return array 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 tokens
*
* @return array Token parse tree
*
* @throws LogicException when nesting errors or mismatched section tags are encountered.
*/
private function _buildTree(ArrayIterator $tokens)
{
$stack = array();
do {
$token = $tokens->current();
$tokens->next();
if ($token === null) {
continue;
} else {
switch ($token[Handlebars_Tokenizer::TYPE]) {
case Handlebars_Tokenizer::T_END_SECTION:
$newNodes = array ();
$continue = true;
do {
$result = array_pop($stack);
if ($result === null) {
throw new LogicException('Unexpected closing tag: /'. $token[Handlebars_Tokenizer::NAME]);
}
if (!array_key_exists(Handlebars_Tokenizer::NODES, $result)
&& isset($result[Handlebars_Tokenizer::NAME])
&& $result[Handlebars_Tokenizer::NAME] == $token[Handlebars_Tokenizer::NAME]
) {
$result[Handlebars_Tokenizer::NODES] = $newNodes;
$result[Handlebars_Tokenizer::END] = $token[Handlebars_Tokenizer::INDEX];
array_push($stack, $result);
break 2;
} else {
array_unshift($newNodes, $result);
}
} while (true);
break;
default:
array_push($stack, $token);
}
}
} while ($tokens->valid());
return $stack;
}
}

207
src/Handlebars/Template.php Normal file
View File

@ -0,0 +1,207 @@
<?php
/**
* This file is part of Handlebars-php
* Base on mustache-php https://github.com/bobthecow/mustache.php
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars base template
* contain some utility method to get context and helpers
*
* @category Xamin
* @package Handlebars
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 (c) ParsPooyesh Co
* @license GPLv3 <http://www.gnu.org/licenses/gpl-3.0.html>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_Template
{
/**
* @var Handlebars_Engine
*/
protected $handlebars;
protected $tree = array();
protected $source = '';
/**
* @var array Run stack
*/
private $_stack = array();
/**
* Handlebars template constructor
*
* @param Handlebars_Engine $engine handlebar engine
* @param array $tree Parsed tree
* @param string $source Handlebars source
*/
public function __construct(Handlebars_Engine $engine, $tree, $source)
{
$this->handlebars = $engine;
$this->tree = $tree;
$this->source = $source;
array_push($this->_stack, array (0, $this->getTree()));
}
/**
* Get current tree
*
* @return array
*/
public function getTree()
{
return $this->tree;
}
/**
* Get current source
*
* @return string
*/
public function getSource()
{
return $this->source;
}
/**
* Get current engine associated with this object
*
* @return Handlebars_Engine
*/
public function getEngine()
{
return $this->handlebars;
}
/**
* Render top tree
*
* @param mixed $context current context
*
* @return string
*/
public function render($context)
{
if (!$context instanceof Handlebars_Context) {
$context = new Handlebars_Context($context);
}
$topTree = end($this->_stack); //This method never pop a value from stack
list($index ,$tree) = $topTree;
$buffer = '';
while (array_key_exists($index, $tree)) {
$current = $tree[$index];
$index++;
switch ($current[Handlebars_Tokenizer::TYPE]) {
case Handlebars_Tokenizer::T_SECTION :
array_push($this->_stack, array(0, $current[Handlebars_Tokenizer::NODES]));
$buffer .= $this->_section($context, $current);
array_pop($this->_stack);
break;
case Handlebars_Tokenizer::T_INVERTED : //TODO: This has no effect, remove the whole ^ thing!
case Handlebars_Tokenizer::T_COMMENT :
$buffer .= '';
break;
case Handlebars_Tokenizer::T_PARTIAL:
case Handlebars_Tokenizer::T_PARTIAL_2:
$buffer .= $this->_partial($context, $current);
break;
case Handlebars_Tokenizer::T_UNESCAPED:
case Handlebars_Tokenizer::T_UNESCAPED_2:
$buffer .= $this->_variables($context, $current, false);
break;
case Handlebars_Tokenizer::T_ESCAPED:
$buffer .= $this->_variables($context, $current, true);
break;
case Handlebars_Tokenizer::T_TEXT:
$buffer .= $current[Handlebars_Tokenizer::VALUE];
break;
default:
throw new RuntimeException('Invalid node type : ' . json_encode($current));
}
}
return $buffer;
}
/**
* Process section nodes
*
* @param Handlebars_Context $context current context
* @param array $current section node data
*
* @return string the result
*/
private function _section(Handlebars_Context $context, $current)
{
$helpers = $this->handlebars->getHelpers();
$sectionName = $current[Handlebars_Tokenizer::NAME];
if ($helpers->has($sectionName)) {
$source = substr(
$this->getSource(),
$current[Handlebars_Tokenizer::INDEX],
$current[Handlebars_Tokenizer::END] - $current[Handlebars_Tokenizer::INDEX]
);
$params = array(
$this, //First argument is this template
$context, //Secound is current context
$current[Handlebars_Tokenizer::ARGS], //Arguments
$source
);
return call_user_func_array($helpers->$sectionName, $params);
} else {
throw new RuntimeException($sectionName . ' is not registered as a helper');
}
}
/**
* Process partial section
*
* @param Handlebars_Context $context current context
* @param array $current section node data
*
* @return string the result
*/
private function _partial($context, $current)
{
$partial = $this->handlebars->loadPartial($current[Handlebars_Tokenizer::NAME]);
return $partial->render($context);
}
/**
* Process partial section
*
* @param Handlebars_Context $context current context
* @param array $current section node data
* @param boolean $escaped escape result or not
*
* @return string the result
*/
private function _variables($context, $current, $escaped)
{
$value = $context->get($current[Handlebars_Tokenizer::NAME]);
if ($escaped) {
$args = $this->handlebars->getEscapeArgs();
array_unshift($args, $value);
$value = call_user_func_array($this->handlebars->getEscape(), array_values($args));
}
return $value;
}
}

View File

@ -0,0 +1,322 @@
<?php
/**
* This file is part of Mustache.php.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
* Changes to match xamin-std and handlebars made by xamin team
*
* PHP version 5.3
*
* @category Xamin
* @package Handlebars
* @author Justin Hileman <dontknow@example.org>
* @author fzerorubigd <fzerorubigd@gmail.com>
* @copyright 2012 Justin Hileman
* @license MIT <http://opensource.org/licenses/mit-license.php>
* @version GIT: $Id$
* @link http://xamin.ir
*/
/**
* Handlebars parser (infact its a mustache parser)
* This class is responsible for turning raw template source into a set of Mustache tokens.
*
* @category Xamin
* @package Handlebars
* @author Justin Hileman <dontknow@example.org>
* @copyright 2012 Justin Hileman
* @license MIT <http://opensource.org/licenses/mit-license.php>
* @version Release: @package_version@
* @link http://xamin.ir
*/
class Handlebars_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 = '^'; //Must remove this
const T_END_SECTION = '/';
const T_COMMENT = '!';
const T_PARTIAL = '>'; //Maybe remove this partials and replace them with helpers
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';
const ARGS = 'args';
protected $state;
protected $tagType;
protected $tag;
protected $buffer;
protected $tokens;
protected $seenTag;
protected $lineStart;
protected $otag;
protected $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)) {
if ($this->tagType == self::T_SECTION || $this->tagType == self::T_INVERTED) {
$newBuffer = explode(' ', trim($this->buffer), 2);
$args = '';
if (count($newBuffer) == 2) {
$args = $newBuffer[1];
}
$this->buffer = $newBuffer[0];
}
$t = 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),
);
if (isset($args)) {
$t[self::ARGS] = $args;
}
$this->tokens[] = $t;
unset($t);
unset($args);
$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.
*
* @return void
*/
protected 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.
*
* @return void
*/
protected 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
*/
protected 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)
*
* @return void
*/
protected 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
*/
protected 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
*/
protected function tagChange($tag, $text, $index)
{
return substr($text, $index, strlen($tag)) === $tag;
}
}

View File

@ -0,0 +1,61 @@
<?php
require "Template.php";
require "Engine.php";
require "Context.php";
require "Tokenizer.php";
require "Parser.php";
require "Cache.php";
require "Cache/Dummy.php";
require "Loader.php";
require "Loader/StringLoader.php";
require "Helpers.php";
$temp = <<<END_HERE
<div id="message"> {{!Place holder for message, leave it be in any case}}
{{#if error}}
<ul>
{{#each errors}}
<li>{{.}}</li>
{{/each}}
</ul>
{{/if}}
</div>
{{#with t}}
{{{form}}}
{{/with}}
{{>Test}} {{! since there is no Test partial and loader is string loder, just Test is printed out}}
{{{slots.search}}}
{{slots.tags}}
Test
{{#each t.errors}}
<li>{{.}}</li>
{{/each}}
END_HERE;
$contextArray =
[
'error' => true,
'errors' => ['err1', 'err2', 'err3'],
'slots' => [ 'search' => '<b>search</b>' ,'tags' => '<b>tags</b>'],
't' =>
[
'errors' => ['t.err1', 't.err2'],
'form' => '<form></form>'
]
];
$engine = new Handlebars_Engine();
$helper = new Handlebars_Helpers();
$engine->setHelpers($helper);
echo $engine->render($temp, $contextArray);