├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── contributing.md ├── funding.yml ├── pull_request_template.md └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── .nycrc ├── INDEX.md ├── changelog.md ├── lib ├── backup.js ├── escape.js ├── index.d.ts ├── index.js ├── locals.js ├── loops.js ├── placeholders.js └── tags.js ├── license ├── package-lock.json ├── package.json ├── readme.md └── test ├── expect ├── attr_param.html ├── basic.html ├── boolean_attr.html ├── conditional.html ├── conditional_customtags.html ├── conditional_expression.html ├── conditional_if.html ├── conditional_if_key_not_exists.html ├── conditional_nested.html ├── conditional_norender.html ├── conditional_tag_break.html ├── custom_delimiters.html ├── directives.html ├── escape_html.html ├── expression_custom_delimiters_ignored.html ├── expression_ignored.html ├── expression_spacing.html ├── local_missing.html ├── local_missing_keep.html ├── local_missing_remove.html ├── local_missing_replace.html ├── loop.html ├── loop_conditional.html ├── loop_conditional_locals.html ├── loop_conflict.html ├── loop_customtag.html ├── loop_locals.html ├── loop_metadata.html ├── loop_nested.html ├── loop_nested_metadata.html ├── loop_object.html ├── raw.html ├── raw_custom.html ├── raw_in_condition.html ├── raw_in_switch.html ├── scope.html ├── scope_nested.html ├── script-locals-global-not-informed.html ├── script-locals-global.html ├── script-locals-remove.html ├── script-locals.html ├── switch.html ├── switch_customtag.html ├── switch_default.html ├── switch_nested.html ├── switch_number.html └── unescaped.html ├── fixtures ├── attr_param.html ├── basic.html ├── boolean_attr.html ├── conditional.html ├── conditional_customtags.html ├── conditional_elseif_error.html ├── conditional_expression.html ├── conditional_expression_error.html ├── conditional_if.html ├── conditional_if_error.html ├── conditional_if_key_not_exists.html ├── conditional_nested.html ├── conditional_norender.html ├── conditional_tag_break.html ├── custom_delimiters.html ├── directives.html ├── escape_html.html ├── expression_custom_delimiters_ignored.html ├── expression_error.html ├── expression_ignored.html ├── expression_spacing.html ├── local_missing.html ├── local_missing_keep.html ├── local_missing_remove.html ├── local_missing_replace.html ├── loop.html ├── loop_conditional.html ├── loop_conditional_locals.html ├── loop_conflict.html ├── loop_customtag.html ├── loop_expression_error.html ├── loop_locals.html ├── loop_metadata.html ├── loop_nested.html ├── loop_nested_metadata.html ├── loop_no_args.html ├── loop_no_attr.html ├── loop_no_collection.html ├── loop_no_in.html ├── loop_object.html ├── raw.html ├── raw_custom.html ├── raw_in_condition.html ├── raw_in_switch.html ├── scope.html ├── scope_nested.html ├── script-locals-global-not-informed.html ├── script-locals-global.html ├── script-locals-remove.html ├── script-locals.html ├── switch.html ├── switch_bad_flow.html ├── switch_customtag.html ├── switch_default.html ├── switch_nested.html ├── switch_no_attr.html ├── switch_no_case_attr.html ├── switch_number.html └── unescaped.html ├── test-conditionals.js ├── test-core.js ├── test-locals.js ├── test-loops.js ├── test-scopes.js └── test-switch.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_size = 2 6 | end_of_line = lf 7 | indent_style = space 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{md,html}] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | ## Problem 2 | 3 | Briefly describe the issue you are experiencing (or the feature you want to see 4 | added to the plugin). Tell us what you were trying to do and what happened 5 | instead. Remember, this is _not_ a place to ask questions. For that, go to 6 | http://gitter.im/posthtml/posthtml 7 | 8 | ## Details 9 | 10 | Describe in more detail the problem you have been experiencing, if necessary. 11 | 12 | ## Error Logs 13 | 14 | Create a [gist](https://gist.github.com) which is a paste of your **full** 15 | logs(_result.tree_ (PostHTML Tree), _result.html_ (HTML)), and link them here. 16 | Do **not** paste your full logs here, as it will make this issue long and hard 17 | to read! If you are reporting a bug, **always** include logs! 18 | 19 | ## Issue [ Code ] 20 | 21 | Please remember that, with sample code; it's easier to reproduce bug and much 22 | faster to fix it. 23 | 24 | Please refer to a simple code example. 25 | 26 | ```bash 27 | $ git clone https://github.com// 28 | ``` 29 | 30 | ## Environment 31 | 32 | Please provide information about your environment. 33 | 34 | | OS | Node | npm | PostHTML | 35 | |:---------------:|:---------:|:---------:|:---------:| 36 | | [name][version] | [version] | [version] | [version] | 37 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | You want to help? You rock! Now, take a moment to be sure your contributions make sense to everyone else. 2 | 3 | ## Reporting Issues 4 | 5 | Found a problem? Want a new feature? 6 | 7 | - See if your issue or idea has [already been reported]. 8 | - Provide a [reduced test case] or a [live example]. 9 | 10 | Remember, a bug is a _demonstrable problem_ caused by _our_ code. 11 | 12 | ## Submitting Pull Requests 13 | 14 | Pull requests are the greatest contributions, so be sure they are focused in scope, and do avoid unrelated commits. 15 | 16 | 1. To begin, [fork this project], clone your fork, and add our upstream. 17 | ```bash 18 | # Clone your fork of the repo into the current directory 19 | git clone https://github.com//PLUGIN_NAME 20 | # Navigate to the newly cloned directory 21 | cd PLUGIN_NAME 22 | # Assign the original repo to a remote called "upstream" 23 | git remote add upstream https://github.com/GITHUB_NAME/PLUGIN_NAME 24 | # Install the tools necessary for development 25 | npm install 26 | ``` 27 | 28 | 2. Create a branch for your feature or fix: 29 | ```bash 30 | # Move into a new branch for a feature 31 | git checkout -b feature/thing 32 | ``` 33 | ```bash 34 | # Move into a new branch for a fix 35 | git checkout -b fix/something 36 | ``` 37 | 38 | 3. Be sure your code follows our practices. 39 | ```bash 40 | # Test current code 41 | npm run test 42 | ``` 43 | 44 | 4. Push your branch up to your fork: 45 | ```bash 46 | # Push a feature branch 47 | git push origin feature/thing 48 | ``` 49 | ```bash 50 | # Push a fix branch 51 | git push origin fix/something 52 | ``` 53 | 54 | 5. Now [open a pull request] with a clear title and description. 55 | 56 | [already been reported]: issues 57 | [fork this project]: fork 58 | [live example]: http://codepen.io/pen 59 | [open a pull request]: https://help.github.com/articles/using-pull-requests/ 60 | [reduced test case]: https://css-tricks.com/reduced-test-cases/ 61 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: posthtml 5 | open_collective: posthtml 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Describe the big picture of your changes here to communicate to the maintainers 4 | why we should accept this pull request. If it fixes a bug or resolves a feature 5 | request, be sure to link to that issue. 6 | 7 | ## Types of Changes 8 | 9 | What types of changes does your code introduce 10 | _Put an `x` in the boxes that apply_ 11 | 12 | - [ ] Bug (non-breaking change which fixes an issue) 13 | - [ ] Feature (non-breaking change which adds functionality) 14 | - [ ] Breaking Change (fix or feature which changes existing functionality) 15 | 16 | ## Checklist 17 | 18 | _Put an `x` in the boxes that apply. You can also fill these out after creating 19 | the PR. If you're unsure about any of them, don't hesitate to ask. We're here to 20 | help! This is a reminder of what we are going to look for before merging your 21 | code._ 22 | 23 | - [ ] I have read the [CONTRIBUTING](/CONTRIBUTING.md) guide 24 | - [ ] Lint and unit tests pass with my changes 25 | - [ ] I have added tests that prove my fix is effective/works 26 | - [ ] I have added necessary documentation (if appropriate) 27 | - [ ] Any dependent changes are merged and published in downstream modules 28 | 29 | ## Further Comments 30 | 31 | If this is a large or complex change, kick off the discussion by explaining why 32 | you chose the solution you did and what alternatives you considered, etc... 33 | 34 | ### Reviewers: @mrmlnc, @jescalan, @michael-ciniawsky, @posthtml/collaborators 35 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Actions Status 2 | on: 3 | pull_request: 4 | types: [opened, synchronize] 5 | branches: 6 | - master 7 | env: 8 | CI: true 9 | 10 | jobs: 11 | run: 12 | name: Node ${{ matrix.node }} on ${{ matrix.os }} 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | node: [10, 12, 14] 19 | os: [ubuntu-latest] 20 | 21 | steps: 22 | - name: Clone repository 23 | uses: actions/checkout@v2 24 | 25 | - name: Set Node.js version 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node }} 29 | 30 | - name: Install npm dependencies 31 | run: npm ci 32 | 33 | - name: Run tests 34 | run: npm run test 35 | 36 | - name: Run Coveralls 37 | uses: coverallsapp/github-action@master 38 | with: 39 | github-token: "${{ secrets.GITHUB_TOKEN }}" 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | 3 | .DS_Store 4 | ._* 5 | 6 | # NODEJS 7 | 8 | .nyc_output 9 | 10 | npm-debug.log 11 | 12 | dmd 13 | coverage 14 | jsdoc-api 15 | node_modules 16 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # FILES 2 | 3 | .travis.yml 4 | .gitignore 5 | .editorconfig 6 | .mversionrc 7 | 8 | CHANGELOG.md 9 | CONTRIBUTING.md 10 | README.md 11 | 12 | npm-debug.log 13 | 14 | # DIRECTORIES 15 | 16 | .github 17 | .nyc_output 18 | 19 | dmd 20 | test 21 | coverage 22 | jsdoc-api 23 | node_modules 24 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": ["lcov", "text"], 3 | "extension": [".js"] 4 | } -------------------------------------------------------------------------------- /INDEX.md: -------------------------------------------------------------------------------- 1 | ## Modules 2 | 3 |
4 |
backup : Object
5 |
6 |
escape : function
7 |
8 |
posthtml-expressionsObject
9 |

Expressions Plugin for PostHTML

10 |
11 |
loops : function
12 |
13 |
placeholders : function
14 |
15 |
tags : function
16 |
17 |
18 | 19 | ## Functions 20 | 21 |
22 |
makeLocalsBackup(keys, locals)Object
23 |

Creates a backup of keys values

