Giter Site home page Giter Site logo

zordius / lightncandy Goto Github PK

View Code? Open in Web Editor NEW
608.0 24.0 76.0 4.32 MB

An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ),

Home Page: https://zordius.github.io/HandlebarsCookbook/

License: MIT License

Shell 0.47% PHP 99.53%
handlebars mustache handlebars-js mustache-spec php lightncandy partials preprocess

lightncandy's Introduction

LightnCandy

โšก๐Ÿญ An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).

CI status: Unit testing Regression testing tested PHP: 7.1, 7.2, 7.3, 7.4, 8.0, 8.1 Coverage Status

Package on packagist: Latest Stable Version License Total Downloads

Features

Installation

Use Composer ( https://getcomposer.org/ ) to install LightnCandy:

composer require zordius/lightncandy:dev-master

UPGRADE NOTICE

Documents

Compile Options

You can apply more options by running LightnCandy::compile($template, $options):

LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONEPHP
));

Default is to compile the template as PHP, which can be run as fast as possible (flags = FLAG_BESTPERFORMANCE).

Error Handling

JavaScript Compatibility

Mustache Compatibility

  • FLAG_MUSTACHELOOKUP : align recursive lookup up behaviors with mustache specification. And, the rendering performance will be worse.
  • FLAG_MUSTACHELAMBDA : support simple lambda logic as mustache specification. And, the rendering performance will be worse.
  • FLAG_NOHBHELPERS : Do not compile handlebars.js builtin helpers. With this option, {{#with}}, {{#if}}, {{#unless}}, {{#each}} means normal section, and {{#with foo}}, {{#if foo}}, {{#unless foo}}, {{#each foo}} will cause compile error.
  • FLAG_MUSTACHESECTION : align section context behaviors with mustache.js.
  • FLAG_MUSTACHE : support all mustache specification but performance drop, same with FLAG_ERROR_SKIPPARTIAL + FLAG_MUSTACHELOOKUP + FLAG_MUSTACHELAMBDA + FLAG_NOHBHELPERS + FLAG_RUNTIMEPARTIAL + FLAG_JSTRUE + FLAG_JSOBJECT.

Handlebars Compatibility

  • FLAG_THIS
  • FLAG_PARENT
  • FLAG_HBESCAPE
  • FLAG_ADVARNAME
  • FLAG_NAMEDARG
  • FLAG_SLASH
  • FLAG_ELSE
  • FLAG_RAWBLOCK: support {{{{raw_block}}}}any_char_or_{{foo}}_as_raw_string{{{{/raw_block}}}}.
  • FLAG_HANDLEBARSLAMBDA : support lambda logic as handlebars.js specification. And, the rendering performance will be worse.
  • FLAG_SPVARS : support special variables include @root, @index, @key, @first, @last. Otherwise, compile these variable names with default parsing logic.
  • FLAG_HANDLEBARS : support most handlebars extensions and also keep performance good, same with FLAG_THIS + FLAG_PARENT + FLAG_HBESCAPE + FLAG_ADVARNAME + FLAG_NAMEDARG + FLAG_SPVARS + FLAG_SLASH + FLAG_ELSE + FLAG_RAWBLOCK.
  • FLAG_HANDLEBARSJS : support most handlebars.js + javascript behaviors and also keep performance good, same with FLAG_JS + FLAG_HANDLEBARS.
  • FLAG_HANDLEBARSJS_FULL : enable all supported handlebars.js behaviors but performance drop, same with FLAG_HANDLEBARSJS + FLAG_INSTANCE + FLAG_RUNTIMEPARTIAL + FLAG_MUSTACHELOOKUP + FLAG_HANDLEBARSLAMBDA.

Handlebars Options

  • FLAG_NOESCAPE
  • FLAG_PARTIALNEWCONTEXT
  • FLAG_IGNORESTANDALONE : prevent standalone detection on {{#foo}}, {{/foo}} or {{^}}, the behavior is same with handlebars.js ignoreStandalone compile time option.
  • FLAG_STRINGPARAMS : pass variable name as string to helpers, the behavior is same with handlebars.js stringParams compile time option.
  • FLAG_KNOWNHELPERSONLY: Only pass current context to lambda, the behavior is same with handlebars.js knownHelpersOnly compile time option.
  • FLAG_PREVENTINDENT : align partial indent behavior with mustache specification. This is same with handlebars.js preventIndent copmile time option.

PHP

Partial Support

Custom Helper

Custom Helper Examples

#mywith (context change)

  • LightnCandy
// LightnCandy sample, #mywith works same with #with
$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => array(
        'mywith' => function ($context, $options) {
            return $options['fn']($context);
        }
    )
));
  • Handlebars.js
// Handlebars.js sample, #mywith works same with #with
Handlebars.registerHelper('mywith', function(context, options) {
    return options.fn(context);
});

#myeach (context change)

  • LightnCandy
// LightnCandy sample, #myeach works same with #each
$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => array(
        'myeach' => function ($context, $options) {
            $ret = '';
            foreach ($context as $cx) {
                $ret .= $options['fn']($cx);
            }
            return $ret;
        }
    )
));
  • Handlebars.js
