├── partials ├── option_mustache ├── option_handlebars.js ├── phpcode ├── footer ├── lcsample ├── toc ├── lccompile ├── section_columns ├── option_lightncandy ├── template ├── lcresult ├── pager ├── quicksample ├── search ├── seealso ├── versions ├── section ├── sample ├── nav ├── sections ├── html_end └── html_start ├── .gitignore ├── bookdata ├── LC-FLAG_INSTANCE.yaml ├── LC-FLAG_BESTPERFORMANCE.yaml ├── LC-FLAG_JS.yaml ├── 0006-raw.yaml ├── LC-FLAG_ERROR_EXCEPTION.yaml ├── LC-FLAG_ERROR_LOG.yaml ├── 0022-blockhelper.yaml ├── LC-FLAG_ERROR_SKIPPARTIAL.yaml ├── LC-FLAG_NOESCAPE.yaml ├── LC-FLAG_JSTRUE.yaml ├── 0015-hbcomment.yaml ├── 0026-subexpression.yaml ├── LC-FLAG_PARENT.yaml ├── LC-FLAG_JSOBJECT.yaml ├── LC-FLAG_METHOD.yaml ├── 0010-comment.yaml ├── 0021-customhelper.yaml ├── LC-FLAG_PROPERTY.yaml ├── LC-FLAG_STANDALONEPHP.yaml ├── LC-FLAG_THIS.yaml ├── LC-FLAG_SLASH.yaml ├── 0030-changecontext.yaml ├── LC-FLAG_ECHO.yaml ├── LC-FLAG_JSLENGTH.yaml ├── LC-FLAG_ELSE.yaml ├── 0011-partial.yaml ├── 0029-safestring.yaml ├── 0012-delimiter.yaml ├── 0020-lookup.yaml ├── LC-FLAG_ADVARNAME.yaml ├── LC-FLAG_NAMEDARG.yaml ├── 0013-dotnotation.yaml ├── LC-FLAG_PARTIALNEWCONTEXT.yaml ├── 0024-partialcontext.yaml ├── 0007-specific-values.yaml ├── 0005-variable-escape.yaml ├── LC-FLAG_RENDER_DEBUG.yaml ├── 0023-namedarguments.yaml ├── 0027-advancedvariable.yaml ├── 0025-parentcontext.yaml ├── LC-FLAG_HBESCAPE.yaml ├── 0004-simple-variable.yaml ├── 0031-whitespace-control.yaml ├── 0032-partial-block.yaml ├── 0014-path.yaml ├── 0018-with.yaml ├── 9900-lc-options.yaml ├── LC-FLAG_RUNTIMEPARTIAL.yaml ├── 0019-each.yaml ├── LC-FLAG_EXTHELPER.yaml ├── 0003-hello.yaml ├── 9000-quickstart.yaml ├── 0009-inverted-block.yaml ├── 0016-if.yaml ├── 0028-dynamicpartial.yaml ├── 9902-lcop-partialresolver.yaml ├── 0001-intro.yaml ├── 0017-unless.yaml ├── 9903-lcop-safestring.yaml ├── 0002-install.yaml ├── 0008-block.yaml ├── 9002-helperoptions.yaml ├── 9001-customhelper.yaml ├── 9901-lcop-helperresolver.yaml ├── 9004-partials.yaml └── 9003-helperescaping.yaml ├── .eslintrc ├── composer.json ├── .github └── workflows │ └── build.yml ├── README.md ├── package.json ├── gulpfile.js ├── style.less └── helpers.js /partials/option_mustache: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /partials/option_handlebars.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /partials/phpcode: -------------------------------------------------------------------------------- 1 | {{{code . type="php" copy=true}}} 2 | -------------------------------------------------------------------------------- /partials/footer: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | vendor/ 3 | generated/ 4 | composer.lock 5 | .exec_tmp_file 6 | render.php 7 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_INSTANCE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | same with FLAG_PROPERTY + FLAG_METHOD 3 | ref: 4 | - LC-FLAG_PROPERTY 5 | - LC-FLAG_METHOD 6 | -------------------------------------------------------------------------------- /partials/lcsample: -------------------------------------------------------------------------------- 1 | {{{anchorHTML "Sample Codes"}}} 2 |

Sample Codes