24 |
25 |
revertBackupedLocals(keys, locals, backup)Object
26 |

Returns the original keys values

27 |
28 |
escapeRegexpString(input)function
29 |

Replace String based on RegExp

30 |
31 |
executeLoop(params, p1, p2, locals, tree)function
32 |

Creates a set of local variables within the loop, and evaluates all nodes within the loop, returning their contents

33 |
34 |
executeScope(scope, locals, node)function
35 |

Runs walk function with arbitrary set of local variables

36 |
37 |
getLoopMeta(index, target)Object
38 |

Returns an object containing loop metadata

39 |
40 |
parseLoopStatement(input)Object
41 |

Given a "loop" parameter from an "each" tag, parses out the param names and expression to be looped.

42 |
43 |
escapeHTML(unescaped)String
44 |

Escape HTML characters with their respective entities

45 |
46 |
placeholders(input, ctx, settings, opts)String
47 |

Replace Expressions

48 |
49 |
getNextTag(nodes, i)Array
50 |

Get the next tag from a node list

51 |
52 |
53 | 54 | 55 | 56 | ## backup : Object 57 | **Requires**: module:fclone 58 | **Properties** 59 | 60 | | Name | Type | Description | 61 | | --- | --- | --- | 62 | | make | function | Make Locals backup | 63 | | revert | function | Revert backuped Locals | 64 | 65 | 66 | 67 | ## escape : function 68 | 69 | 70 | ## posthtml-expressions ⇒ Object 71 | Expressions Plugin for PostHTML 72 | 73 | **Returns**: Object - tree PostHTML Tree 74 | **Requires**: module:vm, module:./tags, module:./loops, module:./escape, module:./backup, module:./placeholders 75 | **Version**: 1.0.0 76 | **Author**: Jeff Escalante Denis (@jescalan), 77 | Denis Malinochkin (mrmlnc), 78 | Michael Ciniawsky (@michael-ciniawsky) 79 | **License**: MIT 80 | 81 | | Param | Type | Description | 82 | | --- | --- | --- | 83 | | options | Object | Options | 84 | 85 | 86 | 87 | ## loops : function 88 | 89 | 90 | ## placeholders : function 91 | **Requires**: module:vm 92 | 93 | 94 | ## tags : function 95 | 96 | 97 | ## makeLocalsBackup(keys, locals) ⇒ Object 98 | Creates a backup of keys values 99 | 100 | **Kind**: global function 101 | **Returns**: Object - backup Backup Locals 102 | 103 | | Param | Type | Description | 104 | | --- | --- | --- | 105 | | keys | Object | Keys | 106 | | locals | Object | Locals | 107 | 108 | 109 | 110 | ## revertBackupedLocals(keys, locals, backup) ⇒ Object 111 | Returns the original keys values 112 | 113 | **Kind**: global function 114 | **Returns**: Object - locals Reverted Locals 115 | 116 | | Param | Type | Description | 117 | | --- | --- | --- | 118 | | keys | Object | Keys | 119 | | locals | Object | Locals | 120 | | backup | Object | Backup | 121 | 122 | 123 | 124 | ## escapeRegexpString(input) ⇒ function 125 | Replace String based on RegExp 126 | 127 | **Kind**: global function 128 | **Returns**: function - input Replaced Input 129 | 130 | | Param | Type | Description | 131 | | --- | --- | --- | 132 | | input | String | Input | 133 | 134 | 135 | 136 | ## executeLoop(params, p1, p2, locals, tree) ⇒ function 137 | Creates a set of local variables within the loop, and evaluates all nodes within the loop, returning their contents 138 | 139 | **Kind**: global function 140 | **Returns**: function - walk Walks the tree and parses all locals within the loop 141 | 142 | | Param | Type | Description | 143 | | --- | --- | --- | 144 | | params | Array | Parameters | 145 | | p1 | String | Parameter 1 | 146 | | p2 | String | Parameter 2 | 147 | | locals | Object | Locals | 148 | | tree | String | Tree | 149 | 150 | 151 | 152 | ## executeScope(scope, locals, node) ⇒ function 153 | Runs walk function with arbitrary set of local variables 154 | 155 | **Kind**: global function 156 | **Returns**: function - walk Walks the tree and parses all locals in scope 157 | 158 | | Param | Type | Description | 159 | | --- | --- | --- | 160 | | scope | Object | Scoped Locals | 161 | | locals | Object | Locals | 162 | | node | Object | Node | 163 | 164 | 165 | 166 | ## getLoopMeta(index, target) ⇒ Object 167 | Returns an object containing loop metadata 168 | 169 | **Kind**: global function 170 | **Returns**: Object - Object containing loop metadata 171 | 172 | | Param | Type | Description | 173 | | --- | --- | --- | 174 | | index | Integer \| Object | Current iteration | 175 | | target | Object | Object being iterated | 176 | 177 | 178 | 179 | ## parseLoopStatement(input) ⇒ Object 180 | Given a "loop" parameter from an "each" tag, parses out the param names and expression to be looped. 181 | 182 | **Kind**: global function 183 | **Returns**: Object - {} Keys && Expression 184 | 185 | | Param | Type | Description | 186 | | --- | --- | --- | 187 | | input | String | Input | 188 | 189 | 190 | 191 | ## escapeHTML(unescaped) ⇒ String 192 | Escape HTML characters with their respective entities 193 | 194 | **Kind**: global function 195 | **Returns**: String - escaped Save HTML 196 | 197 | | Param | Type | Description | 198 | | --- | --- | --- | 199 | | unescaped | String | Unsafe HTML | 200 | 201 | 202 | 203 | ## placeholders(input, ctx, settings, opts) ⇒ String 204 | Replace Expressions 205 | 206 | **Kind**: global function 207 | **Returns**: String - input Replaced Input 208 | 209 | | Param | Type | Description | 210 | | --- | --- | --- | 211 | | input | String | Input | 212 | | ctx | Object | Context | 213 | | settings | Array | Settings | 214 | | opts | Array | Options | 215 | 216 | 217 | 218 | ## getNextTag(nodes, i) ⇒ Array 219 | Get the next tag from a node list 220 | 221 | **Kind**: global function 222 | **Returns**: Array - [] Array containing the next tag 223 | 224 | | Param | Type | Description | 225 | | --- | --- | --- | 226 | | nodes | Array | Nodes | 227 | | i | Number | Accumulator | 228 | 229 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## [1.11.4](https://github.com/posthtml/posthtml-expressions/compare/v1.11.3...v1.11.4) (2024-07-25) 2 | 3 | 4 | 5 | ## [1.11.3](https://github.com/posthtml/posthtml-expressions/compare/v1.11.2...v1.11.3) (2023-11-13) 6 | 7 | 8 | 9 | ## [1.11.2](https://github.com/posthtml/posthtml-expressions/compare/v1.11.1...v1.11.2) (2023-10-17) 10 | 11 | 12 | ### Bug Fixes 13 | 14 | * inline expression ignore regex ([aa83033](https://github.com/posthtml/posthtml-expressions/commit/aa8303315136652b0b07db228d0406b18fd1f79a)) 15 | * use first char of unescapedDelimiters ([48337fc](https://github.com/posthtml/posthtml-expressions/commit/48337fc710aab87880a7ed1dacfd69d70c997a2c)) 16 | 17 | 18 | 19 | ## [1.11.1](https://github.com/posthtml/posthtml-expressions/compare/v1.11.0...v1.11.1) (2023-06-03) 20 | 21 | 22 | 23 | # [1.11.0](https://github.com/posthtml/posthtml-expressions/compare/v1.10.0...v1.11.0) (2022-12-20) 24 | 25 | 26 | 27 | # [1.10.0](https://github.com/posthtml/posthtml-expressions/compare/v1.9.0...v1.10.0) (2022-09-26) 28 | 29 | 30 | ### Features 31 | 32 | * allow global script locals ([a5678f5](https://github.com/posthtml/posthtml-expressions/commit/a5678f5b37082b26b5f23d7ec43de4f0e3b3ec5c)) 33 | 34 | 35 | 36 | # [1.9.0](https://github.com/posthtml/posthtml-expressions/compare/v1.8.1...v1.9.0) (2021-08-23) 37 | 38 | 39 | ### Features 40 | 41 | * remove script locals, close [#120](https://github.com/posthtml/posthtml-expressions/issues/120) ([023fcb9](https://github.com/posthtml/posthtml-expressions/commit/023fcb9845939524265589d5b59741e41ea73aa4)) 42 | 43 | 44 | ### Performance Improvements 45 | 46 | * default locals attr ([73dc216](https://github.com/posthtml/posthtml-expressions/commit/73dc216c2adae6de9582c1c65a83d460df836f0a)) 47 | 48 | 49 | 50 | ## [1.8.1](https://github.com/posthtml/posthtml-expressions/compare/v1.8.0...v1.8.1) (2021-08-10) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * tree.match is not a function ([bee5368](https://github.com/posthtml/posthtml-expressions/commit/bee5368149d7f1258833656f91355e1266f6ee4d)) 56 | 57 | 58 | 59 | # [1.8.0](https://github.com/posthtml/posthtml-expressions/compare/v1.7.1...v1.8.0) (2021-08-09) 60 | 61 | 62 | ### Bug Fixes 63 | 64 | * after update parser/render ([e0bafbe](https://github.com/posthtml/posthtml-expressions/commit/e0bafbeec23b18ae59c40c43dd132fc01bfbc73d)) 65 | * lost posthtml-match-helper ([350c3fd](https://github.com/posthtml/posthtml-expressions/commit/350c3fdf167ee638deb9b3c5cf5ffc98527406b8)) 66 | 67 | 68 | ### Features 69 | 70 | * locals data from script tag, close [#117](https://github.com/posthtml/posthtml-expressions/issues/117) ([e730454](https://github.com/posthtml/posthtml-expressions/commit/e73045458dc90afdb4046a93c394d75800818b8d)) 71 | 72 | 73 | 74 | ## [1.7.1](https://github.com/posthtml/posthtml-expressions/compare/v1.7.0...v1.7.1) (2020-12-02) 75 | 76 | 77 | ### Bug Fixes 78 | 79 | * throw if strict mode ([2c0c0d1](https://github.com/posthtml/posthtml-expressions/commit/2c0c0d1a181b280ba72f76c6530a1c626ab987e6)) 80 | 81 | 82 | 83 | # [1.7.0](https://github.com/posthtml/posthtml-expressions/compare/v1.6.2...v1.7.0) (2020-11-24) 84 | 85 | 86 | 87 | ## [1.6.2](https://github.com/posthtml/posthtml-expressions/compare/v1.6.1...v1.6.2) (2020-11-12) 88 | 89 | 90 | ### Bug Fixes 91 | 92 | * items is not defined in each, close [#107](https://github.com/posthtml/posthtml-expressions/issues/107) ([d14fbbb](https://github.com/posthtml/posthtml-expressions/commit/d14fbbb683460fad4f8b2edb649f9ef759b89038)) 93 | 94 | 95 | 96 | ## [1.6.1](https://github.com/posthtml/posthtml-expressions/compare/v1.6.0...v1.6.1) (2020-11-09) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * check if key exists in locals as text, close [#102](https://github.com/posthtml/posthtml-expressions/issues/102) ([1d7596a](https://github.com/posthtml/posthtml-expressions/commit/1d7596a8a4d12fd29a74f03637057772a9b81d09)) 102 | 103 | 104 | 105 | # [1.6.0](https://github.com/posthtml/posthtml-expressions/compare/v1.5.0...v1.6.0) (2020-10-30) 106 | 107 | 108 | ### Features 109 | 110 | * strictMode show or not throw an exception, close [#102](https://github.com/posthtml/posthtml-expressions/issues/102) ([dbb32c9](https://github.com/posthtml/posthtml-expressions/commit/dbb32c9a9cd0c3b6468547ecea1d1d492e0ac7ce)) 111 | 112 | 113 | 114 | # [1.5.0](https://github.com/posthtml/posthtml-expressions/compare/v1.4.7...v1.5.0) (2020-09-21) 115 | 116 | 117 | ### Features 118 | 119 | * attribute as param, close [#99](https://github.com/posthtml/posthtml-expressions/issues/99) ([74c2b32](https://github.com/posthtml/posthtml-expressions/commit/74c2b3201f1492c4115e0e4b8f71e228f0e70370)) 120 | 121 | 122 | 123 | ## [1.4.7](https://github.com/posthtml/posthtml-expressions/compare/v1.4.6...v1.4.7) (2020-08-28) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * options for parser when nnormalize, close [#97](https://github.com/posthtml/posthtml-expressions/issues/97) ([99c0281](https://github.com/posthtml/posthtml-expressions/commit/99c02815facfcb692d52f37b3d80ad882f4756fb)) 129 | 130 | 131 | 132 | ## [1.4.6](https://github.com/posthtml/posthtml-expressions/compare/v1.4.5...v1.4.6) (2020-08-23) 133 | 134 | 135 | ### Bug Fixes 136 | 137 | * remove node text after condition, close [#62](https://github.com/posthtml/posthtml-expressions/issues/62) ([2cefe7d](https://github.com/posthtml/posthtml-expressions/commit/2cefe7dac40b61e82752c7f03b91296870474194)) 138 | 139 | 140 | 141 | ## [1.4.5](https://github.com/posthtml/posthtml-expressions/compare/v1.4.4...v1.4.5) (2020-07-07) 142 | 143 | 144 | ### Bug Fixes 145 | 146 | * normalize output tree, close [#93](https://github.com/posthtml/posthtml-expressions/issues/93) ([5d820e5](https://github.com/posthtml/posthtml-expressions/commit/5d820e59e0f745444015220937d3e3321071249e)) 147 | 148 | 149 | 150 | ## [1.4.4](https://github.com/posthtml/posthtml-expressions/compare/v1.4.3...v1.4.4) (2020-06-30) 151 | 152 | 153 | ### Bug Fixes 154 | 155 | * escape custom delimiters when ignoring expressions ([53c901e](https://github.com/posthtml/posthtml-expressions/commit/53c901eb8d58e4c1d259db9963bd6817efc11ea2)) 156 | 157 | 158 | 159 | ## [1.4.3](https://github.com/posthtml/posthtml-expressions/compare/v1.4.2...v1.4.3) (2020-06-25) 160 | 161 | 162 | ### Bug Fixes 163 | 164 | * clear raw tag after expression, close [#82](https://github.com/posthtml/posthtml-expressions/issues/82) ([2aaef58](https://github.com/posthtml/posthtml-expressions/commit/2aaef5867c0f7971358226f125b288e9de4d021c)) 165 | 166 | 167 | 168 | 169 | # [1.1.0](https://github.com/posthtml/posthtml-expressions/compare/v1.0.0...v1.1.0) (2016-12-14) 170 | 171 | 172 | ### Bug Fixes 173 | 174 | * **index:** comments ([553b0fa](https://github.com/posthtml/posthtml-expressions/commit/553b0fa)) 175 | * **index:** copy/paste for loops ([c865969](https://github.com/posthtml/posthtml-expressions/commit/c865969)) 176 | 177 | ### Features 178 | 179 | * **index:** add switch statement ([88058d1](https://github.com/posthtml/posthtml-expressions/commit/88058d1)) 180 | 181 | 182 | 183 | 184 | # [1.0.0](https://github.com/posthtml/posthtml-expressions/compare/v0.9.0...v1.0.0) (2016-11-19) 185 | 186 | 187 | ### Bug Fixes 188 | 189 | * patch to fix expressions in else/elseif blocks ([f50c36f](https://github.com/posthtml/posthtml-expressions/commit/f50c36f)) 190 | * **index:** Fix issue with conditional in loops ([58b1f50](https://github.com/posthtml/posthtml-expressions/commit/58b1f50)) 191 | * **index:** remove console.log, fix comment ([c5b7887](https://github.com/posthtml/posthtml-expressions/commit/c5b7887)) 192 | * **lib:** add missing 'use strict' statement ([4078c90](https://github.com/posthtml/posthtml-expressions/commit/4078c90)) 193 | * **placeholders:** Fix typo in "matches" ([e0d38bd](https://github.com/posthtml/posthtml-expressions/commit/e0d38bd)) 194 | 195 | ### Features 196 | 197 | * **index:** scopes ([d3ea9ce](https://github.com/posthtml/posthtml-expressions/commit/d3ea9ce)) 198 | 199 | ### Performance Improvements 200 | 201 | * **index:** Create a copy only for specified keys ([5239bc0](https://github.com/posthtml/posthtml-expressions/commit/5239bc0)) 202 | * **index:** Make a copy of the tree only once ([ba9d706](https://github.com/posthtml/posthtml-expressions/commit/ba9d706)) 203 | * **index:** Optimize scoped locals for `scope` tag ([1046bc4](https://github.com/posthtml/posthtml-expressions/commit/1046bc4)) 204 | -------------------------------------------------------------------------------- /lib/backup.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const cloneDeep = require('fclone') 4 | 5 | /** 6 | * @description Creates a backup of keys values 7 | * 8 | * @method makeLocalsBackup 9 | * 10 | * @param {Object} keys Keys 11 | * @param {Object} locals Locals 12 | * 13 | * @return {Object} backup Backup Locals 14 | */ 15 | function makeLocalsBackup (keys, locals) { 16 | const backup = {} 17 | 18 | for (let i = 0; i < keys.length; i++) { 19 | const key = keys[i] 20 | if (Object.prototype.hasOwnProperty.call(locals, key)) backup[key] = cloneDeep(locals[key]) 21 | } 22 | 23 | return backup 24 | } 25 | 26 | /** 27 | * @description Returns the original keys values 28 | * 29 | * @method revertBackupedLocals 30 | * 31 | * @param {Object} keys Keys 32 | * @param {Object} locals Locals 33 | * @param {Object} backup Backup 34 | * 35 | * @return {Object} locals Reverted Locals 36 | */ 37 | function revertBackupedLocals (keys, locals, backup) { 38 | for (let i = 0; i < keys.length; i++) { 39 | const key = keys[i] 40 | // Remove key from locals 41 | delete locals[key] 42 | 43 | // Revert copied key value 44 | if (Object.prototype.hasOwnProperty.call(backup, key)) locals[key] = backup[key] 45 | } 46 | 47 | return locals 48 | } 49 | 50 | /** 51 | * @module backup 52 | * 53 | * @requires fclone 54 | * 55 | * @type {Object} 56 | * 57 | * @prop {Function} make Make Locals backup 58 | * @prop {Function} revert Revert backuped Locals 59 | */ 60 | module.exports = { make: makeLocalsBackup, revert: revertBackupedLocals } 61 | -------------------------------------------------------------------------------- /lib/escape.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * @description Replace String based on RegExp 5 | * 6 | * @method escapeRegexpString 7 | * 8 | * @param {String} input Input 9 | * 10 | * @return {Function} input Replaced Input 11 | */ 12 | function escapeRegexpString (input) { 13 | // match Operators 14 | const match = /[|\\{}()[\]^$+*?.]/g 15 | 16 | return input.replace(match, '\\$&') 17 | } 18 | 19 | /** 20 | * @module escape 21 | * 22 | * @type {Function} 23 | */ 24 | module.exports = escapeRegexpString 25 | -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | export type PostHTMLExpressions = { 2 | /** 3 | Define the starting and ending delimiters used for expressions. 4 | 5 | @default ['{{', '}}'] 6 | */ 7 | delimiters?: string[]; 8 | 9 | /** 10 | Define the starting and ending delimiters used for unescaped expressions. 11 | 12 | @default ['{{{', '}}}'] 13 | */ 14 | unescapeDelimiters?: string[]; 15 | 16 | /** 17 | Object containing data that will be available under the `page` object. 18 | 19 | @default {} 20 | */ 21 | locals?: Record; 22 | 23 | /** 24 | Attribute name for ` 83 | 84 |
My name: {{name}}
85 | ``` 86 | 87 | ```html 88 | 93 | 94 |
My name: Scrum
95 | ``` 96 | 97 | In addition, the use of script tag allow you to use `locals` defined globally to assign data to variables. 98 | 99 | ```js 100 | posthtml(expressions({ locals: { foo: 'bar' } })) 101 | .process(readFileSync('index.html', 'utf8')) 102 | .then((result) => console.log(result.html)) 103 | ``` 104 | 105 | ```html 106 | 112 | 113 |
My name: {{name}}
114 |
Foo: {{foo}}
115 | ``` 116 | 117 | ```html 118 | 124 | 125 |
My name: {{name}}
126 |
Foo: bar
127 | ``` 128 | #### Missing locals 129 | What to produce in case of referencing a value not in `locals` can be configured by the `missingLocal` and `strictMode` options. 130 | 131 | When `strictMode` is true (default) and leaving the `missingLocal` option `undefined`, then "'foo' is not defined" exception is thrown. 132 | 133 | Setting `strictMode` false and leaving the `missingLocal` option `undefined` results the string `undefined` in the output 134 | 135 | Setting the option `missingLocal` to a string will produce that string in the output regardless the value of option `strictMode`. `missingLocal` can contain the placeholder `{local}` which will be replaced with the name of the missing local in the output. This solution allows to: 136 | 1. Silently ignore missing locals by setting `missingLocal` to `""` 137 | 2. Include the name of the missing local in the output to help detect the which value is missing in `locals` like "#Missing value: {local}" 138 | 139 | |`missingLocal`|`strictMode`|output| 140 | |:----:|:-----:|:----------| 141 | | `undefined` (default) | `true` (default) | Error is thrown | 142 | | `undefined` (default) | `false` | 'undefined' | 143 | | `''` | `false`/`true` | `''` (not output) 144 | | `{local}` | `false`/`true` | original reference like `{{foo}}` 145 | 146 | ### Unescaped Locals 147 | 148 | By default, special characters will be escaped so that they show up as text, rather than html code. For example, if you had a local containing valid html as such: 149 | 150 | ```js 151 | locals: { statement: 'wow!' } 152 | ``` 153 | 154 | ```html 155 |

