From 57d00e6e82fdde09c19dea363598ee87ee17f72f Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Mon, 21 Sep 2015 17:49:17 +0800 Subject: [PATCH 1/7] What to expect... We should also probably check for looping capabilities as well. --- tests/Xamin/HandlebarsTest.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 2e56211..0b32fc4 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -438,6 +438,30 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase $this->setExpectedException('InvalidArgumentException'); $engine->getHelpers()->call('invalid', $engine->loadTemplate(''), new \Handlebars\Context(), '', ''); } + + public function testRegisterHelper() + { + $template = '{{#grand test "test2"}} In 1: {{test4}} {{#grand ../test \'test3\'}} In 2: {{test5}}{{#parent}} In 3: {{test6}} {{../../../test}}{{/parent}}{{/grand}}{{/grand}}'; + $vars = array('test' => 'Hello World'); + $expected = ' In 1: Hello World In 2: This is Test 5 In 3: This is Test 6 Hello World'; + + $loader = new \Handlebars\Loader\StringLoader(); + $engine = new \Handlebars\Handlebars(array('loader' => $loader)); + + $engine->registerHelper('grand', function($test1, $test2, $options) { + return $options['fn'](array( + 'test4' => $test1, + 'test5' => 'This is Test 5' + )); + }); + + $engine->registerHelper('parent', function($options) { + return $options['fn'](array('test6' => 'This is Test 6')); + }); + + + $this->assertEquals($expected, $engine->render($template, $vars)); + } public function testInvalidHelperMustacheStyle() { From 33c76738b46fcbaffa0b8c50f4758568d37971b6 Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Mon, 21 Sep 2015 17:50:00 +0800 Subject: [PATCH 2/7] Added registerHelper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I did it in such a way where I’m not messing with the other methods or classes to get what I wanted. I realize that there maybe no need for the child context, however when trying a permutation of ``` 'fn' => function($data = null) use($context, $template) { $context->push($context->last()); if(is_array($data)) { $context->push($data); } $template->setStopToken('else'); $buffer = $template->render($context); $template->setStopToken(false); $template->discard($context); if(is_array($data)) { $context->pop(); } $context->pop(); return $buffer; } ``` It didn’t parse the `../../../test` correctly in the test. I figured that the ChildContext is a nice pattern overall and doesn’t interfere with the rest of the package anyways… --- src/Handlebars/ChildContext.php | 85 ++++++++++++++++++++++++++ src/Handlebars/Handlebars.php | 105 ++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 src/Handlebars/ChildContext.php diff --git a/src/Handlebars/ChildContext.php b/src/Handlebars/ChildContext.php new file mode 100644 index 0000000..58aea12 --- /dev/null +++ b/src/Handlebars/ChildContext.php @@ -0,0 +1,85 @@ + + * @author Behrooz Shabani + * @author Chris Gray + * @author Ulrik Lystbaek + * @author Dmitriy Simushev + * @author Christian Blanquera + * @copyright 2010-2012 (c) Justin Hileman + * @copyright 2012 (c) ParsPooyesh Co + * @copyright 2013 (c) Behrooz Shabani + * @copyright 2013 (c) f0ruD A + * @license MIT + * @version GIT: $Id$ + * @link http://xamin.ir + */ + +namespace Handlebars; + +/** + * Handlebars context + * Context for a template + * + * @category Xamin + * @package Handlebars + * @author fzerorubigd + * @author Behrooz Shabani + * @copyright 2010-2012 (c) Justin Hileman + * @copyright 2012 (c) ParsPooyesh Co + * @license MIT + * @version Release: @package_version@ + * @link http://xamin.ir + */ + +class ChildContext extends Context +{ + protected $parentContext = null; + + /** + * Sets a parent context in which + * we will case for the ../ in get() + * + * @param Context + * @return void + */ + public function setParent(Context $parent) { + $this->parentContext = $parent; + } + + /** + * Get a available from current context + * Supported types : + * variable , ../variable , variable.variable , variable.[variable] , . + * + * @param string $variableName variable name to get from current context + * @param boolean $strict strict search? if not found then throw exception + * + * @throws \InvalidArgumentException in strict mode and variable not found + * @throws \RuntimeException if supplied argument is a malformed quoted string + * @throws \InvalidArgumentException if variable name is invalid + * @return mixed + */ + public function get($variableName, $strict = false) + { + //if the variable name starts with a ../ + //and we have a parent + if(strpos($variableName, '../') === 0 && $this->parentContext instanceof Context) { + //just remove the first ../ + $variableName = substr($variableName, 3); + + //and let the parent context handle the rest + return $this->parentContext->get($variableName, $strict); + } + + //otherwise, it's business as usual + return parent::get($variableName, $strict); + } +} diff --git a/src/Handlebars/Handlebars.php b/src/Handlebars/Handlebars.php index 0057d4c..fe15525 100755 --- a/src/Handlebars/Handlebars.php +++ b/src/Handlebars/Handlebars.php @@ -235,6 +235,111 @@ class Handlebars { return $this->getHelpers()->has($name); } + + /** + * Add a new helper. + * + * @param string $name helper name + * @param mixed $helper helper callable + * + * @return void + */ + public function registerHelper($name, $helper) + { + $this->addHelper($name, function($template, $context, $arg) use ($helper) + { + $args = $template->parseArguments($arg); + $named = $template->parseNamedArguments($arg); + + foreach($args as $i => $arg) { + //if it's literally string + if($arg instanceof StringWrapper) { + //we have no problems here + $args[$i] = (string) $arg; + continue; + } + + //not sure what to do if it's not a string or StringWrapper + if(!is_string($arg)) { + continue; + } + + //it's a variable and we need to figure out the value of it + $args[$i] = $context->get($arg); + } + + //push the options + $args[] = array( + //special fields + 'data' => array( + 'index' => $context->get('@index'), + 'key' => $context->get('@key'), + 'first' => $context->get('@first'), + 'last' => $context->get('@last')), + // Named arguments + 'hash' => $named, + // A renderer for block helper + 'fn' => function($inContext = null) use($context, $template) + { + $defined = !!$inContext; + + if(!$defined) { + $inContext = $context; + $inContext->push($inContext->last()); + } else if (!$inContext instanceof Context) { + $inContext = new ChildContext($inContext); + $inContext->setParent($context); + } + + $template->setStopToken('else'); + $buffer = $template->render($inContext); + $template->setStopToken(false); + //what if it's a loop ? + $template->rewind(); + //What's the point of this again? + //I mean in this context (literally) + //$template->discard($inContext); + + if($defined) { + $inContext->pop(); + } + + return $buffer; + }, + + // A render for the else block + 'inverse' => function($inContext = null) use($context, $template) + { + $defined = !!$inContext; + + if(!$defined) { + $inContext = $context; + $inContext->push($inContext->last()); + } else if (!$inContext instanceof Context) { + $inContext = new ChildContext($inContext); + $inContext->setParent($context); + } + + $template->setStopToken('else'); + $template->discard($inContext); + $template->setStopToken(false); + $buffer = $template->render($inContext); + + if($defined) { + $inContext->pop(); + } + + return $buffer; + }, + + // The current context. + 'context' => $context, + // The current template + 'template' => $template); + + return call_user_func_array($helper, $args); + }); + } /** * Remove a helper by name. From 0070ad79c009c2052f186ce2239843f2713fee06 Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Mon, 21 Sep 2015 18:49:08 +0800 Subject: [PATCH 3/7] phpcs approval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit “oh the spacing!” --- src/Handlebars/ChildContext.php | 56 ++++---- src/Handlebars/Handlebars.php | 219 +++++++++++++++++--------------- 2 files changed, 147 insertions(+), 128 deletions(-) diff --git a/src/Handlebars/ChildContext.php b/src/Handlebars/ChildContext.php index 58aea12..cbbc67f 100644 --- a/src/Handlebars/ChildContext.php +++ b/src/Handlebars/ChildContext.php @@ -41,20 +41,22 @@ namespace Handlebars; class ChildContext extends Context { - protected $parentContext = null; - - /** - * Sets a parent context in which - * we will case for the ../ in get() - * - * @param Context - * @return void - */ - public function setParent(Context $parent) { - $this->parentContext = $parent; - } - - /** + protected $parentContext = null; + + /** + * Sets a parent context in which + * we will case for the ../ in get() + * + * @param Context $parent parent context + * + * @return void + */ + public function setParent(Context $parent) + { + $this->parentContext = $parent; + } + + /** * Get a available from current context * Supported types : * variable , ../variable , variable.variable , variable.[variable] , . @@ -69,17 +71,19 @@ class ChildContext extends Context */ public function get($variableName, $strict = false) { - //if the variable name starts with a ../ - //and we have a parent - if(strpos($variableName, '../') === 0 && $this->parentContext instanceof Context) { - //just remove the first ../ - $variableName = substr($variableName, 3); - - //and let the parent context handle the rest - return $this->parentContext->get($variableName, $strict); - } - - //otherwise, it's business as usual - return parent::get($variableName, $strict); + //if the variable name starts with a ../ + //and we have a parent + if (strpos($variableName, '../') === 0 + && $this->parentContext instanceof Context + ) { + //just remove the first ../ + $variableName = substr($variableName, 3); + + //and let the parent context handle the rest + return $this->parentContext->get($variableName, $strict); + } + + //otherwise, it's business as usual + return parent::get($variableName, $strict); } } diff --git a/src/Handlebars/Handlebars.php b/src/Handlebars/Handlebars.php index fe15525..b0513eb 100755 --- a/src/Handlebars/Handlebars.php +++ b/src/Handlebars/Handlebars.php @@ -39,7 +39,7 @@ class Handlebars const VERSION = '1.0.0'; /** - * factory method + * Factory method * * @param array $options see __construct's options parameter * @@ -55,41 +55,57 @@ class Handlebars } /** + * Current tokenizer instance + * * @var Tokenizer */ private $_tokenizer; /** + * Current parser instance + * * @var Parser */ private $_parser; /** + * Current helper list + * * @var Helpers */ private $_helpers; /** + * Current loader instance + * * @var Loader */ private $_loader; /** + * Current partial loader instance + * * @var Loader */ private $_partialsLoader; /** + * Current cache instance + * * @var Cache */ private $_cache; /** + * The escape method + * * @var callable escape function to use */ private $_escape = 'htmlspecialchars'; /** + * Parameters for the escpae method above + * * @var array parametes to pass to escape function */ private $_escapeArgs = array( @@ -165,8 +181,8 @@ class Handlebars * @param mixed $data data to use as context * * @return string Rendered template - * @see Handlebars::loadTemplate - * @see Template::render + * @see Handlebars::loadTemplate + * @see Template::render */ public function render($template, $data) { @@ -235,8 +251,8 @@ class Handlebars { return $this->getHelpers()->has($name); } - - /** + + /** * Add a new helper. * * @param string $name helper name @@ -245,100 +261,99 @@ class Handlebars * @return void */ public function registerHelper($name, $helper) - { - $this->addHelper($name, function($template, $context, $arg) use ($helper) - { - $args = $template->parseArguments($arg); - $named = $template->parseNamedArguments($arg); - - foreach($args as $i => $arg) { - //if it's literally string - if($arg instanceof StringWrapper) { - //we have no problems here - $args[$i] = (string) $arg; - continue; - } - - //not sure what to do if it's not a string or StringWrapper - if(!is_string($arg)) { - continue; - } - - //it's a variable and we need to figure out the value of it - $args[$i] = $context->get($arg); - } - - //push the options - $args[] = array( - //special fields - 'data' => array( - 'index' => $context->get('@index'), - 'key' => $context->get('@key'), - 'first' => $context->get('@first'), - 'last' => $context->get('@last')), - // Named arguments - 'hash' => $named, - // A renderer for block helper - 'fn' => function($inContext = null) use($context, $template) - { - $defined = !!$inContext; - - if(!$defined) { - $inContext = $context; - $inContext->push($inContext->last()); - } else if (!$inContext instanceof Context) { - $inContext = new ChildContext($inContext); - $inContext->setParent($context); - } - - $template->setStopToken('else'); - $buffer = $template->render($inContext); - $template->setStopToken(false); - //what if it's a loop ? - $template->rewind(); - //What's the point of this again? - //I mean in this context (literally) - //$template->discard($inContext); - - if($defined) { - $inContext->pop(); - } - - return $buffer; - }, - - // A render for the else block - 'inverse' => function($inContext = null) use($context, $template) - { - $defined = !!$inContext; - - if(!$defined) { - $inContext = $context; - $inContext->push($inContext->last()); - } else if (!$inContext instanceof Context) { - $inContext = new ChildContext($inContext); - $inContext->setParent($context); - } - - $template->setStopToken('else'); - $template->discard($inContext); - $template->setStopToken(false); - $buffer = $template->render($inContext); - - if($defined) { - $inContext->pop(); - } - - return $buffer; - }, - - // The current context. - 'context' => $context, - // The current template - 'template' => $template); - - return call_user_func_array($helper, $args); - }); + { + $callback = function ($template, $context, $arg) use ($helper) { + $args = $template->parseArguments($arg); + $named = $template->parseNamedArguments($arg); + + foreach ($args as $i => $arg) { + //if it's literally string + if ($arg instanceof StringWrapper) { + //we have no problems here + $args[$i] = (string) $arg; + continue; + } + + //not sure what to do if it's not a string or StringWrapper + if (!is_string($arg)) { + continue; + } + + //it's a variable and we need to figure out the value of it + $args[$i] = $context->get($arg); + } + + //push the options + $args[] = array( + //special fields + 'data' => array( + 'index' => $context->get('@index'), + 'key' => $context->get('@key'), + 'first' => $context->get('@first'), + 'last' => $context->get('@last')), + // Named arguments + 'hash' => $named, + // A renderer for block helper + 'fn' => function ($inContext = null) use ($context, $template) { + $defined = !!$inContext; + + if (!$defined) { + $inContext = $context; + $inContext->push($inContext->last()); + } else if (!$inContext instanceof Context) { + $inContext = new ChildContext($inContext); + $inContext->setParent($context); + } + + $template->setStopToken('else'); + $buffer = $template->render($inContext); + $template->setStopToken(false); + //what if it's a loop ? + $template->rewind(); + //What's the point of this again? + //I mean in this context (literally) + //$template->discard($inContext); + + if ($defined) { + $inContext->pop(); + } + + return $buffer; + }, + + // A render for the else block + 'inverse' => function ($inContext = null) use ($context, $template) { + $defined = !!$inContext; + + if (!$defined) { + $inContext = $context; + $inContext->push($inContext->last()); + } else if (!$inContext instanceof Context) { + $inContext = new ChildContext($inContext); + $inContext->setParent($context); + } + + $template->setStopToken('else'); + $template->discard($inContext); + $template->setStopToken(false); + $buffer = $template->render($inContext); + + if ($defined) { + $inContext->pop(); + } + + return $buffer; + }, + + // The current context. + 'context' => $context, + // The current template + 'template' => $template); + + return call_user_func_array($helper, $args); + }; + + $this->addHelper($name, $callback); } /** @@ -366,7 +381,7 @@ class Handlebars } /** - * get current loader + * Get current loader * * @return Loader */ @@ -392,7 +407,7 @@ class Handlebars } /** - * get current partials loader + * Get current partials loader * * @return Loader */ @@ -618,7 +633,7 @@ class Handlebars } /** - * try to tokenize source, or get them from cache if available + * Try to tokenize source, or get them from cache if available * * @param string $source handlebars source code * From 08f4328f9d6f49b8e1980d0d6424d829eecde965 Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Mon, 21 Sep 2015 20:23:20 +0800 Subject: [PATCH 4/7] phpcs files i made no changes to, but got cs errors on.. not sure why. Just let me know what the protocol is for this one. --- src/Handlebars/Context.php | 25 +++++++++++--- src/Handlebars/Helpers.php | 18 +++++++--- src/Handlebars/Parser.php | 17 ++++++---- src/Handlebars/Template.php | 65 ++++++++++++++++++++++++++++--------- 4 files changed, 94 insertions(+), 31 deletions(-) diff --git a/src/Handlebars/Context.php b/src/Handlebars/Context.php index c45ef87..96124e7 100644 --- a/src/Handlebars/Context.php +++ b/src/Handlebars/Context.php @@ -40,7 +40,6 @@ namespace Handlebars; class Context { - /** * List of charcters that cannot be used in identifiers. */ @@ -53,22 +52,30 @@ class Context const NOT_VALID_SEGMENT_NAME_CHARS = "]"; /** + * Context stack + * * @var array stack for context only top stack is available */ protected $stack = array(); /** + * Section stack index + * * @var array index stack for sections */ protected $index = array(); /** + * Object stack keys + * * @var array key stack for objects */ protected $key = array(); /** - * @var array Special variables stack for sections. Each stack element can + * Special variables stack for sections. + * + * @var array Each stack element can * contain elements with "@index", "@key", "@first" and "@last" keys. */ protected $specialVariables = array(); @@ -290,8 +297,18 @@ class Context $bad_chars = preg_quote(self::NOT_VALID_NAME_CHARS, '/'); $bad_seg_chars = preg_quote(self::NOT_VALID_SEGMENT_NAME_CHARS, '/'); - $name_pattern = "(?:[^" . $bad_chars . "\s]+)|(?:\[[^" . $bad_seg_chars . "]+\])"; - $check_pattern = "/^((" . $name_pattern . ")\.)*(" . $name_pattern . ")\.?$/"; + $name_pattern = "(?:[^" + . $bad_chars + . "\s]+)|(?:\[[^" + . $bad_seg_chars + . "]+\])"; + + $check_pattern = "/^((" + . $name_pattern + . ")\.)*(" + . $name_pattern + . ")\.?$/"; + $get_pattern = "/(?:" . $name_pattern . ")/"; if (!preg_match($check_pattern, $variableName)) { diff --git a/src/Handlebars/Helpers.php b/src/Handlebars/Helpers.php index beaad8b..d1e809d 100644 --- a/src/Handlebars/Helpers.php +++ b/src/Handlebars/Helpers.php @@ -23,7 +23,7 @@ namespace Handlebars; /** * Handlebars helpers * - * a collection of helper function. normally a function like + * A collection of helper function. normally a function like * function ($sender, $name, $arguments) $arguments is unscaped arguments and * is a string, not array * @@ -35,10 +35,11 @@ namespace Handlebars; * @version Release: @package_version@ * @link http://xamin.ir */ - class Helpers { /** + * Raw helper array + * * @var array array of helpers */ protected $helpers = array(); @@ -100,7 +101,8 @@ class Helpers { if (!is_callable($helper) && ! $helper instanceof Helper) { throw new \InvalidArgumentException( - "$name Helper is not a callable or doesn't implement the Helper interface." + "$name Helper is not a callable or ". + "doesn't implement the Helper interface." ); } $this->helpers[$name] = $helper; @@ -131,7 +133,13 @@ class Helpers ); } - return call_user_func($this->helpers[$name], $template, $context, $parsedArgs, $source); + return call_user_func( + $this->helpers[$name], + $template, + $context, + $parsedArgs, + $source + ); } /** @@ -169,7 +177,7 @@ class Helpers * @param string $name helper name * * @return boolean - * @see Handlebras_Helpers::has + * @see Handlebras_Helpers::has */ public function __isset($name) { diff --git a/src/Handlebars/Parser.php b/src/Handlebars/Parser.php index 3417bfb..7913ff2 100755 --- a/src/Handlebars/Parser.php +++ b/src/Handlebars/Parser.php @@ -61,7 +61,6 @@ class Parser * @throws \LogicException when nesting errors or mismatched section tags * are encountered. * @return array Token parse tree - * */ private function _buildTree(\ArrayIterator $tokens) { @@ -87,14 +86,20 @@ class Parser && isset($result[Tokenizer::NAME]) && $result[Tokenizer::NAME] == $token[Tokenizer::NAME] ) { - if (isset($result[Tokenizer::TRIM_RIGHT]) && $result[Tokenizer::TRIM_RIGHT]) { - // If the start node has trim right, then its equal with the first item in the loop with + if (isset($result[Tokenizer::TRIM_RIGHT]) + && $result[Tokenizer::TRIM_RIGHT] + ) { + // If the start node has trim right, then its equal + //with the first item in the loop with // Trim left $newNodes[0][Tokenizer::TRIM_LEFT] = true; } - if (isset($token[Tokenizer::TRIM_RIGHT]) && $token[Tokenizer::TRIM_RIGHT]) { - //OK, if we have trim right here, we should pass it to the upper level. + if (isset($token[Tokenizer::TRIM_RIGHT]) + && $token[Tokenizer::TRIM_RIGHT] + ) { + //OK, if we have trim right here, we should + //pass it to the upper level. $result[Tokenizer::TRIM_RIGHT] = true; } @@ -117,4 +122,4 @@ class Parser return $stack; } -} +} \ No newline at end of file diff --git a/src/Handlebars/Template.php b/src/Handlebars/Template.php index f7ab018..3f37e2b 100644 --- a/src/Handlebars/Template.php +++ b/src/Handlebars/Template.php @@ -41,6 +41,8 @@ namespace Handlebars; class Template { /** + * Handlebars instance + * * @var Handlebars */ protected $handlebars; @@ -51,6 +53,8 @@ class Template protected $source = ''; /** + * Run stack + * * @var array Run stack */ private $_stack = array(); @@ -101,13 +105,12 @@ class Template } /** - * set stop token for render and discard method + * Set stop token for render and discard method * * @param string $token token to set as stop token or false to remove * * @return void */ - public function setStopToken($token) { $topStack = array_pop($this->_stack); @@ -116,11 +119,10 @@ class Template } /** - * get current stop token + * Get current stop token * * @return string|bool */ - public function getStopToken() { $topStack = end($this->_stack); @@ -156,26 +158,35 @@ class Template ) { break; } - if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { + if (isset($current[Tokenizer::TRIM_LEFT]) + && $current[Tokenizer::TRIM_LEFT] + ) { $buffer = rtrim($buffer); } $tmp = $this->_renderInternal($current, $context); - if (isset($current[Tokenizer::TRIM_LEFT]) && $current[Tokenizer::TRIM_LEFT]) { + if (isset($current[Tokenizer::TRIM_LEFT]) + && $current[Tokenizer::TRIM_LEFT] + ) { $tmp = rtrim($tmp); } - if ($rTrim || (isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT])) { + if ($rTrim + || (isset($current[Tokenizer::TRIM_RIGHT]) + && $current[Tokenizer::TRIM_RIGHT]) + ) { $tmp = ltrim($tmp); } $buffer .= $tmp; - // Some time, there is more than one string token (first is empty), + // Some time, there is more than + //one string token (first is empty), //so we need to trim all of them in one shot $rTrim = (empty($tmp) && $rTrim) || - isset($current[Tokenizer::TRIM_RIGHT]) && $current[Tokenizer::TRIM_RIGHT]; + isset($current[Tokenizer::TRIM_RIGHT]) + && $current[Tokenizer::TRIM_RIGHT]; } if ($stop) { //Ok break here, the helper should be aware of this. @@ -312,7 +323,9 @@ class Template } // subexpression parsing loop - $subexprs = array(); // will contain all subexpressions inside outermost brackets + // will contain all subexpressions + // inside outermost brackets + $subexprs = array(); $insideOf = array( 'single' => false, 'double' => false ); $lvl = 0; $cur_start = 0; @@ -334,7 +347,11 @@ class Template if ($cur == ')' && ! $insideOf['single'] && ! $insideOf['double']) { $lvl--; if ($lvl == 0) { - $subexprs[] = substr($current[Tokenizer::ARGS], $cur_start, $i - $cur_start); + $subexprs[] = substr( + $current[Tokenizer::ARGS], + $cur_start, + $i - $cur_start + ); } } @@ -353,14 +370,30 @@ class Template Tokenizer::INDEX => $current[Tokenizer::INDEX], Tokenizer::ARGS => implode(" ", array_slice($cmd, 1)) ); + // resolve the node recursively - $resolved = addcslashes($this->_handlebarsStyleSection($context, $section_node), '"'); + $resolved = $this->_handlebarsStyleSection( + $context, + $section_node + ); + + $resolved = addcslashes($resolved, '"'); // replace original subexpression with result - $current[Tokenizer::ARGS] = str_replace('('.$expr.')', '"' . $resolved . '"', $current[Tokenizer::ARGS]); + $current[Tokenizer::ARGS] = str_replace( + '('.$expr.')', + '"' . $resolved . '"', + $current[Tokenizer::ARGS] + ); } } - $return = $helpers->call($sectionName, $this, $context, $current[Tokenizer::ARGS], $source); + $return = $helpers->call( + $sectionName, + $this, + $context, + $current[Tokenizer::ARGS], + $source + ); if ($return instanceof String) { return $this->handlebars->loadString($return)->render($context); @@ -507,9 +540,9 @@ class Template } /** - * get replacing value of a tag + * Get replacing value of a tag * - * will process the tag as section, if a helper with the same name could be + * Will process the tag as section, if a helper with the same name could be * found, so {{helper arg}} can be used instead of {{#helper arg}}. * * @param Context $context current context From 407732cef357ffa85ab3c6651091ce99034dc220 Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Wed, 23 Sep 2015 23:23:53 +0800 Subject: [PATCH 5/7] better registerHelper Tests --- tests/Xamin/HandlebarsTest.php | 241 ++++++++++++++++++++++++++++++--- 1 file changed, 221 insertions(+), 20 deletions(-) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 0b32fc4..bf918da 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -438,29 +438,230 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase $this->setExpectedException('InvalidArgumentException'); $engine->getHelpers()->call('invalid', $engine->loadTemplate(''), new \Handlebars\Context(), '', ''); } - - public function testRegisterHelper() + + public function testRegisterHelper() { - $template = '{{#grand test "test2"}} In 1: {{test4}} {{#grand ../test \'test3\'}} In 2: {{test5}}{{#parent}} In 3: {{test6}} {{../../../test}}{{/parent}}{{/grand}}{{/grand}}'; - $vars = array('test' => 'Hello World'); - $expected = ' In 1: Hello World In 2: This is Test 5 In 3: This is Test 6 Hello World'; - - $loader = new \Handlebars\Loader\StringLoader(); + $loader = new \Handlebars\Loader\StringLoader(); $engine = new \Handlebars\Handlebars(array('loader' => $loader)); + //date_default_timezone_set('GMT'); - $engine->registerHelper('grand', function($test1, $test2, $options) { - return $options['fn'](array( - 'test4' => $test1, - 'test5' => 'This is Test 5' - )); - }); - - $engine->registerHelper('parent', function($options) { - return $options['fn'](array('test6' => 'This is Test 6')); - }); - - - $this->assertEquals($expected, $engine->render($template, $vars)); + //FIRST UP: some awesome helpers!! + + //translations + $translations = array( + 'hello' => 'bonjour', + 'my name is %s' => 'mon nom est %s', + 'how are your %s kids and %s' => 'comment sont les enfants de votre %s et %s' + ); + + //i18n + $engine->registerHelper('_', function($key) use ($translations) { + $args = func_get_args(); + $key = array_shift($args); + $options = array_pop($args); + + //make sure it's a string + $key = (string) $key; + + //by default the translation is the key + $translation = $key; + + //if there is a translation + if(isset($translations[$key])) { + //translate it + $translation = $translations[$key]; + } + + //if there are more arguments + if(!empty($args)) { + //it means the translations was + //something like 'Hello %s' + return vsprintf($translation, $args); + } + + //just return what we got + return $translation; + }); + + //create a better if helper + $engine->registerHelper('when', function($value1, $operator, $value2, $options) { + //make sure value 1 is scalar + if(is_object($value1) && method_exists($value1, '__toString')) { + $value1 = (string) $value1; + } + + //make sure operator is scalar + if(is_object($operator) && method_exists($operator, '__toString')) { + $operator = (string) $operator; + } + + //make sure value 2 is scalar + if(is_object($value2) && method_exists($value2, '__toString')) { + $value2 = (string) $value2; + } + + $valid = false; + //the amazing reverse switch! + switch (true) { + case $operator == 'eq' && $value1 == $value2: + case $operator == '==' && $value1 == $value2: + case $operator == 'req' && $value1 === $value2: + case $operator == '===' && $value1 === $value2: + case $operator == 'neq' && $value1 != $value2: + case $operator == '!=' && $value1 != $value2: + case $operator == 'rneq' && $value1 !== $value2: + case $operator == '!==' && $value1 !== $value2: + case $operator == 'lt' && $value1 < $value2: + case $operator == '<' && $value1 < $value2: + case $operator == 'lte' && $value1 <= $value2: + case $operator == '<=' && $value1 <= $value2: + case $operator == 'gt' && $value1 > $value2: + case $operator == '>' && $value1 > $value2: + case $operator == 'gte' && $value1 >= $value2: + case $operator == '>=' && $value1 >= $value2: + case $operator == 'and' && $value1 && $value2: + case $operator == '&&' && ($value1 && $value2): + case $operator == 'or' && ($value1 || $value2): + case $operator == '||' && ($value1 || $value2): + $valid = true; + break; + } + + if($valid) { + return $options['fn'](); + } + + return $options['inverse'](); + }); + + //a loop helper + $engine->registerHelper('loop', function($object, $options) { + //expected for subtemplates of this block to use + // {{value.profile_name}} vs {{profile_name}} + // {{key}} vs {{@index}} + + $i = 0; + $buffer = array(); + $total = count($object); + + //loop through the object + foreach($object as $key => $value) { + //call the sub template and + //add it to the buffer + $buffer[] = $options['fn'](array( + 'key' => $key, + 'value' => $value, + 'last' => ++$i === $total + )); + } + + return implode('', $buffer); + }); + + //array in + $engine->registerHelper('in', function(array $array, $key, $options) { + //make sure key is scalar + if(is_object($key) && method_exists($key, '__toString')) { + $key = (string) $key; + } + + if(in_array($key, $array)) { + return $options['fn'](); + } + + return $options['inverse'](); + }); + + //converts date formats to other formats + $engine->registerHelper('date', function($time, $format, $options) { + //make sure time is scalar + if(is_object($time) && method_exists($time, '__toString')) { + $time = (string) $time; + } + + //make sure format is scalar + if(is_object($format) && method_exists($format, '__toString')) { + $format = (string) $format; + } + + return date($format, strtotime($time)); + }); + + //nesting helpers, these don't really help anyone :) + $engine->registerHelper('nested1', function($test1, $test2, $options) { + return $options['fn'](array( + 'test4' => $test1, + 'test5' => 'This is Test 5' + )); + }); + + $engine->registerHelper('nested2', function($options) { + return $options['fn'](array('test6' => 'This is Test 6')); + }); + + //NEXT UP: some practical case studies + + //case 1 + $variable1 = array(); + $template1 = "{{_ 'hello'}}, {{_ 'my name is %s' 'Foo'}}! {{_ 'how are your %s kids and %s' '6' 'dog'}}?"; + $expected1 = 'bonjour, mon nom est Foo! comment sont les enfants de votre 6 et dog?'; + + //case 2 + $variable2 = array('gender' => 'female'); + $template2 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}"; + $expected2 = 'Hello maam'; + + //case 3 + $variable3 = array('gender' => 'male'); + $template3 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}"; + $expected3 = 'Hello sir'; + + //case 4 + $variable4 = array( + 'rows' => array( + array( + 'profile_name' => 'Jane Doe', + 'profile_created' => '2014-04-04 00:00:00' + ), + array( + 'profile_name' => 'John Doe', + 'profile_created' => '2015-01-21 00:00:00' + ) + ) + ); + $template4 = "{{#loop rows}}
  • {{value.profile_name}} - {{date value.profile_created 'M d'}}
  • {{/loop}}"; + $expected4 = '
  • Jane Doe - Apr 04
  • John Doe - Jan 21
  • '; + + //case 5 + $variable5 = $variable4; + $variable5['me'] = 'Jack Doe'; + $variable5['admins'] = array('Jane Doe', 'John Doe'); + $template5 = "{{#in admins me}}
      ".$template4."
    {{else}}No Access{{/in}}"; + $expected5 = 'No Access'; + + //case 6 + $variable6 = $variable5; + $variable6['me'] = 'Jane Doe'; + $template6 = $template5; + $expected6 = '
    • Jane Doe - Apr 04
    • John Doe - Jan 21
    '; + + //case 7 + $variable7 = array('test' => 'Hello World'); + $template7 = '{{#nested1 test "test2"}} ' + .'In 1: {{test4}} {{#nested1 ../test \'test3\'}} ' + .'In 2: {{test5}}{{#nested2}} ' + .'In 3: {{test6}} {{../../../test}}{{/nested2}}{{/nested1}}{{/nested1}}'; + $expected7 = ' In 1: Hello World In 2: This is Test 5 In 3: This is Test 6 Hello World'; + + //LAST UP: the actual testing + + $this->assertEquals($expected1, $engine->render($template1, $variable1)); + $this->assertEquals($expected2, $engine->render($template2, $variable2)); + $this->assertEquals($expected3, $engine->render($template3, $variable3)); + $this->assertEquals($expected4, $engine->render($template4, $variable4)); + $this->assertEquals($expected5, $engine->render($template5, $variable5)); + $this->assertEquals($expected6, $engine->render($template6, $variable6)); + $this->assertEquals($expected7, $engine->render($template7, $variable7)); } public function testInvalidHelperMustacheStyle() From 1ba7cdf108964f3992574b0e21f8185409e16c7e Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Wed, 23 Sep 2015 23:34:42 +0800 Subject: [PATCH 6/7] added case descriptions and used an int example --- tests/Xamin/HandlebarsTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index bf918da..31b2177 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -601,22 +601,22 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase //NEXT UP: some practical case studies - //case 1 + //case 1 - i18n $variable1 = array(); - $template1 = "{{_ 'hello'}}, {{_ 'my name is %s' 'Foo'}}! {{_ 'how are your %s kids and %s' '6' 'dog'}}?"; + $template1 = "{{_ 'hello'}}, {{_ 'my name is %s' 'Foo'}}! {{_ 'how are your %s kids and %s' 6 'dog'}}?"; $expected1 = 'bonjour, mon nom est Foo! comment sont les enfants de votre 6 et dog?'; - //case 2 + //case 2 - when $variable2 = array('gender' => 'female'); $template2 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}"; $expected2 = 'Hello maam'; - //case 3 + //case 3 - when else $variable3 = array('gender' => 'male'); $template3 = "Hello {{#when gender '===' 'male'}}sir{{else}}maam{{/when}}"; $expected3 = 'Hello sir'; - //case 4 + //case 4 - loop $variable4 = array( 'rows' => array( array( @@ -632,20 +632,20 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase $template4 = "{{#loop rows}}
  • {{value.profile_name}} - {{date value.profile_created 'M d'}}
  • {{/loop}}"; $expected4 = '
  • Jane Doe - Apr 04
  • John Doe - Jan 21
  • '; - //case 5 + //case 5 - array in $variable5 = $variable4; $variable5['me'] = 'Jack Doe'; $variable5['admins'] = array('Jane Doe', 'John Doe'); $template5 = "{{#in admins me}}
      ".$template4."
    {{else}}No Access{{/in}}"; $expected5 = 'No Access'; - //case 6 + //case 6 - array in else $variable6 = $variable5; $variable6['me'] = 'Jane Doe'; $template6 = $template5; $expected6 = '
    • Jane Doe - Apr 04
    • John Doe - Jan 21
    '; - //case 7 + //case 7 - nested templates and parent-grand variables $variable7 = array('test' => 'Hello World'); $template7 = '{{#nested1 test "test2"}} ' .'In 1: {{test4}} {{#nested1 ../test \'test3\'}} ' From 08ad533e815374080ddd6acb311a2e74ce2f3a66 Mon Sep 17 00:00:00 2001 From: Christian Blanquera Date: Sat, 26 Sep 2015 20:03:52 +0800 Subject: [PATCH 7/7] case for the right String Class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure where i got StringWrapper from. We now don’t need to case for this in the `registerHelper()` method --- src/Handlebars/Handlebars.php | 2 +- tests/Xamin/HandlebarsTest.php | 30 ------------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/Handlebars/Handlebars.php b/src/Handlebars/Handlebars.php index b0513eb..77aac5b 100755 --- a/src/Handlebars/Handlebars.php +++ b/src/Handlebars/Handlebars.php @@ -268,7 +268,7 @@ class Handlebars foreach ($args as $i => $arg) { //if it's literally string - if ($arg instanceof StringWrapper) { + if ($arg instanceof BaseString) { //we have no problems here $args[$i] = (string) $arg; continue; diff --git a/tests/Xamin/HandlebarsTest.php b/tests/Xamin/HandlebarsTest.php index 31b2177..8edaf66 100644 --- a/tests/Xamin/HandlebarsTest.php +++ b/tests/Xamin/HandlebarsTest.php @@ -485,21 +485,6 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase //create a better if helper $engine->registerHelper('when', function($value1, $operator, $value2, $options) { - //make sure value 1 is scalar - if(is_object($value1) && method_exists($value1, '__toString')) { - $value1 = (string) $value1; - } - - //make sure operator is scalar - if(is_object($operator) && method_exists($operator, '__toString')) { - $operator = (string) $operator; - } - - //make sure value 2 is scalar - if(is_object($value2) && method_exists($value2, '__toString')) { - $value2 = (string) $value2; - } - $valid = false; //the amazing reverse switch! switch (true) { @@ -560,11 +545,6 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase //array in $engine->registerHelper('in', function(array $array, $key, $options) { - //make sure key is scalar - if(is_object($key) && method_exists($key, '__toString')) { - $key = (string) $key; - } - if(in_array($key, $array)) { return $options['fn'](); } @@ -574,16 +554,6 @@ class HandlebarsTest extends \PHPUnit_Framework_TestCase //converts date formats to other formats $engine->registerHelper('date', function($time, $format, $options) { - //make sure time is scalar - if(is_object($time) && method_exists($time, '__toString')) { - $time = (string) $time; - } - - //make sure format is scalar - if(is_object($format) && method_exists($format, '__toString')) { - $format = (string) $format; - } - return date($format, strtotime($time)); });