3 |
4 | {{#each lcsample}} 5 | {{> sample type="lightncandy"}} 6 | {{/each}} 7 |
8 | -------------------------------------------------------------------------------- /partials/toc: -------------------------------------------------------------------------------- 1 |
    2 | {{#each @root}} 3 |
  1. {{#if ../number}}{{addOne @index}}. {{/if}}{{title}}
  2. 4 | {{/each}} 5 |
6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | 6 | "parserOptions": { 7 | "ecmaVersion": 6 8 | }, 9 | 10 | "extends": [ 11 | "standard", 12 | "prettier" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_BESTPERFORMANCE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | same with FLAG_ECHO + FLAG_STANDALONEPHP now. This flag may be changed base on performance testing result in the future. 3 | ref: 4 | - LC-FLAG_ECHO 5 | - LC-FLAG_STANDALONEPHP 6 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_JS.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | simulate all supported JavaScript behavior, same with FLAG_JSTRUE + FLAG_JSOBJECT + FLAG_JSLENGTH. 3 | ref: 4 | - LC-FLAG_JSTRUE 5 | - LC-FLAG_JSOBJECT 6 | - LC-FLAG_JSLENGTH 7 | -------------------------------------------------------------------------------- /partials/lccompile: -------------------------------------------------------------------------------- 1 | {{{anchorHTML "Compile Flags"}}} 2 |

Compile Flags:

3 |
4 | {{#each @root}} 5 | {{#if opt_name}} 6 |
LightnCandy::{{opt_name}}
{{{description}}}
7 | {{/if}} 8 | {{/each}} 9 |
10 | -------------------------------------------------------------------------------- /partials/section_columns: -------------------------------------------------------------------------------- 1 | {{#set "samples" (hbonly)}} 2 | {{#section_builder (lookup . @section) column=true}} 3 | {{#if (eq @section "quicksample")}} 4 | {{> quicksample}} 5 | {{else}} 6 | {{> section sections=..}} 7 | {{/if}} 8 | {{/section_builder}} 9 | {{/set}} 10 | -------------------------------------------------------------------------------- /partials/option_lightncandy: -------------------------------------------------------------------------------- 1 | {{#if option}} 2 |

Used option: 3 | {{#each option.lightncandy}} 4 | {{.}} 5 | {{else}} 6 | {{#each option}} 7 | {{.}} 8 | {{/each}} 9 | {{/each}} 10 |

11 | {{/if}} 12 | -------------------------------------------------------------------------------- /partials/template: -------------------------------------------------------------------------------- 1 | {{> html_start}} 2 | {{> nav}} 3 |
4 |

{{title}}

5 |

{{{description}}}

6 | {{#section_builder .}} 7 | {{> sections}} 8 | {{/section_builder}} 9 | {{>seealso}} 10 | {{>pager}} 11 | {{>search}} 12 | {{>footer}} 13 |
14 | {{> html_end}} 15 | -------------------------------------------------------------------------------- /partials/lcresult: -------------------------------------------------------------------------------- 1 | {{#each lcresult}} 2 | {{{anchorHTML @key}}} 3 |

{{@key}}

4 | {{#if note}} 5 |

{{{note}}}

6 | {{/if}} 7 | {{#code code type="php" copy=true result=true errorlog=errorlog}} 8 |

Output

9 | {{.}} 10 | {{/code}} 11 | {{/each}} 12 | -------------------------------------------------------------------------------- /bookdata/0006-raw.yaml: -------------------------------------------------------------------------------- 1 | title: Raw Output 2 | description: 3 | The output of {{{foo}}} or {{&foo}} will not be escaped by template engine. 4 | Samples: 5 | quicksample: 6 | - template: "{{{foo}}}" 7 | data: 8 | foo: Hello & Happy 9 | - template: "{{&foo}}" 10 | data: 11 | foo:

Hello

12 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ERROR_EXCEPTION.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Throw exception when template error. 3 | lcsample: 4 | - template: "{{../foo}}}OK" 5 | compileerror: true 6 | option: 7 | - FLAG_ERROR_EXCEPTION 8 | data: 9 | - template: "{{> not_found}}" 10 | compileerror: true 11 | option: 12 | - FLAG_ERROR_EXCEPTION 13 | data: 14 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ERROR_LOG.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Print error log to stderr when template error. 3 | lcsample: 4 | - template: "{{../foo}}}OK" 5 | compileerror: true 6 | errorlog: true 7 | option: 8 | - FLAG_ERROR_LOG 9 | data: 10 | - template: "{{> not_found}}" 11 | compileerror: true 12 | errorlog: true 13 | option: 14 | - FLAG_ERROR_LOG 15 | data: 16 | -------------------------------------------------------------------------------- /partials/pager: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /bookdata/0022-blockhelper.yaml: -------------------------------------------------------------------------------- 1 | title: Block Custom Helper 2 | hbonly: true 3 | description: "You can provide block custom helper to handle a block" 4 | Samples: 5 | quicksample: 6 | - template: "{{#myloop foo}}Hello, {{.}}!{{/myloop}}" 7 | helper: 8 | myloop: > 9 | function ($arg1, $options) { 10 | return $options['fn']($arg1); 11 | } 12 | data: 13 | foo: World 14 | ref: 15 | - 9002-helperoptions 16 | -------------------------------------------------------------------------------- /partials/quicksample: -------------------------------------------------------------------------------- 1 | {{#each quicksample}} 2 | {{#if note}} 3 |

{{{note}}}

4 | {{else}} 5 |
6 | {{/if}} 7 | {{#set "standard" (data_for_render . type=standard)}} 8 | {{#section_builder @samples column=true}} 9 | {{#with (lookup . @section)}} 10 |
{{.}}
11 | {{#with ../..}} 12 | {{> sample type=.. collapse=true note=0 different=different}} 13 | {{/with}} 14 | {{/with}} 15 | {{/section_builder}} 16 | {{/set}} 17 | {{/each}} 18 | -------------------------------------------------------------------------------- /partials/search: -------------------------------------------------------------------------------- 1 | 12 | 16 | -------------------------------------------------------------------------------- /partials/seealso: -------------------------------------------------------------------------------- 1 | {{#if ref}} 2 | {{{anchorHTML "See Also..."}}} 3 |

See Also...

4 | 17 | {{/if}} 18 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ERROR_SKIPPARTIAL.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Skip 'partial not found' error or exception. Use this to align with mustache specification. 3 | lcsample: 4 | - template: "{{> not_found}}" 5 | note: Error when compile() and partial not found 6 | errorlog: ture 7 | compileerror: true 8 | option: 9 | - FLAG_ERROR_LOG 10 | - template: "OK{{> not_found}}!" 11 | note: Skip the error 12 | option: 13 | - FLAG_ERROR_EXCEPTION 14 | - FLAG_ERROR_SKIPPARTIAL 15 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_NOESCAPE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Do not do any HTML escape on {{var}}. This option is same with handlbars.js noEscape option. 3 | lcsample: 4 | - note: The default behavior is escaping values. 5 | template: "{{foo}}" 6 | data: 7 | foo: "Love & Peace" 8 | - note: Do not escaping values. 9 | template: "{{foo}}" 10 | option: 11 | - FLAG_NOESCAPE 12 | data: 13 | foo: "Love & Peace" 14 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_JSTRUE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Align boolean conversion logic with JavaScript. 3 | lcsample: 4 | - note: "The default behavior: true converts to 1, false converts to empty string." 5 | template: "{{foo}}, {{bar}}" 6 | data: 7 | foo: true 8 | bar: false 9 | - note: Same behavior with handlebars.js and mustache.js. 10 | template: "{{foo}}, {{bar}}" 11 | option: 12 | - FLAG_JSTRUE 13 | data: 14 | foo: true 15 | bar: false 16 | -------------------------------------------------------------------------------- /bookdata/0015-hbcomment.yaml: -------------------------------------------------------------------------------- 1 | title: Extended Comment 2 | hbonly: true 3 | description: "If you wanna include }} inside your comment, you can use extended comment: {{!-- comments allow }} inside it --}}" 4 | Samples: 5 | quicksample: 6 | - template: "Comment: {{! comment with }} is not ok }}" 7 | note: }} can not be placed inside a comment. 8 | - template: "Comment: {{!-- comment with }} is ok --}}" 9 | note: }} can be placed inside an extended comment. 10 | ref: 11 | - 0010-comment 12 | -------------------------------------------------------------------------------- /bookdata/0026-subexpression.yaml: -------------------------------------------------------------------------------- 1 | title: "Subexpression" 2 | hbonly: true 3 | description: "Subexpressions allow you to invoke multiple helpers inside a tag. The result of inner helper will be arguments of outer helpers." 4 | Samples: 5 | quicksample: 6 | - template: "{{#if (iszero foo)}}Foo is zero{{/if}}" 7 | helper: 8 | iszero: > 9 | function ($arg1) { 10 | return ($arg1 === 0); 11 | } 12 | option: 13 | - FLAG_ADVARNAME 14 | data: 15 | foo: 0 16 | ref: 17 | - LC-FLAG_ADVARNAME 18 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_PARENT.yaml: -------------------------------------------------------------------------------- 1 | description: | 2 | Support {{..}} or {{../foo}} in template. Otherwise, {{..}} or {{../foo}} will cause template error. 3 | lcsample: 4 | - note: "Error when compile {{..}}" 5 | compileerror: true 6 | errorlog: true 7 | template: "{{../foo}}OK" 8 | option: 9 | - FLAG_ERROR_LOG 10 | data: 11 | - note: "Enable handlebars.js extension {{..}}" 12 | template: "{{../foo}}OK" 13 | option: 14 | - FLAG_PARENT 15 | data: 16 | ref: 17 | - 0014-path 18 | -------------------------------------------------------------------------------- /partials/versions: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_JSOBJECT.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Align object or associative array conversion logic with JavaScript. 3 | lcsample: 4 | - note: "The default behavior: may cause 'Array to string conversion' warning in PHP > 5.4" 5 | template: "{{foo}}" 6 | data: 7 | foo: 8 | bar: "OK" 9 | - note: Same behavior with handlebars.js and mustache.js. 10 | template: "{{foo}}" 11 | option: 12 | - FLAG_JSOBJECT 13 | data: 14 | foo: [1, 3, 5] 15 | - template: "{{foo}}" 16 | option: 17 | - FLAG_JSOBJECT 18 | data: 19 | foo: 20 | bar: "OK" 21 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_METHOD.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Support object instance attribute access; you must apply this if your data contains object. 3 | perfalert: true 4 | lcsample: 5 | - note: "The default behavior: only do associative array lookup" 6 | template: "{{foo.key}}, {{date.getTimestamp}}" 7 | data: 8 | foo: 9 | key: OK 10 | date: new DateTime() 11 | - note: "executes method of an instance" 12 | template: "{{foo.key}}, {{date.getTimestamp}}" 13 | option: 14 | - FLAG_METHOD 15 | data: 16 | foo: 17 | key: OK 18 | date: new DateTime() 19 | -------------------------------------------------------------------------------- /bookdata/0010-comment.yaml: -------------------------------------------------------------------------------- 1 | title: Comment 2 | description: "A comment begin with! and will be ignored. For example: {{! this is a comment}}" 3 | Samples: 4 | quicksample: 5 | - template: "{{#foo}}Ya!{{! ignored this comment}}{{/foo}}" 6 | data: 7 | foo: "OK" 8 | - template: "{{#foo}}Yes{{! foo is true}}{{/foo}}\n{{^foo}}No{{! foo is false}}{{/foo}}" 9 | data: 10 | foo: true 11 | - template: "Comment example: {{! comment with }} is not ok }}" 12 | note: }} can not be placed inside a comment. 13 | ref: 14 | - 0015-hbcomment 15 | -------------------------------------------------------------------------------- /bookdata/0021-customhelper.yaml: -------------------------------------------------------------------------------- 1 | title: Custom Helper 2 | hbonly: true 3 | description: "You can provide custom helper to handle logic" 4 | Samples: 5 | quicksample: 6 | - template: "{{isequal foo bar}}" 7 | helper: 8 | isequal: > 9 | function ($arg1, $arg2) { 10 | return ($arg1 === $arg2) ? 'Yes' : 'No'; 11 | } 12 | data: 13 | foo: 1 14 | bar: "1" 15 | ref: 16 | - http://handlebarsjs.com/expressions.html 17 | - http://handlebarsjs.com/block_helpers.html 18 | - 0023-namedarguments 19 | - 9002-helperoptions 20 | - LC-FLAG_EXTHELPER 21 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_PROPERTY.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Support object instance attribute access; you must apply this if your data contains object. 3 | perfalert: true 4 | lcsample: 5 | - note: "The default behavior: only do associative array lookup" 6 | template: "{{foo.key}}, {{bar.key}}" 7 | data: 8 | foo: 9 | key: OK 10 | bar: (object)Array('key' => 'Good') 11 | - note: "do instance property lookup" 12 | template: "{{foo.key}}, {{bar.key}}" 13 | option: 14 | - FLAG_PROPERTY 15 | data: 16 | foo: 17 | key: OK 18 | bar: (object)Array('key' => 'Good') 19 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_STANDALONEPHP.yaml: -------------------------------------------------------------------------------- 1 | description: | 2 | Generate stand-alone PHP codes which can be execute without LightnCandy\Runtime class. The compiled PHP code will contain scoped Runtime functions and the file size will be bigger. And, the performance will faster 5 ~10%. 3 | lcsample: 4 | - template: "{{{foo}}}" 5 | note: Default, require LightnCandy\Runtime. 6 | showcode: true 7 | option: 8 | - FLAG_JSTRUE 9 | - template: "{{{foo}}}" 10 | note: Stand alone, include used LightnCandy\Runtime functions. 11 | showcode: true 12 | option: 13 | - FLAG_STANDALONEPHP 14 | - FLAG_JSTRUE 15 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_THIS.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Resolve {{this}} as {{.}} . Otherwise, {{this}} will lookup the variable 'this'. 3 | lcsample: 4 | - note: "The default behavior: {{this}} will look for the value of 'this' key." 5 | template: "{{this}} == {{.}}" 6 | option: 7 | - FLAG_JSOBJECT 8 | data: 9 | this: is good 10 | - note: "{{this}} means {{.}}" 11 | template: "{{this}} == {{.}}" 12 | option: 13 | - FLAG_JSOBJECT 14 | - FLAG_THIS 15 | data: 16 | this: is good 17 | ref: 18 | - 0013-dotnotation 19 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_SLASH.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | "Skip a delimiter when it behind \\ ." 3 | lcsample: 4 | - note: "The default behavior: \\ do nothing" 5 | template: "Yes, {{foo}} is \\{{foo}}" 6 | data: 7 | foo: OK 8 | - note: "You can use this way to display {{ when you do not like to use \\" 9 | template: "Show {{#with \"{{\"}}{{.}}{{/with}} in this way" 10 | data: 11 | foo: OK 12 | - note: "Use \\ to escape a delimiter" 13 | template: "Yes, {{foo}} is \\{{foo}}" 14 | option: 15 | - FLAG_SLASH 16 | data: 17 | foo: OK 18 | -------------------------------------------------------------------------------- /bookdata/0030-changecontext.yaml: -------------------------------------------------------------------------------- 1 | title: Change Context 2 | hbonly: true 3 | description: "Custom helpers can render the inner block with new context" 4 | Samples: 5 | quicksample: 6 | - template: "{{#foo}}Hello, {{this}}{{/foo}}" 7 | option: 8 | lightncandy: 9 | - FLAG_THIS 10 | helper: 11 | foo: > 12 | function ($options) { 13 | // Apply child 'bar' of current context into inner block 14 | return $options['fn']($options['_this']['bar']); 15 | } 16 | data: 17 | bar: World 18 | ref: 19 | - 0021-customhelper 20 | - 9001-customhelper 21 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ECHO.yaml: -------------------------------------------------------------------------------- 1 | description: | 2 | compile to echo 'a', $b, 'c'; to improve performance. This will slow down rendering when the template and data are simple, but will improve 5% ~ 10% when the data is big and looping in the template. 3 | lcsample: 4 | - template: "A{{{foo}}}C" 5 | note: compile to 'a' . $b . 'c' when do not use FLAG_ECHO 6 | showcode: true 7 | option: 8 | - FLAG_JSTRUE 9 | - template: "A{{{foo}}}C" 10 | note: compile to echo 'a', $b, 'c' by default 11 | showcode: true 12 | option: 13 | - FLAG_ECHO 14 | ref: 15 | - LC-FLAG_BESTPERFORMANCE 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zordius/handlebars-cookbook", 3 | "description": "A cookbook of handlebars and mustache, focus on handlebars.js , mustache.js and lightncandy usage", 4 | "homepage": "https://github.com/zordius/HandlebarsCookbook", 5 | "keywords": ["handlebars", "mustache", "template", "logicless"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Zordius Chen", 10 | "email": "1201409+zordius@users.noreply.github.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0" 15 | }, 16 | "require-dev": { 17 | "zordius/lightncandy": "1.2.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_JSLENGTH.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | "support {{foo.length}} when foo is an array (simulate JavaScript Array.proto.length behavior)" 3 | lcsample: 4 | - note: "The default behavior: foo.length do not look for Array.length." 5 | template: "{{foo.length}}, {{bar.length}}" 6 | data: 7 | foo: [1, 2, 3] 8 | bar: 9 | length: go 10 | width: 400 11 | - note: "foo.length will be Array.length." 12 | template: "{{foo.length}}, {{bar.length}}" 13 | option: 14 | - FLAG_JSLENGTH 15 | data: 16 | foo: [1, 2, 3] 17 | bar: 18 | length: go 19 | width: 400 20 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ELSE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Resolve {{else}} or {{^}} as handlebars specification. Otherwise, {{else}} will be resolved as normal variable, and {{^}} will cause template error. 3 | lcsample: 4 | - note: "The default behavior: {{else}} will look for the value of 'else' key." 5 | template: "{{else}}" 6 | data: 7 | else: OK 8 | - template: "{{#if foo}}FOO{{else}}BAR{{/if}}" 9 | data: 10 | foo: true 11 | - template: "{{#if foo}}FOO{{else}}BAR{{/if}}" 12 | note: "{else}}will do the else logic when FLAG_ELSE enabled." 13 | option: 14 | - FLAG_ELSE 15 | data: 16 | foo: true 17 | -------------------------------------------------------------------------------- /bookdata/0011-partial.yaml: -------------------------------------------------------------------------------- 1 | title: Partials 2 | description: 3 | "Partials begins with >, for example: {{> partial_name}}." 4 | Samples: 5 | quicksample: 6 | - template: "Hello, {{> world}}" 7 | partial: 8 | world: "{{foo}}!" 9 | data: 10 | foo: World 11 | - template: "{{>hello}}, {{> world}}\nFoo: {{#bar}}{{> world}}{{/bar}}" 12 | option: 13 | lightncandy: 14 | - FLAG_JSOBJECT 15 | note: The partial will be rendered with current context 16 | partial: 17 | world: "{{foo}}!" 18 | hello: "{{bar}}!" 19 | data: 20 | foo: World 21 | bar: 22 | foo: YA! 23 | ref: 24 | - 0024-partialcontext 25 | -------------------------------------------------------------------------------- /bookdata/0029-safestring.yaml: -------------------------------------------------------------------------------- 1 | title: Use SafeString 2 | hbonly: true 3 | description: "You can use SafeString class to wrap your custom helper result, then it will not be escaped by handlebars.js/LightnCandy." 4 | Samples: 5 | quicksample: 6 | - template: "{{foo}}, {{bar}}" 7 | option: 8 | lightncandy: 9 | - FLAG_JSTRUE 10 | helper: 11 | foo: > 12 | function () { 13 | return 'You&Me'; 14 | } 15 | bar: > 16 | function () { 17 | return new \LightnCandy\SafeString('Now&Then'); 18 | } 19 | data: 20 | null 21 | ref: 22 | - 0021-customhelper 23 | - 9001-customhelper 24 | - 9003-helperescaping 25 | -------------------------------------------------------------------------------- /bookdata/0012-delimiter.yaml: -------------------------------------------------------------------------------- 1 | title: Set Delimiter 2 | description: "Use = to set delimiters from {{ and }} to customized tag" 3 | Samples: 4 | quicksample: 5 | - template: "Hello, {{foo}}.\n{{=<* *>=}}Now, <*foo*>" 6 | note: handlebars.js do not support changing delimiters 7 | standard: mustache 8 | fail: 9 | handlebars.js: true 10 | data: 11 | foo: World 12 | - template: "Hello, {{=[[ ]]=}}\n[[> world]]\n[[> bar]]" 13 | note: Set delimiters will not change into a partial 14 | standard: mustache 15 | fail: 16 | handlebars.js: true 17 | partial: 18 | world: "{{foo}}!" 19 | bar: "<>" 20 | data: 21 | foo: World 22 | -------------------------------------------------------------------------------- /bookdata/0020-lookup.yaml: -------------------------------------------------------------------------------- 1 | title: "Built-in helper - lookup" 2 | hbonly: true 3 | description: "Lookup value by provided base and key" 4 | Samples: 5 | quicksample: 6 | - template: "{{#each foo}}\n{{lookup ../bar .}}:{{.}}!\n{{/each}}" 7 | option: 8 | lightncandy: 9 | - FLAG_PARENT 10 | data: 11 | foo: [Hello, World] 12 | bar: 13 | Hello: first 14 | World: second 15 | - template: "{{#each foo}}{{@key}}:{{.}} => {{lookup ../bar .}}\n{{/each}}" 16 | option: 17 | lightncandy: 18 | - FLAG_PARENT 19 | - FLAG_SPVARS 20 | data: 21 | foo: 22 | Hello: first 23 | World: second 24 | bar: 25 | first: 1 26 | second: 2 27 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_ADVARNAME.yaml: -------------------------------------------------------------------------------- 1 | description: | 2 | Support {{foo.[0].[#te#st].bar}} or {{"some string"}} or {{helper (subexpression ...)}} handlebars style extensions. 3 | lcsample: 4 | - template: "{{[foo.bar]}}" 5 | note: Default to lookup [foo then bar] 6 | data: 7 | foo.bar: GOOD! 8 | foo: 9 | bar: BAD! 10 | "[foo": 11 | "bar]": Default! 12 | - template: "{{[foo.bar]}}" 13 | note: Protect variable names by [ and ] when they contains special characters. 14 | option: 15 | - FLAG_ADVARNAME 16 | data: 17 | foo.bar: GOOD! 18 | foo: 19 | bar: BAD! 20 | ref: 21 | - 0014-path 22 | - 0027-advancedvariable 23 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_NAMEDARG.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Enable named arguments support for partials and helpers. 3 | lcsample: 4 | - template: "{{foo abc=123}}!" 5 | note: Default is not supporting named arguments, 6 | data: 7 | abc: NO 8 | "abc=123": YES 9 | helper: 10 | foo: > 11 | function ($a) { 12 | return "OK=$a"; 13 | } 14 | - template: "{{foo abc=123}}!" 15 | note: Default is not supporting named arguments, 16 | data: 17 | abc: NO 18 | "abc=123": YES 19 | option: 20 | - FLAG_NAMEDARG 21 | helper: 22 | foo: > 23 | function ($option) { 24 | return "OK={$option['hash']['abc']}"; 25 | } 26 | ref: 27 | - 0021-customhelper 28 | - 0022-blockhelper 29 | -------------------------------------------------------------------------------- /bookdata/0013-dotnotation.yaml: -------------------------------------------------------------------------------- 1 | title: Dot Notation 2 | description: Lots of mustache implementation extends the variable lookup syntax with dot notation. 3 | Samples: 4 | quicksample: 5 | - template: "Hello, {{foo.bar}}" 6 | note: "{{foo.bar}} will lookup the bar under the foo." 7 | data: 8 | foo: 9 | bar: World! 10 | - template: "Hello, {{foo.bar}}\n{{foo.bar.moo}}" 11 | note: It is fine when something is not found. 12 | data: 13 | foo: 14 | bar: World! 15 | - template: "{{#people}}\nHello, {{.}}!\n{{/people}}" 16 | note: {{.}} means current context. 17 | data: 18 | people: [John, Peter, Mary] 19 | ref: 20 | - 0014-path 21 | - LC-FLAG_THIS 22 | -------------------------------------------------------------------------------- /partials/section: -------------------------------------------------------------------------------- 1 |

{{@section}}

2 | {{#with (lookup . @section)}} 3 | {{{isStringThenOutput . tag='p'}}} 4 | {{#if content}} 5 | {{{content}}} 6 | {{/if}} 7 | {{#if file}} 8 |

file name: {{file}}

9 | {{/if}} 10 | {{#if code}} 11 | {{{code code type=(code_type) copy=copy}}} 12 | {{/if}} 13 | {{#if code_use}} 14 | {{{code (code_for_require @section) type=(code_type)}}} 15 | {{/if}} 16 | {{#if collectcode}} 17 | {{#code (str_join 18 | (code_for_require @section) 19 | (join (collect (collect ../sections @section ignore=collectcode key="_key") "code" comment="_key") " 20 | ") sep=" 21 | " 22 | ) type=(code_type) copy=true result=true}} 23 |

Output

24 |
{{.}}
25 | {{/code}} 26 | {{/if}} 27 | {{/with}} 28 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_PARTIALNEWCONTEXT.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Create a new context for the partial. This option is same with handlbars.js explicitPartialContext option. 3 | lcsample: 4 | - note: The default behavior is reusing current context when there is no argument for the partial. 5 | template: "{{> foo}}" 6 | partial: 7 | foo: "BAR:{{bar}}" 8 | data: 9 | bar: "Good!" 10 | - note: Create an empty object as new context for the partial without argument. FLAG_RUNTIMEPARTIAL is required. 11 | template: "{{> foo}}" 12 | partial: 13 | foo: "BAR:{{bar}}" 14 | data: 15 | bar: "Good!" 16 | option: 17 | - FLAG_PARTIALNEWCONTEXT 18 | - FLAG_RUNTIMEPARTIAL 19 | ref: 20 | - LC-FLAG_RUNTIMEPARTIAL 21 | -------------------------------------------------------------------------------- /partials/sample: -------------------------------------------------------------------------------- 1 | {{#render}} 2 | {{#if note}} 3 |

{{{note}}}

4 | {{/if}} 5 | {{> (str_join "option" type sep="_")}} 6 | {{#if partial}} 7 | Partials: 8 | 9 | {{#each partial}} 10 | 11 | {{/each}} 12 |
{{@key}}{{.}}
13 | {{/if}} 14 |
15 |
Data:{{{code @codeData type=@codeType}}}
16 |
17 |
Template:{{{code @template type="handlebars"}}}
18 |
Result:{{{code @result.output type="handlebars" class=(result_class different=different)}}}
19 |
20 |
21 | {{#if helper}} 22 |

Check the code to know used helper codes

23 | {{/if}} 24 | {{{code @code type=@codeType copy=true collapse=collapse}}} 25 | {{/render}} 26 | -------------------------------------------------------------------------------- /bookdata/0024-partialcontext.yaml: -------------------------------------------------------------------------------- 1 | title: Change Partial Context 2 | hbonly: true 3 | description: "You can change partial context by providing more arguments after the partial name" 4 | Samples: 5 | quicksample: 6 | - template: "{{>foo bar}}" 7 | note: The first argument will be new context for the partial 8 | partial: 9 | foo: "{{moo}}" 10 | option: 11 | lightncandy: 12 | - FLAG_RUNTIMEPARTIAL 13 | data: 14 | bar: 15 | moo: GOOD! 16 | - template: "{{>foo bar moo='abc'}}" 17 | note: Named arguments will be merged into context 18 | partial: 19 | foo: "{{moo}}" 20 | option: 21 | lightncandy: 22 | - FLAG_RUNTIMEPARTIAL 23 | - FLAG_NAMEDARG 24 | data: 25 | bar: 26 | moo: GOOD! 27 | ref: 28 | - LC-FLAG_RUNTIMEPARTIAL 29 | - LC-FLAG_NAMEDARG 30 | -------------------------------------------------------------------------------- /bookdata/0007-specific-values.yaml: -------------------------------------------------------------------------------- 1 | title: Specific Values 2 | description: 3 | Input values other than string will be rendered in specific way. 4 | Samples: 5 | quicksample: 6 | - template: "{{{foo}}}" 7 | option: 8 | lightncandy: 9 | - FLAG_JSTRUE 10 | data: 11 | foo: true 12 | - template: "{{{foo}}}" 13 | option: 14 | lightncandy: 15 | - FLAG_JSTRUE 16 | data: 17 | foo: false 18 | - template: "{{{foo}}}" 19 | option: 20 | lightncandy: 21 | - FLAG_JSOBJECT 22 | data: 23 | foo: 24 | bar: foo is object 25 | - template: "{{{foo}}}" 26 | option: 27 | lightncandy: 28 | - FLAG_JSOBJECT 29 | data: 30 | foo: [is, an, array] 31 | - template: "{{{foo}}}" 32 | option: 33 | lightncandy: 34 | - FLAG_JSOBJECT 35 | fail: 36 | mustache: true 37 | data: 38 | -------------------------------------------------------------------------------- /partials/nav: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /bookdata/0005-variable-escape.yaml: -------------------------------------------------------------------------------- 1 | title: Variable Escaping 2 | description: 3 | The output of {{foo}} will be escaped by template engine. 4 | Samples: 5 | quicksample: 6 | - template: "{{foo}}" 7 | data: 8 | foo: Hello & Happy 9 | - template: "{{foo}}" 10 | note: mustache.js also escapes / 11 | different: true 12 | data: 13 | foo:

Hello

14 | - template: "{{foo}}" 15 | note: handlebars.js escapes ' , ` and = in his way 16 | different: true 17 | data: 18 | foo: Quotes as ' and " and ` , equal is = 19 | - template: "{{foo}}" 20 | note: The escaping behavior of Lightncandy can be configured to same with handlebars exactly. 21 | different: true 22 | option: 23 | lightncandy: [FLAG_HBESCAPE] 24 | data: 25 | foo: Quotes as ' and " and ` , equal is = 26 | ref: 27 | - LC-FLAG_HBESCAPE 28 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_RENDER_DEBUG.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Generate debug template to show error when rendering. With this flag, the performance of rendering may be slowed. 3 | lcsample: 4 | - template: "OK!{{foo}}" 5 | note: Compile template to debug version, output HTML error hint for browser when variable not found. 6 | option: 7 | - FLAG_RENDER_DEBUG 8 | renderoption: 9 | debug: 10 | - DEBUG_TAGS_HTML 11 | - template: "OK!{{foo}}" 12 | note: Compile template to debug version, output ANSI error hint for terminal when variable not found. 13 | option: 14 | - FLAG_RENDER_DEBUG 15 | renderoption: 16 | debug: 17 | - DEBUG_TAGS_ANSI 18 | - template: "OK!{{foo}}" 19 | note: Compile template to debug version, output to PHP error log when variable not found. 20 | option: 21 | - FLAG_RENDER_DEBUG 22 | renderoption: 23 | debug: 24 | - DEBUG_ERROR_LOG 25 | 26 | ref: 27 | - 9003-helperescaping 28 | -------------------------------------------------------------------------------- /bookdata/0023-namedarguments.yaml: -------------------------------------------------------------------------------- 1 | title: Named Arguments 2 | hbonly: true 3 | description: "You can pass named arguments into custom helper or partial." 4 | Samples: 5 | quicksample: 6 | - template: "{{helper bar foo=123}}" 7 | helper: 8 | helper: > 9 | function ($arg0, $options) { 10 | return $arg0 . ',' . $options['hash']['foo']; 11 | } 12 | option: 13 | lightncandy: 14 | - FLAG_NAMEDARG 15 | data: 16 | bar: ABC 17 | foo: DEF 18 | - template: "{{{link href=foo title=bar}}}" 19 | helper: 20 | link: > 21 | function ($options) { 22 | return '' . $options['hash']['title'] . ''; 23 | } 24 | option: 25 | lightncandy: 26 | - FLAG_NAMEDARG 27 | data: 28 | bar: Yahoo! 29 | foo: http://yahoo.com/ 30 | ref: 31 | - 0021-customhelper 32 | - 0022-blockhelper 33 | - 0024-partialcontext 34 | - LC-FLAG_RUNTIMEPARTIAL 35 | - LC-FLAG_NAMEDARG 36 | -------------------------------------------------------------------------------- /partials/sections: -------------------------------------------------------------------------------- 1 | {{#main_section}} 2 | {{#if (eq @section "lcsample")}} 3 | {{>lcsample}} 4 | {{else if (eq @section "lccompile")}} 5 | {{>lccompile}} 6 | {{else if (eq @section "lcresult")}} 7 | {{>lcresult}} 8 | {{else if (eq @section "hbonly")}} 9 |

This is a handlebars.js extension, mustache do not support this.

10 | {{else if (eq @section "perfalert")}} 11 |

This flag cause bad rendering performance, do not enable it unless you need this feature anyway.

12 | {{else}} 13 | {{{anchorHTML @section}}} 14 |

{{@section}}

15 | {{#if (lookup (lookup . @section) "toc")}} 16 | {{> toc}} 17 | {{else if (lookup (lookup . @section) "version")}} 18 | {{> versions}} 19 | {{else if (lookup (lookup . @section) "phpcode")}} 20 | {{> phpcode (lookup (lookup . @section) "phpcode")}} 21 | {{else}} 22 | {{isStringThenOutput . tag='p'}} 23 | {{>section_columns}} 24 | {{/if}} 25 | {{/if}} 26 | {{/main_section}} 27 | -------------------------------------------------------------------------------- /partials/html_end: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /bookdata/0027-advancedvariable.yaml: -------------------------------------------------------------------------------- 1 | title: "Advanced Variable" 2 | hbonly: true 3 | description: Some special charactors are not allowed to be in an expression. You can use [ and ] to surround an expression, then you can place special charactors inside it. 4 | Samples: 5 | quicksample: 6 | - template: "{{foo.bar}}" 7 | note: . means path 8 | option: 9 | - FLAG_ADVARNAME 10 | data: 11 | foo: 12 | bar: path 13 | "foo.bar": protected 14 | - template: "{{[foo.bar]}}" 15 | note: Now 'foo.bar' is protected by [ and ] and means to look up the key 'foo.bar' . 16 | option: 17 | - FLAG_ADVARNAME 18 | data: 19 | foo: 20 | bar: path 21 | "foo.bar": protected 22 | - template: "{{[1 2]}}" 23 | note: Now '1 2' also feels good. 24 | option: 25 | - FLAG_ADVARNAME 26 | data: 27 | "1 2": I feel good. 28 | - template: "{{[1 2].[#!@]}}" 29 | option: 30 | - FLAG_ADVARNAME 31 | data: 32 | "1 2": 33 | "#!@": Safe! 34 | ref: 35 | - LC-FLAG_ADVARNAME 36 | -------------------------------------------------------------------------------- /bookdata/0025-parentcontext.yaml: -------------------------------------------------------------------------------- 1 | title: "Parent Context" 2 | hbonly: true 3 | description: "Everytime when you enter a new block and current context changed, the new context will be pushed into the context stack. The old context becomes parent context, you can use {{..}} to access parent context." 4 | Samples: 5 | quicksample: 6 | - template: "{{#each foo}}{{bar}}, {{../bar}}{{/each}}" 7 | option: 8 | - FLAG_PARENT 9 | data: 10 | foo: 11 | - bar: Yes! 12 | bar: Another World 13 | - template: > 14 | {{#each foo}} 15 | {{#each bar}} 16 | {{.}},{{../qoo}},{{../../qoo}} 17 | {{/each}} 18 | {{/each}} 19 | option: 20 | - FLAG_PARENT 21 | data: 22 | foo: 23 | 0: 24 | bar: 25 | 0: ABC 26 | 1: DEF 27 | qoo: GHI 28 | qoo: STU 29 | 1: 30 | bar: 31 | 0: V 32 | 1: W 33 | qoo: XYZ 34 | qoo: YO! 35 | qoo: YA! 36 | ref: 37 | - 0008-block 38 | - 0014-path 39 | - 0019-each 40 | - 0022-blockhelper 41 | - 0024-partialcontext 42 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_HBESCAPE.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Align {{foo}} escaping logic with handlebars.js. This causes ', = and ` be escaped in different way. 3 | lcsample: 4 | - note: The default escaping is by htmlentities only. 5 | template: "{{foo}}" 6 | data: 7 | foo: "Love & Peace" 8 | - note: "Escaping ':" 9 | template: "{{foo}}" 10 | data: 11 | foo: "Single Quote is '" 12 | - note: "Escaping ':" 13 | template: "{{foo}}" 14 | option: 15 | - FLAG_HBESCAPE 16 | data: 17 | foo: "Single Quote is '" 18 | - note: "Escaping `:" 19 | template: "{{foo}}" 20 | data: 21 | foo: "Backtick is `" 22 | - note: "Escaping `:" 23 | template: "{{foo}}" 24 | option: 25 | - FLAG_HBESCAPE 26 | data: 27 | foo: "Backtick is `" 28 | - note: "Escaping =:" 29 | template: "{{foo}}" 30 | data: 31 | foo: "Equal is =" 32 | - note: "Escaping =:" 33 | template: "{{foo}}" 34 | option: 35 | - FLAG_HBESCAPE 36 | data: 37 | foo: "Equal is =" 38 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: handlebars cookbook 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: '14' 15 | - name: Setup PHP 16 | uses: shivammathur/setup-php@v2 17 | with: 18 | php-version: 7.4 19 | extensions: mbstring, intl 20 | ini-values: post_max_size=256M, max_execution_time=180 21 | - name: settle libs 22 | run: | 23 | npm install 24 | composer install 25 | - name: build document 26 | run: npm run build 27 | - name: pull generated document to github 28 | if: ${{ github.ref == 'refs/heads/master' }} 29 | env: 30 | GHTK: ${{ secrets.GHTK }} 31 | run: | 32 | git config --global user.name "GithubAction" 33 | git config --global user.email "zordius@users.noreply.gihub.com" 34 | cd generated 35 | git init -b gh-pages 36 | git add . 37 | git commit -m "docs: auto deployed to Github Pages from @${GITHUB_SHA} [ci skip]" 38 | git push --force --quiet "https://${GHTK}@github.com/zordius/HandlebarsCookbook.git" gh-pages 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HandlebarsCookbook 2 | ================== 3 | 4 | [![handlebars cookbook](https://github.com/zordius/HandlebarsCookbook/actions/workflows/build.yml/badge.svg)](https://github.com/zordius/HandlebarsCookbook/actions/workflows/build.yml) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE.txt) 5 | 6 | A cookbook of handlebars and mustache, focus on handlebars.js , mustache.js and lightncandy usage. 7 | 8 | https://zordius.github.io/HandlebarsCookbook/ 9 | 10 | How to Build 11 | ------------ 12 | 13 | * npm install 14 | * composer install 15 | * npm run build 16 | 17 | How to Develop 18 | -------------- 19 | 20 | * npm install 21 | * composer install 22 | * npm start 23 | * open http://localhost:3000/ 24 | 25 | Used Software 26 | ------------- 27 | 28 | * https://packagist.org/packages/zordius/lightncandy 29 | * https://www.npmjs.com/package/browser-sync 30 | * https://www.npmjs.com/package/del 31 | * https://www.npmjs.com/package/gulp 32 | * https://www.npmjs.com/package/gulp-eslint 33 | * https://www.npmjs.com/package/gulp-less 34 | * https://www.npmjs.com/package/handlebars 35 | * https://www.npmjs.com/package/js-yaml 36 | * https://www.npmjs.com/package/mustache 37 | * https://www.npmjs.com/package/prismjs 38 | * https://www.npmjs.com/package/shortid 39 | -------------------------------------------------------------------------------- /bookdata/0004-simple-variable.yaml: -------------------------------------------------------------------------------- 1 | title: Simple Variable 2 | description: 3 | Use {{foo}} will lookup the foo value from the input data. 4 | Samples: 5 | quicksample: 6 | - template: "{{foo}}" 7 | data: 8 | foo: Hello 9 | - template: "{{foo}}" 10 | note: Missing value will be rendered as empty string 11 | data: 12 | bar: missing 13 | - template: "{{true}}" 14 | note: {{true}} will lookup on the key:true. 15 | data: 16 | true: World 17 | - template: "{{false}}" 18 | note: {{false}} will lookup on the key:false. 19 | data: 20 | false: Earth 21 | - template: "{{undefined}}" 22 | note: {{undefined}} will lookup on the key:undefined. 23 | data: 24 | undefined: Word 25 | - template: "{{1}}" 26 | note: {{1}} will lookup on the second item. 27 | data: 28 | - Hello 29 | - World 30 | - template: "{{ foo }}" 31 | note: Spaces before or after the name are allowed. 32 | data: 33 | foo: Good 34 | - template: | 35 | {{ 36 | foo 37 | 38 | }} 39 | note: Line changes before or after the name are allowed. 40 | data: 41 | foo: Good 42 | -------------------------------------------------------------------------------- /bookdata/0031-whitespace-control.yaml: -------------------------------------------------------------------------------- 1 | title: Whitespace Control 2 | hbonly: true 3 | description: "By default the line change for standalone tags will be removed. If you wanna remove more line changes or spaces, you can use ~ in left or right side of a tag." 4 | Samples: 5 | quicksample: 6 | - template: | 7 | Line 1 8 | {{foo}} 9 | Line 3 10 | {{foo}} 11 | Line 5 12 | {{foo}} 13 | Line 7 14 | note: Line change of standalone tags will not be removed 15 | data: 16 | foo: 1 17 | - template: | 18 | Line 1 19 | {{#with foo}} 20 | Line 3 21 | {{else}} 22 | Line 5 23 | {{/with}} 24 | Line 7 25 | note: Line change of standalone block tags and {{else}} will be removed 26 | data: 27 | foo: 1 28 | option: 29 | lightncandy: 30 | - FLAG_THIS 31 | - FLAG_ELSE 32 | - template: | 33 | Line 1 34 | {{foo~}} 35 | Line 3 36 | {{~foo~}} 37 | Line 5 38 | {{~foo}} 39 | Line 7 40 | note: Line changes and spaces will be removed when you use whitespace control charactor 41 | data: 42 | foo: 1 43 | option: 44 | lightncandy: 45 | - FLAG_THIS 46 | - FLAG_ELSE 47 | ref: 48 | -------------------------------------------------------------------------------- /bookdata/0032-partial-block.yaml: -------------------------------------------------------------------------------- 1 | title: Partial Block 2 | hbonly: true 3 | description: "By default it will cause error when a template try to render a missing partial. You can provide failover partial by partial block, it will only rendered when the partial is not provided" 4 | Samples: 5 | quicksample: 6 | - template: | 7 | {{#> testPartial}} 8 | ERROR: testPartial is not found! 9 | {{/testPartial}} 10 | note: Provide failover partial 11 | option: 12 | lightncandy: 13 | - FLAG_RUNTIMEPARTIAL 14 | - template: | 15 | {{#> testPartial}} 16 | ERROR: testPartial is not found! 17 | {{/testPartial}} 18 | note: When partial provided, partial block will be ignored 19 | partial: 20 | testPartial: "Yes I am partial." 21 | option: 22 | lightncandy: 23 | - FLAG_RUNTIMEPARTIAL 24 | - template: | 25 | {{#> testPartial}} 26 | ERROR: testPartial is not found! 27 | {{/testPartial}} 28 | note: use {{> @partial-block}} to render partial block 29 | partial: 30 | testPartial: "Yes I am partial. If I am not here, '{{> @partial-block}}' will replace me." 31 | option: 32 | lightncandy: 33 | - FLAG_RUNTIMEPARTIAL 34 | - FLAG_SPVARS 35 | ref: 36 | - http://handlebarsjs.com/partials.html 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handlebars.cookbook", 3 | "version": "0.0.1", 4 | "description": "A cookbook of handlebars and mustache, focus on handlebars.js , mustache.js and lightncandy usage", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gulp", 8 | "build": "gulp build", 9 | "lint": "gulp lint" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/zordius/HandlebarsCookbook.git" 14 | }, 15 | "keywords": [ 16 | "handlebars", 17 | "mustache", 18 | "template", 19 | "logicless" 20 | ], 21 | "author": "1201409+zordius@users.noreply.github.com", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/zordius/HandlebarsCookbook/issues" 25 | }, 26 | "homepage": "https://github.com/zordius/HandlebarsCookbook", 27 | "devDependencies": { 28 | "browser-sync": "^2.26.7", 29 | "del": "^5.1.0", 30 | "eslint-config-prettier": "^6.1.0", 31 | "eslint-config-standard": "^14.1.0", 32 | "eslint-plugin-import": "^2.18.2", 33 | "eslint-plugin-node": "^9.1.0", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-standard": "^4.0.1", 36 | "gulp": "^4.0.2", 37 | "gulp-eslint": "^6.0.0", 38 | "gulp-less": "4.0.1", 39 | "handlebars": "4.7.7", 40 | "js-yaml": "^3.13.1", 41 | "mustache": "4.2.0", 42 | "prismjs": "^1.27.0", 43 | "shortid": "^2.2.14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const del = require('del') 3 | const eslint = require('gulp-eslint') 4 | const gulpless = require('gulp-less') 5 | 6 | const browserSync = require('browser-sync').create() 7 | const lintFiles = ['*.js'] 8 | const lessFiles = ['*.less'] 9 | 10 | const clean = () => del('generated/*.html') 11 | 12 | const less = () => 13 | gulp.src(lessFiles) 14 | .pipe(gulpless()) 15 | .pipe(gulp.dest('generated')) 16 | .pipe(browserSync.stream()) 17 | 18 | const buildTask = (done) => { 19 | delete require.cache[require.resolve('./build')] 20 | require('./build')() 21 | browserSync.reload() 22 | done() 23 | } 24 | 25 | const build = gulp.series(gulp.parallel(clean, less), buildTask) 26 | 27 | const watchless = gulp.series(less, () => { 28 | gulp.watch(lessFiles, less) 29 | }) 30 | 31 | const lint = () => 32 | gulp.src(lintFiles) 33 | .pipe(eslint()) 34 | .pipe(eslint.format()) 35 | 36 | const watch = gulp.series(build, () => { 37 | process.env.NODE_DEV = 'development' 38 | gulp.watch(lintFiles, gulp.series(lint, build)) 39 | gulp.watch(['partials/*', 'bookdata/*'], build) 40 | }) 41 | 42 | const browsersync = () => { 43 | browserSync.init({ 44 | open: false, 45 | server: { 46 | baseDir: "./generated/" 47 | } 48 | }) 49 | } 50 | 51 | module.exports = { 52 | build, 53 | lint, 54 | default: gulp.parallel(watch, watchless, browsersync) 55 | } 56 | -------------------------------------------------------------------------------- /bookdata/0014-path.yaml: -------------------------------------------------------------------------------- 1 | title: Path 2 | description: Handlebars extends mustache variable lookup with many path syntax 3 | hbonly: true 4 | Samples: 5 | quicksample: 6 | - template: "{{#people}}\n{{..}}!\n{{/people}}" 7 | note: {{..}} means parent context. 8 | option: 9 | lightncandy: 10 | - FLAG_JSOBJECT 11 | - FLAG_PARENT 12 | data: 13 | people: [John, Peter, Mary] 14 | - template: "{{#foo.bar}}\n{{../moo}}\n{{/foo.bar}}" 15 | note: "{{../foo}} will lookup parent context, then search for foo." 16 | option: 17 | lightncandy: 18 | - FLAG_PARENT 19 | data: 20 | foo: 21 | bar: 22 | moo: No 23 | moo: Yes! 24 | - template: | 25 | {{#foo.bar}}{{!<=first context push}} 26 | {{#moo}}{{!<=second context push}} 27 | {{zoo}} , {{../../moo}} 28 | {{/moo}} 29 | {{moo.zoo}} , {{../foo.bar.moo.zoo}} 30 | {{/foo.bar}} 31 | note: "Everytime context changes, the new context will be pushed into the context stack. {{../../moo}} means using the item before previous item in the context stack as the base, then search for moo." 32 | option: 33 | lightncandy: 34 | - FLAG_JSOBJECT 35 | - FLAG_PARENT 36 | data: 37 | foo: 38 | bar: 39 | moo: 40 | zoo: No 41 | moo: Yes! 42 | ref: 43 | - 0008-block 44 | - 0022-blockhelper 45 | - 0025-parentcontext 46 | -------------------------------------------------------------------------------- /bookdata/0018-with.yaml: -------------------------------------------------------------------------------- 1 | title: "Built-in block helper - #with" 2 | hbonly: true 3 | description: "Switch current context" 4 | Samples: 5 | quicksample: 6 | - template: "{{#with foo}}{{bar}}{{/with}}" 7 | data: 8 | foo: 9 | bar: Yes! 10 | - template: "{{#with foo.bar}}{{hey}}, {{ha}}{{/with}}" 11 | data: 12 | foo: 13 | bar: 14 | hey: Hello 15 | ha: world 16 | - template: "{{#with foo.bar}}{{moo}}, {{../bar}}{{/with}}" 17 | note: Use {{..}} to access original context. 18 | option: 19 | lightncandy: 20 | - FLAG_PARENT 21 | data: 22 | foo: 23 | bar: 24 | moo: Hello 25 | bar: world 26 | - template: "{{#with 0}}Current context:{{.}}{{/with}}" 27 | note: With 0 you get 0 28 | option: 29 | lightncandy: 30 | - FLAG_PARENT 31 | - template: "{{#with 1}}Current context:{{.}}{{/with}}" 32 | note: With 1 you get 1 33 | option: 34 | lightncandy: 35 | - FLAG_PARENT 36 | - FLAG_JSOBJECT 37 | - template: "{{#with .}}Current context:{{.}}{{/with}}" 38 | note: With empty array you will skip the included section 39 | option: 40 | lightncandy: 41 | - FLAG_PARENT 42 | data: [] 43 | - template: "{{#with .}}Current context:{{.}}{{/with}}" 44 | note: "With false you will skip the included section" 45 | option: 46 | lightncandy: 47 | - FLAG_PARENT 48 | data: [] 49 | ref: 50 | - 0008-block 51 | - 0025-parentcontext 52 | -------------------------------------------------------------------------------- /bookdata/9900-lc-options.yaml: -------------------------------------------------------------------------------- 1 | title: LightnCandy options 2 | description: > 3 | You can apply more options by running LightnCandy::compile($template, $options). 4 | Example: 5 | phpcode: > 6 | $phpStr = LightnCandy::compile($template, array( 7 | 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONEPHP, // Compile Flags 8 | 'helpers' => array( // list of custom helpers 9 | 'custom_helper_name' => function () { .... } 10 | ), 11 | 'partials' => array( // list of partials 12 | 'partial_name' => 'partial {{foo}} template', 13 | ), 14 | 'helperresolver' => function ($context, $name) { .... }, // callback to load missing helper with name 15 | 'partialresolver' => function ($context, $name) { .... }, // callback to load missing partial with name 16 | 'prepartial' => function ($context, $template, $name) { .... }, // callback to preprocess partials 17 | 18 | 'delimiters' => array('<%', '%>'), // change default delimiters 19 | 'renderex' => '// Compiled at ' . date('Y-m-d h:i:s'), // insert the PHP code into generated render() 20 | 'runtime' => 'MyNameSpace\\MyCustomRuntime', // customized Runtime class 21 | )); 22 | lccompile: true 23 | ref: 24 | - 0011-partial 25 | - 0012-delimiter 26 | - 0021-customhelper 27 | - 9901-lcop-helperresolver 28 | - 9902-lcop-partialresolver 29 | - 9003-helperescaping 30 | - 9004-partials 31 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_RUNTIMEPARTIAL.yaml: -------------------------------------------------------------------------------- 1 | description: 2 | Compile the partial as callable. This enables recursive partials or context change for partials. 3 | lcsample: 4 | - template: "OK{{> foo}}!" 5 | note: Default is to compile the partial as static code 6 | showcode: true 7 | option: 8 | - FLAG_JSOBJECT 9 | partial: 10 | foo: "{{hello}}" 11 | - template: "OK{{> foo}}!" 12 | note: Compile used partial as embed callable code 13 | showcode: true 14 | option: 15 | - FLAG_JSOBJECT 16 | - FLAG_RUNTIMEPARTIAL 17 | partial: 18 | foo: "{{hello}}" 19 | moo: "{{not used}}" 20 | - template: "OK{{> foo bar}}!" 21 | note: Default to not support context change on partial 22 | compileerror: true 23 | errorlog: true 24 | option: 25 | - FLAG_ERROR_LOG 26 | partial: 27 | foo: "{{hello}}" 28 | - template: "OK{{> foo bar}}!" 29 | note: Use another context for the partial 30 | data: 31 | bar: 32 | hello: World 33 | option: 34 | - FLAG_RUNTIMEPARTIAL 35 | partial: 36 | foo: "{{hello}}" 37 | - template: "{{> (foo)}}" 38 | note: When using dynamic partial, all partials will be compiled into your render function. 39 | data: null 40 | showcode: true 41 | partial: 42 | foo1: Partial foo one 43 | foo2: Partial foo two 44 | foo3: Partial foo {{moo}} 45 | option: 46 | - FLAG_RUNTIMEPARTIAL 47 | helper: 48 | foo: | 49 | function () { 50 | return 'foo2'; 51 | } 52 | ref: 53 | - 0024-partialcontext 54 | - 0028-dynamicpartial 55 | -------------------------------------------------------------------------------- /bookdata/0019-each.yaml: -------------------------------------------------------------------------------- 1 | title: "Built-in block helper - #each" 2 | hbonly: true 3 | description: "Loop on an Array or Object." 4 | Samples: 5 | quicksample: 6 | - template: "{{#each foo}}{{.}}!{{/each}}" 7 | note: Loop on an array. 8 | data: 9 | foo: [Hello, World] 10 | - template: "{{#each foo}}\n{{@index}}: {{.}}!\n{{/each}}" 11 | note: "When looping on an array, use {{@index}} for current index in the loop." 12 | option: 13 | lightncandy: [FLAG_SPVARS] 14 | data: 15 | foo: [Hello, World] 16 | - template: "{{#each foo}}{{.}}!{{/each}}" 17 | note: Loop on an Object. 18 | data: 19 | foo: 20 | key: Hello 21 | key2: World 22 | - template: "{{#each foo}}\n{{@key}}:{{.}}!\n{{/each}}" 23 | note: "When looping on an object, use {{@key}} for current key in the loop." 24 | option: 25 | lightncandy: [FLAG_SPVARS] 26 | data: 27 | foo: 28 | first: Hello 29 | second: World 30 | - template: "{{#each foo}}\n{{../title}}:{{.}}!\n{{/each}}" 31 | note: "Use .. to access parent context inside the #each" 32 | option: 33 | lightncandy: 34 | - FLAG_SPVARS 35 | - FLAG_PARENT 36 | data: 37 | foo: 38 | first: Hello 39 | second: World 40 | title: Values 41 | - template: "{{#each foo}}\n{{.}}!\n{{/each}}" 42 | note: "#each do not work on string" 43 | option: 44 | lightncandy: 45 | - FLAG_SPVARS 46 | - FLAG_PARENT 47 | data: 48 | foo: Good 49 | ref: 50 | - 0008-block 51 | - 0025-parentcontext 52 | -------------------------------------------------------------------------------- /partials/html_start: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{@configs.project_name}}{{#if title}} - {{title}}{{/if}} 8 | 9 | 15 | 16 | 17 | 18 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /bookdata/LC-FLAG_EXTHELPER.yaml: -------------------------------------------------------------------------------- 1 | description: | 2 | 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. 3 | lcsample: 4 | - template: "{{foo}}" 5 | note: Default is to include used custom helpers into compiled PHP 6 | option: 7 | - FLAG_HANDLEBARS 8 | showcode: true 9 | helper: 10 | foo: > 11 | function () { 12 | return 'Hello!'; 13 | } 14 | bar: > 15 | function () { 16 | return 'World!'; 17 | } 18 | - template: "{{foo}}, {{bar}}" 19 | note: The generated code will not include custom helper 20 | option: 21 | - FLAG_HANDLEBARS 22 | - FLAG_EXTHELPER 23 | showcode: true 24 | helper: 25 | - foo 26 | - bar 27 | excode: | 28 | function foo () { 29 | return 'Hello!'; 30 | } 31 | function bar () { 32 | return 'World!'; 33 | } 34 | - template: "{{foo}}, {{bar}}" 35 | note: Anonymous function will still be included in generated code 36 | option: 37 | - FLAG_HANDLEBARS 38 | - FLAG_EXTHELPER 39 | showcode: true 40 | helper: 41 | foo: > 42 | function () { 43 | return 'Hello!'; 44 | } 45 | 1: bar 46 | excode: | 47 | function bar () { 48 | return 'World!'; 49 | } 50 | - template: "{{foo}}, {{bar}}" 51 | note: Anonymous function will still be included in generated code 52 | option: 53 | - FLAG_HANDLEBARS 54 | - FLAG_EXTHELPER 55 | showcode: true 56 | helper: 57 | foo: 1 58 | 1: bar 59 | ref: 60 | - 0021-customhelper 61 | - LC-FLAG_STANDALONEPHP 62 | -------------------------------------------------------------------------------- /bookdata/0003-hello.yaml: -------------------------------------------------------------------------------- 1 | title: Hello World 2 | description: 3 | This is a basic example to use mustache or handlebars template. 4 | 5 | Define the template: 6 | lightncandy: 7 | code: $template = 'Hello, {{foo}}!'; 8 | handlebars.js: 9 | code: var template = 'Hello, {{foo}}!'; 10 | mustache: 11 | code: var template = 'Hello, {{foo}}!'; 12 | 13 | Compile the template: 14 | lightncandy: 15 | code: | 16 | $php = LightnCandy::compile($template); 17 | $render = LightnCandy::prepare($php); 18 | handlebars.js: 19 | code: var render = Handlebars.compile(template); 20 | mustache: 21 | content: mustache.js supports pre-parsing. The parsed token tree will be cached. 22 | code: Mustache.parse(template); 23 | 24 | Define the data: 25 | lightncandy: 26 | code: $data = array('foo' => 'world'); 27 | handlebars.js: 28 | code: "var data = {foo: 'world'};" 29 | mustache: 30 | code: "var data = {foo: 'world'};" 31 | 32 | Rendering the template: 33 | lightncandy: 34 | code: echo $render($data); 35 | handlebars.js: 36 | code: console.log(render(data)); 37 | mustache: 38 | code: console.log(Mustache.render(template, data)); 39 | 40 | Reuse the render function: 41 | lightncandy: 42 | note: You do not need to compile template everytime. You can reuse the render function with different data. 43 | code: echo $render($another_data); 44 | handlebars.js: 45 | note: You do not need to compile template everytime. You can reuse the render function with different data. 46 | code: console.log(render(another_data)); 47 | mustache: 48 | note: Mustache does not support pre-compile, so you need to compile the template everytime. 49 | code: console.log(Mustache.render(template, another_data)); 50 | 51 | The full code: 52 | lightncandy: 53 | collectcode: 54 | 6: 55 | handlebars.js: 56 | collectcode: 57 | 6: 58 | mustache: 59 | collectcode: 60 | 6: 61 | -------------------------------------------------------------------------------- /bookdata/9000-quickstart.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy: Quick Start" 2 | Install: | 3 | You may use composer to install LighcnCandy: 4 |
 5 |    composer require zordius/lightncandy:dev-master
 6 |   
7 | lcresult: 8 | Usage: 9 | note: The best practice is compile template into PHP code and save it for later use. 10 | code: | 11 | // Use composer autoloader 12 | require_once('./vendor/autoload.php'); 13 | 14 | use LightnCandy\LightnCandy; 15 | 16 | $template = "Welcome {{name}} , You win \${{value}} dollars!!\n"; 17 | $phpStr = LightnCandy::compile($template); // set compiled PHP code into $phpStr 18 | 19 | // Save the compiled PHP code into a php file 20 | file_put_contents('render.php', ''); 21 | 22 | // Get the render function from the php file 23 | $renderer = include('render.php'); 24 | 25 | // Render by different data 26 | echo "Template is:\n$template\n"; 27 | echo $renderer(array('name' => 'John', 'value' => 10000)); 28 | echo $renderer(array('name' => 'Peter', 'value' => 1000)); 29 | Deprecated way: 30 | note: You can use LightnCandy::prepare($phpStr) to get the render function. This is a deprecated way and should only be used for developing or testing. 31 | code: | 32 | // Use composer autoloader 33 | require_once('./vendor/autoload.php'); 34 | 35 | use LightnCandy\LightnCandy; 36 | 37 | $template = "Welcome {{name}} , You win \${{value}} dollars!!\n"; 38 | $phpStr = LightnCandy::compile($template); // set compiled PHP code into $phpStr 39 | 40 | // Quick and deprecated way to get render function 41 | $renderer = LightnCandy::prepare($phpStr); 42 | 43 | // Render by different data 44 | echo "Template is:\n$template\n"; 45 | echo $renderer(array('name' => 'John', 'value' => 10000)); 46 | ref: 47 | - 0002-install 48 | - 0003-hello 49 | -------------------------------------------------------------------------------- /bookdata/0009-inverted-block.yaml: -------------------------------------------------------------------------------- 1 | title: Inverted Block 2 | description: 3 | An inverted block is started with {{^foo}} and end with {{/foo}}. 4 | Inverted Block for True: 5 | quicksample: 6 | - template: "{{^foo}}Ya!{{/foo}}" 7 | note: An inverted block will not render inner content for an array. 8 | data: 9 | foo: [1, 3, 5] 10 | - template: "{{^foo}}Ya!{{/foo}}" 11 | note: An inverted block will not render inner content for an object. 12 | data: 13 | foo: 14 | bar: 15 | moo: 16 | - template: "{{^foo}}Ya!{{/foo}}" 17 | note: An inverted block will not render inner content for a string. 18 | data: 19 | foo: test 20 | - template: "{{^foo}}Ya!{{/foo}}" 21 | note: An inverted block will not render inner content for a number. 22 | different: true 23 | data: 24 | foo: 1 25 | Inverted Block for False: 26 | quicksample: 27 | - template: "{{^foo}}Ya!{{/foo}}" 28 | note: When the value is false, the inner content will be rendered. 29 | data: 30 | foo: false 31 | - template: "{{^foo}}Ya!{{/foo}}" 32 | note: When the value is empty array, the inner content will be rendered. 33 | data: 34 | foo: [] 35 | - template: "{{^foo}}Ya!{{/foo}}" 36 | note: When the value is null or undefined, the inner content will be rendered. 37 | data: 38 | foo: 39 | - template: "{{^foo}}Ya!{{/foo}}" 40 | note: Mustache.js will think 0 is false. 41 | different: true 42 | data: 43 | foo: 0 44 | - template: "{{^foo}}Ya!{{/foo}}" 45 | note: Mustache.js will think empty string is false. 46 | different: true 47 | data: 48 | foo: "" 49 | Context Switching: 50 | quicksample: 51 | - template: | 52 | {{^foo}}{{foo}},{{bar}}{{/foo}} 53 | note: When rendering inner block, the context will not be switched. 54 | option: 55 | lightncandy: [FLAG_JSTRUE] 56 | data: 57 | foo: false 58 | bar: Good! 59 | - template: "{{^foo}}{{^bar}}{{moo}}{{/bar}}{{/foo}}" 60 | data: 61 | foo: false 62 | bar: false 63 | moo: OH! 64 | ref: 65 | - 0008-block 66 | -------------------------------------------------------------------------------- /bookdata/0016-if.yaml: -------------------------------------------------------------------------------- 1 | title: "Built-in block helper - #if" 2 | hbonly: true 3 | description: "Provide simple if/else rendering logic. " 4 | Samples: 5 | quicksample: 6 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 7 | note: True! 8 | option: 9 | lightncandy: 10 | - FLAG_ELSE 11 | data: 12 | foo: true 13 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 14 | note: None empty string means true 15 | option: 16 | lightncandy: 17 | - FLAG_ELSE 18 | data: 19 | foo: "String" 20 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 21 | note: None empty array means true 22 | option: 23 | lightncandy: 24 | - FLAG_ELSE 25 | data: 26 | foo: [1] 27 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 28 | note: Numbers other than 0 means true 29 | option: 30 | lightncandy: 31 | - FLAG_ELSE 32 | data: 33 | foo: 1 34 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 35 | note: Numbers other than 0 means true 36 | option: 37 | lightncandy: 38 | - FLAG_ELSE 39 | data: 40 | foo: -1 41 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 42 | note: Object or associative array means true 43 | option: 44 | lightncandy: 45 | - FLAG_ELSE 46 | data: 47 | foo: 48 | bar: 49 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 50 | note: False! 51 | option: 52 | lightncandy: 53 | - FLAG_ELSE 54 | data: 55 | foo: false 56 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 57 | note: Empty string means false 58 | option: 59 | lightncandy: 60 | - FLAG_ELSE 61 | data: 62 | foo: "" 63 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 64 | note: Empty array means false 65 | option: 66 | lightncandy: 67 | - FLAG_ELSE 68 | data: 69 | foo: [] 70 | - template: "{{#if foo}}YES{{else}}no{{/if}}" 71 | note: 0 means false 72 | option: 73 | lightncandy: 74 | - FLAG_ELSE 75 | data: 76 | foo: 0 77 | ref: 78 | - 0008-block 79 | - 0017-unless 80 | -------------------------------------------------------------------------------- /bookdata/0028-dynamicpartial.yaml: -------------------------------------------------------------------------------- 1 | title: Dynamic Partial 2 | hbonly: true 3 | description: "You can use a subexpression as partial name. The partial name will be decided by the result of custom helper at run time." 4 | Samples: 5 | quicksample: 6 | - template: "{{>(foo 1)}}, {{>(foo 2)}}" 7 | note: "The return value of helper foo will be partial name" 8 | partial: 9 | bar1: "Hello" 10 | bar2: "World" 11 | helper: 12 | foo: > 13 | function ($arg1) { 14 | return 'bar' . $arg1; 15 | } 16 | option: 17 | lightncandy: 18 | - FLAG_ADVARNAME 19 | - FLAG_RUNTIMEPARTIAL 20 | data: 21 | null 22 | - template: "{{>(foo)}}" 23 | note: "When partial name is not exist it will cause runtime error" 24 | fail: true 25 | errorlog: true 26 | partial: 27 | bar1: "Hello" 28 | bar2: "World" 29 | helper: 30 | foo: > 31 | function () { 32 | return 'bar_not_found'; 33 | } 34 | option: 35 | lightncandy: 36 | - FLAG_ADVARNAME 37 | - FLAG_RUNTIMEPARTIAL 38 | data: 39 | null 40 | - template: "{{#each foo}}{{> (lookup . 'template')}}{{/each}}" 41 | note: "You can use the lookup helper to return the template's name from an object" 42 | partial: 43 | en: "Hello {{name}}. " 44 | es: "Hola {{name}}. " 45 | fr: "Bonjour {{name}}." 46 | data: 47 | foo: [{template: "en", name: "alice"},{template: "es", name: "bob"},{template: "fr", name: "casey"}] 48 | option: 49 | lightncandy: 50 | - FLAG_ADVARNAME 51 | - FLAG_RUNTIMEPARTIAL 52 | - template: "{{#each foo}}{{> (bar .)}}{{/each}}" 53 | note: "You can use dynamic partials to load templates from within a list of their names" 54 | partial: 55 | a: "Hello" 56 | b: "World" 57 | c: "!" 58 | helper: 59 | bar: > 60 | function ($arg) { 61 | return $arg; 62 | } 63 | data: 64 | foo: ["a","b","c"] 65 | option: 66 | lightncandy: 67 | - FLAG_ADVARNAME 68 | - FLAG_RUNTIMEPARTIAL 69 | ref: 70 | - 0021-customhelper 71 | - LC-FLAG_ADVARNAME 72 | - LC-FLAG_RUNTIMEPARTIAL 73 | -------------------------------------------------------------------------------- /bookdata/9902-lcop-partialresolver.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy option: partialresolver" 2 | description: "Use partialresolver option to provide partial content on demand when compile the template." 3 | lcresult: 4 | Partial Resolver: 5 | note: "The return value of partialresolver will be the content of the partial" 6 | code: | 7 | require_once('./vendor/autoload.php'); 8 | use LightnCandy\LightnCandy; 9 | 10 | $template = "{{> foo}}, {{> bar}}! {{> moo}}"; 11 | 12 | $phpStr = LightnCandy::compile($template, array( 13 | 'partialresolver' => function ($cx, $name) { 14 | switch ($name) { 15 | case 'foo': 16 | return 'FOO'; 17 | case 'bar': 18 | return 'BAR'; 19 | default: 20 | return '[partial not found]'; 21 | } 22 | } 23 | )); 24 | 25 | $renderer = LightnCandy::prepare($phpStr); 26 | echo $renderer(); 27 | File Resolver: 28 | note: "You can implement any logic to load partial content from any storage" 29 | code: | 30 | require_once('./vendor/autoload.php'); 31 | use LightnCandy\LightnCandy; 32 | 33 | $template = "{{> foo}}, {{> bar}}! {{> moo}}"; 34 | 35 | $phpStr = LightnCandy::compile($template, array( 36 | 'partialresolver' => function ($cx, $name) { 37 | if (file_exists("$name.tmpl")) { 38 | return file_get_contents("$name.tmpl"); 39 | } 40 | return "[partial (file:$name.tmpl) not found]"; 41 | } 42 | )); 43 | 44 | $renderer = LightnCandy::prepare($phpStr); 45 | echo $renderer(); 46 | Partial Not Found: 47 | note: "When partialresolver return null it means `partial not found` and cause compile time error" 48 | errorlog: true 49 | code: | 50 | require_once('./vendor/autoload.php'); 51 | use LightnCandy\LightnCandy; 52 | 53 | $template = "{{> foo}}, {{> bar}}! {{> moo}}"; 54 | 55 | $phpStr = LightnCandy::compile($template, array( 56 | 'flags' => LightnCandy::FLAG_ERROR_LOG, 57 | 'partialresolver' => function ($cx, $name) { 58 | return; 59 | } 60 | )); 61 | 62 | $renderer = LightnCandy::prepare($phpStr); 63 | ref: 64 | - 9900-lc-options 65 | -------------------------------------------------------------------------------- /bookdata/0001-intro.yaml: -------------------------------------------------------------------------------- 1 | title: About this book 2 | description: 3 | This book contains every handlebars features and example, you can find working example code for both handlebars.js and lightncandy 4 | 5 | What is...: 6 | mustache: Mustache is a logic-less template syntax. It can be used for HTML, config files, source code - anything. It works by expanding tags in a template using values provided in a hash or object. 7 | handlebars.js: Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. 8 | lightncandy: LightnCandy is an extremely fast PHP implementation of handlebars and mustache. 9 | How mustache/handlebars works...: 10 | template: 11 | code: My name is {{name}} and I am {{age}} years old. 12 | + data: 13 | code: | 14 | { 15 | "name": "John", 16 | "age": 18 17 | } 18 | = result: 19 | code: My name is John and I am 18 years old. 20 | Keyboard Shortcuts: | 21 | You may use keyboard to navigate this book fast: 22 | 30 | When the drop down menu is displayed, you can: 31 | 35 | Legend: 36 | Codes: "
codes
" 37 | Correct/Expected: "
Sample output
" 38 | Different: "
Different output
" 39 | Error/Unexpected: "
Error output
" 40 | Partials List: > 41 | 42 | 43 | 44 |
partial_namepartial template
partial_name2partial template...
45 | Options: > 46 | Option1 47 | Option2 48 | Versions: 49 | version: true 50 | Table of Content: 51 | toc: true 52 | -------------------------------------------------------------------------------- /bookdata/0017-unless.yaml: -------------------------------------------------------------------------------- 1 | title: "Built-in block helper - #unless" 2 | hbonly: true 3 | description: "Provide simple unless/else rendering logic which reversed from #if" 4 | Samples: 5 | quicksample: 6 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 7 | note: True! 8 | option: 9 | lightncandy: 10 | - FLAG_ELSE 11 | data: 12 | foo: true 13 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 14 | note: None empty string means true 15 | option: 16 | lightncandy: 17 | - FLAG_ELSE 18 | data: 19 | foo: "String" 20 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 21 | note: None empty array means true 22 | option: 23 | lightncandy: 24 | - FLAG_ELSE 25 | data: 26 | foo: [1] 27 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 28 | note: Numbers other than 0 means true 29 | option: 30 | lightncandy: 31 | - FLAG_ELSE 32 | data: 33 | foo: 1 34 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 35 | note: Numbers other than 0 means true 36 | option: 37 | lightncandy: 38 | - FLAG_ELSE 39 | data: 40 | foo: -1 41 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 42 | note: Object or associative array means true 43 | option: 44 | lightncandy: 45 | - FLAG_ELSE 46 | data: 47 | foo: 48 | bar: 49 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 50 | note: False! 51 | option: 52 | lightncandy: 53 | - FLAG_ELSE 54 | data: 55 | foo: false 56 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 57 | note: Empty string means false 58 | option: 59 | lightncandy: 60 | - FLAG_ELSE 61 | data: 62 | foo: "" 63 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 64 | note: Empty array means false 65 | option: 66 | lightncandy: 67 | - FLAG_ELSE 68 | data: 69 | foo: [] 70 | - template: "{{#unless foo}}YES{{else}}no{{/unless}}" 71 | note: 0 means false 72 | option: 73 | lightncandy: 74 | - FLAG_ELSE 75 | data: 76 | foo: 0 77 | ref: 78 | - 0008-block 79 | - 0016-if 80 | -------------------------------------------------------------------------------- /bookdata/9903-lcop-safestring.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy option: safestring" 2 | description: "Use safestring option to provide your own SafeString implementation or change the default LS classname when FLAG_STANDALONEPHP is enabled." 3 | lcresult: 4 | MySafeString: 5 | note: "You should provide __toString(). If you use \\LightnCandy\\SafeString in your custom helper, it will be replaced by your safestring automatically." 6 | code: | 7 | require_once('./vendor/autoload.php'); 8 | use LightnCandy\LightnCandy; 9 | 10 | class MySafeString { 11 | public function __toString() { 12 | return 'Yes! You&me!'; 13 | } 14 | } 15 | 16 | $template = "{{foo}}, {{bar}}"; 17 | 18 | $phpStr = LightnCandy::compile($template, array( 19 | 'flags' => LightnCandy::FLAG_HANDLEBARS | LightnCandy::FLAG_STANDALONEPHP, 20 | 'helpers' => array( 21 | 'foo' => function () { 22 | return new MySafeString(); 23 | }, 24 | 'bar' => function () { 25 | return new \LightnCandy\SafeString('He&She!'); 26 | } 27 | ), 28 | 'safestring' => 'MySafeString' 29 | )); 30 | 31 | $renderer = LightnCandy::prepare($phpStr); 32 | echo $renderer(); 33 | Rename LS class: 34 | note: "If you specify the safestring option but the class is existing, then the \\LightnCandy\\SafeString class will be used." 35 | code: | 36 | require_once('./vendor/autoload.php'); 37 | use LightnCandy\LightnCandy; 38 | 39 | $template = "{{foo}}, {{bar}}"; 40 | 41 | $phpStr = LightnCandy::compile($template, array( 42 | 'flags' => LightnCandy::FLAG_HANDLEBARS | LightnCandy::FLAG_STANDALONEPHP, 43 | 'helpers' => array( 44 | 'foo' => function () { 45 | return new MySafeString('You&Me!'); 46 | }, 47 | 'bar' => function () { 48 | return new \LightnCandy\SafeString('He&She!'); 49 | } 50 | ), 51 | 'safestring' => 'MySafeString' 52 | )); 53 | 54 | $renderer = LightnCandy::prepare($phpStr); 55 | echo "The Code:\n$phpStr\n\n"; 56 | echo "===========================\nThe Output:\n" . $renderer(); 57 | ref: 58 | - 9003-helperescaping 59 | - 9900-lc-options 60 | - LC-FLAG_STANDALONEPHP 61 | -------------------------------------------------------------------------------- /bookdata/0002-install.yaml: -------------------------------------------------------------------------------- 1 | title: Create your project 2 | description: 3 | No matter PHP or nodejs, you should create a project first. The project definition file provides dependency information, and help you to install required packages by package management tools. Here are examples of project definition file with minimal settings, you may use it as a quick start. You can modify the file to fit your project later. 4 | 5 | Create project definition file: 6 | PHP: 7 | file: composer.json 8 | copy: true 9 | code: !!js/function | 10 | function (options) { 11 | return JSON.stringify({ 12 | "name": "zordius/HandlebarsCookbook", 13 | "description": "A cookbook of handlebars and mustache, focus on handlebars.js , mustache.js and lightncandy usage", 14 | "homepage": "https://github.com/zordius/HandlebarsCookbook", 15 | "require": { 16 | "php": ">=5.4.0" 17 | }, 18 | "require-dev": { 19 | "zordius/lightncandy": options ? options.data.composer['require-dev']['zordius/lightncandy'] : "*" 20 | } 21 | }, null, ' '); 22 | } 23 | nodejs: 24 | file: package.json 25 | copy: true 26 | code: !!js/function | 27 | function (options) { 28 | return JSON.stringify({ 29 | "name": "handlebars.cookbook", 30 | "version": "0.0.1", 31 | "description": "A cookbook of handlebars and mustache, focus on handlebars.js , mustache.js and lightncandy usage", 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/zordius/HandlebarsCookbook.git" 35 | }, 36 | "homepage": "https://github.com/zordius/HandlebarsCookbook", 37 | "devDependencies": { 38 | "handlebars": options ? options.data.package.devDependencies.handlebars : "*", 39 | "mustache": options ? options.data.package.devDependencies.mustache : "*" 40 | } 41 | }, null, ' '); 42 | } 43 | Install dependency: 44 | PHP: 45 | code: composer install 46 | nodejs: 47 | code: npm install 48 | Require the library: 49 | lightncandy: 50 | note: You may require composer autoloader to do autoload for you, then you can use LightnCandy classed without require() class files. 51 | code_use: 1 52 | handlebars.js: 53 | code_use: 1 54 | mustache: 55 | code_use: 1 56 | Start to use the library: Now the library is ready to go! 57 | -------------------------------------------------------------------------------- /bookdata/0008-block.yaml: -------------------------------------------------------------------------------- 1 | title: Block 2 | description: 3 | A block is started with {{#foo}} and end with {{/foo}}. 4 | Block for Loop: 5 | quicksample: 6 | - template: "{{#foo}}Ya!{{/foo}}" 7 | note: A block will loop inner content on an array. 8 | data: 9 | foo: [1, 3, 5] 10 | Block for True: 11 | quicksample: 12 | - template: "{{#foo}}Ya!{{/foo}}" 13 | note: A block will render inner content for an object. 14 | data: 15 | foo: 16 | bar: 17 | moo: 18 | - template: "{{#foo}}Ya!{{/foo}}" 19 | note: A block will render inner content for a string. 20 | data: 21 | foo: test 22 | - template: "{{#foo}}Ya!{{/foo}}" 23 | note: A block will render inner content for a number. 24 | different: true 25 | data: 26 | foo: 1 27 | Block for False: 28 | quicksample: 29 | - template: "{{#foo}}Ya!{{/foo}}" 30 | note: When the value is false, the inner content will not be rendered. 31 | data: 32 | foo: false 33 | - template: "{{#foo}}Ya!{{/foo}}" 34 | note: When the value is null or undefined, the inner content will not be rendered. 35 | data: 36 | foo: 37 | - template: "{{#foo}}Ya!{{/foo}}" 38 | note: empty array means false. 39 | data: 40 | foo: [] 41 | - template: "{{#foo}}Ya!{{/foo}}" 42 | note: Only mustache.js will think 0 as false. 43 | different: true 44 | data: 45 | foo: 0 46 | - template: "{{#foo}}Ya!{{/foo}}" 47 | note: Only mustache.js will think empty string as false. 48 | different: true 49 | data: 50 | foo: "" 51 | Context Switching: 52 | quicksample: 53 | - template: | 54 | {{foo}},{{bar}} 55 | {{#foo}}{{foo}},{{bar}}{{/foo}} 56 | note: When rendering inner block, the context will be switched. 57 | option: 58 | lightncandy: [FLAG_JSOBJECT] 59 | data: 60 | foo: 61 | foo: Hello 62 | bar: World 63 | bar: OK 64 | - template: "{{#foo}}{{#bar}}{{moo}}{{/bar}}{{/foo}}" 65 | note: Lookup deeper value in this way. 66 | data: 67 | foo: 68 | bar: 69 | moo: MOO! 70 | - template: "{{#foo}}{{bar}}{{/foo}}" 71 | note: Loop then context switched into different items 72 | data: 73 | foo: 74 | - bar: "Yes, " 75 | - bar: "hello " 76 | - bar: "world." 77 | ref: 78 | - 0009-inverted-block 79 | - 0022-blockhelper 80 | -------------------------------------------------------------------------------- /bookdata/9002-helperoptions.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy: The $options Object" 2 | description: "The $options object will be appended into custom helper arguments, you can receive many usefull infomation from this object." 3 | lcresult: 4 | Named Arguments: 5 | note: "You can pass arguments to your custom helpers as key=value pairs, these named arguments can be received by the $options object as key value pairs in $options['hash']." 6 | code: | 7 | require_once('./vendor/autoload.php'); 8 | use LightnCandy\LightnCandy; 9 | 10 | function my_helper ($arg1, $options) { 11 | return "ARG1: $arg1\nNamed arguments: " . print_r($options['hash'], true); 12 | } 13 | 14 | // foo will be $arg1 15 | // named arguments will be in $options['hash'] as key => value pairs 16 | $template = '{{{my_helper foo bar="abc" [na me]="string!" test=foo}}}'; 17 | 18 | $phpStr = LightnCandy::compile($template, array( 19 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 20 | 'helpers' => array( 21 | 'my_helper', 22 | ) 23 | )); 24 | $renderer = LightnCandy::prepare($phpStr); 25 | echo $renderer(array( 26 | 'foo' => 'Hello!' 27 | )); 28 | Inner Block: 29 | note: "You can render the content of inner block in your block custom helper, it can be done by running $options['fn']()." 30 | code: | 31 | require_once('./vendor/autoload.php'); 32 | use LightnCandy\LightnCandy; 33 | 34 | function my_helper ($options) { 35 | return "#Inner Content: '" . $options['fn']() . "'"; 36 | } 37 | 38 | $template = '{{#my_helper}}Hello World!{{/my_helper}}'; 39 | 40 | $phpStr = LightnCandy::compile($template, array( 41 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 42 | 'helpers' => array( 43 | 'my_helper', 44 | ) 45 | )); 46 | $renderer = LightnCandy::prepare($phpStr); 47 | echo $renderer(); 48 | Change Context: 49 | note: "If you pass parameter into the function $options['fn'], it will become new context of the inner block." 50 | code: | 51 | require_once('./vendor/autoload.php'); 52 | use LightnCandy\LightnCandy; 53 | 54 | function my_helper ($options) { 55 | return "#Inner Content: '" . $options['fn']("This is new context") . "'"; 56 | } 57 | 58 | $template = '{{#my_helper}}Hello, {{.}}!{{/my_helper}}'; 59 | 60 | $phpStr = LightnCandy::compile($template, array( 61 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 62 | 'helpers' => array( 63 | 'my_helper', 64 | ) 65 | )); 66 | $renderer = LightnCandy::prepare($phpStr); 67 | echo $renderer(); 68 | ref: 69 | - http://handlebarsjs.com/expressions.html 70 | - http://handlebarsjs.com/block_helpers.html 71 | - 0021-customhelper 72 | - 0023-namedarguments 73 | - 0025-parentcontext 74 | - 9001-customhelper 75 | - LC-FLAG_NAMEDARG 76 | - LC-FLAG_ADVARNAME 77 | -------------------------------------------------------------------------------- /style.less: -------------------------------------------------------------------------------- 1 | body > .container-fluid { 2 | padding-top: 70px; 3 | } 4 | 5 | h2, h4 { 6 | border-top:1px dotted #ccc; 7 | padding-top:10px; 8 | } 9 | 10 | h2 { 11 | padding-left: 20px; 12 | } 13 | 14 | a.glyphicon { 15 | position: relative; 16 | top: 60px; 17 | } 18 | 19 | a[name] { 20 | position: absolute; 21 | } 22 | 23 | h5 { 24 | color:#555; 25 | } 26 | 27 | dd { 28 | padding: 10px 20px; 29 | } 30 | 31 | pre { 32 | margin: 10px 0; 33 | } 34 | 35 | .copy { 36 | position:absolute; 37 | top:-1000px; 38 | left:-1000px; 39 | height:0; 40 | width:0; 41 | } 42 | 43 | .lcflag { 44 | border-left: 8px solid #aaf; 45 | padding: 0 5px; 46 | margin-right: 10px; 47 | background: #eef; 48 | } 49 | 50 | .result { 51 | background:#efe; 52 | } 53 | 54 | .different { 55 | background:#ffd; 56 | } 57 | .error { 58 | background:#fdd; 59 | } 60 | 61 | .pager { 62 | border-top:1px solid #aaa; 63 | padding-top:30px; 64 | } 65 | 66 | .hbonly { 67 | color: #800; 68 | } 69 | 70 | .perfalert { 71 | color: #c00; 72 | } 73 | 74 | .helperhint { 75 | color: #600; 76 | } 77 | 78 | p.note { 79 | color: #006; 80 | } 81 | 82 | .lcsample p.note { 83 | font-weight: 900; 84 | border-top: 1px dotted #eee; 85 | padding-top: 10px; 86 | } 87 | 88 | .partials { 89 | width:100%; 90 | background:#eff; 91 | tr:nth-child(odd) { 92 | background:#eef; 93 | } 94 | td, th { 95 | padding:3px 5px; 96 | } 97 | th { 98 | text-align:right; 99 | width:25%; 100 | } 101 | td { 102 | background:rgba(255,255,255,0.5); 103 | white-space: pre; 104 | word-break: break-all; 105 | word-wrap: break-word; 106 | } 107 | } 108 | 109 | .search { 110 | min-height: 200px; 111 | * { 112 | box-sizing: content-box; 113 | } 114 | .gsc-input-box-focus { 115 | box-shadow: 0 0 3px #00f; 116 | } 117 | } 118 | 119 | .footer { 120 | text-align: center; 121 | font-size: 11px; 122 | color: #444; 123 | } 124 | 125 | .navbar label { 126 | font-weight: normal; 127 | margin: 0; 128 | cursor: pointer; 129 | display: block; 130 | } 131 | 132 | @media (max-width: 768px) { 133 | .navbar-fixed-top { 134 | position:relative; 135 | } 136 | body > .container-fluid { 137 | padding-top: 0px; 138 | } 139 | } 140 | 141 | @media (min-width: 768px) { 142 | .dropdown-menu { 143 | overflow-y: auto; 144 | max-height: 200px; 145 | } 146 | 147 | @media (min-height: 600px) { 148 | .dropdown-menu { 149 | max-height: 520px; 150 | } 151 | } 152 | 153 | @media (min-height: 700px) { 154 | .dropdown-menu { 155 | max-height: 620px; 156 | } 157 | } 158 | 159 | @media (min-height: 800px) { 160 | .dropdown-menu { 161 | max-height: 720px; 162 | } 163 | } 164 | 165 | @media (min-height: 900px) { 166 | .dropdown-menu { 167 | max-height: 820px; 168 | } 169 | } 170 | 171 | @media (min-height: 1000px) { 172 | .dropdown-menu { 173 | max-height: 920px; 174 | } 175 | } 176 | 177 | @media (min-height: 1200px) { 178 | .dropdown-menu { 179 | max-height: 1120px; 180 | } 181 | } 182 | 183 | @media (min-height: 1400px) { 184 | .dropdown-menu { 185 | max-height: 1320px; 186 | } 187 | } 188 | 189 | @media (min-height: 2000px) { 190 | .dropdown-menu { 191 | max-height: 1920px; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /bookdata/9001-customhelper.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy: Custom Helper" 2 | lcresult: 3 | Compile option 'helpers': 4 | note: "When compile(), you can use the compile option helpers to provide custom helpers. NOTICE: FLAG_NAMEDARG is required for named arguments, FLAG_ADVARNAME is required for string or subexpression arguments" 5 | code: | 6 | require_once('./vendor/autoload.php'); 7 | use LightnCandy\LightnCandy; 8 | 9 | function my_helper_function () { 10 | return 'OK! (my_helper_function)'; 11 | } 12 | 13 | function my_other_helper () { 14 | return 'OK! (my_other_helper)'; 15 | } 16 | 17 | class myClass { 18 | public static function myStaticMethod () { 19 | return 'OK! (myClass::myStaticMethod)'; 20 | } 21 | } 22 | 23 | $template = "1. {{my_helper_function}}\n2. {{myClass::myStaticMethod}}\n3. {{helper_name}}\n4. {{helper_name2}}\n5. {{helper_name3}}"; 24 | $phpStr = LightnCandy::compile($template, array( 25 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 26 | 'helpers' => array( 27 | // 1. You may pass your function name 28 | // When the function is not exist, you get compile time error 29 | // Template: {{my_helper_functoin ....}} 30 | 'my_helper_function', 31 | 32 | // 2. You may also provide a static call from a class 33 | // In this case, the helper name is same with provided full name 34 | // Template: {{myClass::myStaticMethod ....}} 35 | 'myClass::myStaticMethod', 36 | 37 | // 3. You may also provide an alias name for helper function 38 | // This help you to mapping different function to a preferred helper name 39 | // Template: {{helper_name ....}} 40 | 'helper_name' => 'my_other_helper', 41 | 42 | // 4. Alias also works well for static call of a class 43 | // This help you to mapping different function to a preferred helper name 44 | // Template: {{helper_name2 ....}} 45 | 'helper_name2' => 'myClass::myStaticMethod', 46 | 47 | // 5. Anonymous function should be provided with alias 48 | // The function will be included in generaed code always 49 | // Template: {{helper_name3 ....}} 50 | 'helper_name3' => function () { 51 | return 'OK! (helper_name3)'; 52 | } 53 | ) 54 | )); 55 | $renderer = LightnCandy::prepare($phpStr); 56 | echo $renderer(null); 57 | Custom Helper Interface: 58 | note: 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. 59 | code: | 60 | require_once('./vendor/autoload.php'); 61 | use LightnCandy\LightnCandy; 62 | 63 | $template = << LightnCandy::FLAG_HANDLEBARS, 72 | 'helpers' => array( 73 | 'helper' => function ($arg1) { 74 | return "ARG 1: $arg1"; 75 | }, 76 | 'helper2' => function ($arg1, $arg2) { 77 | return "ARG 1: $arg1, ARG2: $arg2"; 78 | } 79 | ) 80 | )); 81 | $renderer = LightnCandy::prepare($phpStr); 82 | echo $renderer(array('name' => 'John')); 83 | ref: 84 | - http://handlebarsjs.com/expressions.html 85 | - http://handlebarsjs.com/block_helpers.html 86 | - 0021-customhelper 87 | - 0023-namedarguments 88 | - 9002-helperoptions 89 | - LC-FLAG_NAMEDARG 90 | - LC-FLAG_ADVARNAME 91 | -------------------------------------------------------------------------------- /bookdata/9901-lcop-helperresolver.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy option: helperresolver" 2 | description: "Use helperresolver option to provide helper on demand when compile the template." 3 | lcresult: 4 | Name Resolver: 5 | note: "helperresolver can return a function name for a helper." 6 | code: | 7 | require_once('./vendor/autoload.php'); 8 | use LightnCandy\LightnCandy; 9 | 10 | $template = "{{foo}}, {{bar}}! {{moo}}"; 11 | 12 | function helper1 () { 13 | return 'Hello'; 14 | } 15 | 16 | function helper2 () { 17 | return 'World'; 18 | } 19 | 20 | $phpStr = LightnCandy::compile($template, array( 21 | 'helperresolver' => function ($cx, $name) { 22 | switch ($name) { 23 | case 'foo': 24 | return 'helper1'; 25 | case 'bar': 26 | return 'helper2'; 27 | default: 28 | return; 29 | } 30 | } 31 | )); 32 | 33 | $renderer = LightnCandy::prepare($phpStr); 34 | echo $renderer(); 35 | Runnable Resolver: 36 | note: "helperresolver can return a Runnable function for a helper." 37 | code: | 38 | require_once('./vendor/autoload.php'); 39 | use LightnCandy\LightnCandy; 40 | 41 | $template = "{{foo}}, {{bar}}! {{moo}}"; 42 | 43 | $phpStr = LightnCandy::compile($template, array( 44 | 'helperresolver' => function ($cx, $name) { 45 | switch ($name) { 46 | case 'foo': 47 | return function () { 48 | return 'FOO'; 49 | }; 50 | case 'bar': 51 | return function () { 52 | return 'MOO'; 53 | }; 54 | default: 55 | return; 56 | } 57 | } 58 | )); 59 | 60 | $renderer = LightnCandy::prepare($phpStr); 61 | echo $renderer(); 62 | Helper Lookup: 63 | note: "helperresolver can return 1 or true when the $name is a helper. The compiled render function will not include the actural helper code, which can be provided at runtime." 64 | code: | 65 | require_once('./vendor/autoload.php'); 66 | use LightnCandy\LightnCandy; 67 | 68 | $template = "{{foo}}, {{bar}}! {{moo}}"; 69 | 70 | $phpStr = LightnCandy::compile($template, array( 71 | 'flags' => LightnCandy::FLAG_EXTHELPER, // Do not provide helpers when compile 72 | 'helperresolver' => function ($cx, $name) { 73 | return true; // Every $name is valid helper name 74 | } 75 | )); 76 | 77 | $renderer = LightnCandy::prepare($phpStr); 78 | 79 | // Classes and instance for rendering 80 | class BAR { 81 | private $secret = ''; 82 | function __construct($start) { 83 | $this->secret = $start; 84 | } 85 | function getSecret() { 86 | return $this->secret; 87 | } 88 | } 89 | 90 | $bar = new BAR('haha'); 91 | 92 | class MOO { 93 | public static function test() { 94 | return 'MOO~'; 95 | } 96 | } 97 | 98 | // You should provide helper functions when rendering 99 | // because they are not in the $renderer 100 | echo $renderer(0, array( 101 | 'helpers' => array( 102 | 'foo' => function () { // The helper can be a callable 103 | return 'FOO!'; 104 | }, 105 | 'bar' => [$bar, 'getSecret'], // The helper can be a method of an instance 106 | 'moo' => 'MOO::test' // The helper can be a static function of a class 107 | ) 108 | )); 109 | None Helper: 110 | note: "When the helperresolver return null, the $name will not be resolved as a helper." 111 | code: | 112 | require_once('./vendor/autoload.php'); 113 | use LightnCandy\LightnCandy; 114 | 115 | $template = "{{foo}}, {{bar}}! {{moo}}"; 116 | 117 | $phpStr = LightnCandy::compile($template, array( 118 | 'helperresolver' => function ($cx, $name) { 119 | return; 120 | } 121 | )); 122 | 123 | $renderer = LightnCandy::prepare($phpStr); 124 | 125 | $data = array( 126 | 'foo' => 'Hello', 127 | 'bar' => 'World', 128 | 'moo' => 'GOOD', 129 | ); 130 | 131 | echo $renderer($data); 132 | ref: 133 | - 9900-lc-options 134 | - LC-FLAG_EXTHELPER 135 | -------------------------------------------------------------------------------- /bookdata/9004-partials.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy: Deep Dive Partials" 2 | lcresult: 3 | Static Partial: 4 | note: "LightnCandy will compile partials as included content by default for better performance." 5 | code: | 6 | require_once('./vendor/autoload.php'); 7 | use LightnCandy\LightnCandy; 8 | 9 | $template = "\ncode for {{>foo}} just like\ncode for {{{bar}}}"; 10 | 11 | $phpStr = LightnCandy::compile($template, array( 12 | 'partials' => array( 13 | 'foo' => '{{{bar}}}' 14 | ) 15 | )); 16 | 17 | echo $phpStr; 18 | Runtime Partial: 19 | note: "With FLAG_RUNTIMEPARTIAL option, partials will be compiled as functions which can handle context change." 20 | code: | 21 | require_once('./vendor/autoload.php'); 22 | use LightnCandy\LightnCandy; 23 | 24 | $template = "\ncode for {{>foo}} is different to\ncode for {{{bar}}}"; 25 | 26 | $phpStr = LightnCandy::compile($template, array( 27 | 'flags' => LightnCandy::FLAG_RUNTIMEPARTIAL, 28 | 'partials' => array( 29 | 'foo' => '{{{bar}}}' 30 | ) 31 | )); 32 | 33 | echo $phpStr; 34 | Partial Usage: 35 | note: "Only used partials will be compiled into render function by default." 36 | code: | 37 | require_once('./vendor/autoload.php'); 38 | use LightnCandy\LightnCandy; 39 | 40 | $template = "{{>foo}}, {{>bar}}"; 41 | 42 | $phpStr = LightnCandy::compile($template, array( 43 | 'flags' => LightnCandy::FLAG_RUNTIMEPARTIAL, 44 | 'partials' => array( 45 | 'foo' => '{{{foo}}}', 46 | 'bar' => '{{{bar}}}', 47 | 'moo' => '{{{moo}}} not used', 48 | 'qoo' => '{{{qoo}}} not in render function', 49 | ) 50 | )); 51 | 52 | echo $phpStr; 53 | Dynamic Partial: 54 | note: "When you using dynamic partial in template, all provided partials will be compiled into render function." 55 | code: | 56 | require_once('./vendor/autoload.php'); 57 | use LightnCandy\LightnCandy; 58 | 59 | $template = "{{>foo}}, {{>(bar)}}"; 60 | 61 | $phpStr = LightnCandy::compile($template, array( 62 | 'flags' => LightnCandy::FLAG_ADVARNAME | LightnCandy::FLAG_RUNTIMEPARTIAL, 63 | 'helpers' => array( 64 | 'bar' => function () { 65 | return 'bar'; 66 | } 67 | ), 68 | 'partials' => array( 69 | 'foo' => '{{{foo}}}', 70 | 'bar' => '{{{bar}}}', 71 | 'moo' => '{{{moo}}} not used', 72 | 'qoo' => '{{{qoo}}} in render function', 73 | ) 74 | )); 75 | 76 | echo $phpStr; 77 | Compile Without Partials: 78 | note: "With FLAG_ERROR_SKIPPARTIAL option it will not error when you compile without partials. This can reduce the size of render function." 79 | code: | 80 | require_once('./vendor/autoload.php'); 81 | use LightnCandy\LightnCandy; 82 | 83 | $template = "{{>foo}}, {{>(bar)}}"; 84 | 85 | $phpStr = LightnCandy::compile($template, array( 86 | 'flags' => LightnCandy::FLAG_ADVARNAME | LightnCandy::FLAG_RUNTIMEPARTIAL | LightnCandy::FLAG_ERROR_SKIPPARTIAL, 87 | 'helpers' => array( 88 | 'bar' => function () { 89 | return 'bar'; 90 | } 91 | ) 92 | )); 93 | 94 | // Warning: This code will cause 'partial not found' error when you execute it without providing required partials. 95 | echo $phpStr; 96 | Runtime Partials: 97 | note: "You can provide more precompiled partials to render functions." 98 | code: | 99 | require_once('./vendor/autoload.php'); 100 | use LightnCandy\LightnCandy; 101 | 102 | $template = "{{>foo}}, {{>(bar)}}"; 103 | 104 | $phpStr = LightnCandy::compile($template, array( 105 | 'flags' => LightnCandy::FLAG_ADVARNAME | LightnCandy::FLAG_RUNTIMEPARTIAL | LightnCandy::FLAG_ERROR_SKIPPARTIAL, 106 | 'helpers' => array( 107 | 'bar' => function () { 108 | return 'bar'; 109 | } 110 | ) 111 | )); 112 | $renderer = LightnCandy::prepare($phpStr); 113 | 114 | // It will be better to precompile partials and store them into files, 115 | // prevent to use LightnCandy::prepare() in production environment. 116 | $partials = array( 117 | 'foo' => LightnCandy::prepare('return ' . LightnCandy::compilePartial('Hello')), 118 | 'bar' => LightnCandy::prepare('return ' . LightnCandy::compilePartial('World')), 119 | ); 120 | 121 | echo $renderer(null, array( 122 | 'partials' => $partials 123 | )); 124 | ref: 125 | - 0028-dynamicpartial 126 | - LC-FLAG_ADVARNAME 127 | - LC-FLAG_ERROR_SKIPPARTIAL 128 | - LC-FLAG_RUNTIMEPARTIAL 129 | -------------------------------------------------------------------------------- /bookdata/9003-helperescaping.yaml: -------------------------------------------------------------------------------- 1 | title: "LightnCandy: Use SafeString" 2 | description: "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. You can also return a LightnCandy\\SafeString object, it will not be HTML escaped." 3 | lcresult: 4 | SafeString will not work without proper compile flag: 5 | note: "SafeString will not work when none of FLAG_THIS, FLAG_HBESCAPE, FLAG_JSOBJECT, FLAG_JSTRUE, FLAG_RENDER_DEBUG is enabled!" 6 | code: | 7 | require_once('./vendor/autoload.php'); 8 | use LightnCandy\LightnCandy; 9 | 10 | // Please use full namespace as \LightnCandy\SafeString() 11 | // Do not just use SafeString() , it will cause error when FLAG_STANDALONEPHP enabled 12 | function my_helper1 () { 13 | return new \LightnCandy\SafeString('You&Me!'); 14 | } 15 | 16 | $template = '{{my_helper1}}'; 17 | 18 | $phpStr = LightnCandy::compile($template, array( 19 | 'helpers' => array( 20 | 'my_helper1' 21 | ) 22 | )); 23 | $renderer = LightnCandy::prepare($phpStr); 24 | echo $renderer(); 25 | Prevent HTML Escape in your custom helper: 26 | code: | 27 | require_once('./vendor/autoload.php'); 28 | use LightnCandy\LightnCandy; 29 | 30 | // Please use full namespace as \LightnCandy\SafeString() 31 | // Do not just use SafeString() , it will cause error when FLAG_STANDALONEPHP enabled 32 | function my_helper1 () { 33 | return new \LightnCandy\SafeString('You&Me!'); 34 | } 35 | 36 | function my_helper2 () { 37 | return 'Now&Then!'; 38 | } 39 | 40 | $template = '{{my_helper1}} , {{my_helper2}}'; 41 | 42 | $phpStr = LightnCandy::compile($template, array( 43 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 44 | 'helpers' => array( 45 | 'my_helper1', 46 | 'my_helper2', 47 | ) 48 | )); 49 | $renderer = LightnCandy::prepare($phpStr); 50 | echo $renderer(); 51 | Force HTML Escape in your custom helper: 52 | note: "You can force HTML escaping the input string by adding second parameter as true when construct the SafeString object." 53 | code: | 54 | require_once('./vendor/autoload.php'); 55 | use LightnCandy\LightnCandy; 56 | 57 | // Please use full namespace as \LightnCandy\SafeString() 58 | // Do not just use SafeString() , it will cause error when FLAG_STANDALONEPHP enabled 59 | function my_helper1 () { 60 | return new \LightnCandy\SafeString('You&Me, it\'s good!', true); 61 | } 62 | 63 | function my_helper2 () { 64 | return 'Now&Then!'; 65 | } 66 | 67 | $template = '{{{my_helper1}}} , {{{my_helper2}}}, {{my_helper2}}'; 68 | 69 | $phpStr = LightnCandy::compile($template, array( 70 | 'flags' => LightnCandy::FLAG_HANDLEBARS, 71 | 'helpers' => array( 72 | 'my_helper1', 73 | 'my_helper2', 74 | ) 75 | )); 76 | $renderer = LightnCandy::prepare($phpStr); 77 | echo $renderer(); 78 | HTML Escape just like handlebars.js: 79 | note: "Use 'encq' as second parameter when construct the SafeString object, then the HTML escape behavior will 100% align with handlebars.js. (EX: ' -> &#039;)" 80 | code: | 81 | require_once('./vendor/autoload.php'); 82 | use LightnCandy\LightnCandy; 83 | 84 | // Please use full namespace as \LightnCandy\SafeString() 85 | // Do not just use SafeString() , it will cause error when FLAG_STANDALONEPHP enabled 86 | function my_helper1 () { 87 | return new \LightnCandy\SafeString('You&Me, it\'s good!', 'encq'); 88 | } 89 | 90 | function my_helper2 () { 91 | return 'Now&Then, it\'s time!'; 92 | } 93 | 94 | $template = '{{{my_helper1}}} , {{{my_helper2}}}, {{my_helper2}}'; 95 | 96 | $phpStr = LightnCandy::compile($template, array( 97 | 'flags' => LightnCandy::FLAG_JS, 98 | 'helpers' => array( 99 | 'my_helper1', 100 | 'my_helper2', 101 | ) 102 | )); 103 | $renderer = LightnCandy::prepare($phpStr); 104 | echo $renderer(); 105 | Use SafeString inside your input data: 106 | note: "You can use \\LightnCandy\\SafeString inside your input data to specify different escaping hehavior." 107 | code: | 108 | require_once('./vendor/autoload.php'); 109 | use LightnCandy\LightnCandy; 110 | 111 | $template = '{{foo}} , {{bar}}, {{moo}}'; 112 | 113 | $phpStr = LightnCandy::compile($template, array( 114 | 'flags' => LightnCandy::FLAG_JS 115 | )); 116 | $renderer = LightnCandy::prepare($phpStr); 117 | echo $renderer(array( 118 | 'foo' => "'A&B'", 119 | 'bar' => new \LightnCandy\SafeString("'C&D'"), 120 | 'moo' => new \LightnCandy\SafeString("'E&F'", 'encq') 121 | )); 122 | SafeString and FLAG_STANDALONEPHP: 123 | note: "When the FLAG_STANDALONEPHP flag enabled, LightnCandy will embed the \\LightnCandy\\SafeString class into generated code and rename it into shorter version: LS. If you like to use \\LightnCandy\\SafeString instance in your input data with the standalone PHP, you should reuse the LS class inside the compiled templates." 124 | code: | 125 | require_once('./vendor/autoload.php'); 126 | use LightnCandy\LightnCandy; 127 | 128 | $template = '{{foo}} , {{bar}}, {{moo}}'; 129 | 130 | $phpStr = LightnCandy::compile($template, array( 131 | 'flags' => LightnCandy::FLAG_JS | LightnCandy::FLAG_STANDALONEPHP 132 | )); 133 | $renderer = LightnCandy::prepare($phpStr); 134 | echo $renderer(array( 135 | 'foo' => "'A&B'", 136 | // LS only exists after any standalone template included 137 | 'bar' => new LS("'C&D'"), 138 | 'moo' => new LS("'E&F'", 'encq') 139 | )); 140 | ref: 141 | - 0021-customhelper 142 | - 0029-safestring 143 | - 9903-lcop-safestring 144 | - LC-FLAG_HBESCAPE 145 | - LC-FLAG_JSOBJECT 146 | - LC-FLAG_JSTRUE 147 | - LC-FLAG_RENDER_DEBUG 148 | - LC-FLAG_THIS 149 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var handlebars = require('handlebars') 3 | var Prism = require('prismjs') 4 | var child = require('child_process') 5 | var shortid = require('shortid').generate 6 | var defaultSP = ' ' 7 | var tmpFile = '.exec_tmp_file' 8 | 9 | require('prismjs/components/prism-javascript') 10 | require('prismjs/components/prism-php') 11 | require('prismjs/components/prism-handlebars') 12 | require('prismjs/components/prism-markup-templating') 13 | 14 | /* eslint no-unused-vars: off */ 15 | var consoleLog = function (O) { 16 | return O 17 | } 18 | 19 | var helpers = { 20 | code_for_require: function (type) { 21 | switch (type) { 22 | case 'lightncandy': 23 | return 'require(\'./vendor/autoload.php\');\nuse LightnCandy\\LightnCandy;' 24 | case 'handlebars.js': 25 | return 'var Handlebars = require(\'handlebars\');' 26 | case 'mustache': 27 | return 'var Mustache = require(\'mustache\');' 28 | } 29 | console.warn('unknown code type in code_for_require():' + type) 30 | return '' 31 | }, 32 | 33 | code_for_set: function (name, type) { 34 | switch (type) { 35 | case 'lightncandy': 36 | return '$' + name + ' = ' 37 | case 'handlebars.js': 38 | case 'mustache': 39 | return 'var ' + name + ' = ' 40 | } 41 | console.warn('unknown code type in code_for_set():' + type) 42 | return '' 43 | }, 44 | 45 | phptojs: function (code) { 46 | return code 47 | .replace(/\$options\['_this'\]/, 'this') 48 | .replace(/\$/g, '').replace(/ \. /g, ' + ') 49 | .replace(/\\LightnCandy\\SafeString/, 'Handlebars.SafeString') 50 | }, 51 | 52 | reindent: function (code, space) { 53 | return code.replace(/\n/g, '\n' + space) 54 | }, 55 | 56 | code_for_helper: function (helper, type) { 57 | if (!helper) { 58 | return '' 59 | } 60 | 61 | switch (type) { 62 | case 'lightncandy': 63 | return helpers.reindent('array(\n' + Object.keys(helper).map(function (K) { 64 | return (Math.floor(parseInt(K)) === K) ? ("'" + helper[K] + "'") : (' ' + helpers.escapeString(K, type) + ' => ' + helper[K]) 65 | }).join(',') + ')', ' ') 66 | default: 67 | return '{\n' + Object.keys(helper).map(function (K) { 68 | return ' ' + helpers.escapeString(K, type) + ': ' + helpers.phptojs(helper[K]) 69 | }).join(',') + '}' 70 | } 71 | }, 72 | 73 | code_for_excode: function (excode, type) { 74 | return excode || '' 75 | }, 76 | 77 | code_for_option: function (option, type) { 78 | var opt = (option && option[type]) ? option[type] : option 79 | 80 | if (! opt) { 81 | return '' 82 | } 83 | 84 | switch (type) { 85 | case 'lightncandy': 86 | return opt.map(function (V) { 87 | return 'LightnCandy::' + V 88 | }).join(' | ') 89 | case 'handlebars.js': 90 | case 'mustache': 91 | return '' 92 | } 93 | console.warn('unknown code type in code_for_option():' + type) 94 | return '' 95 | }, 96 | 97 | code_for_partial: function (partial, type) { 98 | var par = (partial && partial[type]) ? partial[type] : partial 99 | 100 | if (!par) { 101 | return '' 102 | } 103 | 104 | switch (type) { 105 | case 'lightncandy': 106 | par = Object.keys(par).map(function (K) { 107 | return helpers.escapeString(K, type) + ' => ' + helpers.escapeString(par[K], type) 108 | }) 109 | return par.length ? helpers.reindent('array(\n ' + par.join(',\n ') + '\n)', ' ') : '' 110 | case 'handlebars.js': 111 | case 'mustache': 112 | return JSON.stringify(par, undefined, defaultSP) 113 | } 114 | 115 | console.warn('unknown code type in code_for_option():' + type) 116 | return '' 117 | }, 118 | 119 | code_for_data: function (data, type) { 120 | switch (type) { 121 | case 'handlebars.js': 122 | case 'mustache': 123 | return ['js', JSON.stringify(data, undefined, defaultSP)] 124 | case 'lightncandy': 125 | return ['php', helpers.php_array(data, '')] 126 | default: 127 | console.warn('unknown code type in code_for_data():' + type) 128 | } 129 | }, 130 | 131 | code_for_compile: function (type, opt, par, hlp, norender) { 132 | var EX = [] 133 | switch (type) { 134 | case 'lightncandy': 135 | if (opt) { 136 | EX.push('"flags" => ' + opt) 137 | } 138 | if (par) { 139 | EX.push('"partials" => ' + par) 140 | } 141 | if (hlp) { 142 | EX.push('"helpers" => ' + hlp) 143 | } 144 | EX = EX.length ? (', array(\n ' + EX.join(',\n ') + '\n)') : '' 145 | return '$php = LightnCandy::compile($template' + EX + ');' + (norender ? '': '\n$render = LightnCandy::prepare($php);') 146 | case 'handlebars.js': 147 | if (hlp) { 148 | EX.push('Handlebars.registerHelper(' + hlp + ');\n') 149 | } 150 | return EX.join('') + (norender ? '' : 'var render = ') + 'Handlebars.compile(template);' 151 | case 'mustache': 152 | return '' 153 | } 154 | console.warn('unknown code type in code_for_compile():' + type) 155 | return '' 156 | }, 157 | 158 | hbonly: function (options) { 159 | return this.hbonly ? ['lightncandy', 'handlebars.js'] : options.data.samples 160 | }, 161 | 162 | code_for_renderdebug: function (opt) { 163 | return 'array(\n "debug" => ' + opt.debug.map(function (V) { 164 | return '\\LightnCandy\\Runtime::' + V 165 | }).join(' | ') + '\n)' 166 | }, 167 | 168 | code_for_render: function (type, opt, par) { 169 | var EX = [] 170 | switch (type) { 171 | case 'lightncandy': 172 | if (opt) { 173 | return 'echo $render($data, ' + helpers.code_for_renderdebug(opt) + ');' 174 | } 175 | return 'echo $render($data);' 176 | case 'handlebars.js': 177 | if (par) { 178 | EX.push(' partials: ' + par) 179 | } 180 | EX = EX.length ? (', {\n' + EX.join(',\n') + '}') : '' 181 | return 'console.log(render(data' + EX + '));' 182 | case 'mustache': 183 | return 'console.log(Mustache.render(template, data' + (par ? (', ' + par) : '') + '));' 184 | } 185 | console.warn('unknown code type in code_for_render():' + type) 186 | return '' 187 | }, 188 | 189 | php_array: function (D, sp) { 190 | var spp = sp + defaultSP 191 | 192 | if (D === null) { 193 | return 'NULL' 194 | } 195 | 196 | if (Array.isArray(D)) { 197 | return 'array(\n' + D.map(function (V) { 198 | return spp + helpers.php_array(V, spp) 199 | }).join(',\n') + '\n' + sp + ')' 200 | } 201 | 202 | if (typeof D === 'object') { 203 | return 'array(\n' + Object.keys(D).map(function (K) { 204 | var V = D[K] 205 | return spp + helpers.escapeString(K, 'lightncandy') + ' => ' + helpers.php_array(V, spp) 206 | }).join(',\n') + '\n' + sp + ')' 207 | } 208 | 209 | if (typeof D === 'string') { 210 | return D.match(/^\(object\)|new \w/) ? D : helpers.escapeString(D, 'lightncandy') 211 | } 212 | 213 | if (D === true) { 214 | return 'true' 215 | } 216 | 217 | if (D === false) { 218 | return 'false' 219 | } 220 | 221 | if (D === undefined) { 222 | return 'NULL' 223 | } 224 | 225 | return D 226 | }, 227 | 228 | result_for_code: function (code, type, fail, log) { 229 | var result 230 | if (type === 'php') { 231 | if (process.env.NODE_DEV !== 'development') { 232 | code = 'error_reporting(E_ERROR | E_PARSE);\n' + code 233 | code = 'if (function_exists("xdebug_disable")) {xdebug_disable();}\n' + code 234 | } 235 | fs.writeFileSync(tmpFile, '') 236 | try { 237 | result = { 238 | output: child.execSync('php ' + tmpFile + (log ? ' 2>&1' : ' 2>null')).toString(), 239 | code: 0 240 | } 241 | } catch (E) { 242 | result = { 243 | output: E.message, 244 | code: 98765 245 | } 246 | } 247 | if (log) { 248 | result.code = 99999 249 | } 250 | fs.unlinkSync(tmpFile) 251 | } else { 252 | try { 253 | /* eslint no-eval: off */ 254 | result = { 255 | code: 0, 256 | output: eval(code.replace(/console\.log/g, 'consoleLog').replace(/require\('handlebars'\);/, 'require(\'handlebars\').create();')) 257 | } 258 | } catch (E) { 259 | result = { 260 | code: 1, 261 | output: E.message 262 | } 263 | } 264 | } 265 | if (result.code && (fail === undefined)) { 266 | console.warn('## Bad result when execute code:\n' + code + '\n') 267 | console.warn(result) 268 | } 269 | return result 270 | }, 271 | 272 | result_class: function (options) { 273 | if (options.data.result.code > 0) { 274 | return 'error' 275 | } 276 | 277 | if (options.data.standard && (options.data.result.output !== options.data.standard.result.output)) { 278 | if (!options.hash.different) { 279 | console.warn('!!Render result is not same with standard.\n!Standard:\n"' + options.data.standard.result.output + '"\n!Result:\n"' + options.data.result.output + '"\n') 280 | } 281 | return 'different' 282 | } 283 | 284 | return 'result' 285 | }, 286 | 287 | escapeString: function (str, type) { 288 | return (type === 'lightncandy') ? ('"' + helpers.doubleQuote(str) + '"') : ("'" + helpers.singleQuote(str) + "'") 289 | }, 290 | 291 | doubleQuote: function (str) { 292 | return (str && str.replace) ? str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') : str 293 | }, 294 | 295 | singleQuote: function (str) { 296 | return (str && str.replace) ? str.replace(/\\/g, '\\\\').replace(/'/g, '\\\'').replace(/\n/g, '\\n') : str 297 | }, 298 | 299 | data_for_render: function (cx, options) { 300 | var type = options.hash.type || cx.type || 'handlebars.js' 301 | var input = options.hash.data || cx.data 302 | var opt = options.hash.option || cx.option 303 | var renderopt = options.hash.renderoption || cx.renderoption 304 | var par = options.hash.partial || cx.partial 305 | var showcode = options.hash.showcode || cx.showcode 306 | var norender = options.hash.compileerror || cx.compileerror || showcode 307 | var data = helpers.code_for_data(input, type) 308 | var Option = helpers.code_for_option(opt, type) 309 | var Helper = helpers.code_for_helper(options.hash.helper || cx.helper, type) 310 | var Partial = helpers.code_for_partial(par, type) 311 | var fail = options.fail || cx.fail || norender 312 | var errlog = options.errorlog || cx.errorlog 313 | 314 | var ret = { 315 | type: type, 316 | template: options.hash.template || cx.template, 317 | codeData: data[1], 318 | codeType: data[0], 319 | codeRequire: helpers.code_for_require(type), 320 | codeSetData: helpers.code_for_set('data', type) + data[1] + ';', 321 | codeExtra: helpers.code_for_excode(options.hash.excode || cx.excode, type), 322 | codeCompile: helpers.code_for_compile(type, Option, Partial, Helper, norender), 323 | codeRender: helpers.code_for_render(type, renderopt, Partial), 324 | codePartial: Partial 325 | } 326 | 327 | ret.codeSetTemplate = helpers.code_for_set('template', type) + helpers.escapeString(ret.template, type) + ";" 328 | 329 | ret.code = [ 330 | ret.codeRequire, 331 | ret.codeSetTemplate, 332 | ret.codeExtra, 333 | ret.codeCompile, 334 | norender ? '' : ret.codeSetData, 335 | norender ? '' : ret.codeRender, 336 | showcode ? 'echo $php' : '' 337 | ].join('\n') 338 | 339 | if (typeof fail === 'object') { 340 | fail = fail[type] 341 | } 342 | 343 | ret.result = helpers.result_for_code(ret.code, ret.codeType, fail, errlog) 344 | 345 | return ret 346 | }, 347 | 348 | render: function (options) { 349 | var data = handlebars.createFrame(options.data) 350 | 351 | if (data.standard && data.standard.type === (this.type || options.hash.type)) { 352 | Object.assign(data, data.standard) 353 | } else { 354 | Object.assign(data, helpers.data_for_render(this, options)) 355 | } 356 | 357 | return options.fn(this, {data: data}) 358 | }, 359 | 360 | code: function (cx, options) { 361 | var btn = [] 362 | var id = 'code_' + shortid() 363 | var code = '' 364 | var result = '' 365 | var type = options.hash.type 366 | var classes = options.hash.class ? [options.hash.class] : [] 367 | var className 368 | 369 | if (typeof cx === 'function') { 370 | cx = cx.apply(this, [options]) 371 | } 372 | 373 | code = code + helpers.remove_dupe_cr(cx) 374 | 375 | if (options.hash.collapse) { 376 | classes.push('collapse') 377 | } 378 | 379 | className = classes.length ? (' class="' + classes.join(' ') + '"') : '' 380 | 381 | if (options.hash.result !== undefined) { 382 | result = helpers.result_for_code(code, options.hash.type, options.hash.errorlog, options.hash.errorlog) 383 | 384 | if (((result.code === 0) || options.hash.errorlog) && (result.output !== '')) { 385 | result = options.fn(result.output) 386 | } else { 387 | result = '' 388 | } 389 | } 390 | 391 | if (options.hash.collapse) { 392 | btn.push('') 393 | } 394 | 395 | if (options.hash.copy) { 396 | btn.push('') 397 | } 398 | 399 | return (btn.length ? ('
Source Code ' + btn.join(' ') + '
') : '') 400 | + '
' + Prism.highlight(code, Prism.languages[type], type) + '
' 401 | + result 402 | }, 403 | 404 | isStringThenOutput: function (cx, options) { 405 | if (typeof cx !== 'string') { 406 | return 407 | } 408 | if (options.hash.tag) { 409 | return '<' + options.hash.tag + '>' + cx + '' 410 | } 411 | return cx 412 | }, 413 | 414 | addOne: function (I) { 415 | return I + 1 416 | }, 417 | 418 | collect: function (cx, key, options) { 419 | if (typeof cx !== 'object') { 420 | return 421 | } 422 | return Object.keys(cx).reduce(function (O, K, I) { 423 | var V = cx[K] 424 | if (V && V[key]) { 425 | if (!options.hash.ignore || (options.hash.ignore[I] === undefined)) { 426 | if (options.hash.key) { 427 | V[key][options.hash.key] = K 428 | } 429 | if (options.hash.comment && V[options.hash.comment]) { 430 | O.push('\n// ' + V[options.hash.comment]) 431 | } 432 | O.push(V[key]) 433 | } 434 | } 435 | return O 436 | }, []) 437 | }, 438 | 439 | str_join: function () { 440 | var arg = Array.prototype.slice.call(arguments) 441 | var options = arg.pop() 442 | 443 | return arg.join(options.hash.sep || '') 444 | }, 445 | 446 | join: function (cx, sep) { 447 | return (cx && cx.join) ? cx.join(sep) : '' 448 | }, 449 | 450 | book_writer: function (data, options) { 451 | var refs = data.reduce(function (O, V) { 452 | O[V.pagename] = V 453 | return O 454 | }, {}) 455 | 456 | data.forEach(function (D, I) { 457 | var T = D.pagename.match(/LC-(.+)/) 458 | if (T) { 459 | D.title = 'LightnCandy option: ' + T[1] 460 | D.opt_name = T[1] 461 | } 462 | if (D.hbonly) { 463 | D.title = 'Handlebars: ' + D.title 464 | } 465 | }) 466 | 467 | data.forEach(function (D, I) { 468 | var Data = handlebars.createFrame(options.data) 469 | 470 | Data.refs = refs 471 | 472 | if (I > 0) { 473 | Data.page_prev = data[I - 1] 474 | } 475 | if (data[I + 1] !== undefined) { 476 | Data.page_next = data[I + 1] 477 | } 478 | 479 | console.log('>> Writing the page:' + D.pagename) 480 | fs.writeFileSync(options.data.configs.out_dir + D.pagename + '.html', options.fn(D, {data: Data})) 481 | }) 482 | }, 483 | 484 | section_builder: function (cx, options) { 485 | if (!cx) { 486 | return '' 487 | } 488 | if (typeof cx !== 'object') { 489 | return '

' + cx + '

' 490 | } 491 | var sections = Object.keys(cx).reduce(function (ret, S) { 492 | var data = handlebars.createFrame(options.data) 493 | data.section = S 494 | var R = options.fn(cx, {data: data}) 495 | if (R !== '') { 496 | ret.push(R) 497 | } 498 | return ret 499 | }, []) 500 | if (options.hash.column && sections.length > 1) { 501 | return '
' + sections.map(function (S) { 502 | return '
' + S + '
' 503 | }).join('') + '
' 504 | } 505 | return sections.join('') 506 | }, 507 | 508 | main_section: function (options) { 509 | switch (options.data.section) { 510 | case 'title': 511 | case 'description': 512 | case 'pagename': 513 | case 'ref': 514 | case 'opt_name': 515 | return '' 516 | default: 517 | return options.fn(this) 518 | } 519 | }, 520 | 521 | eq: function (a, b) { 522 | return (a === b) 523 | }, 524 | 525 | or: function () { 526 | var arg = Array.prototype.slice.call(arguments, 0, -1) 527 | 528 | for (var I in arg) { 529 | if (arg[I]) { 530 | return arg[I] 531 | } 532 | } 533 | }, 534 | 535 | match: function (regexp, string) { 536 | return (new RegExp(regexp)).test(string) 537 | }, 538 | 539 | set: function (name, value, options) { 540 | var data = handlebars.createFrame(options.data) 541 | 542 | data[name] = value 543 | 544 | return options.fn(this, {data: data}) 545 | }, 546 | 547 | code_type: function (options) { 548 | var T = options.hash.section || options.data.section 549 | 550 | if (this.file) { 551 | switch (this.file.match(/\.(.+)$/)[1]) { 552 | default: 553 | return 'js' 554 | } 555 | } 556 | 557 | if (T) { 558 | switch (T) { 559 | case 'template': 560 | return 'handlebars' 561 | case 'mustache': 562 | case 'handlebars.js': 563 | case 'nodejs': 564 | case 'JavaScript': 565 | return 'js' 566 | case 'php': 567 | case 'lightncandy': 568 | return 'php' 569 | default: 570 | return 'js' 571 | } 572 | } 573 | }, 574 | 575 | remove_dupe_cr: function (txt) { 576 | return (txt && txt.replace) ? txt.replace(/\n+$/, '').replace(/\n{3,10}/g, '\n\n') : '' 577 | }, 578 | 579 | anchor: function (name) { 580 | return encodeURIComponent(name.replace(/ /g, '')) 581 | }, 582 | 583 | anchorHTML: function (name) { 584 | var N = helpers.anchor(name) 585 | return '' 586 | } 587 | } 588 | 589 | module.exports = helpers 590 | --------------------------------------------------------------------------------