The fox said, {{ statement }}

156 | ``` 157 | 158 | ```html 159 |

The fox said, <strong>wow!<strong>

160 | ``` 161 | 162 | In your browser, you would see the angle brackets, and it would appear as intended. However, if you wanted it instead to be parsed as html, you would need to use the `unescapeDelimiters`, which by default are three curly brackets, like this: 163 | 164 | ```html 165 |

The fox said, {{{ strongStatement }}}

166 | ``` 167 | 168 | In this case, your code would render as html: 169 | 170 | ```html 171 |

The fox said, wow!

172 | ``` 173 | 174 | ### Expressions 175 | 176 | You are not limited to just directly rendering local variables either, you can include any type of javascript expressions and it will be evaluated, with the result rendered. For example: 177 | 178 | ```html 179 |

in production!

180 | ``` 181 | 182 | With this in mind, it is strongly recommended to limit the number and complexity of expressions that are run directly in your template. You can always move the logic back to your config file and provide a function to the locals object for a smoother and easier result. For example: 183 | 184 | ```js 185 | locals: { 186 | isProduction: (env) => env === 'production' ? 'active' : 'hidden' 187 | } 188 | ``` 189 | 190 | ```html 191 |

in production!

192 | ``` 193 | 194 | #### Ignoring Expressions 195 | 196 | Many JavaScript frameworks use `{{` and `}}` as expression delimiters. It can even happen that another framework uses the same _custom_ delimiters you have defined in this plugin. 197 | 198 | You can tell the plugin to completely ignore an expression by prepending `@` to the delimiters: 199 | 200 | ```html 201 |

