/** * */ public function renderView($viewCode, $viewContext = null, $viewRoot = null) { // Fix the view root if (empty($viewRoot) === true) { $viewRoot = PATH_APP . '/Views'; } // Check whether the view exists $pathToView = $viewRoot . '/' . $viewCode; if (file_exists($pathToView) === false) { \z\e(EXCEPTION_VIEW_NOT_FOUND, ['pathToView' => $pathToView, 'viewCode' => $viewCode, 'viewContext' => $viewContext]); } // Load the view $view = file_get_contents($pathToView); // Compile the view $code = \LightnCandy\LightnCandy::compile($view, ['basedir' => [PATH_APP . '/Views'], 'fileext' => [''], 'flags' => \LightnCandy\LightnCandy::FLAG_ERROR_EXCEPTION | \LightnCandy\LightnCandy::FLAG_HANDLEBARS | \LightnCandy\LightnCandy::FLAG_RENDER_DEBUG | \LightnCandy\LightnCandy::FLAG_RUNTIMEPARTIAL | \LightnCandy\LightnCandy::FLAG_THIS, 'helpers' => ['locale' => function () { return \z\service('manager/culture')->localeCode; }, 'str' => function ($indexed, $associative) { // Build arguments $arguments = $this->buildArguments(['stringCode'], $indexed, $associative); // Get the string $result = \z\str($arguments['stringCode']); return $result; }]]); // Build path to code $pathToCode = '/tmp/' . sha1('fbenard/zero_' . $pathToView) . '.php'; // Get the view renderer file_put_contents($pathToCode, $code); $renderer = (require $pathToCode); // Render the view $result = $renderer($viewContext); return $result; }
public function __construct($config) { $this->config = $config; $str = LightnCandy::compile(file_get_contents($this->config['template'])); $this->path = sprintf('%s.php', tempnam($config['tmp'], 'tpl')); file_put_contents($this->path, sprintf("<?php\n%s", $str)); $this->renderer = (require_once $this->path); }
/** * Setup token delimiter by default or provided string * * @param array<string,array|string|integer> $context Current context * @param string $left left string of a token * @param string $right right string of a token */ protected static function setupToken(&$context, $left = '{{', $right = '}}') { parent::setupToken($context, $left, $right); if (self::$compileHelpersOnly) { $helperTokens = array(); foreach ($context['helpers'] as $helper => $value) { $helperTokens[] = $helper . '.*?'; } $helperTokens = implode('|', $helperTokens); $context['tokens']['search'] = "/^(.*?)(\\s*)({$left})(~?)([\\^#\\/!&>]?)(" . $helperTokens . ")(~?)({$right})(\\s*)(.*)\$/s"; } }
/** * @dataProvider issueProvider */ public function testIssues($issue) { global $tmpdir; $php = LightnCandy::compile($issue['template'], isset($issue['options']) ? $issue['options'] : null); $context = LightnCandy::getContext(); $parsed = print_r(LightnCandy::$lastParsed, true); if (count($context['error'])) { $this->fail('Compile failed due to:' . print_r($context['error'], true) . "\nPARSED: {$parsed}"); } $renderer = LightnCandy::prepare($php); $this->assertEquals($issue['expected'], $renderer($issue['data'], array('debug' => $issue['debug'])), "PHP CODE:\n{$php}\n{$parsed}"); }
public function compile($name) { $source = $this->getLoader()->getSource($name); $cacheKey = $this->getCacheFilename($name); $phpStr = ''; try { $this->partials->exchangeArray([new FileResource($this->getLoader()->getCacheKey($name))]); $phpStr = LightnCandy::compile($source, $this->options); } catch (\Exception $e) { throw new LoaderException($e->getMessage()); } $this->cache->write($cacheKey, '<?php // ' . $name . PHP_EOL . $phpStr, $this->partials->getArrayCopy()); return $phpStr; }
public function renderWidget($widget) { $filename = get_class($widget) . '.hbs'; $filename = str_replace('\\', '/', $filename); $filename = $this->themePath . '/' . $filename; if (!file_exists($filename)) { throw new RuntimeException("Can't find widget template: " . $filename); } $template = file_get_contents($filename); $helper = new Helper(); $phpStr = LightnCandy::compile($template, ['flags' => LightnCandy::FLAG_INSTANCE | LightnCandy::FLAG_METHOD, 'helpers' => ['route' => 'Materia\\Helper::route']]); $renderer = LightnCandy::prepare($phpStr); $data = ['widget' => $widget]; $data['urlGenerator'] = $this->urlGenerator; $data['materia'] = $this; return $renderer($data); }
/** * @dataProvider jsonSpecProvider */ public function testSpecs($spec) { global $tmpdir; $flag = LightnCandy::FLAG_MUSTACHE | LightnCandy::FLAG_ERROR_EXCEPTION; if ($spec['name'] == 'Interpolation - Expansion' || $spec['name'] == 'Interpolation - Alternate Delimiters' || $spec['desc'] == 'Lambdas used for sections should receive the raw section string.' || $spec['name'] == 'Section - Expansion' || $spec['name'] == 'Section - Alternate Delimiters' || $spec['name'] == 'Section - Multiple Calls' || $spec['name'] == 'Inverted Section') { $this->markTestIncomplete('Not supported case: complex mustache lambdas'); } if (isset($spec['data']['lambda']['php'])) { $spec['data']['lambda'] = getFunctionCode('function ($text = null) {' . $spec['data']['lambda']['php'] . '}'); } foreach (array($flag, $flag | LightnCandy::FLAG_STANDALONEPHP) as $f) { global $calls; $calls = 0; $php = LightnCandy::compile($spec['template'], array('flags' => $f, 'partials' => isset($spec['partials']) ? $spec['partials'] : null, 'basedir' => $tmpdir)); $parsed = print_r(LightnCandy::$lastParsed, true); $renderer = LightnCandy::prepare($php); $this->assertEquals($spec['expected'], $renderer($spec['data'], array('debug' => 0)), "SPEC:\n" . print_r($spec, true) . "\nPHP CODE: {$php}\nPARSED: {$parsed}"); } }
/** * @dataProvider jsonSpecProvider */ public function testSpecs($spec) { global $tmpdir; global $tested; global $test_flags; recursive_unset($spec, '!sparsearray'); recursive_lambda_fix($spec['data']); if (isset($spec['options']['data'])) { recursive_lambda_fix($spec['options']['data']); } //// Skip bad specs // 1. No expected nor exception in spec if (!isset($spec['expected']) && !isset($spec['exception'])) { $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , no expected result in spec, skip."); } // 2. Not supported case: foo/bar path if ($spec['it'] === 'literal paths' && $spec['no'] === 58 || $spec['it'] === 'literal paths' && $spec['no'] === 59 || $spec['it'] === 'this keyword nested inside path' || $spec['it'] === 'this keyword nested inside helpers param' || $spec['it'] === 'should handle invalid paths' || $spec['it'] === 'parameter data throws when using complex scope references' || $spec['it'] === 'block with complex lookup using nested context') { $this->markTestIncomplete('Not supported case: foo/bar path'); } // 3. Different API, no need to test if ($spec['it'] === 'registering undefined partial throws an exception') { $this->markTestIncomplete('Not supported case: just skip it'); } // 4. block parameters, special case now do not support if ($spec['it'] === 'should not take presedence over pathed values') { $this->markTestIncomplete('Not supported case: just skip it'); } // 5. Not supported case: helperMissing and blockHelperMissing if ($spec['it'] === 'if a context is not found, helperMissing is used' || $spec['it'] === 'if a context is not found, custom helperMissing is used' || $spec['it'] === 'if a value is not found, custom helperMissing is used' || $spec['it'] === 'should include in simple block calls' || $spec['it'] === 'should include full id' || $spec['it'] === 'should include full id if a hash is passed' || $spec['it'] === 'lambdas resolved by blockHelperMissing are bound to the context') { $this->markTestIncomplete('Not supported case: just skip it'); } // 6. Not supported case: misc if ($spec['description'] === 'compat mode' || $spec['description'] === 'directives' || $spec['file'] === 'specs/handlebars/spec/track-ids.json' || $spec['it'] === 'knows how to report the correct line number in errors' || $spec['it'] === 'knows how to report the correct line number in errors when the first character is a newline' || $spec['it'] === 'should allow block params on chained helpers' || $spec['it'] === 'chained inverted sections' || $spec['description'] === 'decorators' || $spec['it'] === 'helper for raw block gets parameters' || $spec['template'] === '{{foo}') { $this->markTestIncomplete('Not supported case: just skip it'); } // TODO: require fix if ($spec['template'] === '{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}' || $spec['it'] === 'functions returning safestrings shouldn\'t be escaped' || $spec['it'] === 'rendering function partial in vm mode' || $spec['it'] === 'provides each nested helper invocation its own options hash' || $spec['template'] === '{{echo (header)}}' || $spec['it'] === 'block functions without context argument' || $spec['it'] === 'depthed block functions with context argument' || $spec['it'] === 'block functions with context argument') { $this->markTestIncomplete('TODO: require fix'); } // FIX SPEC if ($spec['it'] === 'should take presednece over parent block params') { $spec['helpers']['goodbyes']['php'] = 'function($options) { static $value; if($value === null) { $value = 1; } return $options->fn(array("value" => "bar"), array("blockParams" => ($options["fn.blockParams"] === 1) ? array($value++, $value++) : null));}'; } if ($spec['it'] === 'should handle undefined and null' && $spec['expected'] === 'true true object') { $spec['expected'] = 'true true array'; } foreach ($test_flags as $f) { // setup helpers $tested++; $helpers = array(); $helpersList = ''; foreach (array_merge(isset($spec['globalHelpers']) && is_array($spec['globalHelpers']) ? $spec['globalHelpers'] : array(), isset($spec['helpers']) && is_array($spec['helpers']) ? $spec['helpers'] : array()) as $name => $func) { if (!isset($func['php'])) { $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , no PHP helper code provided for this case."); } $hname = preg_replace('/\\.|\\//', '_', "custom_helper_{$spec['no']}_{$tested}_{$name}"); $helpers[$name] = $hname; $helper = preg_replace('/\\$options->(\\w+)/', '$options[\'$1\']', patch_this(preg_replace('/\\$block\\/\\*\\[\'(.+?)\'\\]\\*\\/->(.+?)\\(/', '$block[\'$2\'](', patch_safestring(preg_replace('/function/', "function {$hname}", $func['php'], 1))))); if ($spec['it'] === 'helper block with complex lookup expression' && $name === 'goodbyes') { $helper = preg_replace('/\\[\'fn\'\\]\\(\\)/', '[\'fn\'](array())', $helper); } $helpersList .= "{$helper}\n"; eval($helper); } try { $partials = isset($spec['globalPartials']) ? $spec['globalPartials'] : array(); // Do not use array_merge() here because it destories numeric key if (isset($spec['partials'])) { foreach ($spec['partials'] as $k => $v) { $partials[$k] = $v; } } if (isset($spec['compileOptions']['preventIndent'])) { if ($spec['compileOptions']['preventIndent']) { $f = $f | LightnCandy::FLAG_PREVENTINDENT; } } if (isset($spec['compileOptions']['explicitPartialContext'])) { if ($spec['compileOptions']['explicitPartialContext']) { $f = $f | LightnCandy::FLAG_PARTIALNEWCONTEXT; } } if (isset($spec['compileOptions']['ignoreStandalone'])) { if ($spec['compileOptions']['ignoreStandalone']) { $f = $f | LightnCandy::FLAG_IGNORESTANDALONE; } } if (isset($spec['compileOptions']['stringParams'])) { if ($spec['compileOptions']['stringParams']) { $f = $f | LightnCandy::FLAG_STRINGPARAMS; } } if (isset($spec['compileOptions']['knownHelpersOnly'])) { if ($spec['compileOptions']['knownHelpersOnly']) { $f = $f | LightnCandy::FLAG_KNOWNHELPERSONLY; } } $php = LightnCandy::compile($spec['template'], array('flags' => $f, 'hbhelpers' => $helpers, 'basedir' => $tmpdir, 'partials' => $partials)); $parsed = print_r(LightnCandy::$lastParsed, true); } catch (Exception $e) { // Exception as expected, pass! if (isset($spec['exception'])) { continue; } // Failed this case $this->fail('Exception:' . $e->getMessage()); } $renderer = LightnCandy::prepare($php); if ($spec['description'] === 'Tokenizer') { // no compile error means passed continue; } try { $ropt = array('debug' => Runtime::DEBUG_ERROR_EXCEPTION); if (isset($spec['options']['data'])) { $ropt['data'] = $spec['options']['data']; } $result = $renderer($spec['data'], $ropt); } catch (Exception $e) { if (!isset($spec['expected'])) { // expected error and catched here, so passed continue; } $this->fail("Rendering Error in {$spec['file']}#{$spec['description']}]#{$spec['no']}:{$spec['it']} PHP CODE: {$php}\nPARSED: {$parsed}\n" . $e->getMessage()); } if (!isset($spec['expected'])) { $this->fail('Should Fail:' . print_r($spec, true) . "PHP CODE: {$php}\nPARSED: {$parsed}\nHELPERS:{$helpersList}"); } $this->assertEquals($spec['expected'], $result, "[{$spec['file']}#{$spec['description']}]#{$spec['no']}:{$spec['it']} PHP CODE: {$php}\nPARSED: {$parsed}\nHELPERS:{$helpersList}"); } }
/** * Render the template using the mustache engine * * @args * template: The template uri for render * args: The args that used for render */ public function render($template, $args = array()) { if (func_num_args() > 2) { // They must using the variable args method $args = func_get_args(); $template = array_shift($args); return $this->render($template, $args); } // Get the cache file name by md5 it $hash = md5($template); $cacheDir = $this->filecache->cacheDir(); if (!file_exists($cacheDir)) { // Create the directory if not exists mkdir($cacheDir, 0755, true); } $phpname = \Clips\path_join($cacheDir, 'tpl_' . $hash . '.php'); $debug = \Clips\config('debug_template'); // Check if we can found the compiled php if (!file_exists($phpname) || $debug) { // Can't found this template in cache, read it from resource $resource = new \Clips\Resource($template); $str = $resource->contents(); $flags = \Clips\context('template_flags'); if (!$flags) { $flags = \Clips\config('template_flags'); if ($flags) { $flags = $flags[0]; } else { $flags = LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_HANDLEBARS | LightnCandy::FLAG_HANDLEBARSJS; } } $opts = array('flags' => $flags); $partials = \Clips\context('template_partials'); if ($partials) { $opts['partials'] = $partials; } $default_helpers = array('site_url' => '\\Clips\\Libraries\\site_url', 'static_url' => '\\Clips\\Libraries\\static_url', 'php' => '\\Clips\\Libraries\\php_call'); $default_block_helpers = array('times' => '\\Clips\\Libraries\\times', 'randtimes' => '\\Clips\\Libraries\\randtimes'); $helpers = \Clips\context('template_helpers'); if ($helpers) { $opts['helpers'] = array_merge($default_helpers, $helpers); } else { $opts['helpers'] = $default_helpers; } $block_helpers = \Clips\context('template_block_helpers'); if ($block_helpers) { $opts['hbhelpers'] = array_merge($default_block_helpers, $block_helpers); } else { $opts['hbhelpers'] = $default_block_helpers; } if ($str) { $php = "<?php " . PHP_EOL . LightnCandy::compile($str, $opts); // Save it to php file file_put_contents($phpname, $php); } } if (file_exists($phpname)) { $renderer = (include $phpname); return $renderer((array) $args); } return ''; }
/** * @dataProvider errorProvider */ public function testErrors($test) { global $tmpdir; $php = LightnCandy::compile($test['template'], $test['options']); $context = LightnCandy::getContext(); // This case should be compiled without error if (!isset($test['expected'])) { return; } $this->assertEquals($test['expected'], $context['error'], "Code: {$php}"); }
<?php use LightnCandy\LightnCandy; $template = '{{> (partial_name_helper type)}}'; $data = array('type' => 'dog', 'name' => 'Lucky', 'age' => 5); function partial_name_helper($type) { switch ($type[0]) { case 'man': case 'woman': return 'people'; case 'dog': case 'cat': return 'animal'; default: return 'default'; } } $php = LightnCandy::compile($template, array('flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_RUNTIMEPARTIAL | LightnCandy::FLAG_ERROR_EXCEPTION, 'helpers' => array('partial_name_helper'), 'partials' => array('people' => 'This is {{name}}, he is {{age}} years old.', 'animal' => 'This is {{name}}, it is {{age}} years old.', 'default' => 'This is {{name}}.'))); $renderer = LightnCandy::prepare($php); echo "Data:\n"; print_r($data); echo "\nTemplate:\n{$template}\n"; echo "\nCode:\n{$php}\n\n"; echo "\nOutput:\n"; echo $renderer($data); echo "\n";
/** * @dataProvider compileProvider */ public function testUsedFeature($test) { LightnCandy::compile($test['template'], $test['options']); $context = LightnCandy::getContext(); $this->assertEquals($test['expected'], $context['usedFeature']); }
/** * Fetch rendered template * * @param string $template Template pathname relative to templates directory * @param array $data Associative array of template variables * * @return string */ public function fetch($template, $data = []) { $data = array_merge($this->defaultVariables, $data); $php = Engine::compile($this->getTemplate($template), array('flags' => Engine::FLAG_ERROR_EXCEPTION | Engine::FLAG_ERROR_LOG | Engine::FLAG_INSTANCE | Engine::FLAG_MUSTACHE | Engine::FLAG_HANDLEBARS, 'basedir' => $this->directories, 'fileext' => $this->extensions, 'helpers' => $this->helpers, 'hbhelpers' => $this->block_helpers)); $renderer = Engine::prepare($php); return $renderer(array_merge($data ?: array()), LCRun3::DEBUG_ERROR_LOG); }