]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - vendor/zordius/lightncandy/README.md
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / vendor / zordius / lightncandy / README.md
1 LightnCandy
2 ===========
3
4 ⚡🍭 An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).
5
6 Travis CI status: [![Unit testing](https://travis-ci.org/zordius/lightncandy.svg?branch=master)](https://travis-ci.org/zordius/lightncandy) [![Regression testing](https://travis-ci.org/zordius/HandlebarsTest.svg?branch=master)](https://travis-ci.org/zordius/HandlebarsTest)
7
8 Scrutinizer CI status: [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/zordius/lightncandy.svg)](https://scrutinizer-ci.com/g/zordius/lightncandy/)
9
10 Package on packagist: [![Latest Stable Version](https://poser.pugx.org/zordius/lightncandy/v/stable.svg)](https://packagist.org/packages/zordius/lightncandy) [![License](https://poser.pugx.org/zordius/lightncandy/license.svg)](https://github.com/zordius/lightncandy/blob/master/LICENSE.txt) [![Total Downloads](https://poser.pugx.org/zordius/lightncandy/downloads)](https://packagist.org/packages/zordius/lightncandy) [![HHVM Status](http://hhvm.h4cc.de/badge/zordius/lightncandy.svg)](http://hhvm.h4cc.de/package/zordius/lightncandy)
11
12 Features
13 --------
14
15 * Logicless template: mustache ( http://mustache.github.com/ ) or handlebars ( http://handlebarsjs.com/ ) .
16 * Compile template to **pure PHP** code. Examples:
17    * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.tmpl">Template A</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.php">PHP A</a>
18    * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.tmpl">Template B</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.php">PHP B</a>
19 * **FAST!**
20    * Runs 3~6 times faster than <a href="https://github.com/bobthecow/mustache.php">mustache.php</a>.
21    * Runs 2~7 times faster than <a href="https://github.com/dingram/mustache-php">mustache-php</a>.
22    * Runs 15~50 times faster than <a href="https://github.com/XaminProject/handlebars.php">handlebars.php</a>.
23    * Detail performance test reports can be found <a href="https://github.com/zordius/HandlebarsTest">here</a>, go http://zordius.github.io/HandlebarsTest/ to see charts.
24 * **SMALL!** single PHP file, only 124K!
25 * **ROBUST!**
26    * 100% support <a href="https://github.com/mustache/spec">mustache spec v1.1.2</a> (without lambda module)
27    * Supports almost all <a href="https://github.com/kasperisager/handlebars-spec">handlebars.js spec</a>
28    * Output <a href="https://github.com/zordius/HandlebarsTest/blob/master/FEATURES.md">SAME</a> with <a href="https://github.com/wycats/handlebars.js">handlebars.js</a>
29 * **FLEXIBLE!**
30    * Lot of <a href="#compile-options">options</a> to change features and behaviors.
31 * Context generation
32    * Analyze used features from your template (use `LightnCandy::getContext()` to get it) .
33 * Debug
34    * <a href="#template-debugging">Generate debug version template</a>
35       * Find out missing data when rendering template.
36       * Generate visually debug template.
37 * Standalone Template
38    * The compiled PHP code can run without any PHP library. You do not need to include LightnCandy when execute rendering function.
39
40 Installation
41 ------------
42
43 Use Composer ( https://getcomposer.org/ ) to install LightnCandy:
44
45 ```
46 composer require zordius/lightncandy:dev-master
47 ```
48
49 Or, download LightnCandy from github:
50
51 ```
52 curl -LO https://github.com/zordius/lightncandy/raw/master/src/lightncandy.php
53 ```
54
55 LightnCandy requirement: PHP 5.3.0+ .
56
57 **UPGRADE NOTICE**
58
59 * Please check <a href="HISTORY.md">HISTORY.md</a> for versions history.
60 * Please check <a href="UPGRADE.md">UPGRADE.md</a> for upgrade notice.
61
62 Usage
63 -----
64 ```php
65 // THREE STEPS TO USE LIGHTNCANDY
66 // Step 1. require the lib, compile template, and get the PHP code as string
67 require('src/lightncandy.php');
68
69 $template = "Welcome {{name}} , You win \${{value}} dollars!!\n";
70 $phpStr = LightnCandy::compile($template);  // compiled PHP code in $phpStr
71
72 // Step 2A. (Usage 1) use LightnCandy::prepare to get rendering function
73 //   DEPRECATED , it may require PHP setting allow_url_fopen=1 ,
74 //   and allow_url_fopen=1 is not secure .
75 //   When allow_url_fopen = 0, prepare() will create tmp file then include it, 
76 //   you will need to add your tmp directory into open_basedir.
77 //   YOU MAY NEED TO CHANGE PHP SETTING BY THIS WAY
78 $renderer = LightnCandy::prepare($phpStr);
79
80
81 // Step 2B. (Usage 2) Store your render function in a file 
82 //   You decide your compiled template file path and name, save it.
83 //   You can load your render function by include() later.
84 //   RECOMMENDED WAY
85 file_put_contents($php_inc, $phpStr);
86 $renderer = include($php_inc);
87
88
89 // Step 3. run native PHP render function any time
90 echo "Template is:\n$template\n\n";
91 echo $renderer(Array('name' => 'John', 'value' => 10000));
92 echo $renderer(Array('name' => 'Peter', 'value' => 1000));
93 ```
94
95 The output will be:
96
97 ```
98 Template is:
99 Welcome {{name}} , You win ${{value}} dollars!!
100
101
102 Welcome John , You win $10000 dollars!!
103 Welcome Peter , You win $1000 dollars!!
104 ```
105
106 Compile Options
107 ---------------
108
109 You can apply more options by running `LightnCandy::compile($template, $options)`:
110
111 ```php
112 LightnCandy::compile($template, Array(
113     'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONE
114 ));
115 ```
116
117 Default is to compile the template as PHP, which can be run as fast as possible (flags = `FLAG_BESTPERFORMANCE`).
118
119 * `FLAG_ERROR_LOG` : error_log() when found any template error
120 * `FLAG_ERROR_EXCEPTION` : throw exception when found any template error
121 * `FLAG_ERROR_SKIPPARTIAL` : skip 'partial not found' error/exception. Use this to align with mustache specification.
122 * `FLAG_NOESCAPE` : do not do any HTML escape on {{var}}.
123 * `FLAG_STANDALONE` : generate stand-alone PHP codes, which can be execute without including LightnCandy.php. The compiled PHP code will contain scoped user function, somehow larger. And, the performance of the template will slow 1 ~ 10%.
124 * `FLAG_JSTRUE` : generate 'true' or 'false' when value is true or false (JavaScript behavior). Otherwise, true/false will generate ''.
125 * `FLAG_JSOBJECT` : generate '[object Object]' for associated array, generate ',' separated values for array (JavaScript behavior). Otherwise, all PHP array will generate '' or 'Array'.
126 * `FLAG_THIS` : resolve `{{this}}` as `{{.}}` in template. Otherwise, `{{this}}` will be resolved as normal variable.
127 * `FLAG_WITH` : support `{{#with var}}` in template. Otherwise, `{{#with var}}` will cause template error.
128 * `FLAG_PARENT` : support `{{../var}}` in template. Otherwise, `{{../var}}` will cause template error.
129 * `FLAG_JSQUOTE` : escape `'` to `&#x27;` , <code>&#x60;</code> to `&#x60;` . Otherwise, `'` will be escaped to `&#039;` , <code>&#x60;</code> will not be touched.
130 * `FLAG_ADVARNAME` : support `{{foo.[0].[#te#st].bar}}` style advanced variable naming in template. Use this flag if you wanna use `"some string"` or `(subexpresssion)` as argument.
131 * `FLAG_NAMEDARG` : support named arguments for custom helper `{{helper name1=val1 nam2=val2 ...}}`.
132 * `FLAG_EXTHELPER` : do not including custom helper codes into compiled PHP codes. This reduces the code size, but you need to take care of your helper functions when rendering. If you forget to include required functions when execute rendering function, `undefined function` runtime error will be triggered. NOTE: Anonymous functions will always be placed into generated codes.
133 * `FLAG_RUNTIMEPARTIAL` : compile partial as runtime function, This enables recursive partials or context change for partials.
134 * `FLAG_SLASH` : Skip a delimiter when it behind `\` .
135 * `FLAG_ELSE` : support `{{else}}` or `{{^}}` as handlebars specification. Otherwise, `{{else}}` will be resolved as normal variable , and {{^}} will cause template error.
136 * `FLAG_RAWBLOCK`: support `{{{{raw_block}}}} any char or {{foo}} as none parsed raw string {{{{/raw_block}}}}`.
137 * `FLAG_PROPERTY` : support object instance attribute access. You MUST apply this if your data contains object. And, the rendering performance will be worse.
138 * `FLAG_METHOD` : support object instance method access. You MUST apply this if your data contains object. And, the rendering performance will be worse.
139 * `FLAG_INSTANCE` : same with `FLAG_PROPERTY` + `FLAG_METHOD`
140 * `FLAG_SPACECTL` : support space control `{{~ }}` or `{{ ~}}` in template. Otherwise, `{{~ }}` or `{{ ~}}` will cause template error.
141 * `FLAG_SPVARS` : support special variables include @root, @index, @key, @first, @last. Otherwise, compile these variable names with default parsing logic.
142 * `FLAG_JS` : simulate all JavaScript string conversion behavior, same with `FLAG_JSTRUE` + `FLAG_JSOBJECT`.
143 * `FLAG_HANDLEBARS` : support all handlebars extensions (which mustache do not supports) , same with `FLAG_THIS` + `FLAG_WITH` + `FLAG_PARENT` + `FLAG_JSQUOTE` + `FLAG_ADVARNAME` + `FLAG_NAMEDARG` + `FLAG_SLASH` + `FLAG_ELSE` + `FLAG_RAWBLOCK` + `FLAG_MUSTACHESP` + `FLAG_MUSTACHEPAIN`.
144 * `FLAG_HANDLEBARSJS` : align with handlebars.js behaviors, same with `FLAG_JS` + `FLAG_HANDLEBARS`.
145 * `FLAG_MUSTACHESP` : align line change and spacing behaviors with mustache specification.
146 * `FLAG_MUSTACHELOOKUP` : align recursive lookup up behaviors with mustache specification. And, the rendering performance will be worse.
147 * `FLAG_MUSTACHEPAIN` : align partial indent behavior with mustache specification.
148 * `FLAG_MUSTACHE` : support all mustache specification, same with `FLAG_ERROR_SKIPPARTIAL` + `FLAG_MUSTACHESP` + `FLAG_MUSTACHELOOKUP` + `FLAG_MUSTACHEPAIN`.
149 * `FLAG_ECHO` : compile to `echo 'a', $b, 'c';` to improve performance. This will slow down rendering when the template and data are simple, but will improve 1% ~ 7% when the data is big and looping in the template.
150 * `FLAG_BESTPERFORMANCE` : same with `FLAG_ECHO` now. This flag may be changed base on performance testing result in the future.
151 * `FLAG_RENDER_DEBUG` : generate debug template to show error when rendering. With this flag, the performance of rendering may be slowed.
152 * `FLAG_BARE`: generate PHP code without PHP tags `<?php` and `?>`
153
154 Partial Support
155 ---------------
156
157 LightnCandy supports partial when compile time. You can provide partials by `partials` option when `compile()`:
158
159 ```php
160 LightnCandy::compile($template, Array(
161     'partials' => Array(
162         'name' => 'template: {{name}}',
163     ),
164 ));
165 ```
166
167 You can also provide partials by files. When `compile()`, LightnCandy will search template files from `basedir` in the option if you provided one or more. Default template file name is `*.tmpl`, you can change or add more template file extensions with `fileext` option. 
168
169 ```php
170 // Loading partial from file system only when valid directory is provided by basedir option
171 // '.' means getpwd()
172 LightnCandy::compile($template, Array(
173     'basedir' => '.'
174 ));
175
176 // Multiple basedir and fileext are supported
177 LightnCandy::compile($template, Array(
178     'flags' => LightnCandy::FLAG_STANDALONE,
179     'basedir' => Array(
180         '/usr/local/share/handlebars/templates',
181         '/usr/local/share/my_project/templates',
182         '/usr/local/share/my_project/partials',
183     ),
184     'fileext' => Array(
185         '.tmpl',
186         '.mustache',
187         '.handlebars',
188     )
189 ));
190 ```
191
192 With this setting, when you include a partial by `{{> partial_name}}`, LightnCandy will search in this order:
193 * /usr/local/share/handlebars/templates/partial_name.tmpl
194 * /usr/local/share/handlebars/templates/partial_name.mustache
195 * /usr/local/share/handlebars/templates/partial_name.handlebars
196 * /usr/local/share/my_project/templates/partial_name.tmpl
197 * /usr/local/share/my_project/templates/partial_name.mustache
198 * /usr/local/share/my_project/templates/partial_name.handlebars
199 * /usr/local/share/my_project/partials/partial_name.tmpl
200 * /usr/local/share/my_project/partials/partial_name.mustache
201 * /usr/local/share/my_project/partials/partial_name.handlebars
202
203 By default, partial uses the same context with original template. If you want to change context for the partial, you may add one more argument after the partial name:
204
205 ```
206 {{>partial_name .}} // Same as {{>partial_name}}
207 {{>partial_name foo}} // Change input context to foo, FLAG_RUNTIMEPARTIAL required
208 {{>partial_name ..}} // use {{..}} as new input context, FLAG_RUNTIMEPARTIAL required
209
210 {{>partial_name .. key=bar}} // use {{..}} as new input context, FLAG_RUNTIMEPARTIAL required
211                              // also merge key into new context.
212 ```
213
214 Dynamic Partial
215 ---------------
216
217 You can use dynamic partial name by passing a custom helper as subexpression syntax, for example: `{{> (foo)}}` . the return value of custom helper `foo` will be the partial name.
218
219 ```php
220 $php = LightnCandy::compile('{{> (partial_name_helper obj_type)}}', Array(
221     'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_RUNTIMEPARTIAL,
222     'helpers' => Array(
223         'partial_name_helper' => function ($args) {
224             switch ($args[0]) {
225                 ....
226             }
227         }
228     ),
229     'partials' => Array(
230         'people' => 'This is {{name}}, he is {{age}} years old.',
231         'animal' => 'This is {{name}}, it is {{age}} years old.',
232     )
233 ));
234
235 $renderer = LightnCandy::prepare($php);
236
237 // Will use people partial and output: 'This is John, he is 15 years old.'
238 echo $renderer(Array(
239    'obj_type' => 'people',
240    'name' => 'John',
241    'age' => '15',
242 ));
243 ```
244
245 When you using dynamic partial, LightnCandy will compile all partials inside the `partials` option into template. This makes the generated code larger, but this can make sure all partials are included for rendering. (TODO: add an example to show how to provide partials across templates to reduce size)
246
247 Custom Helper
248 -------------
249
250 Custom helper can help you deal with common template tasks, for example: provide URL and text then generate a link. To know more about custom helper, you can read original handlebars.js document here: http://handlebarsjs.com/expressions.html . 
251
252 **NOTICE**: custom helpers to handle single tag `{{xxx}}` or a section `{{#yyy}} ... {{/yyy}}` are absolutely different in LightnCandy. To know more about creating custom helpers to handle `{{#yyy}} ... {{/yyy}}`, please refer to <a href="#block-custom-helper">Block Custom Helper</a>.
253
254 When `compile()`, LightnCandy will lookup helpers from generated custom helper name table. You can register custom helpers with `helpers` option (**NOTICE**: `FLAG_NAMEDARG` is required for named arguments, `FLAG_ADVARNAME` is required for string or subexpression arguments):
255
256 ```php
257 LightnCandy::compile($template, Array(
258     // FLAG_NAMEDARG is required if you want to use named arguments
259     'flags' => LightnCandy::FLAG_HANDLEBARS
260     'helpers' => Array(
261         // 1. You may pass your function name
262         //    When the function is not exist, you get compile time error
263         //    In this case, the helper name is same with function name
264         //    Template: {{my_helper_functoin ....}}
265         'my_helper_function',
266
267         // 2. You may also provide a static call from a class
268         //    In this case, the helper name is same with provided full name
269         //    **DEPRECATED** It is not valid in handlebars.js 
270         //    Template: {{myClass::myStaticMethod ....}}
271         'myClass::myStaticMethod',
272
273         // 3. You may also provide an alias name for helper function
274         //    This help you to mapping different function to a preferred helper name
275         //    Template: {{helper_name ....}}
276         'helper_name' => 'my_other_helper',
277
278         // 4. Alias also works well for static call of a class
279         //    This help you to mapping different function to a preferred helper name
280         //    Template: {{helper_name2 ....}}
281         'helper_name2' => 'myClass::func',
282
283         // 5. Anonymous function should be provided with alias
284         //    The function will be included in generaed code always
285         //    Template: {{helper_name3 ....}}
286         'helper_name3' => function ($arg1, $arg2) {
287             return "<a href=\"{$arg1}\">{$arg2}</a>";
288         }
289     )
290 ));
291 ```
292
293 Custom Helper Interface
294 -----------------------
295
296 The input arguments are processed by LightnCandy automatically, you do not need to worry about variable name processing or current context. You can also use double quoted string as input:
297
298 ```
299 {{{helper name}}}           // This send processed {{{name}}} into the helper
300 {{{helper ../name}}}        // This send processed {{{../name}}} into the helper
301 {{{helper "Test"}}}         // This send the string "Test" into the helper (FLAG_ADVARNAME is required)
302 {{helper "Test"}}           // This send the string "Test" into the helper and escape the helper result
303 {{{helper "Test" ../name}}} // This send string "Test" as first parameter,
304                             // and processed {{{../name}}} as second parameter into the helper
305 ```
306
307 Your custom helper function will be executed with two arguments. The first one is noname arguments, the second one is named arguments:
308
309 ```php
310 function myhelper ($args, $named) {
311     if (count($args)) {
312         // handle no name arguments....
313     }
314     // access foo=bar from $named['foo'] ...
315 }
316 ```
317
318 In your template:
319
320 ```
321 {{{helper name=value}}}        // This send processed {{{value}}} into $named['name']
322 {{{helper name="value"}}}      // This send the string "value" into $named['name']
323 {{{helper [na me]="value"}}}   // You can still protect the name with [ ]
324                                // so you get $named['na me'] as the string 'value'
325 {{{helper url name="value"}}}  // This send processed {{{url}}}  into $args[0]
326                                // and the string "value" into $named['name']
327 ```
328
329 Custom Helper Escaping
330 ----------------------
331
332 The return value of your custom helper should be a string. When your custom helper be executed from {{ }} , the return value will be HTML escaped. You may execute your helper by {{{ }}} , then the original helper return value will be outputted directly.
333
334 When you need to do different escaping logic, you can return extended information by Array($responseString, $escape_flag) , here are some custom helper return value cases:
335
336 ```php
337 // escaping is handled by lightncandy and decided by template
338 // if the helper is in {{ }} , you get 'The U&amp;ME Helper is ececuted!'
339 // if the helper is in {{{ }}} , you get 'The U&ME Helper is executed!'
340 return 'The U&ME Helper is executed!';
341
342 // Same as above because the escape_flag is DEFAULT
343 // 0, false, null, undefined, or '' means DEFAULT
344 return Array('The U&ME Helper is executed!');
345 return Array('The U&ME Helper is executed!', false);
346 return Array('The U&ME Helper is executed!', 0);
347
348 // escaping is handled by the helper, lightncandy will do nothing
349 // No matter in {{ }} or {{{ }}} , you get 'Exact&Same output \' \" Ya!'
350 return Array('Exact&Same output \' " Ya!', 'raw');
351
352 // force lightncandy escaping the helper result
353 // No matter in {{ }} or {{{ }}} , you get 'Not&amp;Same output &#039; &quot; Ya!'
354 return Array('Not&Same output \' " Ya!', 'enc');
355
356 // force lightncandy escaping the helper result in handlebars.js way
357 // No matter in {{ }} or {{{ }}} , you get 'Not&amp;Same output &#x27; &quot; Ya!'
358 return Array('Not&Same output \' " Ya!', 'encq');
359 ```
360
361 In most case, a custom helper should always return a string. If you design a custom helper to be executed inside a subexpression, you can return an object or an array by this way:
362
363 ```php
364 // return an object
365 return Array($anObject, 'asis');
366
367 // in another way
368 return Array($anObject, 'raw');
369
370 // return Array(1, 3, 5)
371 return Array(Array(1, 3, 5), 'any_string_but_not_enc_nor_encq');
372 ```
373
374 Block Custom Helper
375 -------------------
376
377 Block custom helper must be used as a section, the section is started with `{{#helper_name ...}}` and ended with `{{/helper_name}}`.
378
379 You may use block custom helper to:
380
381 1. Provide advanced condition logic which is different from `{{#if ...}}` ... `{{/if}}` .
382 2. Modify current context for the inner block.
383 3. Provide different context to the inner block.
384
385 You can register block custom helpers with `blockhelpers` option:
386
387 ```php
388 LightnCandy::compile($template, Array(
389     'blockhelpers' => Array(    // The usage of blockhelpers option is similar with helpers option.
390         'my_helper_function',   // You can use function name, class name with static method,
391         ...                     // and choose preferred helper name by providing key name.
392     )
393 ));
394 ```
395
396 Block Custom Helper Interface
397 -----------------------------
398
399 LightnCandy handled all input arguments for you, you will receive current context and parsed arguments. The return value of helper function will become new context then be passed into inner block. If you do not return any value, or return null, the inner block will not be rendered. For example:
400
401 ```php
402 // Only render inner block when input > 5
403 // {{#helper_iffivemore total_people}}More then 5 people, discount!{{/helper_iffivemore}}
404 function helper_iffivemore($cx, $args, $named) {
405     return $args[0] > 5 ? $cx : null;
406 }
407
408 // You can use named arguments, too
409 // {{#helper_if value=people logic="more" tovalue=5}}Yes the logic is true{{/helper_if}}
410 function helper_if($cx, $args, $named) {
411     switch ($args['logic']) {
412     case 'more':
413         return $named['value'] > $named['tovalue'] ? $cx : null;
414     case 'less':
415         return $named['value'] < $named['tovalue'] ? $cx : null;
416     case 'eq':
417         return $named['value'] == $named['tovalue'] ? $cx : null;
418     }
419 }
420
421 // Provide default values for name and salary
422 // {{#helper_defaultpeople}}Hello, {{name}} ....Your salary will be {{salary}}{{/helper_defaultpeople}}
423 function helper_defaultpeople($cx, $args, $named) {
424     if (!isset($cx['name'])) {
425         $cx['name'] = 'Sir';
426     }
427     $cx['salary'] = isset($cx['salary']) ? '$' . $cx['salary'] : 'unknown';
428     return $cx;
429 }
430
431 // Provide specific context to innerblock
432 // {{#helper_sample}}Preview Name:{{name}} , Salary:{{salary}}.{{/helper_sample}}
433 function helper_sample($cx, $args) {
434     return Array('name' => 'Sample Name', 'salary' => 'Sample Salary');
435 }
436 ```
437
438 You cannot provide new rendered result, nor handle loop in your block custom helper. To provide different rendering result, you should use <a href="#custom-helper">custom helper</a>. To handle loop, you should use `{{#each}}` . For example:
439
440 ```php
441 // Provide specific context to innerblock, then loop on it.
442 // {{#helper_categories}}{{#each .}}<li><a href="?id={{id}}">{{name}}</a></li>{{/each}}{{/helper_categories}}
443 function helper_categories($cx, $args) {
444     return getMyCategories(); // Array('category1', 'category2', ...)
445 }
446 ```
447
448 The mission of a block custom helper is only focus on providing different context or logic to inner block, nothing else. If you like to do things beyond these restrictions, please using `hbhelpers` and keep reading to next section.
449
450 Handlebars.js' Custom Helper
451 ----------------------------
452
453 You can implement helpers more like Handlebars.js way with `hbhelpers` option, all matched single custom helper and block custom helper will be handled. In Handlebars.js, a block custom helper can rendener child block by executing `options.fn`; or change context by send new context as first parameter. Here are some examples to explain the behavior of `hbhelpers` custom helper:
454
455 **#mywith (context change)**
456 * LightnCandy
457 ```php
458 // LightnCandy sample, #mywith works same with #with
459 $php = LightnCandy::compile($template, Array(
460     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
461     'hbhelpers' => Array(
462         'mywith' => function ($context, $options) {
463             return $options['fn']($context);
464         }
465     )
466 ));
467 ```
468
469 * Handlebars.js
470 ```javascript
471 // Handlebars.js sample, #mywith works same with #with
472 Handlebars.registerHelper('mywith', function(context, options) {
473     return options.fn(context);
474 });
475 ```
476
477 **#myeach (context change)**
478 * LightnCandy
479 ```php
480 // LightnCandy sample, #myeach works same with #each
481 $php = LightnCandy::compile($template, Array(
482     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
483     'hbhelpers' => Array(
484         'myeach' => function ($context, $options) {
485             $ret = '';
486             foreach ($context as $cx) {
487                 $ret .= $options['fn']($cx);
488             }
489             return $ret;
490         }
491     )
492 ));
493 ```
494
495 * Handlebars.js
496 ```javascript
497 // Handlebars.js sample, #myeach works same with #each
498 Handlebars.registerHelper('myeach', function(context, options) {
499     var ret = '', i, j = context.length;
500     for (i = 0; i < j; i++) {
501         ret = ret + options.fn(context[i]);
502     }
503     return ret;
504 });
505 ```
506
507 **#myif (no context change)**
508 * LightnCandy
509 ```php
510 // LightnCandy sample, #myif works same with #if
511 $php = LightnCandy::compile($template, Array(
512     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
513     'hbhelpers' => Array(
514         'myif' => function ($conditional, $options) {
515             if ($conditional) {
516                 return $options['fn']();
517             } else {
518                 return $options['inverse']();
519             }
520         }
521     )
522 ));
523 ```
524
525 * Handlebars.js
526 ```javascript
527 // Handlebars.js sample, #myif works same with #if
528 Handlebars.registerHelper('myif', function(conditional, options) {
529     if (conditional) {
530         return options.fn(this);
531     } else {
532         return options.inverse(this);
533     }
534 });
535 ```
536
537 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}}`.
538
539 **Hashed arguments**
540 * LightnCandy
541 ```php
542 $php = LightnCandy::compile($template, Array(
543     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
544     'hbhelpers' => Array(
545         'sample' => function ($arg1, $arg2, $options) {
546             // All hashed arguments are in $options['hash']
547         }
548     )
549 ));
550 ```
551
552 * Handlebars.js
553 ```javascript
554 Handlebars.registerHelper('sample', function(arg1, arg2, options) {
555     // All hashed arguments are in options.hash
556 });
557 ```
558
559 **Data variables and context**
560
561 You can get special data variables from `$options['data']`. Using `$options['_this']` to receive current context.
562
563 ```php
564 $php = LightnCandy::compile($template, Array(
565     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
566     'hbhelpers' => Array(
567         'getRoot' => function ($options) {
568             print_r($options['_this']); // dump current context
569             return $options['data']['root']; // same as {{@root}}
570         }
571     )
572 ));
573 ```
574
575 * Handlebars.js
576 ```javascript
577 Handlebars.registerHelper('getRoot', function(options) {
578     console.log(this); // dump current context
579     return options.data.root; // same as {{@root}}
580 });
581 ```
582
583 **Private variables**
584
585 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}}`.
586
587 * LightnCandy
588 ```php
589 $php = LightnCandy::compile($template, Array(
590     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
591     'hbhelpers' => Array(
592         'list' => function ($context, $options) {
593             $out = '';
594             $data = $options['data'];
595
596             foreach ($context as $idx => $cx) {
597                 $data['index'] = $idx;
598                 $out .= $options['fn']($cx, Array('data' => $data));
599             }
600
601             return $out;
602         }
603     )
604 ));
605 ```
606
607 * Handlebars.js
608 ```javascript
609 Handlebars.registerHelper('list', function(context, options) {
610   var out = '';
611   var data = options.data ? Handlebars.createFrame(options.data) : undefined;
612
613   for (var i=0; i<context.length; i++) {
614     if (data) {
615       data.index = i;
616     }
617     out += options.fn(context[i], {data: data});
618   }
619   return out;
620 });
621 ```
622
623 **Escaping**
624
625 When a Handlebars.js style custom helper be used as block tags, LightnCandy will not escape the result. When it is a single {{...}} tag, LightnCandy will escape the result. To change the escape behavior, you can return extended information by Array(), please read <a href="#custom-helper-escaping">Custom Helper Escaping</a> for more.
626
627 Template Debugging
628 ------------------
629
630 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.
631
632 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 LCRun3 options into render function to know more rendering error (missing data). For example:
633
634 ```php
635 $template = "Hello! {{name}} is {{gender}}.
636 Test1: {{@root.name}}
637 Test2: {{@root.gender}}
638 Test3: {{../test3}}
639 Test4: {{../../test4}}
640 Test5: {{../../.}}
641 Test6: {{../../[test'6]}}
642 {{#each .}}
643 each Value: {{.}}
644 {{/each}}
645 {{#.}}
646 section Value: {{.}}
647 {{/.}}
648 {{#if .}}IF OK!{{/if}}
649 {{#unless .}}Unless not OK!{{/unless}}
650 ";
651
652 // compile to debug version
653 $php = LightnCandy::compile($template, Array(
654     'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS
655 ));
656
657 // Get the render function
658 $renderer = LightnCandy::prepare($php);
659
660 // error_log() when missing data:
661 //   LCRun3: [gender] is not exist
662 //   LCRun3: ../[test] is not exist
663 $renderer(Array('name' => 'John'), LCRun3::DEBUG_ERROR_LOG);
664
665 // Output visual debug template with ANSI color:
666 echo $renderer(Array('name' => 'John'), LCRun3::DEBUG_TAGS_ANSI);
667
668 // Output debug template with HTML comments:
669 echo $renderer(Array('name' => 'John'), LCRun3::DEBUG_TAGS_HTML);
670 ```
671
672 The ANSI output will be: 
673
674 <a href="tests/example_debug.php"><img src="https://github.com/zordius/lightncandy/raw/master/example_debug.png"/></a>
675
676 Here are the list of LCRun3 debug options for render function:
677
678 * `DEBUG_ERROR_LOG` : error_log() when missing required data
679 * `DEBUG_ERROR_EXCEPTION` : throw exception when missing required data
680 * `DEBUG_TAGS` : turn the return value of render function into normalized mustache tags
681 * `DEBUG_TAGS_ANSI` : turn the return value of render function into normalized mustache tags with ANSI color
682 * `DEBUG_TAGS_HTML` : turn the return value of render function into normalized mustache tags with HTML comments
683
684 Preprocess Partials
685 -------------------
686
687 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:
688
689 ```php
690 $php = LightnCandy::compile($template, Array(
691     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
692     'prepartial' => function ($partial, $name) {
693         return "<!-- partial start: $name -->$partial<!-- partial end: $name -->";
694     }
695 ));
696 ```
697
698 You may also extend `LightnCandy` by override the `prePartial()` static method to turn your preprocess into a built-in feature.
699
700 Customize Render Function
701 -------------------------
702
703 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:
704
705 ```php
706 $php = LightnCandy::compile($template, Array(
707     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
708     'renderex' => '// Compiled at ' . date('Y-m-d h:i:s')
709 ));
710 ```
711
712 Your render function will be:
713
714 ```php
715 function ($in) {$
716     $cx = array(...);
717     // compiled at 1999-12-31 00:00:00
718     return .....
719 }
720 ```
721
722 Please make sure the passed in `renderex` is valid PHP, LightnCandy will not check it.
723
724 Customize Rendering Runtime Class
725 ---------------------------------
726
727 If you want to extend `LCRun3` class and replace default rendering runtime library, you may use `lcrun` when `compile()` . For example, this sample will generate render function based on your extended `MyLCRunClass`:
728
729 ```php
730 // Customized rendering runtime library to debug {{{foo}}}
731 class MyLCRunClass extends LCRun3 {
732     public static function raw($cx, $v) {
733         return '[[DEBUG:raw()=>' . var_export($v, true) . ']]';
734     }
735 }
736
737 // Use MyLCRunClass as rendering runtime library
738 $php = LightnCandy::compile($template, Array(
739     'flags' => LightnCandy::FLAG_HANDLEBARSJS,
740     'lcrun' => 'MyLCRunClass'
741 ));
742 ```
743
744 Please make sure `MyLCRunClass` exists when compile() or rendering based on your `FLAG_STANDALONE` .
745
746 Unsupported Feature (so far)
747 ----------------------------
748
749 * [NEVER] `{{foo/bar}}` style variable name, it is deprecated in official handlebars.js document, please use this style: `{{foo.bar}}`.
750 * [maybe] mustache lambda : runtime time compile based on input value is far from lightncandy nature, not in the plan now.
751
752 Suggested Handlebars Template Practices
753 ---------------------------------------
754
755 * 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.
756 * use `{{{val}}}` when you do not require HTML escaped output on the value. It is better performance, too.
757 * If you wanna display `{{`, use this: `{{{"{{"}}}`, prevent using `\{{`.
758 * 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.
759 * 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.
760 * Recompile your templates when you upgrade LightnCandy every time.
761
762 Detail Feature list
763 -------------------
764
765 Go http://handlebarsjs.com/ to see more feature description about handlebars.js. All features align with it.
766
767 * Exact same CR/LF behavior with handlebars.js
768 * Exact same CR/LF bahavior with mustache spec (require `FLAG_MUSTACHESP`)
769 * Exact same 'true' or 'false' output with handlebars.js (require `FLAG_JSTRUE`)
770 * Exact same '[object Object]' output or join(',' array) output with handlebars.js (require `FLAG_JSOBJECT`)
771 * Can place heading/tailing space, tab, CR/LF inside `{{ var }}` or `{{{ var }}}`
772 * Indent behavior of the partial same with mustache spec (require `FLAG_MUSTACHEPAIN`)
773 * Recursive variable lookup to parent context behavior same with mustache spec (require `FLAG_MUSTACHELOOKUP`)
774 * `{{{value}}}` or `{{&value}}` : raw variable
775    * true as 'true' (require `FLAG_JSTRUE`)
776    * false as 'false' (require `FLAG_TRUE`)
777 * `{{value}}` : HTML escaped variable
778    * true as 'true' (require `FLAG_JSTRUE`)
779    * false as 'false' (require `FLAG_JSTRUE`)
780 * `{{{path.to.value}}}` : dot notation, raw
781 * `{{path.to.value}}` : dot notation, HTML escaped 
782 * `{{.}}` : current context, HTML escaped
783 * `{{{.}}}` : current context, raw
784 * `{{this}}` : current context, HTML escaped (require `FLAG_THIS`)
785 * `{{{this}}}` : current context, raw (require `FLAG_THIS`)
786 * `{{#value}}` : section
787    * false, undefined and null will skip the section
788    * true will run the section with original scope
789    * All others will run the section with new scope (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
790 * `{{/value}}` : end section
791 * `{{^value}}` : inverted section
792    * false, undefined and null will run the section with original scope
793    * All others will skip the section (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
794 * `{{! comment}}` : comment
795 * `{{!-- comment or {{ or }} --}}` : extended comment that can contain }} or {{ .
796 * `{{=<% %>=}}` : set delimiter to custom string , the custom string can not contain `=` . Check http://mustache.github.io/mustache.5.html for more example.
797 * `{{#each var}}` : each loop
798 * `{{#each}}` : each loop on {{.}}
799 * `{{/each}}` : end loop
800 * `{{#if var}}` : run if logic with original scope (null, false, empty Array and '' will skip this block)
801 * `{{/if}}` : end if
802 * `{{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`)
803 * `{{#unless var}}` : run unless logic with original scope (null, false, empty Array and '' will render this block)
804 * `{{#with var}}` : change context scope. If the var is false, skip included section. (require `FLAG_WITH`)
805 * `{{../var}}` : parent template scope. (require `FLAG_PARENT`)
806 * `{{>file}}` : partial; include another template inside a template.
807 * `{{>file foo}}` : partial with new context (require `FLAG_RUNTIMEPARTIAL`)
808 * `{{>file foo bar=another}}` : partial with new context which mixed with followed key value (require `FLAG_RUNTIMEPARTIAL`)
809 * `{{>(helper) foo}}` : include dynamic partial by name provided from a helper (require `FLAG_RUNTIMEPARTIAL`)
810 * `{{@index}}` : references to current index in a `{{#each}}` loop on an array. (require `FLAG_SPVARS`)
811 * `{{@key}}` : references to current key in a `{{#each}}` loop on an object. (require `FLAG_SPVARS`)
812 * `{{@root}}` : references to root context. (require `FLAG_SPVARS`)
813 * `{{@first}}` : true when looping at first item. (require `FLAG_SPVARS`)
814 * `{{@last}}` : true when looping at last item. (require `FLAG_SPVARS`)
815 * `{{@root.path.to.value}}` : references to root context then follow the path. (require `FLAG_SPVARS`)
816 * `{{@../index}}` : access to parent loop index. (require `FLAG_SPVARS` and `FLAG_PARENT`)
817 * `{{@../key}}` : access to parent loop key. (require `FLAG_SPVARS` and `FLAG_PARENT`)
818 * `{{foo.[ba.r].[#spec].0.ok}}` : references to $CurrentConext['foo']['ba.r']['#spec'][0]['ok'] . (require `FLAG_ADVARNAME`)
819 * `{{~any_valid_tag}}` : Space control, remove all previous spacing (includes CR/LF, tab, space; stop on any none spacing character) (require `FLAG_SPACECTL`)
820 * `{{any_valid_tag~}}` : Space control, remove all next spacing (includes CR/LF, tab, space; stop on any none spacing character) (require `FLAG_SPACECTL`)
821 * `{{{helper var}}}` : Execute custom helper then render the result
822 * `{{helper var}}` : Execute custom helper then render the HTML escaped result
823 * `{{helper "str"}}` or `{{helper 'str'}}` : Execute custom helper with string arguments (require `FLAG_ADVARNAME`)
824 * `{{helper 123 null true false undefined}}` : Pass number, true, false, null or undefined into helper
825 * `{{helper name1=var name2=var2}}` : Execute custom helper with named arguments (require `FLAG_NAMEDARG`)
826 * `{{#helper ...}}...{{/helper}}` : Execute block custom helper
827 * `{{helper (helper2 foo) bar}}` : Execute custom helpers as subexpression (require `FLAG_ADVARNAME`)
828 * `{{{{raw_block}}}} {{will_not_parsed}} {{{{/raw_block}}}}` : Raw block (require FLAG_RAWBLOCK`)
829
830 *TODO*
831
832 * https://github.com/wycats/handlebars.js/issues/1114
833 * https://github.com/wycats/handlebars.js/issues/1099
834 * https://github.com/wycats/handlebars.js/issues/1092
835
836 Framework Integration
837 ---------------------
838
839 - [Slim 3.0.x](https://github.com/endel/slim-lightncandy-view)
840 - [Laravel 4](https://github.com/samwalshnz/lightncandy-l4)
841 - [Laravel 5](https://github.com/ProAI/laravel-handlebars)
842 - [yii2](https://github.com/kfreiman/yii2-lightncandy)
843
844 Tools
845 -----
846
847 - CLI: https://github.com/PXLbros/LightnCandy-CLI