The @{{ foo }} is strong with this one.

202 | ``` 203 | 204 | Result: 205 | 206 | ```html 207 |

The {{ foo }} is strong with this one.

208 | ``` 209 | 210 | ### Conditionals 211 | 212 | Conditional logic uses normal html tags, and modifies/replaces them with the results of the logic. If there is any chance of a conflict with other custom tag names, you are welcome to change the tag names this plugin looks for in the options. For example, given the following config: 213 | 214 | ```js 215 | locals: { foo: 'foo' } 216 | ``` 217 | 218 | ```html 219 | 220 |

Foo really is bar! Revolutionary!

221 |
222 | 223 | 224 |

Foo is wow, oh man.

225 |
226 | 227 | 228 |

Foo is probably just foo in the end.

229 |
230 | ``` 231 | 232 | ```html 233 |

Foo is probably just foo in the end.

234 | ``` 235 | 236 | Anything in the `condition` attribute is evaluated directly as an expressions. 237 | 238 | It should be noted that this is slightly cleaner-looking if you are using the [SugarML parser](https://github.com/posthtml/sugarml). But then again so is every other part of html. 239 | 240 | ```sml 241 | if(condition="foo === 'bar'") 242 | p Foo really is bar! Revolutionary! 243 | 244 | elseif(condition="foo === 'wow'") 245 | p Foo is wow, oh man. 246 | 247 | else 248 | p Foo is probably just foo in the end. 249 | ``` 250 | 251 | #### `conditionalTags` 252 | 253 | Type: `array`\ 254 | Default: `['if', 'elseif', 'else']` 255 | 256 | You can define custom tag names to use for creating a conditional. 257 | 258 | Example: 259 | 260 | ```js 261 | conditionalTags: ['when', 'elsewhen', 'otherwise'] 262 | ``` 263 | 264 | ```html 265 | 266 |

Foo really is bar! Revolutionary!

267 |
268 | 269 | 270 |

Foo is wow, oh man.

271 |
272 | 273 | 274 |

Foo is probably just foo in the end.

275 |
276 | ``` 277 | 278 | Note: tag names must be in the exact order as the default ones. 279 | 280 | ### Switch statement 281 | 282 | Switch statements act like streamline conditionals. They are useful for when you want to compare a single variable against a series of constants. 283 | 284 | ```js 285 | locals: { foo: 'foo' } 286 | ``` 287 | 288 | ```html 289 | 290 | 291 |

Foo really is bar! Revolutionary!

292 |
293 | 294 |

Foo is wow, oh man.

295 |
296 | 297 |

Foo is probably just foo in the end.

298 |
299 |
300 | ``` 301 | 302 | ```html 303 |

Foo is probably just foo in the end.

304 | ``` 305 | 306 | Anything in the `expression` attribute is evaluated directly as an expressions. 307 | 308 | #### `switchTags` 309 | 310 | Type: `array`\ 311 | Default: `['switch', 'case', 'default']` 312 | 313 | You can define custom tag names to use when creating a switch. 314 | 315 | Example: 316 | 317 | ```js 318 | switchTags: ['clause', 'when', 'fallback'] 319 | ``` 320 | 321 | ```html 322 | 323 | 324 |

Foo really is bar! Revolutionary!

325 |
326 | 327 |

Foo is wow, oh man.

328 |
329 | 330 |

Foo is probably just foo in the end.

331 |
332 |
333 | ``` 334 | 335 | Note: tag names must be in the exact order as the default ones. 336 | 337 | ### Loops 338 | 339 | You can use the `each` tag to build loops. It works with both arrays and objects. For example: 340 | 341 | ```js 342 | locals: { 343 | array: ['foo', 'bar'], 344 | object: { foo: 'bar' } 345 | } 346 | ``` 347 | 348 | **Array** 349 | ```html 350 | 351 |

{{ index }}: {{ item }}

352 |
353 | ``` 354 | 355 | ```html 356 |

1: foo

357 |

2: bar

358 | ``` 359 | 360 | **Object** 361 | ```html 362 | 363 |

{{ key }}: {{ value }}

364 |
365 | ``` 366 | 367 | ```html 368 |

foo: bar

369 | ``` 370 | 371 | The value of the `loop` attribute is not a pure expressions evaluation, and it does have a tiny and simple custom parser. Essentially, it starts with one or more variable declarations, comma-separated, followed by the word `in`, followed by an expressions. 372 | 373 | 374 | ```html 375 | 376 |

{{ item }}

377 |
378 | ``` 379 | 380 | So you don't need to declare all the available variables (in this case, the index is skipped), and the expressions after `in` doesn't need to be a local variable, it can be any expressions. 381 | 382 | #### `loopTags` 383 | 384 | Type: `array`\ 385 | Default: `['each']` 386 | 387 | You can define custom tag names to use for creating loops: 388 | 389 | Example: 390 | 391 | ```js 392 | loopTags: ['each', 'for'] 393 | ``` 394 | 395 | You can now also use the `` tag when writing a loop: 396 | 397 | ```html 398 | 399 |

{{ item }}