// Handlebars.js sample, #myeach works same with #each
Handlebars.registerHelper('myeach', function(context, options) {
    var ret = '', i, j = context.length;
    for (i = 0; i < j; i++) {
        ret = ret + options.fn(context[i]);
    }
    return ret;
});

#myif (no context change)

  • LightnCandy
// LightnCandy sample, #myif works same with #if
$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => array(
        'myif' => function ($conditional, $options) {
            if ($conditional) {
                return $options['fn']();
            } else {
                return $options['inverse']();
            }
        }
    )
));
  • Handlebars.js
// Handlebars.js sample, #myif works same with #if
Handlebars.registerHelper('myif', function(conditional, options) {
    if (conditional) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
});

You can use isset($options['fn']) to detect your custom helper is a block or not; you can also use isset($options['inverse']) to detect the existence of {{else}}.

Data variables and context

You can get special data variables from $options['data']. Using $options['_this'] to receive current context.

$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => array(
        'getRoot' => function ($options) {
            print_r($options['_this']); // dump current context
            return $options['data']['root']; // same as {{@root}}
        }
    )
));
  • Handlebars.js
Handlebars.registerHelper('getRoot', function(options) {
    console.log(this); // dump current context
    return options.data.root; // same as {{@root}}
});

Private variables

You can inject private variables into inner block when you execute child block with second parameter. The example code showed similar behavior with {{#each}} which sets index for child block and can be accessed with {{@index}}.

  • LightnCandy
$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => array(
        'list' => function ($context, $options) {
            $out = '';
            $data = $options['data'];

            foreach ($context as $idx => $cx) {
                $data['index'] = $idx;
                $out .= $options['fn']($cx, array('data' => $data));
            }

            return $out;
        }
    )
));
  • Handlebars.js
Handlebars.registerHelper('list', function(context, options) {
  var out = '';
  var data = options.data ? Handlebars.createFrame(options.data) : undefined;

  for (var i=0; i<context.length; i++) {
    if (data) {
      data.index = i;
    }
    out += options.fn(context[i], {data: data});
  }
  return out;
});

Change Delimiters

You may change delimiters from {{ and }} to other strings. In the template, you can use {{=<% %>=}} to change delimiters to <% and %> , but the change will not affect included partials.

If you want to change default delimiters for a template and all included partials, you may compile() it with delimiters option:

LightnCandy::compile('I wanna use <% foo %> as delimiters!', array(
    'delimiters' => array('<%', '%>')
));

Template Debugging

When template error happened, LightnCandy::compile() will return false. You may compile with FLAG_ERROR_LOG to see more error message, or compile with FLAG_ERROR_EXCEPTION to catch the exception.

You may generate debug version of templates with FLAG_RENDER_DEBUG when compile() . The debug template contained more debug information and slower (TBD: performance result) , you may pass extra LightnCandy\Runtime options into render function to know more rendering error (missing data). For example:

$template = "Hello! {{name}} is {{gender}}.
Test1: {{@root.name}}
Test2: {{@root.gender}}
Test3: {{../test3}}
Test4: {{../../test4}}
Test5: {{../../.}}
Test6: {{../../[test'6]}}
{{#each .}}
each Value: {{.}}
{{/each}}
{{#.}}
section Value: {{.}}
{{/.}}
{{#if .}}IF OK!{{/if}}
{{#unless .}}Unless not OK!{{/unless}}
";

// compile to debug version
$phpStr = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS
));

// Save the compiled PHP code into a php file
file_put_contents('render.php', '<?php ' . $phpStr . '?>');

// Get the render function from the php file
$renderer = include('render.php');

// error_log() when missing data:
//   LightnCandy\Runtime: [gender] is not exist
//   LightnCandy\Runtime: ../[test] is not exist
$renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_ERROR_LOG));

// Output visual debug template with ANSI color:
echo $renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_ANSI));

// Output debug template with HTML comments:
echo $renderer(array('name' => 'John'), array('debug' => LightnCandy\Runtime::DEBUG_TAGS_HTML));

The ANSI output will be:

Here are the list of LightnCandy\Runtime debug options for render function:

  • DEBUG_ERROR_LOG : error_log() when missing required data
  • DEBUG_ERROR_EXCEPTION : throw exception when missing required data
  • DEBUG_TAGS : turn the return value of render function into normalized mustache tags
  • DEBUG_TAGS_ANSI : turn the return value of render function into normalized mustache tags with ANSI color
  • DEBUG_TAGS_HTML : turn the return value of render function into normalized mustache tags with HTML comments

Preprocess Partials

If you want to do extra process before the partial be compiled, you may use prepartial when compile(). For example, this sample adds HTML comments to identify the partial by the name:

$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'prepartial' => function ($context, $template, $name) {
        return "<!-- partial start: $name -->$template<!-- partial end: $name -->";
    }
));

You may also extend LightnCandy\Partial by override the prePartial() static method to turn your preprocess into a built-in feature.

Customize Render Function

If you want to do extra tasks inside render function or add more comment, you may use renderex when compile() . For example, this sample embed the compile time comment into the template:

$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'renderex' => '// Compiled at ' . date('Y-m-d h:i:s')
));

Your render function will be:

function ($in) {
    $cx = array(...);
    // compiled at 1999-12-31 00:00:00
    return .....
}

Please make sure the passed in renderex is valid PHP, LightnCandy will not check it.

Customize Rendering Runtime Class

If you want to extend LightnCandy\Runtime class and replace the default runtime library, you may use runtime when compile() . For example, this sample will generate render function based on your extended MyRunTime:

// Customized runtime library to debug {{{foo}}}
class MyRunTime extends LightnCandy\Runtime {
    public static function raw($cx, $v) {
        return '[[DEBUG:raw()=>' . var_export($v, true) . ']]';
    }
}

// Use MyRunTime as runtime library
$php = LightnCandy::compile($template, array(
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'runtime' => 'MyRunTime'
));

Please make sure MyRunTime exists when compile() or rendering based on your FLAG_STANDALONEPHP .

Unsupported Feature

  • {{foo/bar}} style variable name, it is deprecated in official handlebars.js document, please use this style: {{foo.bar}}.

Suggested Handlebars Template Practices

  • Prevent to use {{#with}} . I think {{path.to.val}} is more readable then {{#with path.to}}{{val}}{{/with}}; when using {{#with}} you will confusing on scope changing. {{#with}} only save you very little time when you access many variables under same path, but cost you a lot time when you need to understand then maintain a template.
  • use {{{val}}} when you do not require HTML escaped output on the value. It is better performance, too.
  • Prevent to use custom helper if you want to reuse your template in different language. Or, you may need to implement different versions of helper in different languages.
  • For best performance, you should only use 'compile on demand' pattern when you are in development stage. Before you go to production, you can LightnCandy::compile() on all your templates, save all generated PHP codes, and deploy these generated files (You may need to maintain a build process for this) . DO NOT COMPILE ON PRODUCTION , it also a best practice for security. Adding cache for 'compile on demand' is not the best solution. If you want to build some library or framework based on LightnCandy, think about this scenario.
  • Recompile your templates when you upgrade LightnCandy every time.
  • Persistant ESCAPING practice of { or } for both handlebars and lightncandy:
    • If you want to display atomic }} , you can just use it without any trick. EX: {{foo}} }}
    • If you want to display } just after any handlebars token, you can use this: {{#with "}"}}{{.}}{{/with}} . EX: {{foo}}{{#with "}"}}{{.}}{{/with}}
    • If you want to display atomic { , you can just use it without any trick. EX: { and {{foo}}.
    • If you want to display {{ , you can use {{#with "{{"}}{{.}}{{/with}}. EX: {{#with "{{"}}{{.}}{{/with}}{{foo}}

Detail Feature list

Go http://handlebarsjs.com/ to see more feature description about handlebars.js. All features align with it.

  • Exact same CR/LF behavior with handlebars.js
  • Exact same CR/LF bahavior with mustache spec
  • Exact same 'true' or 'false' output with handlebars.js (require FLAG_JSTRUE)
  • Exact same '[object Object]' output or join(',' array) output with handlebars.js (require FLAG_JSOBJECT)
  • Can place heading/tailing space, tab, CR/LF inside {{ var }} or {{{ var }}}
  • Indent behavior of the partial same with mustache spec
  • Recursive variable lookup to parent context behavior same with mustache spec (require FLAG_MUSTACHELOOKUP)
  • {{{value}}} or {{&value}} : raw variable
    • true as 'true' (require FLAG_JSTRUE)
    • false as 'false' (require FLAG_TRUE)
  • {{value}} : HTML escaped variable
    • true as 'true' (require FLAG_JSTRUE)
    • false as 'false' (require FLAG_JSTRUE)
  • {{{path.to.value}}} : dot notation, raw
  • {{path.to.value}} : dot notation, HTML escaped
  • {{.}} : current context, HTML escaped
  • {{{.}}} : current context, raw
  • {{this}} : current context, HTML escaped (require FLAG_THIS)
  • {{{this}}} : current context, raw (require FLAG_THIS)
  • {{#value}} : section
    • false, undefined and null will skip the section
    • true will run the section with original scope
    • All others will run the section with new scope (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
  • {{/value}} : end section
  • {{^value}} : inverted section
    • false, undefined and null will run the section with original scope
    • All others will skip the section (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
  • {{! comment}} : comment
  • {{!-- comment or {{ or }} --}} : extended comment that can contain }} or {{ .
  • {{=<% %>=}} : set delimiter to custom string , the custom string can not contain = . Check http://mustache.github.io/mustache.5.html for more example.
  • {{#each var}} : each loop
  • {{#each}} : each loop on {{.}}
  • {{/each}} : end loop
  • {{#each bar as |foo|}} : echo loop on bar and set the value as foo. (require FLAG_ADVARNAME)
  • {{#each bar as |foo moo|}} : echo loop on bar, set the value as foo, set the index as moo. (require FLAG_ADVARNAME)
  • {{#if var}} : run if logic with original scope (null, false, empty Array and '' will skip this block)
  • {{#if foo includeZero=true}} : result as true when foo === 0 (require FLAG_NAMEDARG)
  • {{/if}} : end if
  • {{else}} or {{^}} : run else logic, should between {{#if var}} and {{/if}} ; or between {{#unless var}} and {{/unless}}; or between {{#foo}} and {{/foo}}; or between {{#each var}} and {{/each}}; or between {{#with var}} and {{/with}}. (require FLAG_ELSE)
  • {{#if foo}} ... {{else if bar}} ... {{/if}} : chained if else blocks
  • {{#unless var}} : run unless logic with original scope (null, false, empty Array and '' will render this block)
  • {{#unless foo}} ... {{else if bar}} ... {{/unless}} : chained unless else blocks
  • {{#unless foo}} ... {{else unless bar}} ... {{/unless}} : chained unless else blocks
  • {{#foo}} ... {{else bar}} ... {{/foo}} : custom helper chained else blocks
  • {{#with var}} : change context scope. If the var is false or an empty array, skip included section.
  • {{#with bar as |foo|}} : change context to bar and set the value as foo. (require FLAG_ADVARNAME)
  • {{lookup foo bar}} : lookup foo by value of bar as key.
  • {{../var}} : parent template scope. (require FLAG_PARENT)
  • {{>file}} : partial; include another template inside a template.
  • {{>file foo}} : partial with new context (require FLAG_RUNTIMEPARTIAL)
  • {{>file foo bar=another}} : partial with new context which mixed with followed key value (require FLAG_RUNTIMEPARTIAL)
  • {{>(helper) foo}} : include dynamic partial by name provided from a helper (require FLAG_RUNTIMEPARTIAL)
  • {{@index}} : references to current index in a {{#each}} loop on an array. (require FLAG_SPVARS)
  • {{@key}} : references to current key in a {{#each}} loop on an object. (require FLAG_SPVARS)
  • {{@root}} : references to root context. (require FLAG_SPVARS)
  • {{@first}} : true when looping at first item. (require FLAG_SPVARS)
  • {{@last}} : true when looping at last item. (require FLAG_SPVARS)
  • {{@root.path.to.value}} : references to root context then follow the path. (require FLAG_SPVARS)
  • {{@../index}} : access to parent loop index. (require FLAG_SPVARS and FLAG_PARENT)
  • {{@../key}} : access to parent loop key. (require FLAG_SPVARS and FLAG_PARENT)
  • {{foo.[ba.r].[#spec].0.ok}} : references to $CurrentConext['foo']['ba.r']['#spec'][0]['ok'] . (require FLAG_ADVARNAME)
  • {{~any_valid_tag}} : Space control, remove all previous spacing (includes CR/LF, tab, space; stop on any none spacing character)
  • {{any_valid_tag~}} : Space control, remove all next spacing (includes CR/LF, tab, space; stop on any none spacing character)
  • {{{helper var}}} : Execute custom helper then render the result
  • {{helper var}} : Execute custom helper then render the HTML escaped result
  • {{helper "str"}} or {{helper 'str'}} : Execute custom helper with string arguments (require FLAG_ADVARNAME)
  • {{helper 123 null true false undefined}} : Pass number, true, false, null or undefined into helper
  • {{helper name1=var name2=var2}} : Execute custom helper with named arguments (require FLAG_NAMEDARG)
  • {{#helper ...}}...{{/helper}} : Execute block custom helper
  • {{helper (helper2 foo) bar}} : Execute custom helpers as subexpression (require FLAG_ADVARNAME)
  • {{{{raw_block}}}} {{will_not_parsed}} {{{{/raw_block}}}} : Raw block (require FLAG_RAWBLOCK)
  • {{#> foo}}block{{/foo}} : Partial block, provide foo partial default content (require FLAG_RUNTIMEPARTIAL)
  • {{#> @partial-block}} : access partial block content inside a partial
  • {{#*inline "partial_name"}}...{{/inline}} : Inline partial, provide a partial and overwrite the original one.
  • {{log foo}} : output value to stderr for debug.

Developer Notes

Please read CONTRIBUTING.md for development environment setup.

Framework Integration

Tools

lightncandy's People

Contributors

brunobg avatar cjbarth avatar dixstonz3 avatar ebernhardson avatar endel avatar flashvnn avatar flip111 avatar hfhchan avatar jmgq avatar keeto avatar kfreiman avatar krinkle avatar legoktm avatar matthiasmullie avatar puritys avatar reedy avatar shahyar avatar southparkfan avatar ssnau avatar thesjg avatar yurikuzn avatar zordius avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lightncandy's Issues

Helper input organization

Helper

$helper = function ($input, $options=[]) {
    print_r(func_get_args());
};

Context

['test' => 'X']

Usage

{{SomeHelper test a="1" b="2"}}

Expected / Documented Input

Array
(
    [0] => X 
    [hash] => Array ( 
        [a] => 1 
        [b] => 2 
    )
 )

Current Input

Array
(
    [0] => Array
        (
            [0] => X
            [a] => 1
            [b] => 2
        )
)

Resolution?

I think that the data should not be modified, but it should be organized consistently. I prefer how the docs say, basically, populating the vars, and putting all of the html style arguments into the hash key of the options argument

Custom Helpers

I read the docs, and I understand that it is the philosophy of this project not to support custom helpers for the purpose of cross-language compatibility.

That being said, I think it should be up to the developer, so, for example, if they want to use custom helpers, they can go ahead and implement them in whatever languages they are using.

Is there any plan to add this feature in the future? I want to use this library, but I've got many custom helpers functions for my PHP Handlebars code, so that's a bit of a show stopper.

I'm looking into add into my fork:

  • support for specifying custom helpers as callbacks
  • support for compiling these calls
  • support for {{#Helper}} with no closing tag
  • support for {{#Helper with attributes}}
  • support for {{#Helper with="named attributes"}}

I am currently using the Xamin Handlebars implementation... but the lack of caching is a show stopper as I'd like to use it on high traffic sites.

Any recommendations you can give me on implementing this would be appreciated.

Great work!

Partial and moore

Hi Zordius,
I like you appreciate my suggestion, after work I will send you a list of think I try to implement in your library. So maybe we can discuss together for sincronize the threads. What you think?

Custom helper escaping not working

Returning array($res, false) in helper callback results in output being escaped, when it should not.

Test code:

require('lightncandy.php');

$template = '{{helper}}';

function myhelper($params, $hash) {
  return array("<h1>hello</h1>", false);
}

$php = LightnCandy::compile($template, array(
  "flags" => LightnCandy::FLAG_NAMEDARG,
  "helpers" => array(
    "helper" => "myhelper"
  )
));

echo "Compiled code: $php";

$tpl = LightnCandy::prepare($php);

echo $tpl(array());

Actual Output:

&lt;h1&gt;hello&lt;/h1&gt;

Expected Output:

<h1>hello</h1>

It is worth noting that if {{{ }}} is used the result is not escaped.


Tested as of a4a680f

Support instance attribute access.

Currently, lightncandy compiles {{var.name}} to (is_array($in['var']) && isset($in(['var']['name'])) ? $in['var']['name'] : null. This does not allow for the case var might be a class instance.

It would be great to also support instance attributes - maybe by adding an additional ternary check.

Incidentally, Handlebars.php supports this. Ran into it while trying to port to lightncandy. Currently I'm working around by recursing through the context before render and converting all instances to arrays. I'd rather not have to do that, though.

Problem with length

Hi,

First of all, thank you for all your work, it's really impressive

I have a problem with length, i've got several template and the compiler doesn't seem to recognize ".length" : {{an_array.length}} (I try to find in the source but didn't find anything relative to)
Is it implemented ?
Thank you by advance

Unreable value using segment-literal notation

When reading from non-valid identifiers, using something like this:

{
    "content" : {
        "@type": "value"
    }
}

I can read the value using lightncandy with:

{{content.@type}}

But not:

{{content.[@type]}}

The problem is, the first tag does not work with HandlebarsJS, the second one does.

Using the second syntax would be ideal in order to have server-side and client-side compatibility.

Behavior changes when data type changes

Template:

{{#items}}
    {{#value}}{{.}}{{/value}}
{{/items}}

When parameters are like the following everything is ok:

            'items' => [
                ['value'=>1],
            ],

Error is triggered when 'value' is changed to string:

            'items' => [
                ['value'=>'1'],
            ],

Text of error:

htmlentities() expects parameter 1 to be string, array given

Compiled template:

<?php return function ($in, $debugopt = 1) {
    $cx = Array(
        'flags' => Array(
            'jstrue' => false,
            'jsobj' => false,
            'spvar' => false,
            'prop' => false,
            'method' => false,
            'mustlok' => false,
            'debug' => $debugopt,
        ),
        'helpers' => Array(),
        'blockhelpers' => Array(),
        'hbhelpers' => Array(),
        'partials' => Array(),
        'scopes' => Array($in),
        'sp_vars' => Array(),

    );
    return ''.LCRun3::sec($cx, ((is_array($in) && isset($in['items'])) ? $in['items'] : null), $in, false, function($cx, $in) {return '
    '.LCRun3::sec($cx, ((is_array($in) && isset($in['value'])) ? $in['value'] : null), $in, false, function($cx, $in) {return ''.htmlentities($in, ENT_QUOTES, 'UTF-8').'';}).'
';}).'';
}
?>

Support {{number}} is not valid in handlebars.js

MEMO: {{0}} is not valid in handlebars.js, but it can stands for first child of an array. So far lightncandy supports {{0}} , {{1}} , more. But, we may need a proper runtime / compile time flag to turn off this feature to ensure the rendering results can align with handlebars.js.

RAW {{{page.title}}}} results in array.

When using {{{ var }}} the compiled code shows only "array." At that point of the template.

I have objects that are used as input (typecasting for now, suggestions appreciated) but even a test with a simple array value fails.

And is there a way to a change the extension the loader looks for?

Mistake in documentation

The inverse function closure in block helpers is called 'inv' not 'inverse' as your docs state.

Template variables overridden by helper

Hello @zordius

I just noticed the following: If I have a {{term_link}} helper and I also pass term_link in the template named arguments, the value is overridden by the helper.

I consider the variable should be prioritized instead of the helper as long as it is a named argument.

What do you think?

Passing booleans to helper arguments

We should be able to pass booleans to helpers. For example, say we have this helper:

{{my_helper a=true b=1}}

LightnCandy does a lookup of true and sets a to null. I believe the expected and sane behavior would be to allow the compiler to have reserved values, such as true, false and null and pass these as-is to helpers.

Right now, the workaround I have is that I set these as variables before rendering, and some times within a given block I find myself doing @root.true to compare against true.

Maybe I'm doing something wrong? I double checked the flags but can't find a way to do this.

helper :: Array to String conversion error

What I did:

The helper

function helper_toEuro($price){
    if(is_numeric(trim($price))){
        $price = number_format($price, 2, ',', '.');
        $price = str_replace(',00', ',-', $price);
        return "&euro; ".$price; 
     } else {
         return $price;
     }
}

The init of LightnCandy (a part of)

// Prepare compiler 
$oLightnCandySettings   = Array(
                                'flags' => LightnCandy::FLAG_STANDALONE | LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_PARENT | LightnCandy::FLAG_SPVARS,
                                'basedir' => Array($this->baseDir),
                                'fileext' => $this->utils->addHeadingValue($this->extensions, "."),
                                'helpers' => Array(
                                    "helper_toEuro",
                                    "helper_scoreToStars",
                                    "helper_uniqueId"
                                )
                            );
// compile    
$sCompiledTemplate       = LightnCandy::compile($sTemplate, $oLightnCandySettings);

And how I call the helper :

{{{helper_toEuro "8"}}}
{{{helper_toEuro F}}}

The following error is thrown:

Notice: Array to string conversion in /mnt/htdocs/inbouwWerkmap3.0/application/class/lightncandy/src/lightncandy.php on line 1357

Custom Helper w/ alias

Running into a problem making a custom helper...

try {
    $template_compiled = LightnCandy::compile($template_html, array(
                'flags' => LightnCandy::FLAG_ERROR_EXCEPTION,
                'helpers' => array(
                    'date_format' => 'meetup_date_format'
                )
    ));
} catch (Exception $e) {
    return $e->getMessage();
}

The exception

Parse error: syntax error, unexpected 'meetup_date_format' (T_STRING), expecting '(' in C:\Windows\Temp\lci9721.tmp on line 9

runtime partial support

In #28 , we know lightncandy do not support recursive partial include.
In #62 , we know lightncandy do not support context change when include partial.

These 2 issues can be done by runtime partial logic , but need further works. Now not in plan, just memo.

Helpers not receiving $input arg

Hello,

Helper functions don't receive $input arg. According to specs:

When you pass arguments as name=value pairs, the input to your custom helper will > > turn into only one associative array. For example, when your custom helper is function > ($input) {...}

It's worth mentioning that if a list of arguments is passed, it works as expected.

Here's how to reproduce:

require('./lightncandy.php');

$template = '{{helper name="ernie" blah=true}}';

function myhelper() {
  print_r(func_get_args());
  exit();
}

$php = LightnCandy::compile($template, array(
  "helpers" => array(
    "helper" => "myhelper"
  )
));

$tpl = LightnCandy::prepare($php);

echo $tpl(array()); 

Output of code above:

Array
(
    [0] => 
    [1] => 
)

Many thanks for this great library!

Regards

move to handlebars.js 1.3

handlebars.js 1.3 supports new variable syntax like: {{articles.[10].[#attr]}}
so, lightncandy should version up for these new features.

Nested compilation (->compile template from helper function)

Hey there. Thank you very much for this great library.

It's fun to use and pretty flexible. However, I'm struggling to convert one aspect of my JS implementation: Compiling a new template from within a helper function.

For example, I am using a custom "partial" helper instead of using partials directly. This not only allows passing additional arguments into the partial (in case you need to overwrite some variables in the scope of the partial), it also allows responsive use of different templates.

I might call {{partial 'article'}}, but the helper will also look for 'article-phone' or 'article-tablet' depending on the screen size.

This works beautifully with handlebars.js, but does not seem to be possible with this library right now

LightnCandy::compile($template,$options);

The options array is applied every time you compile a template and there is no way around that. Since helpers are defined within the options array, I'd need access to the array while declaring it.

I hacked around this brain-melting detail by adding a new static method that applies the options array once, and removed the code from the compile() method This seemed to work at first. For reference, this is the helper I am using:

$helpers = array(
    'partial'     => function ( $name, $context ) {
        $template = file_get_contents( TPL_DIR . '/' . $name . '.html' );
        $php = LightnCandy::compile( $template );
        $renderer = LightnCandy::prepare( $php );
        echo $renderer( $context[ 'data' ][ 'root' ] );
    });

But then I noticed that the generated HTML was not "nested" as it should have been.

Consider the following structure:

/* main template */

<div id="main">
    {{partial 'body'}}
</div>
/* body partial */

<div id="body">
    {{partial 'article'}}
    This is the body
</div>
/* article partial */

<div id="article">
    I am the article
</div>

In my tests, this resulted in the following HTML:

<div id="main">
    <div id="article">
        I am the article
    </div>
    <div id="body">
        This is the body
    </div>
</div>


While it should have been

<div id="main">
    <div id="body">
        <div id="article">
            I am the article
        </div>
        This is the body
    </div>
</div>

Is there a more sane approach to achieve what I am trying to do. Is it possible at all?
Since I am heavily relying on a custom partial helper for the responsive functionality of this project, this is an absolute must have for me.

Thank you for your time (and sorry for the wall of text)

Documentation is not clear on how to use blockhelpers

Hey there,

So, I ended up having to look in the source and figure out that you have to use the 'blockhelpers' array key to add functions to use as block helpers. I was adding them to 'helpers' and couldn't figure out why they weren't working. Could you update the documentation to reflect this?

Thanks!

Partial lookups no longer take into account paths.

Upon checking the current head, the following no longer works:

{{> partial/some-partial}}

when compiled with the following:

$x = LightnCandy::compile(
  $templateStr,
  array(
    'flags' => LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_STANDALONE | LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_SPVARS,
    'basedir' => array(
       'some_path/to/templates'
    )
);

The file some-partial is located in some_path/to/templates/partials/some-partial. This used to work some time ago, but now silently fails to compile. Is this new behaviour?

Unable to render partial inside conditional

The following is not possible with LightnCandy:

{{#if true}} {{> some-partial}} {{/if}}

Returns this error:

... Uncaught exception 'Exception' with message 'Unclosed token {{{#if true}}} !!' ...

The expected behavior would be to render the partial inside the block.

Lack of Handlebars.SafeString Support

In Handlebars.js, you can bypass the string escaping when using a Helper function by wrapping your return string with Handlebars.SafeString(String). However, such a thing doesn't appear to exist in lightncandy. It also provides Handlebars.Utils.escapeExpression(String) for manual escaping in such a scenario.

The option "basedir" for compile is not working.

file at C:/www/test/index.php

<?php
include 'lightncandy.php';
$phpStr = LightnCandy::compile('layout', Array(
    'basedir' => Array(
        'C:/www/test/views'
    ),
    'fileext' => Array(
        '.html'
    )
));
$renderer = LightnCandy::prepare($phpStr);
echo $renderer(array('var' => 'test'));

file at C:/www/test/views/layout.html

<h1> {{ var }} </h1>

It just output the string "layout"

PHP Notice Array to string conversion in compileCustomHelper

I think should be

return $context['ops']['seperator'] . static::getFuncName($context, 'ch', "$ch[0] " . $v[0]) . "\$cx, '$ch[0]', {$v[0]}, '$fn'" . ($named ? ', true' : '') . "){$context['ops']['seperator']}";

instead of

return $context['ops']['seperator'] . self::getFuncName($context, 'ch', "$ch " . $v[1]) . "\$cx, '$ch[0]', {$v[0]}, '$fn'" . ($named ? ', true' : '') . "){$context['ops']['seperator']}";

Subexpressions support

Hi Zordius,

We're working on a project and we're trying to use subexpressions for handlebars.
Will subexpressions be supported in lightncandy?

Thanks!

allow_url_include = Off

Hi Zordius,
I was testing your template library and I found it great!

But I saw for using your library I need to turn on allow_url_include, not to bad but this requirement would expose all my environment to accept string inside the include(), so I decide to do a small change in the prepare().

Basic I create a temporary file and after I include it.

I need to do more testing for see the difference also in performance, I let you know if is working good.

With this I let you know I fork your repository, I hope you are happy of that.
Regards, Mauro.

Memory usage compare
Sec. execution

Edit: I don't think it is bad. Just a little price to pay for little bit of security.

partial include from sub directories

currently (version 0.14) im unable to specify partials in sub directories to a basedir

ex: {{>my_sub_directory/test}}

it will only work if i add "my_sub_directory/" to basedir configuration. Im quite sure it worked before but around two weeks ago i stopped, i can see some changes has been made to

13ba7d4

case '>':
            self::readPartial($vars[0][0], $context);
 return true;

a quick workaround i did was to replace it with

case '>':
            self::readPartial(implode(DIRECTORY_SEPARATOR, $vars[0]), $context);
 return true;

the problem might actually be that the parser does not see "my_sub_directory/test" as one string so in $vars the string is split up into an array with 2 elements split by "/"

{{#with}} does not work

I am trying to compile the following template:

{{#with items}}

{{/with}}

And get the error:

Unexpect token /with

Flags used for the compiler:

[\LightnCandy::FLAG_WITH]

Send root context value to helper or partial

I tried the following :

{{{helper_people @root.site.people}}}

This receives a string as parameter [object object]

I tried a partial (prefered)

{{@root.site.people}}
{{>reusable/partial}}
{{/@root.site.people}}

This results in a compile error

I tried a partial like this:

{{>reusable/partial @root.site.people}}

Nothing happens....

When calling the helper or the partial with an object that is not in the root context, the partial and helper renders fine.

Helper input being filtered?

It looks like input to helpers is being filtered on the way in.

So for example, if I have a helper:

$helper = function test ($array) {
return is_array($array) ? 'T' : 'F';
}

With the template:

{{{test tmp}}}

With the context:

$context = ['tmp' => ['A', 'B', 'C']];

The value that gets sent into the helper will end up being the string A,B,C and not the array.

This seems wrong to me. I can understand filtering the data coming out, but the values going in should not be manipulated at all, they should be whatever they happened to be in the context... an array, a multidimensional array, object etc. Because the purpose of helpers is often time to take some complex data and convert it into a string format.

Makes sense?

Error when trying to use anonymous function as a helper

I'm having trouble to use an anonymous function as a helper, the compiled code is wrong

Error:
syntax error, unexpected '=>' (T_DOUBLE_ARROW), expecting ')'

$compileOptions = [
    'flags' => LightnCandy::FLAG_HANDLEBARSJS,
    'helpers' => [
        'url',
        'render' => function($view,$data = array()) {
            return View::make($view,$data);
        }
    ]
];

LightnCandy::compile(file_get_contents($view),$compileOptions);
// template:
<div class="terms-text">
{{render "artists-terms"}}
</div>
// compiled code

<?php return function ($in, $debugopt = 1) {
    $cx = Array(
        'flags' => Array(
            'jstrue' => true,
            'jsobj' => true,
            'spvar' => true,
            'debug' => $debugopt,
        ),
        'helpers' => Array(            'url' => function($path = null, $parameters = array(), $secure = null)
    {
        return app('url')->to($path, $parameters, $secure);
    },
            'render' =>                 'render' => function($view,$data = array()) {
                    return View::make($view,$data);
                }
,
)
...

It's doubling the " 'render' => " definition at the compiled code. There's something wrong with my setup, or it is really an issue?

recursive partial detection

lightncandy should error and stop compile a partial when recureive partial detected and FLAG_RUNTIMEPARTIAL not used.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.