400 |
401 | ``` 402 | 403 | #### Loop meta 404 | 405 | Inside a loop, you have access to a special `loop` object, which contains information about the loop currently being executed: 406 | 407 | - `loop.index` - the current iteration of the loop (0 indexed) 408 | - `loop.remaining` - number of iterations until the end (0 indexed) 409 | - `loop.first` - boolean indicating if it's the first iteration 410 | - `loop.last` - boolean indicating if it's the last iteration 411 | - `loop.length` - total number of items 412 | 413 | Example: 414 | 415 | ```html 416 | 417 |
  • Item value: {{ item }}
  • 418 |
  • Current iteration of the loop: {{ loop.index }}
  • 419 |
  • Number of iterations until the end: {{ loop.remaining }}
  • 420 |
  • This {{ loop.first ? 'is' : 'is not' }} the first iteration
  • 421 |
  • This {{ loop.last ? 'is' : 'is not' }} the last iteration
  • 422 |
  • Total number of items: {{ loop.length }}
  • 423 |
    424 | ``` 425 | 426 | ### Scopes 427 | 428 | You can replace locals inside certain area wrapped in a `` tag. For example you can use it after [posthtml-include](https://github.com/posthtml/posthtml-include) 429 | 430 | ```js 431 | locals: { 432 | author: { name: 'John'}, 433 | editor: { name: 'Jeff'} 434 | } 435 | ``` 436 | 437 | ```html 438 | 439 | 440 | 441 | 442 | 443 | 444 | ``` 445 | 446 | ```html 447 |
    448 |
    {{ name }}
    449 | {{ name }}'s avatar 450 | more info 451 |
    452 | ``` 453 | 454 | #### `scopeTags` 455 | 456 | Type: `array`\ 457 | Default: `['scope']` 458 | 459 | You can define a custom tag name to use for creating scopes: 460 | 461 | Example: 462 | 463 | ```js 464 | scopeTags: ['context'] 465 | ``` 466 | 467 | You can now also use the `` tag when writing a scope: 468 | 469 | ```html 470 | 471 | 472 | 473 | ``` 474 | 475 | ### Ignored tag 476 | 477 | Anything inside this tag will not be parsed, allowing you to output delimiters and anything the plugin would normally parse, in their original form. 478 | 479 | ```html 480 | 481 | 482 |

    Output {{ foo }} as-is

    483 |
    484 |
    485 | ``` 486 | 487 | ```html 488 | 489 |

    Output {{ foo }} as-is

    490 |
    491 | ``` 492 | 493 | You can customize the name of the tag: 494 | 495 | ```js 496 | var opts = { 497 | ignoredTag: 'verbatim', 498 | locals: { foo: 'bar' } } 499 | } 500 | 501 | posthtml(expressions(opts)) 502 | .process(readFileSync('index.html', 'utf8')) 503 | .then((result) => console.log(result.html)) 504 | ``` 505 | 506 | ```html 507 | 508 | 509 |

    Output {{ foo }} as-is

    510 |
    511 |
    512 | ``` 513 | 514 | ```html 515 | 516 |

    Output {{ foo }} as-is

    517 |
    518 | ``` 519 | 520 |

    Maintainers

    521 | 522 | 523 | 524 | 525 | 531 | 537 | 538 | 539 |
    526 | 528 |
    529 | Jeff Escalante 530 |
    532 | 534 |
    535 | Denis Malinochkin 536 |
    540 | 541 |

    Contributors

    542 | 543 | 544 | 545 | 546 | 552 | 558 | 564 | 565 | 566 |
    547 | 549 |
    550 | Michael Ciniawsky 551 |
    553 | 555 |
    556 | Krillin 557 |
    559 | 561 |
    562 | Cosmin Popovici 563 |
    567 | 568 | 569 | [npm]: https://img.shields.io/npm/v/posthtml-expressions.svg 570 | [npm-url]: https://npmjs.com/package/posthtml-expressions 571 | 572 | [node]: https://img.shields.io/node/v/posthtml-expressions.svg 573 | [node-url]: https://nodejs.org/ 574 | 575 | [deps]: https://david-dm.org/posthtml/posthtml-expressions.svg 576 | [deps-url]: https://david-dm.org/posthtml/posthtml-expressions 577 | 578 | [tests]: https://github.com/posthtml/posthtml-expressions/workflows/Actions%20Status/badge.svg?style=flat-square 579 | [tests-url]: https://github.com/posthtml/posthtml-expressions/actions?query=workflow%3A%22CI+tests%22 580 | 581 | [cover]: https://coveralls.io/repos/github/posthtml/posthtml-expressions/badge.svg 582 | [cover-url]: https://coveralls.io/github/posthtml/posthtml-expressions 583 | 584 | [style]: https://img.shields.io/badge/code%20style-standard-yellow.svg 585 | [style-url]: http://standardjs.com/ 586 | -------------------------------------------------------------------------------- /test/expect/attr_param.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/expect/basic.html: -------------------------------------------------------------------------------- 1 | x wow x 2 x 2 |

    x wow x

    3 | -------------------------------------------------------------------------------- /test/expect/boolean_attr.html: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /test/expect/conditional.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |

    x

    5 | -------------------------------------------------------------------------------- /test/expect/conditional_customtags.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |

    x

    5 | -------------------------------------------------------------------------------- /test/expect/conditional_expression.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    bar

    4 |

    x

    5 | -------------------------------------------------------------------------------- /test/expect/conditional_if.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |

    x

    5 | 6 |

    x

    7 |

    x

    8 | 9 | x 10 | 11 | x -------------------------------------------------------------------------------- /test/expect/conditional_if_key_not_exists.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/posthtml/posthtml-expressions/8bd4f9204cdaa6665684ac6618fef408cb550948/test/expect/conditional_if_key_not_exists.html -------------------------------------------------------------------------------- /test/expect/conditional_nested.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    y

    4 | 5 |

    z

    6 |

    y

    7 |

    x

    8 | -------------------------------------------------------------------------------- /test/expect/conditional_norender.html: -------------------------------------------------------------------------------- 1 |

    x

    2 |

    x

    3 | -------------------------------------------------------------------------------- /test/expect/conditional_tag_break.html: -------------------------------------------------------------------------------- 1 |

    if

    2 |

    x

    3 | 4 |

    else

    5 |
    6 | -------------------------------------------------------------------------------- /test/expect/custom_delimiters.html: -------------------------------------------------------------------------------- 1 | x wow x 2 x 2 |

    x wow x

    3 | x&x 4 | -------------------------------------------------------------------------------- /test/expect/directives.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/expect/escape_html.html: -------------------------------------------------------------------------------- 1 | x&x 2 | x"x 3 | x<x 4 | x>x 5 | -------------------------------------------------------------------------------- /test/expect/expression_custom_delimiters_ignored.html: -------------------------------------------------------------------------------- 1 | ${ foo } 2 |

    3 | Here's one ${ variable } and here's ${ another }. And some bar. 4 |

    5 | 6 | ignored: ${ leaveAsIs } 7 | ignoredUnescaped: ${{ leaveAsIs }} 8 | rendered: bar 9 | -------------------------------------------------------------------------------- /test/expect/expression_ignored.html: -------------------------------------------------------------------------------- 1 | {{ foo }} 2 |

    3 | Here's one {{ variable }} and here's {{ another }}. And some bar. 4 |

    5 | 6 | ignored: {{ leaveAsIs }} 7 | ignoredUnescaped: {{{ leaveAsIs }}} 8 | rendered: bar 9 | -------------------------------------------------------------------------------- /test/expect/expression_spacing.html: -------------------------------------------------------------------------------- 1 | x X x X x X x X x 2 | xXxXxXxXx 3 | -------------------------------------------------------------------------------- /test/expect/local_missing.html: -------------------------------------------------------------------------------- 1 |

    undefinedundefined

    -------------------------------------------------------------------------------- /test/expect/local_missing_keep.html: -------------------------------------------------------------------------------- 1 |

    {{foo}}

    -------------------------------------------------------------------------------- /test/expect/local_missing_remove.html: -------------------------------------------------------------------------------- 1 |

    -------------------------------------------------------------------------------- /test/expect/local_missing_replace.html: -------------------------------------------------------------------------------- 1 |

    Error: {{foo}} undefined

    -------------------------------------------------------------------------------- /test/expect/loop.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    0: 1

    4 | 5 |

    1: 2

    6 | 7 |

    2: 3

    8 | 9 |

    x

    10 | -------------------------------------------------------------------------------- /test/expect/loop_conditional.html: -------------------------------------------------------------------------------- 1 |

    x

    2 |
      3 | 4 | 5 |
    • 1
    • 6 | 7 | 8 |
    • 2 % 2 === 0
    • 9 | 10 | 11 |
    • 3
    • 12 | 13 |
    14 |

    x

    15 | -------------------------------------------------------------------------------- /test/expect/loop_conditional_locals.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 | 4 | Page 1 5 | 6 | 7 | Page 2 8 | 9 | 10 | Page 3 11 | 12 |

    x

    13 | -------------------------------------------------------------------------------- /test/expect/loop_conflict.html: -------------------------------------------------------------------------------- 1 |

    bar

    2 | 3 |

    0: 1

    4 | 5 |

    1: 2

    6 | 7 |

    2: 3

    8 | 9 |

    bar

    10 | -------------------------------------------------------------------------------- /test/expect/loop_customtag.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    0: 1

    4 | 5 |

    1: 2

    6 | 7 |

    2: 3

    8 | 9 | 10 |

    0: 1

    11 | 12 |

    1: 2

    13 | 14 |

    2: 3

    15 | 16 |

    x

    17 | -------------------------------------------------------------------------------- /test/expect/loop_locals.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    0, 1, bar

    4 | 5 |

    1, 2, bar

    6 | 7 |

    2, 3, bar

    8 | 9 |

    x

    10 | -------------------------------------------------------------------------------- /test/expect/loop_metadata.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    • Item: 1
    • 4 |
    • Current iteration of the loop: 0
    • 5 |
    • Number of iterations until the end: 2
    • 6 |
    • This is the first iteration
    • 7 |
    • This is not the last iteration
    • 8 |
    • Total number of items: 3
    • 9 | 10 |
    • Item: 2
    • 11 |
    • Current iteration of the loop: 1
    • 12 |
    • Number of iterations until the end: 1
    • 13 |
    • This is not the first iteration
    • 14 |
    • This is not the last iteration
    • 15 |
    • Total number of items: 3
    • 16 | 17 |
    • Item: 3
    • 18 |
    • Current iteration of the loop: 2
    • 19 |
    • Number of iterations until the end: 0
    • 20 |
    • This is not the first iteration
    • 21 |
    • This is the last iteration
    • 22 |
    • Total number of items: 3
    • 23 | 24 |
    25 | -------------------------------------------------------------------------------- /test/expect/loop_nested.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    c1: [1,2,3]

    4 | 5 |

    1

    6 | 7 |

    2

    8 | 9 |

    3

    10 | 11 | 12 |

    c2: [4,5,6]

    13 | 14 |

    4

    15 | 16 |

    5

    17 | 18 |

    6

    19 | 20 | 21 |

    x

    22 | -------------------------------------------------------------------------------- /test/expect/loop_nested_metadata.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    • foo: [1,2]
    • 4 |
    • There are 1 iterations remaining on the foo object
    • 5 | 6 |
        7 |
      • Nested item value: 1
      • 8 |
      • Current iteration of the loop: 0
      • 9 |
      • Number of iterations until the end: 1
      • 10 |
      • This is the first iteration
      • 11 |
      • This is not the last iteration
      • 12 |
      • Total number of items: 2
      • 13 |
      14 | 15 |
        16 |
      • Nested item value: 2
      • 17 |
      • Current iteration of the loop: 1
      • 18 |
      • Number of iterations until the end: 0
      • 19 |
      • This is not the first iteration
      • 20 |
      • This is the last iteration
      • 21 |
      • Total number of items: 2
      • 22 |
      23 | 24 | 25 |
    • bar: [3,4]
    • 26 |
    • There are 0 iterations remaining on the bar object
    • 27 | 28 |
        29 |
      • Nested item value: 3
      • 30 |
      • Current iteration of the loop: 0
      • 31 |
      • Number of iterations until the end: 1
      • 32 |
      • This is the first iteration
      • 33 |
      • This is not the last iteration
      • 34 |
      • Total number of items: 2
      • 35 |
      36 | 37 |
        38 |
      • Nested item value: 4
      • 39 |
      • Current iteration of the loop: 1
      • 40 |
      • Number of iterations until the end: 0
      • 41 |
      • This is not the first iteration
      • 42 |
      • This is the last iteration
      • 43 |
      • Total number of items: 2
      • 44 |
      45 | 46 | 47 |
    48 | -------------------------------------------------------------------------------- /test/expect/loop_object.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    a: b

    4 | 5 |

    c: d

    6 | 7 |

    x

    8 | -------------------------------------------------------------------------------- /test/expect/raw.html: -------------------------------------------------------------------------------- 1 | 2 |

    Output {{ foo }} as is

    3 |
    4 |

    {{ foo }}

    5 | -------------------------------------------------------------------------------- /test/expect/raw_custom.html: -------------------------------------------------------------------------------- 1 | 2 |

    Output {{ foo }} as is

    3 |
    4 |

    {{ foo }}

    5 | -------------------------------------------------------------------------------- /test/expect/raw_in_condition.html: -------------------------------------------------------------------------------- 1 | 2 | {{ foo }} 3 | 4 |

    bar

    5 | 6 | -------------------------------------------------------------------------------- /test/expect/raw_in_switch.html: -------------------------------------------------------------------------------- 1 | Hello from Earth, {{ username }} 2 | 3 | 4 | 5 | Hello from Moscow, {{ username }} 6 | -------------------------------------------------------------------------------- /test/expect/scope.html: -------------------------------------------------------------------------------- 1 |

    x Scope x

    2 | 3 |

    Hi. I am John. I am 26 years old.

    4 |

    test

    5 | 6 |

    x Scope x

    7 | -------------------------------------------------------------------------------- /test/expect/scope_nested.html: -------------------------------------------------------------------------------- 1 |

    global

    2 | 3 |

    scope

    4 | 5 |

    one

    6 | 7 | 8 |

    two

    9 | 10 | 11 |

    global

    12 | -------------------------------------------------------------------------------- /test/expect/script-locals-global-not-informed.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    My name: Scrum
    9 |
    My age: not informed
    10 | -------------------------------------------------------------------------------- /test/expect/script-locals-global.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    My name: Scrum
    9 |
    My age: 25
    10 | -------------------------------------------------------------------------------- /test/expect/script-locals-remove.html: -------------------------------------------------------------------------------- 1 |
    My name: Scrum
    2 | -------------------------------------------------------------------------------- /test/expect/script-locals.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
    My name: Scrum
    8 | -------------------------------------------------------------------------------- /test/expect/switch.html: -------------------------------------------------------------------------------- 1 | 2 |

    Hello, from Germany!

    3 | -------------------------------------------------------------------------------- /test/expect/switch_customtag.html: -------------------------------------------------------------------------------- 1 | 2 |

    Hello, from United States!

    3 | -------------------------------------------------------------------------------- /test/expect/switch_default.html: -------------------------------------------------------------------------------- 1 | 2 |

    Hello, from Earth!

    3 | -------------------------------------------------------------------------------- /test/expect/switch_nested.html: -------------------------------------------------------------------------------- 1 | 2 |

    Hello, from Russia!

    3 | -------------------------------------------------------------------------------- /test/expect/switch_number.html: -------------------------------------------------------------------------------- 1 | 2 |

    3

    3 | -------------------------------------------------------------------------------- /test/expect/unescaped.html: -------------------------------------------------------------------------------- 1 | y x&x y x&x y 2 | wow 3 | -------------------------------------------------------------------------------- /test/fixtures/attr_param.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/basic.html: -------------------------------------------------------------------------------- 1 | x {{ test }} x {{ 1 + 1 }} x 2 |

    x {{ test }} x

    3 | -------------------------------------------------------------------------------- /test/fixtures/boolean_attr.html: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /test/fixtures/conditional.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |
    5 | 6 |

    it doesn't work

    7 |
    8 | 9 |

    it really doesn't work

    10 |
    11 | 12 |

    it definitely doesn't work

    13 |
    14 |

    x

    15 | -------------------------------------------------------------------------------- /test/fixtures/conditional_customtags.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |
    5 | 6 |

    it doesn't work

    7 |
    8 | 9 |

    it really doesn't work

    10 |
    11 | 12 |

    it definitely doesn't work

    13 |
    14 |

    x

    15 | -------------------------------------------------------------------------------- /test/fixtures/conditional_elseif_error.html: -------------------------------------------------------------------------------- 1 | 2 |

    hi

    3 |
    4 | 5 |

    hi

    6 | 7 | -------------------------------------------------------------------------------- /test/fixtures/conditional_expression.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |
    5 | 6 |

    {{ foo }}

    7 |
    8 |

    x

    9 | -------------------------------------------------------------------------------- /test/fixtures/conditional_expression_error.html: -------------------------------------------------------------------------------- 1 | 2 |

    hi

    3 |
    4 | -------------------------------------------------------------------------------- /test/fixtures/conditional_if.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    it works!

    4 |
    5 |

    x

    6 | 7 |

    x

    8 | 9 |

    it works!

    10 |
    11 |

    x

    12 | 13 | x 14 | 15 | it works! 16 | 17 | x 18 | -------------------------------------------------------------------------------- /test/fixtures/conditional_if_error.html: -------------------------------------------------------------------------------- 1 | 2 |

    hi

    3 |
    4 | 5 |

    hi

    6 |
    7 | -------------------------------------------------------------------------------- /test/fixtures/conditional_if_key_not_exists.html: -------------------------------------------------------------------------------- 1 | 2 | it works! 3 | 4 | 5 | 6 | {{ key_not_exists.foo }} 7 | it works! 8 | 9 | 10 | 11 | 12 | {{ item.text }} 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/conditional_nested.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    y

    4 | 5 |

    z

    6 |
    7 |

    y

    8 |
    9 |

    x

    10 | -------------------------------------------------------------------------------- /test/fixtures/conditional_norender.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    hi

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/conditional_tag_break.html: -------------------------------------------------------------------------------- 1 | 2 |

    if

    3 |
    4 |

    x

    5 | 6 |

    else

    7 |
    8 | -------------------------------------------------------------------------------- /test/fixtures/custom_delimiters.html: -------------------------------------------------------------------------------- 1 | x %[ test ]% x %[ 1 + 1 ]% x 2 |

    x %[ test ]% x

    3 | %[[ 'x&x' ]]% 4 | -------------------------------------------------------------------------------- /test/fixtures/directives.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/escape_html.html: -------------------------------------------------------------------------------- 1 | {{ 'x&x' }} 2 | {{ 'x"x' }} 3 | x{{ lt }}x 4 | x{{ gt }}x 5 | -------------------------------------------------------------------------------- /test/fixtures/expression_custom_delimiters_ignored.html: -------------------------------------------------------------------------------- 1 | @${ foo } 2 |

    3 | Here's one @${ variable } and here's @${ another }. And some ${ foo }. 4 |

    5 | 6 | ignored: @${ leaveAsIs } 7 | ignoredUnescaped: @${{ leaveAsIs }} 8 | rendered: ${ foo } 9 | -------------------------------------------------------------------------------- /test/fixtures/expression_error.html: -------------------------------------------------------------------------------- 1 | {{ @#!$R }} 2 | -------------------------------------------------------------------------------- /test/fixtures/expression_ignored.html: -------------------------------------------------------------------------------- 1 | @{{ foo }} 2 |

    3 | Here's one @{{ variable }} and here's @{{ another }}. And some {{ foo }}. 4 |

    5 | 6 | ignored: @{{ leaveAsIs }} 7 | ignoredUnescaped: @{{{ leaveAsIs }}} 8 | rendered: {{ foo }} 9 | -------------------------------------------------------------------------------- /test/fixtures/expression_spacing.html: -------------------------------------------------------------------------------- 1 | x {{foo}} x {{ foo }} x {{{ foo }}} x {{{foo}}} x 2 | x{{foo}}x{{ foo }}x{{{ foo }}}x{{{foo}}}x 3 | -------------------------------------------------------------------------------- /test/fixtures/local_missing.html: -------------------------------------------------------------------------------- 1 |

    {{foo}}{{foo? "bar": ""}}

    -------------------------------------------------------------------------------- /test/fixtures/local_missing_keep.html: -------------------------------------------------------------------------------- 1 |

    {{foo}}

    -------------------------------------------------------------------------------- /test/fixtures/local_missing_remove.html: -------------------------------------------------------------------------------- 1 |

    {{foo}}

    -------------------------------------------------------------------------------- /test/fixtures/local_missing_replace.html: -------------------------------------------------------------------------------- 1 |

    {{foo}}

    -------------------------------------------------------------------------------- /test/fixtures/loop.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_conditional.html: -------------------------------------------------------------------------------- 1 |

    x

    2 |
      3 | 4 | 5 |
    • {{ number }} % 2 === 0
    • 6 |
      7 | 8 |
    • {{ number }}
    • 9 |
      10 |
      11 |
    12 |

    x

    13 | -------------------------------------------------------------------------------- /test/fixtures/loop_conditional_locals.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 | 4 | {{ page.title }} 5 | 6 | 7 | {{ page.title }} 8 | 9 | 10 |

    x

    11 | -------------------------------------------------------------------------------- /test/fixtures/loop_conflict.html: -------------------------------------------------------------------------------- 1 |

    {{item}}

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    {{item}}

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_customtag.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 | 6 |

    {{index}}: {{item}}

    7 |
    8 |

    x

    9 | -------------------------------------------------------------------------------- /test/fixtures/loop_expression_error.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_locals.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}, {{item}}, {{ foo }}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_metadata.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    • Item: {{ item }}
    • 4 |
    • Current iteration of the loop: {{ loop.index }}
    • 5 |
    • Number of iterations until the end: {{ loop.remaining }}
    • 6 |
    • This {{ loop.first ? 'is' : 'is not' }} the first iteration
    • 7 |
    • This {{ loop.last ? 'is' : 'is not' }} the last iteration
    • 8 |
    • Total number of items: {{ loop.length }}
    • 9 |
      10 |
    11 | -------------------------------------------------------------------------------- /test/fixtures/loop_nested.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{ key }}: {{ JSON.stringify(item_category) }}

    4 | 5 |

    {{ item }}

    6 |
    7 |
    8 |

    x

    9 | -------------------------------------------------------------------------------- /test/fixtures/loop_nested_metadata.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    • {{ key }}: {{ JSON.stringify(value) }}
    • 4 |
    • There are {{ loop.remaining }} iterations remaining on the {{ key }} object
    • 5 | 6 |
        7 |
      • Nested item value: {{ item }}
      • 8 |
      • Current iteration of the loop: {{ loop.index }}
      • 9 |
      • Number of iterations until the end: {{ loop.remaining }}
      • 10 |
      • This {{ loop.first ? 'is' : 'is not' }} the first iteration
      • 11 |
      • This {{ loop.last ? 'is' : 'is not' }} the last iteration
      • 12 |
      • Total number of items: {{ loop.length }}
      • 13 |
      14 |
      15 |
      16 |
    17 | -------------------------------------------------------------------------------- /test/fixtures/loop_no_args.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_no_attr.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_no_collection.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_no_in.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{index}}: {{item}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/loop_object.html: -------------------------------------------------------------------------------- 1 |

    x

    2 | 3 |

    {{key}}: {{value}}

    4 |
    5 |

    x

    6 | -------------------------------------------------------------------------------- /test/fixtures/raw.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Output {{ foo }} as is

    4 |
    5 |

    {{ foo }}

    6 |
    7 | -------------------------------------------------------------------------------- /test/fixtures/raw_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Output {{ foo }} as is

    4 |
    5 |

    {{ foo }}

    6 |
    7 | -------------------------------------------------------------------------------- /test/fixtures/raw_in_condition.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ foo }} 4 | 5 | 6 |

    {{ foo }}

    7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/raw_in_switch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 | Hello from Earth, {{ username }} 7 | 8 |
    9 | 10 | 11 | 12 | Hello from Moscow, {{ username }} 13 | 14 | 15 |

    Hello, from Earth!

    16 |
    17 |
    18 | -------------------------------------------------------------------------------- /test/fixtures/scope.html: -------------------------------------------------------------------------------- 1 |

    x {{name}} x

    2 | 3 |

    Hi. I am {{name}}. I am {{age}} years old.

    4 |

    {{key}}

    5 |
    6 |

    x {{name}} x

    7 | -------------------------------------------------------------------------------- /test/fixtures/scope_nested.html: -------------------------------------------------------------------------------- 1 |

    {{key}}

    2 | 3 |

    {{key}}

    4 | 5 |

    {{key}}

    6 |
    7 | 8 |

    {{key}}

    9 |
    10 |
    11 |

    {{key}}

    12 | -------------------------------------------------------------------------------- /test/fixtures/script-locals-global-not-informed.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    My name: {{name}}
    9 |
    My age: {{ age }}
    10 | -------------------------------------------------------------------------------- /test/fixtures/script-locals-global.html: -------------------------------------------------------------------------------- 1 | 7 | 8 |
    My name: {{name}}
    9 |
    My age: {{ age }}
    10 | -------------------------------------------------------------------------------- /test/fixtures/script-locals-remove.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
    My name: {{name}}
    8 | -------------------------------------------------------------------------------- /test/fixtures/script-locals.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
    My name: {{name}}
    8 | -------------------------------------------------------------------------------- /test/fixtures/switch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Germany!

    7 |
    8 | 9 |

    Hello, from United States!

    10 |
    11 | 12 |

    Hello, from Earth!

    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/fixtures/switch_bad_flow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 |

    What?

    6 | 7 |

    Hello, from Earth!

    8 |
    9 |
    10 | -------------------------------------------------------------------------------- /test/fixtures/switch_customtag.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Germany!

    7 |
    8 | 9 |

    Hello, from United States!

    10 |
    11 | 12 |

    Hello, from Earth!

    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/fixtures/switch_default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Germany!

    7 |
    8 | 9 |

    Hello, from United States!

    10 |
    11 | 12 |

    Hello, from Earth!

    13 |
    14 |
    15 | -------------------------------------------------------------------------------- /test/fixtures/switch_nested.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Germany!

    7 |
    8 | 9 |

    Hello, from United States!

    10 |
    11 | 12 | 13 | 14 |

    Hello, from Russia!

    15 |
    16 | 17 |

    Hello, from Germany!

    18 |
    19 | 20 |

    Hello, from United States!

    21 |
    22 | 23 |

    Hello, from Earth!

    24 |
    25 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /test/fixtures/switch_no_attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Earth!

    7 |
    8 |
    9 | -------------------------------------------------------------------------------- /test/fixtures/switch_no_case_attr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    Hello, from Russia!

    4 |
    5 | 6 |

    Hello, from Earth!

    7 |
    8 |
    9 | -------------------------------------------------------------------------------- /test/fixtures/switch_number.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

    2

    4 |
    5 | 6 |

    3

    7 |
    8 | 9 |

    default

    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /test/fixtures/unescaped.html: -------------------------------------------------------------------------------- 1 | y {{ 'x&x' }} y {{{ 'x&x' }}} y 2 | {{{ el }}} 3 | -------------------------------------------------------------------------------- /test/test-conditionals.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const expressions = require('../lib') 8 | 9 | const fixture = (file) => { 10 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 11 | } 12 | 13 | const expect = (file) => { 14 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 15 | } 16 | 17 | function process (t, name, options, log = false) { 18 | return posthtml([expressions(options)]) 19 | .process(fixture(name)) 20 | .then((result) => { 21 | log && console.log(result.html) 22 | 23 | return clean(result.html) 24 | }) 25 | .then((html) => { 26 | t.is(html, expect(name).trim()) 27 | }) 28 | } 29 | 30 | function error (name, cb, options) { 31 | return posthtml([expressions(options)]) 32 | .process(fixture(name)) 33 | .catch(cb) 34 | } 35 | 36 | function clean (html) { 37 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 38 | } 39 | 40 | test('Conditionals', (t) => { 41 | return process(t, 'conditional', { locals: { foo: 'bar' } }) 42 | }) 43 | 44 | test('Conditionals - only "if" condition', (t) => { 45 | return process(t, 'conditional_if', { locals: { foo: 'bar', bool: '' } }) 46 | }) 47 | 48 | test('Conditionals - no render', (t) => { 49 | return process(t, 'conditional_norender', {}) 50 | }) 51 | 52 | test('Conditionals - "if" tag missing condition', (t) => { 53 | return error('conditional_if_error', (err) => { 54 | t.is(err.toString(), 'Error: the "if" tag must have a "condition" attribute') 55 | }) 56 | }) 57 | 58 | test('Conditionals - "elseif" tag missing condition', (t) => { 59 | return error('conditional_elseif_error', (err) => { 60 | t.is(err.toString(), 'Error: the "elseif" tag must have a "condition" attribute') 61 | }) 62 | }) 63 | 64 | test('Conditionals - other tag in middle of statement', (t) => { 65 | return process(t, 'conditional_tag_break', {}) 66 | }) 67 | 68 | test('Conditionals - nested conditionals', (t) => { 69 | return process(t, 'conditional_nested', {}) 70 | }) 71 | 72 | test('conditional - expression error', (t) => { 73 | return error('conditional_expression_error', (err) => { 74 | t.is(err.name, 'SyntaxError') 75 | }) 76 | }) 77 | 78 | test('Conditionals - if key exists in locals', (t) => { 79 | return process(t, 'conditional_if_key_not_exists', { strictMode: false }) 80 | }) 81 | 82 | test('Conditionals - custom tags', (t) => { 83 | return process(t, 'conditional_customtags', { 84 | conditionalTags: ['zif', 'zelseif', 'zelse'], 85 | locals: { foo: 'bar' } 86 | }) 87 | }) 88 | 89 | test('Conditionals - expression in else/elseif', (t) => { 90 | return process(t, 'conditional_expression', { 91 | locals: { foo: 'bar' } 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /test/test-core.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const beautify = require('posthtml-beautify') 8 | const expressions = require('../lib') 9 | 10 | const fixture = (file) => { 11 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 12 | } 13 | 14 | const expect = (file) => { 15 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 16 | } 17 | 18 | function process (t, name, options, log = false, plugins = [expressions(options)], processOptions = {}) { 19 | return posthtml(plugins) 20 | .process(fixture(name), processOptions) 21 | .then((result) => { 22 | log && console.log(result.html) 23 | 24 | return clean(result.html) 25 | }) 26 | .then((html) => { 27 | t.is(html, expect(name).trim()) 28 | }) 29 | } 30 | 31 | function error (name, cb, opts) { 32 | return posthtml([expressions(opts)]) 33 | .process(fixture(name)) 34 | .catch(cb) 35 | } 36 | 37 | function clean (html) { 38 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 39 | } 40 | 41 | test('Basic', (t) => { 42 | return process(t, 'basic', { locals: { test: 'wow' } }) 43 | }) 44 | 45 | test('Escaped', (t) => { 46 | return process(t, 'escape_html', { locals: { lt: '<', gt: '>' } }) 47 | }) 48 | 49 | test('Unescaped', (t) => { 50 | return process(t, 'unescaped', { 51 | locals: { el: 'wow' } 52 | }) 53 | }) 54 | 55 | test('Delimiters', (t) => { 56 | return process(t, 'custom_delimiters', { 57 | delimiters: ['%[', ']%'], 58 | unescapeDelimiters: ['%[[', ']]%'], 59 | locals: { test: 'wow' } 60 | }) 61 | }) 62 | 63 | test('Expressions - spacing', (t) => { 64 | return process(t, 'expression_spacing', { locals: { foo: 'X' } }) 65 | }) 66 | 67 | test('Expressions - error', (t) => { 68 | return error('expression_error', (err) => { 69 | t.is(err.message, 'SyntaxError: Invalid or unexpected token') 70 | }) 71 | }) 72 | 73 | test('Expressions - ignored', (t) => { 74 | return process(t, 'expression_ignored', { locals: { foo: 'bar' } }) 75 | }) 76 | 77 | test('Expressions with custom delimiters - ignored', (t) => { 78 | return process(t, 'expression_custom_delimiters_ignored', { 79 | delimiters: ['${', '}'], 80 | unescapeDelimiters: ['${{', '}}'], 81 | locals: { foo: 'bar' } 82 | }) 83 | }) 84 | 85 | test('Raw output', (t) => { 86 | return process(t, 'raw', { locals: { foo: 'bar' } }) 87 | }) 88 | 89 | test('Raw output - inside condition', (t) => { 90 | return process(t, 'raw_in_condition', { locals: { foo: 'bar' } }) 91 | }) 92 | 93 | test('Raw output - custom tag', (t) => { 94 | return process(t, 'raw_custom', { ignoredTag: 'verbatim', locals: { foo: 'bar' } }) 95 | }) 96 | 97 | test('Boolean attribute', (t) => { 98 | return process(t, 'boolean_attr', null, false, [beautify(), expressions()]) 99 | }) 100 | 101 | test('Attribute as param', (t) => { 102 | return process(t, 'attr_param', null, false, [beautify(), expressions({ locals: { param: 'checked' } })]) 103 | }) 104 | 105 | test('Directives options', (t) => { 106 | return process(t, 'directives', null, false, [expressions()], { 107 | directives: [{ 108 | name: '?php', 109 | start: '<', 110 | end: '>' 111 | }] 112 | }) 113 | }) 114 | 115 | test('local - missing - error', (t) => { 116 | return error('local_missing', (err) => { 117 | t.is(err.message, "'foo' is not defined") 118 | }) 119 | }) 120 | 121 | test('local - missing - undefined', (t) => { 122 | return process(t, 'local_missing', { strictMode: false }) 123 | }) 124 | 125 | test('local - missing - keep', (t) => { 126 | return process(t, 'local_missing_keep', { missingLocal: '{local}' }) 127 | }) 128 | 129 | test('local - missing - keep / strictMode:false', (t) => { 130 | return process(t, 'local_missing_keep', { missingLocal: '{local}', strictMode: false }) 131 | }) 132 | 133 | test('local - missing - replace', (t) => { 134 | return process(t, 'local_missing_replace', { missingLocal: 'Error: {local} undefined' }) 135 | }) 136 | -------------------------------------------------------------------------------- /test/test-locals.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const expressions = require('../lib') 8 | 9 | const fixture = (file) => { 10 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 11 | } 12 | 13 | const expect = (file) => { 14 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 15 | } 16 | 17 | function process (t, name, options, log = false, plugins = [expressions(options)], processOptions = {}) { 18 | return posthtml(plugins) 19 | .process(fixture(name), processOptions) 20 | .then((result) => { 21 | log && console.log(result.html) 22 | 23 | return clean(result.html) 24 | }) 25 | .then((html) => { 26 | t.is(html, expect(name).trim()) 27 | }) 28 | } 29 | 30 | function clean (html) { 31 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 32 | } 33 | 34 | test('Basic', (t) => { 35 | return process(t, 'script-locals') 36 | }) 37 | 38 | test('Global Locals - setting global locals', (t) => { 39 | return process(t, 'script-locals-global', { locals: { displayAge: true } }) 40 | }) 41 | 42 | test('Global Locals - no global locals informed', (t) => { 43 | return process(t, 'script-locals-global-not-informed') 44 | }) 45 | 46 | test('Remove script locals', (t) => { 47 | return process(t, 'script-locals-remove', { removeScriptLocals: true }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/test-loops.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const expressions = require('../lib') 8 | 9 | const fixture = (file) => { 10 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 11 | } 12 | 13 | const expect = (file) => { 14 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 15 | } 16 | 17 | function process (t, name, options, log = false) { 18 | return posthtml([expressions(options)]) 19 | .process(fixture(name)) 20 | .then((result) => { 21 | log && console.log(result.html) 22 | 23 | return clean(result.html) 24 | }) 25 | .then((html) => { 26 | t.is(html, expect(name).trim()) 27 | }) 28 | } 29 | 30 | function error (name, cbErr, cbSuccess, pluginOptions) { 31 | return posthtml([expressions(pluginOptions)]) 32 | .process(fixture(name)) 33 | .then(cbSuccess) 34 | .catch(cbErr) 35 | } 36 | 37 | function clean (html) { 38 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 39 | } 40 | 41 | test('Loops', (t) => { 42 | return process(t, 'loop', { locals: { items: [1, 2, 3] } }) 43 | }) 44 | 45 | test('Loops - {Object}', (t) => { 46 | return process(t, 'loop_object', { 47 | locals: { items: { a: 'b', c: 'd' } } 48 | }) 49 | }) 50 | 51 | test('Loops - nested', (t) => { 52 | return process(t, 'loop_nested', { 53 | locals: { items: { c1: [1, 2, 3], c2: [4, 5, 6] } } 54 | }) 55 | }) 56 | 57 | test('Loops - locals included', (t) => { 58 | return process(t, 'loop_locals', { 59 | locals: { items: [1, 2, 3], foo: 'bar' } 60 | }) 61 | }) 62 | 63 | test('Loops - conditional included', (t) => { 64 | return process(t, 'loop_conditional', { 65 | locals: { items: [1, 2, 3] } 66 | }) 67 | }) 68 | 69 | test('Loops - conditional and locals included', (t) => { 70 | return process(t, 'loop_conditional_locals', { 71 | locals: { 72 | pages: [ 73 | { path: '/page1', title: 'Page 1' }, 74 | { path: '/page2', title: 'Page 2' }, 75 | { path: '/page3', title: 'Page 3' } 76 | ], 77 | current_path: '/page1' 78 | } 79 | }) 80 | }) 81 | 82 | test('Loops - conflicting locals', (t) => { 83 | return process(t, 'loop_conflict', { 84 | locals: { items: [1, 2, 3], item: 'bar' } 85 | }) 86 | }) 87 | 88 | test('Loops - custom tag', (t) => { 89 | return process(t, 'loop_customtag', { 90 | loopTags: ['for', 'each'], 91 | locals: { items: [1, 2, 3] } 92 | }) 93 | }) 94 | 95 | test('Loops - no loop attribute', (t) => { 96 | return error('loop_no_attr', (err) => { 97 | t.is(err.message, 'the "each" tag must have a "loop" attribute') 98 | }) 99 | }) 100 | 101 | test('Loops - no array or object passed', (t) => { 102 | return error('loop_no_collection', (err) => { 103 | t.is(err.toString(), 'Error: You must provide an array or object to loop through') 104 | }) 105 | }) 106 | 107 | test('Loops - no array or object passed with disabled strict mode', (t) => { 108 | return error('loop_no_collection', () => {}, (response) => { 109 | t.truthy(response) 110 | }, { strictMode: false }) 111 | }) 112 | 113 | test('Loops - no loop arguments', (t) => { 114 | return error('loop_no_args', (err) => { 115 | t.truthy(err.toString() === 'Error: You must provide at least one loop argument') 116 | }) 117 | }) 118 | 119 | test('Loops - no "in" keyword', (t) => { 120 | return error('loop_no_in', (err) => { 121 | t.truthy(err.toString() === "Error: Loop statement lacking 'in' keyword") 122 | }) 123 | }) 124 | 125 | test('Loops - expression error', (t) => { 126 | return error('loop_expression_error', (err) => { 127 | t.is(err.message, 'SyntaxError: Invalid or unexpected token') 128 | }) 129 | }) 130 | 131 | test('Loops - metadata', (t) => { 132 | return process(t, 'loop_metadata', { 133 | locals: { items: [1, 2, 3] } 134 | }) 135 | }) 136 | 137 | test('Loops - nested metadata', (t) => { 138 | return process(t, 'loop_nested_metadata', { 139 | locals: { items: { foo: [1, 2], bar: [3, 4] } } 140 | }) 141 | }) 142 | -------------------------------------------------------------------------------- /test/test-scopes.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const expressions = require('../lib') 8 | 9 | const fixture = (file) => { 10 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 11 | } 12 | 13 | const expect = (file) => { 14 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 15 | } 16 | 17 | function process (t, name, options, log = false) { 18 | return posthtml([expressions(options)]) 19 | .process(fixture(name)) 20 | .then((result) => { 21 | log && console.log(result.html) 22 | 23 | return clean(result.html) 24 | }) 25 | .then((html) => { 26 | t.is(html, expect(name).trim()) 27 | }) 28 | } 29 | 30 | function clean (html) { 31 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 32 | } 33 | 34 | test('Scopes', (t) => { 35 | return process(t, 'scope', { 36 | locals: { 37 | author: { name: 'John', age: 26 }, 38 | name: 'Scope', 39 | key: 'test' 40 | } 41 | }) 42 | }) 43 | 44 | test('Scopes - nested', (t) => { 45 | return process(t, 'scope_nested', { 46 | locals: { 47 | key: 'global', 48 | scope: { 49 | key: 'scope', 50 | one: { 51 | key: 'one' 52 | }, 53 | two: { 54 | key: 'two' 55 | } 56 | } 57 | } 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /test/test-switch.js: -------------------------------------------------------------------------------- 1 | const test = require('ava') 2 | 3 | const join = require('path').join 4 | const readSync = require('fs').readFileSync 5 | 6 | const posthtml = require('posthtml') 7 | const expressions = require('../lib') 8 | 9 | const fixture = (file) => { 10 | return readSync(join(__dirname, 'fixtures', `${file}.html`), 'utf8') 11 | } 12 | 13 | const expect = (file) => { 14 | return readSync(join(__dirname, 'expect', `${file}.html`), 'utf8') 15 | } 16 | 17 | function process (t, name, options, log = false) { 18 | return posthtml([expressions(options)]) 19 | .process(fixture(name)) 20 | .then((result) => { 21 | log && console.log(result.html) 22 | 23 | return clean(result.html) 24 | }) 25 | .then((html) => { 26 | t.is(html, expect(name).trim()) 27 | }) 28 | } 29 | 30 | function error (name, cb) { 31 | return posthtml([expressions()]) 32 | .process(fixture(name)) 33 | .catch(cb) 34 | } 35 | 36 | function clean (html) { 37 | return html.replace(/[^\S\r\n]+$/gm, '').trim() 38 | } 39 | 40 | test('Switch', (t) => { 41 | return Promise.all([ 42 | process(t, 'switch', { locals: { country: 'germany' } }) 43 | ]) 44 | }) 45 | 46 | test('Switch - default branch', (t) => { 47 | return Promise.all([ 48 | process(t, 'switch_default', { locals: { country: 'venezuela' } }) 49 | ]) 50 | }) 51 | 52 | test('Switch - nested', (t) => { 53 | return Promise.all([ 54 | process(t, 'switch_nested', { 55 | locals: { 56 | country_one: 'venezuela', 57 | country_two: 'russia' 58 | } 59 | }) 60 | ]) 61 | }) 62 | 63 | test('Switch - custom tag', (t) => { 64 | return process(t, 'switch_customtag', { 65 | switchTags: ['s', 'c', 'd'], 66 | locals: { country: 'us' } 67 | }) 68 | }) 69 | 70 | test('Switch - dynamic expression', (t) => { 71 | return Promise.all([ 72 | process(t, 'switch_number', { locals: { items: [1, 2, 3] } }) 73 | ]) 74 | }) 75 | 76 | test('Switch - no switch attribute', (t) => { 77 | return error('switch_no_attr', (err) => { 78 | t.is(err.toString(), 'Error: the "switch" tag must have a "expression" attribute') 79 | }) 80 | }) 81 | 82 | test('Switch - no case attribute', (t) => { 83 | return error('switch_no_case_attr', (err) => { 84 | t.is(err.message, 'the "switch" tag must have a "expression" attribute') 85 | }) 86 | }) 87 | 88 | test('Switch - bad flow', (t) => { 89 | return error('switch_bad_flow', (err) => { 90 | t.is(err.message, 'the "switch" tag can contain only "case" tags and one "default" tag') 91 | }) 92 | }) 93 | 94 | test('Switch - raw tag', (t) => { 95 | return Promise.all([ 96 | process(t, 'raw_in_switch', { locals: { country: 'germany', city: 'moscow' } }) 97 | ]) 98 | }) 99 | --------------------------------------------------------------------------------