Compare commits

...
113 Commits
Author SHA1 Message Date
semantic-release-bot 151ff4cbf3 chore(release): 9.23.1 [skip ci]
## [9.23.1](https://github.com/harttle/liquidjs/compare/v9.23.0...v9.23.1) (2021-02-19)

### Bug Fixes

* lenientIf not working for the umd bundle, closes [#313](https://github.com/harttle/liquidjs/issues/313) ([2e66e8b](https://github.com/harttle/liquidjs/commit/2e66e8b))
2021-02-19 15:29:27 +00:00
harttle 2e66e8b02e fix: lenientIf not working for the umd bundle, closes #313 2021-02-19 23:10:15 +08:00
semantic-release-bot 24306722ec chore(release): 9.23.0 [skip ci]
# [9.23.0](https://github.com/harttle/liquidjs/compare/v9.22.1...v9.23.0) (2021-02-12)

### Bug Fixes

* respect `fs` in parser options, for [#233](https://github.com/harttle/liquidjs/issues/233) ([4e82da6](https://github.com/harttle/liquidjs/commit/4e82da6))

### Features

* support filters in if/unless/case, see [#287](https://github.com/harttle/liquidjs/issues/287) ([2f059f6](https://github.com/harttle/liquidjs/commit/2f059f6))
* support function calls, closes [#222](https://github.com/harttle/liquidjs/issues/222) ([e37824f](https://github.com/harttle/liquidjs/commit/e37824f))
* support layout none, closes [#299](https://github.com/harttle/liquidjs/issues/299) ([81e11bb](https://github.com/harttle/liquidjs/commit/81e11bb))
2021-02-12 14:28:02 +00:00
harttle 2f059f6a25 feat: support filters in if/unless/case, see #287 2021-02-12 22:22:41 +08:00
harttle 4e82da689e fix: respect fs in parser options, for #233 2021-02-12 15:17:21 +08:00
harttle e37824fd8a feat: support function calls, closes #222 2021-02-12 13:47:30 +08:00
harttle 81e11bb9c4 feat: support layout none, closes #299 2021-02-12 01:57:30 +08:00
semantic-release-bot 71d5d24813 chore(release): 9.22.1 [skip ci]
## [9.22.1](https://github.com/harttle/liquidjs/compare/v9.22.0...v9.22.1) (2021-02-05)

### Bug Fixes

* default to precedence 1 for custom operators ([20f559e](https://github.com/harttle/liquidjs/commit/20f559e))
2021-02-05 16:00:50 +00:00
Jason EtcovitchandJun Yang 20f559e545 fix: default to precedence 1 for custom operators 2021-02-05 23:57:36 +08:00
harttle dce75a1a22 docs: fix get started code block 2021-02-05 09:48:28 +08:00
semantic-release-bot 93ec151374 chore(release): 9.22.0 [skip ci]
# [9.22.0](https://github.com/harttle/liquidjs/compare/v9.21.0...v9.22.0) (2021-02-04)

### Features

* compact filter ([f42c217](https://github.com/harttle/liquidjs/commit/f42c217))
2021-02-04 14:52:00 +00:00
Peter deHaanandJun Yang 29db1bb0e2 Update sidebar.yml
Add `compact` filter to sidebar nav.
2021-02-04 22:48:02 +08:00
harttle f42c217fc0 feat: compact filter 2021-02-04 22:47:34 +08:00
semantic-release-bot ec4a711e00 chore(release): 9.21.0 [skip ci]
# [9.21.0](https://github.com/harttle/liquidjs/compare/v9.20.1...v9.21.0) (2021-02-04)

### Features

* add `operators` option for custom operators ([75591cd](https://github.com/harttle/liquidjs/commit/75591cd))
* create trie programmatically in options ([befc33c](https://github.com/harttle/liquidjs/commit/befc33c))
* export OperatorMap type ([bc87e19](https://github.com/harttle/liquidjs/commit/bc87e19))
* export Operators from operator.ts ([6a7c280](https://github.com/harttle/liquidjs/commit/6a7c280))
* rename to defaultOperators and Operators ([8734e2e](https://github.com/harttle/liquidjs/commit/8734e2e))
2021-02-04 13:51:46 +00:00
Peter deHaanandJun Yang 03893d585e Typo: 'where' filter on filters/overview.md 2021-02-04 21:48:28 +08:00
allcontributors[bot]andJun Yang 3b97c38ee5 docs: update .all-contributorsrc [skip ci] 2021-02-04 09:12:26 +08:00
allcontributors[bot]andJun Yang 48a876db49 docs: update README.md [skip ci] 2021-02-04 09:12:26 +08:00
Jason EtcovitchandJun Yang befc33c4eb feat: create trie programmatically in options 2021-02-04 09:11:28 +08:00
Jason EtcovitchandJun Yang 8734e2e6ce feat: rename to defaultOperators and Operators 2021-02-04 09:11:28 +08:00
Jason EtcovitchandJun Yang 18055c8855 test: add LiquidOptions#operators test 2021-02-04 09:11:28 +08:00
Jason EtcovitchandJun Yang 6a7c280f04 feat: export Operators from operator.ts 2021-02-04 09:11:28 +08:00
Jason EtcovitchandJun Yang bc87e19e3e feat: export OperatorMap type 2021-02-04 09:11:28 +08:00
Jason EtcovitchandJun Yang 75591cdbfc feat: add operators option for custom operators 2021-02-04 09:11:28 +08:00
semantic-release-bot 5901611af5 chore(release): 9.20.1 [skip ci]
## [9.20.1](https://github.com/harttle/liquidjs/compare/v9.20.0...v9.20.1) (2021-01-24)

### Bug Fixes

* allow string literals contain delimiters, fixes [#288](https://github.com/harttle/liquidjs/issues/288) ([9c40da7](https://github.com/harttle/liquidjs/commit/9c40da7))
2021-01-24 04:52:24 +00:00
harttle 9c40da7369 fix: allow string literals contain delimiters, fixes #288 2021-01-24 12:49:19 +08:00
semantic-release-bot 5e7598ce45 chore(release): 9.20.0 [skip ci]
# [9.20.0](https://github.com/harttle/liquidjs/compare/v9.19.0...v9.20.0) (2021-01-23)

### Features

* support `{{block.super}}`, see [#38](https://github.com/harttle/liquidjs/issues/38) ([a3af44d](https://github.com/harttle/liquidjs/commit/a3af44d))
2021-01-23 19:01:03 +00:00
harttle 42591a04d8 docs: update dist links 2021-01-24 02:57:48 +08:00
harttle a3af44dc10 feat: support {{block.super}}, see #38 2021-01-24 02:57:48 +08:00
dependabot[bot]andJun Yang 0e0dc2adf0 chore(deps): bump ini from 1.3.5 to 1.3.7
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot[bot] <[email protected]>
2021-01-24 02:36:33 +08:00
harttle 7b909c22a1 docs: demo for globals, for #286 2020-12-22 23:19:09 +08:00
semantic-release-bot 626f911503 chore(release): 9.19.0 [skip ci]
# [9.19.0](https://github.com/harttle/liquidjs/compare/v9.18.0...v9.19.0) (2020-12-18)

### Bug Fixes

* lint ([de32259](https://github.com/harttle/liquidjs/commit/de32259))
* move offset adding complexity inside TimezoneDate ([26b2175](https://github.com/harttle/liquidjs/commit/26b2175))
* simpler timezone regex and non-null offset ([e3ecfe3](https://github.com/harttle/liquidjs/commit/e3ecfe3))

### Features

* add preserveTimezones option ([d70cd2a](https://github.com/harttle/liquidjs/commit/d70cd2a))
* parse and handle date timezone offsets ([c16c787](https://github.com/harttle/liquidjs/commit/c16c787))
2020-12-18 14:07:14 +00:00
JoonasandJun Yang de32259bb9 fix: lint 2020-12-18 22:03:58 +08:00
JoonasandJun Yang d70cd2a1ea feat: add preserveTimezones option 2020-12-18 22:03:58 +08:00
JoonasandJun Yang 26b217542b fix: move offset adding complexity inside TimezoneDate 2020-12-18 22:03:58 +08:00
JoonasandJun Yang e3ecfe3adc fix: simpler timezone regex and non-null offset 2020-12-18 22:03:58 +08:00
wyoziandJun Yang c16c787861 feat: parse and handle date timezone offsets 2020-12-18 22:03:58 +08:00
semantic-release-bot 13f5f95d01 chore(release): 9.18.0 [skip ci]
# [9.18.0](https://github.com/harttle/liquidjs/compare/v9.17.0...v9.18.0) (2020-12-17)

### Bug Fixes

* address refactor comments ([6a0ad10](https://github.com/harttle/liquidjs/commit/6a0ad10))

### Features

* add option for keeping variable type in output ([cd92e77](https://github.com/harttle/liquidjs/commit/cd92e77))
2020-12-17 15:17:09 +00:00
harttle c8db5e6259 docs: try fix special chars in changelog.md 2020-12-17 23:08:54 +08:00
allcontributors[bot]andJun Yang 9e511ecd14 docs: update .all-contributorsrc [skip ci] 2020-12-16 10:23:26 +08:00
allcontributors[bot]andJun Yang 45d95bb8d8 docs: update README.md [skip ci] 2020-12-16 10:23:26 +08:00
PixcellandJun Yang 8d3eca6ee2 stringify value in emitter when concatenating it 2020-12-16 10:20:10 +08:00
Alex de WergifosseandJun Yang 6a0ad102a8 fix: address refactor comments 2020-12-16 10:20:10 +08:00
Alex de WergifosseandJun Yang b8e4b3337b test: add tests for keepOutputType option 2020-12-16 10:20:10 +08:00
Alex de WergifosseandJun Yang cd92e77726 feat: add option for keeping variable type in output 2020-12-16 10:20:10 +08:00
allcontributors[bot]andJun Yang e84fbf32ad docs: update .all-contributorsrc [skip ci] 2020-12-14 22:49:50 +08:00
allcontributors[bot]andJun Yang 0266b27838 docs: update README.md [skip ci] 2020-12-14 22:49:50 +08:00
semantic-release-bot 090c88eb92 chore(release): 9.17.0 [skip ci]
# [9.17.0](https://github.com/harttle/liquidjs/compare/v9.16.1...v9.17.0) (2020-12-07)

### Bug Fixes

* elsif is not supported for unless, fixes [#268](https://github.com/harttle/liquidjs/issues/268) ([2bbf501](https://github.com/harttle/liquidjs/commit/2bbf501))
* enforce string-type pattern in `replace`, fixes [#243](https://github.com/harttle/liquidjs/issues/243) ([c8afa39](https://github.com/harttle/liquidjs/commit/c8afa39))
* raw block not ignoring {% characters, fixes [#263](https://github.com/harttle/liquidjs/issues/263) ([a492d8e](https://github.com/harttle/liquidjs/commit/a492d8e))

### Features

* passing liquid to FilterImpl, closes [#277](https://github.com/harttle/liquidjs/issues/277) ([f9f595f](https://github.com/harttle/liquidjs/commit/f9f595f))
2020-12-07 16:34:49 +00:00
harttle e15803fae4 test: fix e2e test 2020-12-08 00:30:32 +08:00
harttle 117673a725 style: make eslint check .ts files 2020-12-08 00:24:07 +08:00
harttle ef43120d6e test: fix test cases after rebase 2020-12-08 00:20:43 +08:00
harttle f9f595f0f4 feat: passing liquid to FilterImpl, closes #277 2020-12-08 00:17:27 +08:00
harttle 2bbf50171b fix: elsif is not supported for unless, fixes #268 2020-12-08 00:16:36 +08:00
harttle a492d8e23d fix: raw block not ignoring {% characters, fixes #263 2020-12-08 00:12:20 +08:00
harttle c8afa39a01 fix: enforce string-type pattern in replace, fixes #243 2020-12-08 00:08:31 +08:00
sschuldenzuckerandJun Yang d8f9091a12 add and use error classes for undefined variables
cleans up funky message-matching code in expression.ts for lenient.
2020-12-06 22:58:08 +08:00
sschuldenzuckerandJun Yang ca18a579a1 Expression: minor type change & simplification
pre-fetching postfix simplifies code for 'lenient'
2020-12-06 22:58:08 +08:00
sschuldenzuckerandJun Yang 6f2b24f7ca implement lenientIf for the unless tag, too 2020-12-06 22:58:08 +08:00
sschuldenzuckerandJun Yang 87d7dd0d37 docs for the lenientIf option 2020-12-06 22:58:08 +08:00
sschuldenzuckerandJun Yang 768fb79e32 Implement lenientIf option (resolve #265; default off)
See description of the option in liquid-options.ts.
2020-12-06 22:58:08 +08:00
sschuldenzuckerandJun Yang c920ebb282 fix test: don't pass {} for Context 2020-12-06 22:58:08 +08:00
Jun YangandGitHub 9706e8b411 docs: add a security policy, see #254 2020-11-29 20:59:00 +08:00
dependabot[bot]andJun Yang 75698eefec chore(deps): bump highlight.js from 9.18.1 to 9.18.5 in /docs
Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 9.18.1 to 9.18.5.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/9.18.5/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/9.18.1...9.18.5)

Signed-off-by: dependabot[bot] <[email protected]>
2020-11-25 21:56:22 +08:00
dependabot[bot]andJun Yang dfd358c0a2 chore(deps): bump highlight.js from 9.15.9 to 9.18.5
Bumps [highlight.js](https://github.com/highlightjs/highlight.js) from 9.15.9 to 9.18.5.
- [Release notes](https://github.com/highlightjs/highlight.js/releases)
- [Changelog](https://github.com/highlightjs/highlight.js/blob/9.18.5/CHANGES.md)
- [Commits](https://github.com/highlightjs/highlight.js/compare/9.15.9...9.18.5)

Signed-off-by: dependabot[bot] <[email protected]>
2020-11-25 21:55:58 +08:00
dependabot[bot]andJun Yang 4c70946fa1 chore(deps-dev): bump semantic-release from 15.13.19 to 17.2.3
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 15.13.19 to 17.2.3.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v15.13.19...v17.2.3)

Signed-off-by: dependabot[bot] <[email protected]>
2020-11-20 22:11:04 +08:00
Jun YangandGitHub 9d9c0c1ca7 docs: update the UMD url and jsFiddle demo, see #264 2020-11-08 12:18:37 +08:00
semantic-release-bot a9d3a5a402 chore(release): 9.16.1 [skip ci]
## [9.16.1](https://github.com/harttle/liquidjs/compare/v9.16.0...v9.16.1) (2020-10-09)

### Bug Fixes

* braced property access ([18a807e](https://github.com/harttle/liquidjs/commit/18a807e))
2020-10-09 13:22:06 +00:00
wyoziandJun Yang 30dffb675e Test for broken quoted property access 2020-10-09 21:18:40 +08:00
wyoziandJun Yang e67c3c6924 Make end brace peek stricter and fix the test 2020-10-09 21:18:40 +08:00
wyoziandJun Yang e2e410f334 Add incomplete quoted property access test 2020-10-09 21:18:40 +08:00
wyoziandJun Yang 27ce44238e Test getVariableAsText 2020-10-09 21:18:40 +08:00
wyoziandJun Yang abfaf8a042 Test quoted property access tokenizing 2020-10-09 21:18:40 +08:00
wyoziandJun Yang 18a807ea2e fix: braced property access 2020-10-09 21:18:40 +08:00
harttle a6126c3bea docs: add amit777 as contributor for code 2020-10-08 16:57:59 +08:00
harttle a96d772963 docs: format 2020-10-08 14:40:58 +08:00
semantic-release-bot 732ef2605e chore(release): 9.16.0 [skip ci]
# [9.16.0](https://github.com/harttle/liquidjs/compare/v9.15.1...v9.16.0) (2020-10-08)

### Features

* support jsTruthy, [#255](https://github.com/harttle/liquidjs/issues/255) [#257](https://github.com/harttle/liquidjs/issues/257) ([72ee7b4](https://github.com/harttle/liquidjs/commit/72ee7b4))
2020-10-08 05:15:00 +00:00
harttle 72ee7b4c1e feat: support jsTruthy, #255 #257 2020-10-08 13:11:02 +08:00
Amit GuptaandJun Yang f807383f2a resolved issues raised by hhartle 2020-10-08 12:43:37 +08:00
Amit GuptaandJun Yang 02bfed1c57 Added jsTruthy flag to constuctor in order to enable JS style truthiness evaluation 2020-10-08 12:43:37 +08:00
harttle b11df79b91 test: make it pass regardless timezone, #257 2020-10-08 09:38:10 +08:00
semantic-release-bot 8ee9bd7ba8 chore(release): 9.15.1 [skip ci]
## [9.15.1](https://github.com/harttle/liquidjs/compare/v9.15.0...v9.15.1) (2020-10-03)

### Bug Fixes

* allow quoted variable name in capture, fixes [#252](https://github.com/harttle/liquidjs/issues/252) ([5b3f419](https://github.com/harttle/liquidjs/commit/5b3f419))
2020-10-03 14:06:41 +00:00
harttle 5b3f4195f0 fix: allow quoted variable name in capture, fixes #252 2020-10-03 22:03:17 +08:00
dependabot[bot]andJun Yang 236d971705 chore(deps): bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <[email protected]>
2020-10-03 20:36:06 +08:00
Jun YangandGitHub 0cd4d87d46 docs: update the demo on index page, fixes #247 2020-09-11 22:30:21 +08:00
harttle 1fdfd4a68a docs: show social share on small screens 2020-08-12 20:37:07 +08:00
dependabot[bot]andJun Yang c1f73a9f03 chore(deps): bump prismjs from 1.19.0 to 1.21.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.19.0 to 1.21.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.19.0...v1.21.0)

Signed-off-by: dependabot[bot] <[email protected]>
2020-08-08 22:14:03 +08:00
semantic-release-bot d8f550e447 chore(release): 9.15.0 [skip ci]
# [9.15.0](https://github.com/harttle/liquidjs/compare/v9.14.1...v9.15.0) (2020-08-04)

### Features

* export toPromise and toValue, see [#158](https://github.com/harttle/liquidjs/issues/158) ([2e5ab98](https://github.com/harttle/liquidjs/commit/2e5ab98))
2020-08-04 16:58:57 +00:00
harttle 2e5ab98bfc feat: export toPromise and toValue, see #158 2020-08-05 00:46:01 +08:00
harttle 0dbb7792d7 docs: fix button style 2020-08-01 22:00:12 +08:00
dependabot[bot]andJun Yang e10ce657dd chore(deps): bump elliptic from 6.5.0 to 6.5.3
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.0 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.0...v6.5.3)

Signed-off-by: dependabot[bot] <[email protected]>
2020-07-30 22:42:19 +08:00
harttle 3713fa08b2 docs: credit Peter deHaan as contributor 2020-07-25 23:31:48 +08:00
harttle c5002d8cad docs: location.hash for source code 2020-07-25 01:23:47 +08:00
dependabot[bot]andJun Yang 00b0a5708c chore(deps): bump lodash from 4.17.15 to 4.17.19 in /docs
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <[email protected]>
2020-07-20 21:14:42 +08:00
semantic-release-bot f82ee8a18c chore(release): 9.14.1 [skip ci]
## [9.14.1](https://github.com/harttle/liquidjs/compare/v9.14.0...v9.14.1) (2020-07-08)

### Bug Fixes

* enumerate Promises (e.g. in for & tablerow) ([#237](https://github.com/harttle/liquidjs/issues/237)) ([941dd66](https://github.com/harttle/liquidjs/commit/941dd66))
2020-07-08 17:40:43 +00:00
dependabot[bot]andJun Yang 632bb6b077 chore(deps): bump npm from 6.13.4 to 6.14.6
Bumps [npm](https://github.com/npm/cli) from 6.13.4 to 6.14.6.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v6.13.4...v6.14.6)

Signed-off-by: dependabot[bot] <[email protected]>
2020-07-09 01:36:21 +08:00
allcontributors[bot]GitHuballcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
0b6ee9bca1 docs: add tejasmanohar as a contributor (#238)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2020-06-28 09:57:45 +08:00
Tejas ManoharandGitHub 941dd668fe fix: enumerate Promises (e.g. in for & tablerow) (#237)
* fix: enumerate Promise<array> (e.g. in {% for ... %})

Previously, a Promise of an array was not being enumerated in
{% for %}, for example. This is misleading since the library
handles promises elsewhere (e.g. if you {% assign x = promiseArray %}
and then {% for v in x %}, it worked just fine.

This PR makes Promises of arrays handled by changing toEnumerable
to handle then-ables. This affects other iterators, too, e.g. tablerow,
so I put in a test for that as well.
2020-06-28 09:55:56 +08:00
semantic-release-bot 11ffd65503 chore(release): 9.14.0 [skip ci]
# [9.14.0](https://github.com/harttle/liquidjs/compare/v9.13.0...v9.14.0) (2020-06-25)

### Features

* setup universal browser and node builds ([6cf6ffa](https://github.com/harttle/liquidjs/commit/6cf6ffa))
2020-06-25 12:14:45 +00:00
Jun YangandGitHub ce0280ca9f Merge pull request #235 from harttle/all-contributors/add-TrySound
docs: add TrySound as a contributor
2020-06-25 20:10:15 +08:00
allcontributors[bot]andGitHub 1066ef0f76 docs: update .all-contributorsrc [skip ci] 2020-06-25 12:09:50 +00:00
allcontributors[bot]andGitHub 7b38c28d52 docs: update README.md [skip ci] 2020-06-25 12:09:49 +00:00
Jun YangandGitHub 97e7983bd8 Merge pull request #234 from TrySound/proper-modules
feat: setup universal browser and node builds
2020-06-25 20:08:47 +08:00
Bogdan Chadkin 6cf6ffae90 feat: setup universal browser and node builds
In this diff I fixed es modules distribution for bundlers.

Before we had two problems
- bundlers use "module" field to find es module build
- bundlers cannot use provided es module with node target

In this diff I addressed both of them. Now "main" and "module" fields
points to "node" specific versions. "browser" field provides aliases
with browser compatible builds.

All modern browsers support both "module" and "browser" fields.
2020-06-25 13:30:56 +03:00
semantic-release-bot 04873a4e9c chore(release): 9.13.0 [skip ci]
# [9.13.0](https://github.com/harttle/liquidjs/compare/v9.12.0...v9.13.0) (2020-06-25)

### Features

* async filters, closes [#232](https://github.com/harttle/liquidjs/issues/232) ([e36f3ff](https://github.com/harttle/liquidjs/commit/e36f3ff))
2020-06-25 05:56:31 +00:00
harttle 1a08efd402 docs: better style for API doc 2020-06-25 13:15:02 +08:00
harttle e36f3ff291 feat: async filters, closes #232 2020-06-25 12:30:56 +08:00
harttle f3f017bd37 style: spaces and extra NL 2020-05-30 22:13:20 +08:00
harttle 0d20a344b6 docs: manifest for liquidjs.com 2020-05-23 02:14:23 +08:00
Jun YangandGitHub 3acbdfc1b7 Merge pull request #229 from stedman/patch-1
docs: Add hashes to `for` tag documentation
2020-05-23 01:56:13 +08:00
Anthony CiccarelloandJun Yang ed994bdbd5 fix elsif spelling on tags overview in docs 2020-05-23 01:56:03 +08:00
dependabot[bot]andJun Yang 27a4c7666b chore(deps): bump jquery from 3.4.1 to 3.5.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Commits](https://github.com/jquery/jquery/compare/3.4.1...3.5.0)

Signed-off-by: dependabot[bot] <[email protected]>
2020-05-23 01:55:30 +08:00
harttle 064ea99f49 docs: add credit for @stedman (#229) and @aciccarello (#223) 2020-05-22 11:44:23 +08:00
harttle 71fb8a8b7c chore: +pre-commit hook, -commitlint on travis 2020-05-22 11:41:32 +08:00
Steve Stedman acdd121a61 docs: Add hashes to for tag documentation
This extremely helpful information was found at https://github.com/Shopify/liquid/wiki/Liquid-for-Designers#allowed-collection-types
2020-05-21 11:11:01 -05:00
127 changed files with 6980 additions and 1106 deletions
+84 -2
View File
@@ -7,8 +7,8 @@
"README.md"
],
"imageSize": 100,
"commit": true,
"commitConvention": "eslint",
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "harttle",
@@ -269,6 +269,88 @@
"contributions": [
"doc"
]
},
{
"login": "stedman",
"name": "Steve Stedman",
"avatar_url": "https://avatars1.githubusercontent.com/u/183122?v=4",
"profile": "https://stedman.dev",
"contributions": [
"doc"
]
},
{
"login": "aciccarello",
"name": "Anthony Ciccarello",
"avatar_url": "https://avatars0.githubusercontent.com/u/11273838?v=4",
"profile": "https://ciccarello.me",
"contributions": [
"doc"
]
},
{
"login": "TrySound",
"name": "Bogdan Chadkin",
"avatar_url": "https://avatars0.githubusercontent.com/u/5635476?v=4",
"profile": "https://twitter.com/IAmTrySound",
"contributions": [
"code"
]
},
{
"login": "tejasmanohar",
"name": "Tejas Manohar",
"avatar_url": "https://avatars0.githubusercontent.com/u/5959235?v=4",
"profile": "https://hightouch.io",
"contributions": [
"code"
]
},
{
"login": "pdehaan",
"name": "Peter deHaan",
"avatar_url": "https://avatars2.githubusercontent.com/u/557895?v=4",
"profile": "http://about.me/peterdehaan",
"contributions": [
"doc"
]
},
{
"login": "amit777",
"name": "amit777",
"avatar_url": "https://avatars0.githubusercontent.com/u/2703309?v=4",
"profile": "https://github.com/amit777",
"contributions": [
"code",
"financial"
]
},
{
"login": "sschuldenzucker",
"name": "Steffen Schuldenzucker",
"avatar_url": "https://avatars3.githubusercontent.com/u/1100776?v=4",
"profile": "http://www.ifi.uzh.ch/en/ce/people/schuldenzucker.html",
"contributions": [
"code"
]
},
{
"login": "Pixcell",
"name": "Pixcell",
"avatar_url": "https://avatars0.githubusercontent.com/u/4005291?v=4",
"profile": "https://github.com/Pixcell",
"contributions": [
"code"
]
},
{
"login": "JasonEtco",
"name": "Jason Etcovitch",
"avatar_url": "https://avatars.githubusercontent.com/u/10660468?v=4",
"profile": "https://jasonet.co",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
-1
View File
@@ -8,7 +8,6 @@ jobs:
name: 'Linting'
script:
- npm run lint
- commitlint-travis
- name: 'Coverage'
script: npm run coverage-coveralls
- stage: release
+150
View File
@@ -1,3 +1,153 @@
## [9.23.1](https://github.com/harttle/liquidjs/compare/v9.23.0...v9.23.1) (2021-02-19)
### Bug Fixes
* lenientIf not working for the umd bundle, closes [#313](https://github.com/harttle/liquidjs/issues/313) ([2e66e8b](https://github.com/harttle/liquidjs/commit/2e66e8b))
# [9.23.0](https://github.com/harttle/liquidjs/compare/v9.22.1...v9.23.0) (2021-02-12)
### Bug Fixes
* respect `fs` in parser options, for [#233](https://github.com/harttle/liquidjs/issues/233) ([4e82da6](https://github.com/harttle/liquidjs/commit/4e82da6))
### Features
* support filters in if/unless/case, see [#287](https://github.com/harttle/liquidjs/issues/287) ([2f059f6](https://github.com/harttle/liquidjs/commit/2f059f6))
* support function calls, closes [#222](https://github.com/harttle/liquidjs/issues/222) ([e37824f](https://github.com/harttle/liquidjs/commit/e37824f))
* support layout none, closes [#299](https://github.com/harttle/liquidjs/issues/299) ([81e11bb](https://github.com/harttle/liquidjs/commit/81e11bb))
## [9.22.1](https://github.com/harttle/liquidjs/compare/v9.22.0...v9.22.1) (2021-02-05)
### Bug Fixes
* default to precedence 1 for custom operators ([20f559e](https://github.com/harttle/liquidjs/commit/20f559e))
# [9.22.0](https://github.com/harttle/liquidjs/compare/v9.21.0...v9.22.0) (2021-02-04)
### Features
* compact filter ([f42c217](https://github.com/harttle/liquidjs/commit/f42c217))
# [9.21.0](https://github.com/harttle/liquidjs/compare/v9.20.1...v9.21.0) (2021-02-04)
### Features
* add `operators` option for custom operators ([75591cd](https://github.com/harttle/liquidjs/commit/75591cd))
* create trie programmatically in options ([befc33c](https://github.com/harttle/liquidjs/commit/befc33c))
* export OperatorMap type ([bc87e19](https://github.com/harttle/liquidjs/commit/bc87e19))
* export Operators from operator.ts ([6a7c280](https://github.com/harttle/liquidjs/commit/6a7c280))
* rename to defaultOperators and Operators ([8734e2e](https://github.com/harttle/liquidjs/commit/8734e2e))
## [9.20.1](https://github.com/harttle/liquidjs/compare/v9.20.0...v9.20.1) (2021-01-24)
### Bug Fixes
* allow string literals contain delimiters, fixes [#288](https://github.com/harttle/liquidjs/issues/288) ([9c40da7](https://github.com/harttle/liquidjs/commit/9c40da7))
# [9.20.0](https://github.com/harttle/liquidjs/compare/v9.19.0...v9.20.0) (2021-01-23)
### Features
* support `{{block.super}}`, see [#38](https://github.com/harttle/liquidjs/issues/38) ([a3af44d](https://github.com/harttle/liquidjs/commit/a3af44d))
# [9.19.0](https://github.com/harttle/liquidjs/compare/v9.18.0...v9.19.0) (2020-12-18)
### Bug Fixes
* lint ([de32259](https://github.com/harttle/liquidjs/commit/de32259))
* move offset adding complexity inside TimezoneDate ([26b2175](https://github.com/harttle/liquidjs/commit/26b2175))
* simpler timezone regex and non-null offset ([e3ecfe3](https://github.com/harttle/liquidjs/commit/e3ecfe3))
### Features
* add preserveTimezones option ([d70cd2a](https://github.com/harttle/liquidjs/commit/d70cd2a))
* parse and handle date timezone offsets ([c16c787](https://github.com/harttle/liquidjs/commit/c16c787))
# [9.18.0](https://github.com/harttle/liquidjs/compare/v9.17.0...v9.18.0) (2020-12-17)
### Bug Fixes
* address refactor comments ([6a0ad10](https://github.com/harttle/liquidjs/commit/6a0ad10))
### Features
* add option for keeping variable type in output ([cd92e77](https://github.com/harttle/liquidjs/commit/cd92e77))
# [9.17.0](https://github.com/harttle/liquidjs/compare/v9.16.1...v9.17.0) (2020-12-07)
### Bug Fixes
* elsif is not supported for unless, fixes [#268](https://github.com/harttle/liquidjs/issues/268) ([2bbf501](https://github.com/harttle/liquidjs/commit/2bbf501))
* enforce string-type pattern in `replace`, fixes [#243](https://github.com/harttle/liquidjs/issues/243) ([c8afa39](https://github.com/harttle/liquidjs/commit/c8afa39))
* raw block not ignoring {% characters, fixes [#263](https://github.com/harttle/liquidjs/issues/263) ([a492d8e](https://github.com/harttle/liquidjs/commit/a492d8e))
### Features
* passing liquid to FilterImpl, closes [#277](https://github.com/harttle/liquidjs/issues/277) ([f9f595f](https://github.com/harttle/liquidjs/commit/f9f595f))
## [9.16.1](https://github.com/harttle/liquidjs/compare/v9.16.0...v9.16.1) (2020-10-09)
### Bug Fixes
* braced property access ([18a807e](https://github.com/harttle/liquidjs/commit/18a807e))
# [9.16.0](https://github.com/harttle/liquidjs/compare/v9.15.1...v9.16.0) (2020-10-08)
### Features
* support jsTruthy, [#255](https://github.com/harttle/liquidjs/issues/255) [#257](https://github.com/harttle/liquidjs/issues/257) ([72ee7b4](https://github.com/harttle/liquidjs/commit/72ee7b4))
## [9.15.1](https://github.com/harttle/liquidjs/compare/v9.15.0...v9.15.1) (2020-10-03)
### Bug Fixes
* allow quoted variable name in capture, fixes [#252](https://github.com/harttle/liquidjs/issues/252) ([5b3f419](https://github.com/harttle/liquidjs/commit/5b3f419))
# [9.15.0](https://github.com/harttle/liquidjs/compare/v9.14.1...v9.15.0) (2020-08-04)
### Features
* export toPromise and toValue, see [#158](https://github.com/harttle/liquidjs/issues/158) ([2e5ab98](https://github.com/harttle/liquidjs/commit/2e5ab98))
## [9.14.1](https://github.com/harttle/liquidjs/compare/v9.14.0...v9.14.1) (2020-07-08)
### Bug Fixes
* enumerate Promises (e.g. in for & tablerow) ([#237](https://github.com/harttle/liquidjs/issues/237)) ([941dd66](https://github.com/harttle/liquidjs/commit/941dd66))
# [9.14.0](https://github.com/harttle/liquidjs/compare/v9.13.0...v9.14.0) (2020-06-25)
### Features
* setup universal browser and node builds ([6cf6ffa](https://github.com/harttle/liquidjs/commit/6cf6ffa))
# [9.13.0](https://github.com/harttle/liquidjs/compare/v9.12.0...v9.13.0) (2020-06-25)
### Features
* async filters, closes [#232](https://github.com/harttle/liquidjs/issues/232) ([e36f3ff](https://github.com/harttle/liquidjs/commit/e36f3ff))
# [9.12.0](https://github.com/harttle/liquidjs/compare/v9.11.11...v9.12.0) (2020-05-15)
+44 -30
View File
@@ -32,7 +32,7 @@ npm install --save liquidjs
Or use the UMD bundle from jsDelivr:
```html
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>
```
More details, refer to [The Setup Guide][setup].
@@ -59,45 +59,59 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://harttle.land"><img src="https://avatars3.githubusercontent.com/u/4427974?v=4" width="100px;" alt=""/><br /><sub><b>Jun Yang</b></sub></a><br /><a href="#maintenance-harttle" title="Maintenance">🚧</a> <a href="https://github.com/harttle/liquidjs/commits?author=harttle" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/chenos"><img src="https://avatars0.githubusercontent.com/u/2993310?v=4" width="100px;" alt=""/><br /><sub><b>chenos</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=chenos" title="Code">💻</a></td>
<td align="center"><a href="https://zachleat.com/"><img src="https://avatars2.githubusercontent.com/u/39355?v=4" width="100px;" alt=""/><br /><sub><b>Zach Leatherman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/issues?q=author%3Azachleat" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/thardy"><img src="https://avatars3.githubusercontent.com/u/120636?v=4" width="100px;" alt=""/><br /><sub><b>Tim Hardy</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thardy" title="Code">💻</a></td>
<td align="center"><a href="https://paulrobertlloyd.com/"><img src="https://avatars3.githubusercontent.com/u/813383?v=4" width="100px;" alt=""/><br /><sub><b>Paul Robert Lloyd</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=paulrobertlloyd" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/issues?q=author%3Apaulrobertlloyd" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://twitter.com/alecdotbiz"><img src="https://avatars2.githubusercontent.com/u/1925840?v=4" width="100px;" alt=""/><br /><sub><b>Alec Larson</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=aleclarson" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/pmalouin"><img src="https://avatars1.githubusercontent.com/u/1411117?v=4" width="100px;" alt=""/><br /><sub><b>Patrick Malouin</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=pmalouin" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/commits?author=pmalouin" title="Documentation">📖</a></td>
<td align="center"><a href="https://harttle.land"><img src="https://avatars3.githubusercontent.com/u/4427974?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jun Yang</b></sub></a><br /><a href="#maintenance-harttle" title="Maintenance">🚧</a> <a href="https://github.com/harttle/liquidjs/commits?author=harttle" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/chenos"><img src="https://avatars0.githubusercontent.com/u/2993310?v=4?s=100" width="100px;" alt=""/><br /><sub><b>chenos</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=chenos" title="Code">💻</a></td>
<td align="center"><a href="https://zachleat.com/"><img src="https://avatars2.githubusercontent.com/u/39355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zach Leatherman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/issues?q=author%3Azachleat" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://github.com/thardy"><img src="https://avatars3.githubusercontent.com/u/120636?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tim Hardy</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thardy" title="Code">💻</a></td>
<td align="center"><a href="https://paulrobertlloyd.com/"><img src="https://avatars3.githubusercontent.com/u/813383?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Paul Robert Lloyd</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=paulrobertlloyd" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/issues?q=author%3Apaulrobertlloyd" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://twitter.com/alecdotbiz"><img src="https://avatars2.githubusercontent.com/u/1925840?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alec Larson</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=aleclarson" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/pmalouin"><img src="https://avatars1.githubusercontent.com/u/1411117?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Patrick Malouin</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=pmalouin" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/commits?author=pmalouin" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://jaswrks.com"><img src="https://avatars3.githubusercontent.com/u/1563559?v=4" width="100px;" alt=""/><br /><sub><b>jaswrks</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jaswrks" title="Code">💻</a></td>
<td align="center"><a href="https://oott123.com"><img src="https://avatars2.githubusercontent.com/u/905663?v=4" width="100px;" alt=""/><br /><sub><b>三三</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=oott123" title="Code">💻</a> <a href="#ideas-oott123" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/ssendev"><img src="https://avatars0.githubusercontent.com/u/450793?v=4" width="100px;" alt=""/><br /><sub><b>ssendev</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ssendev" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/commits?author=ssendev" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/wojtask9"><img src="https://avatars3.githubusercontent.com/u/6099236?v=4" width="100px;" alt=""/><br /><sub><b>wojtask9</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=wojtask9" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/thelornenelson"><img src="https://avatars3.githubusercontent.com/u/24596583?v=4" width="100px;" alt=""/><br /><sub><b>Andrew Barclay</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thelornenelson" title="Code">💻</a></td>
<td align="center"><a href="https://www.stam.pr/"><img src="https://avatars2.githubusercontent.com/u/142338?v=4" width="100px;" alt=""/><br /><sub><b>Cory Mawhorter</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=cmawhorter" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/thehappybug"><img src="https://avatars0.githubusercontent.com/u/3393530?v=4" width="100px;" alt=""/><br /><sub><b>Mehdi Jaffery</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thehappybug" title="Code">💻</a></td>
<td align="center"><a href="https://jaswrks.com"><img src="https://avatars3.githubusercontent.com/u/1563559?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jaswrks</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jaswrks" title="Code">💻</a></td>
<td align="center"><a href="https://oott123.com"><img src="https://avatars2.githubusercontent.com/u/905663?v=4?s=100" width="100px;" alt=""/><br /><sub><b>三三</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=oott123" title="Code">💻</a> <a href="#ideas-oott123" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/ssendev"><img src="https://avatars0.githubusercontent.com/u/450793?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ssendev</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ssendev" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/commits?author=ssendev" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/wojtask9"><img src="https://avatars3.githubusercontent.com/u/6099236?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wojtask9</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=wojtask9" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/thelornenelson"><img src="https://avatars3.githubusercontent.com/u/24596583?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Barclay</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thelornenelson" title="Code">💻</a></td>
<td align="center"><a href="https://www.stam.pr/"><img src="https://avatars2.githubusercontent.com/u/142338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cory Mawhorter</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=cmawhorter" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/thehappybug"><img src="https://avatars0.githubusercontent.com/u/3393530?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mehdi Jaffery</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=thehappybug" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/robinbijlani"><img src="https://avatars0.githubusercontent.com/u/2503108?v=4" width="100px;" alt=""/><br /><sub><b>Robin Bijlani</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=robinbijlani" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/issues?q=author%3Arobinbijlani" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://www.rmkennedy.com"><img src="https://avatars3.githubusercontent.com/u/8356669?v=4" width="100px;" alt=""/><br /><sub><b>Ryan Kennedy</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ryaninvents" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/strax"><img src="https://avatars2.githubusercontent.com/u/587213?v=4" width="100px;" alt=""/><br /><sub><b>Sami Kukkonen</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=strax" title="Code">💻</a></td>
<td align="center"><a href="https://ScottFreeCode.github.io/"><img src="https://avatars3.githubusercontent.com/u/16506071?v=4" width="100px;" alt=""/><br /><sub><b>Scott Santucci</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ScottFreeCode" title="Code">💻</a></td>
<td align="center"><a href="http://stevenrescigno.com"><img src="https://avatars3.githubusercontent.com/u/8505293?v=4" width="100px;" alt=""/><br /><sub><b>Steven </b></sub></a><br /><a href="#example-stevenanthonyrevo" title="Examples">💡</a> <a href="https://github.com/harttle/liquidjs/commits?author=stevenanthonyrevo" title="Code">💻</a></td>
<td align="center"><a href="https://efcl.info/"><img src="https://avatars1.githubusercontent.com/u/19714?v=4" width="100px;" alt=""/><br /><sub><b>azu</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=azu" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/wyozi"><img src="https://avatars3.githubusercontent.com/u/4894573?v=4" width="100px;" alt=""/><br /><sub><b>Joonas</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=wyozi" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/robinbijlani"><img src="https://avatars0.githubusercontent.com/u/2503108?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Robin Bijlani</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=robinbijlani" title="Code">💻</a> <a href="https://github.com/harttle/liquidjs/issues?q=author%3Arobinbijlani" title="Bug reports">🐛</a></td>
<td align="center"><a href="https://www.rmkennedy.com"><img src="https://avatars3.githubusercontent.com/u/8356669?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ryan Kennedy</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ryaninvents" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/strax"><img src="https://avatars2.githubusercontent.com/u/587213?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sami Kukkonen</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=strax" title="Code">💻</a></td>
<td align="center"><a href="https://ScottFreeCode.github.io/"><img src="https://avatars3.githubusercontent.com/u/16506071?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Scott Santucci</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ScottFreeCode" title="Code">💻</a></td>
<td align="center"><a href="http://stevenrescigno.com"><img src="https://avatars3.githubusercontent.com/u/8505293?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steven </b></sub></a><br /><a href="#example-stevenanthonyrevo" title="Examples">💡</a> <a href="https://github.com/harttle/liquidjs/commits?author=stevenanthonyrevo" title="Code">💻</a></td>
<td align="center"><a href="https://efcl.info/"><img src="https://avatars1.githubusercontent.com/u/19714?v=4?s=100" width="100px;" alt=""/><br /><sub><b>azu</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=azu" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/wyozi"><img src="https://avatars3.githubusercontent.com/u/4894573?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joonas</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=wyozi" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/jamelait"><img src="https://avatars1.githubusercontent.com/u/14369255?v=4" width="100px;" alt=""/><br /><sub><b>Jamel A.</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jamelait" title="Code">💻</a></td>
<td align="center"><a href="https://brandonpittman.net"><img src="https://avatars0.githubusercontent.com/u/967145?v=4" width="100px;" alt=""/><br /><sub><b>Brandon Pittman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=brandonpittman" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tgrandgent"><img src="https://avatars3.githubusercontent.com/u/17069042?v=4" width="100px;" alt=""/><br /><sub><b>tgrandgent</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tgrandgent" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mastodon0"><img src="https://avatars1.githubusercontent.com/u/7924332?v=4" width="100px;" alt=""/><br /><sub><b>Martin Schuster</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=mastodon0" title="Code">💻</a></td>
<td align="center"><a href="http://js.chenlei.me"><img src="https://avatars0.githubusercontent.com/u/6339390?v=4" width="100px;" alt=""/><br /><sub><b>Ray</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=richardo2016" title="Tests">⚠️</a> <a href="https://github.com/harttle/liquidjs/commits?author=richardo2016" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/CriGoT"><img src="https://avatars0.githubusercontent.com/u/1936786?v=4" width="100px;" alt=""/><br /><sub><b>Cristofer Gonzales</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=CriGoT" title="Code">💻</a></td>
<td align="center"><a href="https://www.raymondcamden.com"><img src="https://avatars3.githubusercontent.com/u/393660?v=4" width="100px;" alt=""/><br /><sub><b>Raymond Camden</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=cfjedimaster" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/jamelait"><img src="https://avatars1.githubusercontent.com/u/14369255?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jamel A.</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=jamelait" title="Code">💻</a></td>
<td align="center"><a href="https://brandonpittman.net"><img src="https://avatars0.githubusercontent.com/u/967145?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brandon Pittman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=brandonpittman" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tgrandgent"><img src="https://avatars3.githubusercontent.com/u/17069042?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tgrandgent</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tgrandgent" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/mastodon0"><img src="https://avatars1.githubusercontent.com/u/7924332?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Martin Schuster</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=mastodon0" title="Code">💻</a></td>
<td align="center"><a href="http://js.chenlei.me"><img src="https://avatars0.githubusercontent.com/u/6339390?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ray</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=richardo2016" title="Tests">⚠️</a> <a href="https://github.com/harttle/liquidjs/commits?author=richardo2016" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/CriGoT"><img src="https://avatars0.githubusercontent.com/u/1936786?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristofer Gonzales</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=CriGoT" title="Code">💻</a></td>
<td align="center"><a href="https://www.raymondcamden.com"><img src="https://avatars3.githubusercontent.com/u/393660?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Raymond Camden</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=cfjedimaster" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://stedman.dev"><img src="https://avatars1.githubusercontent.com/u/183122?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steve Stedman</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=stedman" title="Documentation">📖</a></td>
<td align="center"><a href="https://ciccarello.me"><img src="https://avatars0.githubusercontent.com/u/11273838?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anthony Ciccarello</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=aciccarello" title="Documentation">📖</a></td>
<td align="center"><a href="https://twitter.com/IAmTrySound"><img src="https://avatars0.githubusercontent.com/u/5635476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bogdan Chadkin</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=TrySound" title="Code">💻</a></td>
<td align="center"><a href="https://hightouch.io"><img src="https://avatars0.githubusercontent.com/u/5959235?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tejas Manohar</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tejasmanohar" title="Code">💻</a></td>
<td align="center"><a href="http://about.me/peterdehaan"><img src="https://avatars2.githubusercontent.com/u/557895?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peter deHaan</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=pdehaan" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/amit777"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4?s=100" width="100px;" alt=""/><br /><sub><b>amit777</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=amit777" title="Code">💻</a> <a href="#financial-amit777" title="Financial">💵</a></td>
<td align="center"><a href="http://www.ifi.uzh.ch/en/ce/people/schuldenzucker.html"><img src="https://avatars3.githubusercontent.com/u/1100776?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Steffen Schuldenzucker</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=sschuldenzucker" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/Pixcell"><img src="https://avatars0.githubusercontent.com/u/4005291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pixcell</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=Pixcell" title="Code">💻</a></td>
<td align="center"><a href="https://jasonet.co"><img src="https://avatars.githubusercontent.com/u/10660468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jason Etcovitch</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=JasonEtco" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
[shopify/liquid]: https://shopify.github.io/liquid/
+13
View File
@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
Only the latest major version is supported with security updates. It can be changed if many people are relying on a specific version for some reason. If this is the case, welcome to file an issue.
## Reporting a Vulnerability
Please contact yangjvn@126.com to report a vulnerability.
- It will be fixed very soon (maybe within 1 week or couple of days) if that vulnerability obviously affects common use cases.
- Otherwise, it'll be scheduled with a priority just like requested features (which is lower than bugs).
- If it's declined, you'll receive an email (more oftenly there will be a discuss).
+1 -1
View File
@@ -23,4 +23,4 @@ for file in $(find docs/source/api -name "*.md"); do
fi
done
cp -r docs/source/api docs/source/zh-cn/api
cp -r docs/source/api docs/source/zh-cn/api
+9 -1
View File
@@ -9,8 +9,16 @@ npm run build
cp CNAME public/
cp ../CHANGELOG.md source/tutorials/changelog.md
sed -i \
-e 's/{%/{% raw %}{%{% endraw %}/g' \
-e 's/{{/{% raw %}{{{% endraw %}/g' \
-e '1 s/"/\&quot;/g' \
-e '1 s/</\&lt;/g' \
-e '1 s/>/\&gt;/g' \
source/tutorials/changelog.md
cp source/tutorials/changelog.md source/zh-cn/tutorials/changelog.md
sed -i '1i ---\ntitle: Changelog\nauto: true\n---\n' source/tutorials/changelog.md
cp ../CHANGELOG.md source/zh-cn/tutorials/changelog.md
sed -i '1i ---\ntitle: 更新日志\nauto: true\n---\n' source/zh-cn/tutorials/changelog.md
if [ "$HEXO_ALGOLIA_INDEXING_KEY" != "" ]; then
+1 -1
View File
@@ -13,7 +13,7 @@ for size in "${apple[@]}"; do
done
cp logo.png apple-touch-icon.png
convert logo.png \
\( +clone -alpha extract \
\( +clone -alpha extract \
-draw 'fill black polygon 0,0 0,80 80,0 fill white circle 80,80 80,0' \
\( +clone -flip \) -compose Multiply -composite \
\( +clone -flop \) -compose Multiply -composite \
+3 -3
View File
@@ -4,7 +4,7 @@ const isQuote = c => c === '"' || c === "'"
const isOperator = c => '!=<>'.includes(c)
const isNumber = c => c >= '0' && c <= '9'
const isCharacter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
const isVariable = c => '_-?'.includes(c) || isCharacter(c) || isNumber(c)
const isIdentifier = c => '_-?'.includes(c) || isCharacter(c) || isNumber(c)
const isBlank = c => c === '\n' || c === '\t' || c === ' ' || c === '\r' || c === '\v' || c === '\f'
const isInlineBlank = c => c === '\t' || c === ' ' || c === '\r'
const isSign = c => c === '-' || c === '+'
@@ -15,7 +15,7 @@ const types = []
for (let i = 0; i < 128; i++) {
const c = String.fromCharCode(i)
let n = 0
if (isVariable(c)) n |= 1
if (isIdentifier(c)) n |= 1
if (isOperator(c)) n |= 2
if (isBlank(c)) n |= 4
if (isQuote(c)) n |= 8
@@ -31,7 +31,7 @@ console.log(`
// This file is generated by bin/character-gen.js
// bitmask character types to boost performance
export const TYPES = [${types.join(', ')}]
export const VARIABLE = 1
export const IDENTIFIER = 1
export const OPERATOR = 2
export const BLANK = 4
export const QUOTE = 8
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<title>liquidjs for the browser</title>
<script src="node_modules/liquidjs/dist/liquid.min.js"></script>
<script src="node_modules/liquidjs/dist/liquid.browser.min.js"></script>
</head>
<body>
+1
View File
@@ -0,0 +1 @@
<small>{{title}}</small>
+7
View File
@@ -0,0 +1,7 @@
<html>
<head><meta name="title" content="{{title}}"></head>
<body>
{% block %}{% endblock %}
{% render "footer.liquid" %}
</body>
</html>
+3 -3
View File
@@ -2,7 +2,8 @@ const { Liquid } = require('liquidjs')
const engine = new Liquid({
root: __dirname,
extname: '.liquid'
extname: '.liquid',
globals: {title: 'LiquidJS Demo'}
})
engine.registerTag('header', {
@@ -17,8 +18,7 @@ engine.registerTag('header', {
})
const ctx = {
todos: ['fork and clone', 'make it better', 'make a pull request'],
title: 'Welcome to liquidjs!'
todos: ['fork and clone', 'make it better', 'make a pull request']
}
engine.renderFile('todolist', ctx)
+3 -3
View File
@@ -1,7 +1,7 @@
{%header content: "welcome to liquid" | capitalize%}
{% layout 'html.liquid' %}
{%header content: title | capitalize%}
<ul>
{% for todo in todos %}
<li>{{forloop.index}} - {{todo}}</li>
{% endfor %}
</ul>
</ul>
-2
View File
@@ -8,8 +8,6 @@ timezone: UTC
url: https://liquidjs.com
root: /
permalink: news/:year/:month/:day/:title/
archive_dir: news
code_dir: downloads/code
relative_link: true
new_post_name: :year-:month-:day-:title.md # File name of new posts
post_asset_folder: true
+9 -9
View File
@@ -2127,9 +2127,9 @@
}
},
"highlight.js": {
"version": "9.18.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.1.tgz",
"integrity": "sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg=="
"version": "9.18.5",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.18.5.tgz",
"integrity": "sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA=="
},
"html-entities": {
"version": "1.2.1",
@@ -2528,9 +2528,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash.assignin": {
"version": "4.2.0",
@@ -3060,9 +3060,9 @@
"integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE="
},
"prismjs": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz",
"integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==",
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz",
"integrity": "sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==",
"requires": {
"clipboard": "^2.0.0"
}
+2 -1
View File
@@ -27,6 +27,7 @@ filters:
at_most: at_most.html
capitalize: capitalize.html
ceil: ceil.html
compact: compact.html
date: date.html
default: default.html
divided_by: divided_by.html
@@ -135,4 +136,4 @@ api:
LiquidOptions: "../interfaces/liquid_options_.liquidoptions.html"
NormalizedFullOptions: "../interfaces/liquid_options_.normalizedfulloptions.html"
PlainObject: "../interfaces/context_scope_.plainobject.html"
TagImplOptions: "../interfaces/template_tag_tag_impl_options_.tagimploptions.html"
TagImplOptions: "../interfaces/template_tag_tag_impl_options_.tagimploptions.html"
+1 -1
View File
@@ -11,7 +11,7 @@ Categories | Filters
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, remove, remove_first, truncate, truncatewords
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
Array | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse
Date | date
[shopify/liquid]: https://github.com/Shopify/liquid
+30
View File
@@ -0,0 +1,30 @@
{
"short_name": "LiquidJS",
"name": "LiquidJS",
"icons": [
{
"src": "/icon/apple-touch-icon-57x57.png",
"type": "image/png",
"sizes": "57x57"
},
{
"src": "/icon/favicon-96x96.png",
"type": "image/png",
"sizes": "96x96"
},
{
"src": "/icon/favicon-196x196.png",
"type": "image/png",
"sizes": "196x196"
},
{
"src": "/icon/apple-touch-icon.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#0f83ce",
"background_color": "#0f83ce"
}
+18 -1
View File
@@ -8,7 +8,7 @@ Iteration tags run blocks of code repeatedly.
### for...in
Repeatedly executes a block of code. For a full list of attributes available within a `for` loop.
Repeatedly executes a block of code. For a full list of attributes available within a `for` loop, see [forloop](#forloop).
Input
```liquid
@@ -22,6 +22,23 @@ Output
hat shirt pants
```
For loops can iterate over arrays, hashes, and [ranges of integers](#range).
When iterating a hash, item[0] contains the key, and item[1] contains the value:
Input
```liquid
{% for item in hash %}
* {{ item[0] }}: {{ item[1] }}
{% endfor %}
```
Output
```text
* key1: value1
* key2: value2
```
### else
Specifies a fallback case for a `for` loop which will run if the loop has zero length.
+1 -1
View File
@@ -9,7 +9,7 @@ There're a dozen of tags supported by LiquidJS, with all tags in [shopify/liquid
Category | Purpose | Tags
--- | --- | ---
Iteration | iterate over a collection | for, cycle, tablerow
Control Flow | control the execution branch of template rendering | if, unless, elif, else, case, when
Control Flow | control the execution branch of template rendering | if, unless, elsif, else, case, when
Variable | define and alter variables | assign, increment, decrement
File | include another template or extend a layout template | render, include, layout
Language | temporarily disable LiquidJS syntax | raw, comment
+1 -1
View File
@@ -26,4 +26,4 @@ LiquidJS 9 has some fundamental improvements, including bugfixes, new features a
* LiquidJS no longer has a default export, use `import {Liquid} from 'liquidjs'` instead. The `window.Liquid` for the UMD bundle is also changed to `window.liquidjs.Liquid`;
* The duplicate static method `Liquid.evalValue` is removed, use the instance method `liquid.evalValue` instead;
* Shipped to Node.js 8, the CJS bundle (main entry in Node.js) nolonger supports Node.js &leq; 6. ESM (dist/liquid.esm.js) and UMD (dist/liquid.js, dist/liquid.min.js) bundles are not affected.
* Shipped to Node.js 8, the CJS bundle (main entry in Node.js) nolonger supports Node.js &leq; 6. ESM (dist/liquid.node.esm.js) and UMD (dist/liquid.browser.umd.js, dist/liquid.browser.min.js) bundles are not affected.
+8
View File
@@ -65,6 +65,12 @@ Before 2.0.1, <code>extname</code> is set to `.liquid` by default. To change tha
**globals** is used to define global variables available to all templates even in cases of [render tag][render]. See [3185][185] for details.
## jsTruthy
**jsTruthy** is used to use standard Javascript truthiness rather than the Shopify.
it defaults to false. For example, when set to true, a blank string would evaluate to false with jsTruthy. With Shopify's truthiness, a blank string is true.
## Trimming
**greedy**, **trimOutputLeft**, **trimOutputRight**, **trimTagLeft**, **trimTagRight** options are used to eliminate extra newlines and indents in templates arround Liquid Constructs. See [Whitespace Control][wc] for details.
@@ -83,6 +89,8 @@ Before 2.0.1, <code>extname</code> is set to `.liquid` by default. To change tha
**strictVariables** is used to assert variable existence. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause a render exception. Defaults to `false`.
**lenientIf** modifies the behavior of `strictVariables` to allow handling optional variables. If set to `true`, an undefined variable will *not* cause an exception in the following two situations: a) it is the condition to an `if`, `elsif`, or `unless` tag; b) it occurs right before a `default` filter. Irrelevant if `strictVariables` is not set. Defaults to `false`.
{% note info Non-existent Tags %}
Non-existent tags always throw errors during pasrsing and this behaviour can not be customized.
{% endnote %}
+4 -4
View File
@@ -42,12 +42,12 @@ Pre-built UMD bundles are also available:
```html
<!--for production-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>
<!--for development-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.js"></script>
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.umd.js"></script>
```
{% note info Working Demo %} Here's a living demo on jsFiddle: <a href="https://jsfiddle.net/x43eb0z6/" target="_blank">jsfiddle.net/x43eb0z6</a>, and the source code is also available in <a href="https://github.com/harttle/liquidjs/blob/master/demo/browser/" target="_blank">liquidjs/demo/browser/</a>.{% endnote %}
{% note info Working Demo %} Here's a living demo on jsFiddle: <a href="https://jsfiddle.net/pd4jhzLs/1/" target="_blank">jsfiddle.net/pd4jhzLs/1/</a>, and the source code is also available in <a href="https://github.com/harttle/liquidjs/blob/master/demo/browser/" target="_blank">liquidjs/demo/browser/</a>.{% endnote %}
{% note warn Compatibility %} You may need a <a href="https://github.com/taylorhakes/promise-polyfill" target="_blank">Promise polyfill</a> for legacy browsers like IE and Android UC, see <a href="http://caniuse.com/#feat=promises" target="_blank">caniuse statistics</a>. {% endnote %}
@@ -69,4 +69,4 @@ echo 'Hello, {{ name }}.' | npx liquidjs '{"name": "Snake"}'
A ReactJS demo is also added by [@stevenanthonyrevo](https://github.com/stevenanthonyrevo), see [liquidjs/demo/reactjs/](https://github.com/harttle/liquidjs/blob/master/demo/reactjs/).
[intro]: ./intro-to-liquid.html
[intro]: ./intro-to-liquid.html
+20 -1
View File
@@ -17,11 +17,30 @@ value | truthy | falsy
`string` | ✔️ |
`empty string` | ✔️ |
`0` | ✔️ |
`integer` | ✔️ |
`integer` | ✔️ |
`float` | ✔️ |
`array` | ✔️ |
`empty array` | ✔️ |
## Use JavaScript Truthy
Note that liquidjs use Shopify's truthiness by default. But it can be toggled to used standard JavaScript truthiness by setting the **jsTruthy** option to `true`.
value | truthy | falsy
--- | --- | ---
`true` | ✔️ |
`false` | | ✔️
`null` | | ✔️
`undefined` | | ✔️
`string` | ✔️ |
`empty string` | | ✔️
`0` | | ✔️
`integer` | ✔️ |
`float` | ✔️ |
`array` | ✔️ |
`empty array` | ✔️ |
[ruby]: https://shopify.github.io/liquid
[sl]: https://www.npmjs.com/package/liquidjs
[diff]: https://github.com/harttle/liquidjs#differences-and-limitations
+3 -3
View File
@@ -2,9 +2,9 @@
title: compact
---
Removes any `nil` values from an array.
从数组里移除任何 `null` `undefined` 值。
For this example, assume `site.pages` is an array of content pages for a website, and some of these pages have an attribute called `category` that specifies their content category. If we `map` those categories to an array, some of the array items might be `nil` if any pages do not have a `category` attribute.
假设 `site.pages` 是网页列表,有些网页包含 `category` 属性用来标明类别。如果把它们 `map` 到数组里,那么对于没有 `category` 属性的元素就会是 `undefined`
输入
```liquid
@@ -26,7 +26,7 @@ For this example, assume `site.pages` is an array of content pages for a website
- technology
```
By using `compact` when we create our `site_categories` array, we can remove all the `nil` values in the array.
使用 `compact` 创建 `site_categories` 数组,可以移除所有 `null` `undefined` 值。
输入
```liquid
+1 -1
View File
@@ -26,4 +26,4 @@ LiquidJS 9 有一些基础性的改进,包括一些缺陷修复、新特性、
* LiquidJS 不再有默认导出了,以后要使用 `import {Liquid} from 'liquidjs'` 语法。使用 UMD 包里的 `window.Liquid` 也需要改为 `window.liquidjs.Liquid`
* 移除了重复的静态方法 `Liquid.evalValue`,统一使用示例方法 `liquid.evalValue`
* 支持的最低目标平台为 Node.js 8,CJS 包(Node.js 下的主入口)不再支持 Node.js &leq; 6 了,ESMdist/liquid.esm.js)和 UMDdist/liquid.js, dist/liquid.min.js)包不受影响。
* 支持的最低目标平台为 Node.js 8,CJS 包(Node.js 下的主入口)不再支持 Node.js &leq; 6 了,ESMdist/liquid.browser.esm.js)和 UMDdist/liquid.browser.umd.js, dist/liquid.browser.min.js)包不受影响。
+6
View File
@@ -63,6 +63,12 @@ LiquidJS 把这个选项默认值设为 <code>true</code> 以兼容于 shopify/l
**globals** 用来定义对所有模板可见的全局变量。包括 [render tag][render] 引入的子模板,见 [3185][185]。
## jsTruthy
**jsTruthy** 用来使用 Javascript 的真值判断,默认为 `false` 使用 Shopify 方式。
例如,空字符串在 JavaScript 中为假(`jsTruthy``true` 时),在 Shopify 真值表中为真。
## 换行和缩进
**greedy**, **trimOutputLeft**, **trimOutputRight**, **trimTagLeft**, **trimTagRight** 选项用来移除 Liquid 语法周围的换行和缩进,详情请参考 [Whitespace Control][wc]。
+2 -2
View File
@@ -41,8 +41,8 @@ engine
LiquidJS 预先构建了 UMD Bundle,可以通过 jsDelivr CDN 来引用:
```html
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.min.js"></script> <!--生产环境-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.js"></script> <!--开发环境t-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script> <!--生产环境-->
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.umd.js"></script> <!--开发环境-->
```
{% note info 示例 %} 这里有一个 jsFiddle 上的在线例子:<a href="https://jsfiddle.net/x43eb0z6/" target="_blank">jsfiddle.net/x43eb0z6</a>,其源码也可以在 <a href="https://github.com/harttle/liquidjs/blob/master/demo/browser/" target="_blank">liquidjs/demo/browser/</a> 找到。{% endnote %}
@@ -8,7 +8,7 @@ title: 真和假
根据 [Shopify 的文档](https://shopify.github.io/liquid/basics/truthy-and-falsy/)Ruby 版本除了 `false``nil` 之外的所有值都是真,但 JavaScript 有完全不同的类型系统,比如我们有 `undefined` 类型,以及不区分 `integer``float`,因此有些不同:
value | truthy | falsy
| 真 | 假
--- | --- | ---
`true` | ✔️ |
`false` | | ✔️
@@ -22,6 +22,24 @@ value | truthy | falsy
`array` | ✔️ |
`empty array` | ✔️ |
## 使用 JavaScript 真值
liquidjs 默认使用 Shopify 的真值表,但可以通过设置 **jsTruthy** 选项为 `true` 来使用标准的 JavaScript 真值。
值 | 真 | 假
--- | --- | ---
`true` | ✔️ |
`false` | | ✔️
`null` | | ✔️
`undefined` | | ✔️
`string` | ✔️ |
`empty string` | | ✔️
`0` | | ✔️
`integer` | ✔️ |
`float` | ✔️ |
`array` | ✔️ |
`empty array` | ✔️ |
[ruby]: https://shopify.github.io/liquid
[sl]: https://www.npmjs.com/package/liquidjs
[diff]: https://github.com/harttle/liquidjs#differences-and-limitations
+3 -3
View File
@@ -28,9 +28,9 @@
<ul id="intro-cmd-wrap">
<li class="intro-code-item"><span class="hl">import</span> { Liquid } <span class="hl">from</span> 'liquidjs'</li>
<li class="intro-code-item"><span class="hl">const</span> engine = <span class="hl">new</span> Liquid()</li>
<li class="intro-code-item"><span class="hl">const</span> tpl = engine.parse(<span class="hl-str">'Welcome to &#x7b;&#x7b;v}}!'</span>))</li>
<li class="intro-code-item"><span class="hl">const</span> html = <span class="hl">await</span> engine.render(tpl, {v: "Liquid"})</li>
<li class="intro-code-item"><span class="hl-comment">// html === "Welcome to Liquid!"</span></li>
<li class="intro-code-item"><span class="hl">const</span> tpl = engine.parse(<span class="hl-str">'Welcome to &#x7b;&#x7b;v}}!'</span>)</li>
<li class="intro-code-item">engine.render(tpl, {v: "Liquid"}).then(console.log)</li>
<li class="intro-code-item"><span class="hl-comment">// Outputs "Welcome to Liquid!"</span></li>
</ul>
<div id="intro-get-started-wrap">
<a href="tutorials/setup.html" id="intro-get-started-link">{{ __('index.get_started') }}</a>
+1 -1
View File
@@ -1,5 +1,5 @@
{% if page.layout === 'playground' %}
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/src-min/ace.js"></script>
{% endif %}
+9
View File
@@ -30,6 +30,15 @@
<td align="center"><a href="https://github.com/mastodon0"><img src="https://avatars1.githubusercontent.com/u/7924332?v=4" width="100px;" alt="Martin Schuster"/></a></td>
<td align="center"><a href="http://js.chenlei.me"><img src="https://avatars0.githubusercontent.com/u/6339390?v=4" width="100px;" alt="Ray"/></a></td>
<td align="center"><a href="https://github.com/CriGoT"><img src="https://avatars0.githubusercontent.com/u/1936786?v=4" width="100px;" alt="Cristofer Gonzales"/></a></td>
<td align="center"><a href="https://www.raymondcamden.com"><img src="https://avatars3.githubusercontent.com/u/393660?v=4" width="100px;" alt="Raymond Camden"/></a></td>
<td align="center"><a href="https://stedman.dev"><img src="https://avatars1.githubusercontent.com/u/183122?v=4" width="100px;" alt="Steve Stedman"/></a></td>
<td align="center"><a href="https://ciccarello.me"><img src="https://avatars0.githubusercontent.com/u/11273838?v=4" width="100px;" alt="Anthony Ciccarello"/></a></td>
<td align="center"><a href="https://twitter.com/IAmTrySound"><img src="https://avatars0.githubusercontent.com/u/5635476?v=4" width="100px;" alt="Bogdan Chadkin"/></a></td>
<td align="center"><a href="https://hightouch.io"><img src="https://avatars0.githubusercontent.com/u/5959235?v=4" width="100px;" alt="Tejas Manohar"/></a></td>
<td align="center"><a href="http://about.me/peterdehaan"><img src="https://avatars2.githubusercontent.com/u/557895?v=4" width="100px;" alt="Peter deHaan"/></a></td>
<td align="center"><a href="https://github.com/amit777"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4" width="100px;" alt="amit777"/></a></td>
<td align="center"><a href="http://www.ifi.uzh.ch/en/ce/people/schuldenzucker.html"><img src="https://avatars3.githubusercontent.com/u/1100776?v=4" width="100px;" alt="Steffen Schuldenzucker"/></a></td>
<td align="center"><a href="https://github.com/Pixcell"><img src="https://avatars0.githubusercontent.com/u/4005291?v=4" width="100px;" alt="Pixcell"/></a></td>
</tr>
</table>
+1
View File
@@ -4,6 +4,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net/">
<link rel="manifest" href="/manifest.json">
<!-- Canonical links -->
<link rel="canonical" href="{{ url }}">
<!-- Alternative links -->
+2 -4
View File
@@ -50,10 +50,8 @@
background: color-link-hover
#banner-share
display: none
display: block
padding-bottom: 60px
@media mq-normal
display: block
#intro-news-list
background-color: darken(color-navy, 10%)
@@ -178,7 +176,7 @@
transition: 0.2s
color: color-link
&:hover
background: @border-color
background: lighten(color-link, 20%)
color: #fff
#sponsors-wrap, #contributors-wrap
+7 -1
View File
@@ -116,12 +116,18 @@ note-warn = hsl(0, 100%, 50%)
font-size: 2em
h2
font-size: 1.5em
border-bottom: 1px solid color-border
padding-bottom: 10px
margin-bottom: 15px
h3
font-size: 1.3em
h1, h2, h3, h4, h5, h6
line-height: 1em
font-weight: bold
margin: 1em 0
hr
border-bottom: none;
border-top: 1px dashed color-border;
a
color: color-link
text-decoration: none
@@ -206,7 +212,7 @@ note-warn = hsl(0, 100%, 50%)
background: #eee
.article-footer
margin: 30px 0 0
margin: 60px 0 0
border-top: 1px solid color-border
text-align: center
color: color-gray
+17 -2
View File
@@ -66,9 +66,8 @@
preview.renderer.setShowGutter(false);
preview.renderer.setPadding(20);
editor.setValue(sourceEl.textContent, 1);
editor.setValue(getInitialValue(), 1);
editor.on('change', update);
editor.focus();
update();
function createEditor(id, lang) {
@@ -83,8 +82,16 @@
return editor;
}
function getInitialValue() {
try {
return atou(location.hash.slice(1)) || sourceEl.textContent
} catch (e) {}
return sourceEl.textContent
}
async function update() {
const tpl = editor.getValue();
history.replaceState({}, '', '#' + utoa(tpl));
try {
const html = await engine.parseAndRender(tpl);
preview.setValue(html, 1);
@@ -93,6 +100,14 @@
throw err;
}
}
function atou(str) {
return decodeURIComponent(escape(atob(str)))
}
function utoa(str) {
return btoa(unescape(encodeURIComponent(str)))
}
}());
if ('serviceWorker' in navigator) {
View File
+5099 -473
View File
File diff suppressed because it is too large Load Diff
+13 -9
View File
@@ -1,13 +1,17 @@
{
"name": "liquidjs",
"version": "9.12.0",
"version": "9.23.1",
"description": "A simple, expressive, safe and Shopify compatible template engine in pure JavaScript.",
"main": "dist/liquid.cjs.js",
"es2015": "dist/liquid.esm.js",
"browser": "dist/liquid.js",
"main": "dist/liquid.node.cjs.js",
"module": "dist/liquid.node.esm.js",
"es2015": "dist/liquid.browser.esm.js",
"browser": {
"./dist/liquid.node.cjs.js": "./dist/liquid.browser.umd.js",
"./dist/liquid.node.esm.js": "./dist/liquid.browser.esm.js"
},
"types": "dist/liquid.d.ts",
"scripts": {
"lint": "eslint .",
"lint": "eslint '**/*.ts' .",
"check": "npm test && npm run lint",
"unit": "mocha \"test/unit/**/*.ts\"",
"integration": "mocha \"test/integration/**/*.ts\"",
@@ -16,7 +20,7 @@
"benchmark": "node --expose-gc benchmark/index",
"coverage": "nyc --reporter=html mocha \"test/{unit,integration}/**/*.ts\"",
"coverage-coveralls": "nyc mocha \"test/{unit,integration}/**/*.ts\" && nyc report --reporter=text-lcov | coveralls",
"build": "rollup -c rollup.config.ts && ls -lh dist",
"build": "rm -rf dist && rollup -c rollup.config.ts && ls -lh dist",
"build-docs": "bin/build-docs.sh",
"watch": "tsc --watch"
},
@@ -57,7 +61,6 @@
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@commitlint/travis-cli": "^8.2.0",
"@semantic-release/changelog": "^3.0.2",
"@semantic-release/commit-analyzer": "^6.1.0",
"@semantic-release/git": "^7.0.8",
@@ -88,7 +91,7 @@
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-standard": "^4.0.0",
"express": "^4.16.4",
"husky": "^3.0.9",
"husky": "^4.2.5",
"jsdom": "^13.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
@@ -97,7 +100,7 @@
"rollup-plugin-replace": "^2.1.0",
"rollup-plugin-typescript2": "^0.21.1",
"rollup-plugin-uglify": "^6.0.2",
"semantic-release": "^15.13.19",
"semantic-release": "^17.2.3",
"sinon": "^7.5.0",
"sinon-chai": "^3.3.0",
"supertest": "^3.4.2",
@@ -135,6 +138,7 @@
},
"husky": {
"hooks": {
"pre-commit": "npm run check",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
+37 -16
View File
@@ -15,9 +15,9 @@ const treeshake = {
}
const input = './src/liquid.ts'
const cjs = {
const nodeCjs = {
output: [{
file: 'dist/liquid.cjs.js',
file: 'dist/liquid.node.cjs.js',
format: 'cjs',
banner
}],
@@ -36,16 +36,37 @@ const cjs = {
input
}
const esm = {
const nodeEsm = {
output: [{
file: 'dist/liquid.esm.js',
file: 'dist/liquid.node.esm.js',
format: 'esm',
banner
}],
external: ['path', 'fs'],
plugins: [typescript({
tsconfigOverride: {
include: [ 'src' ],
exclude: [ 'test', 'benchmark' ],
compilerOptions: {
target: 'ES2017',
module: 'ES2015'
}
}
})],
treeshake,
input
}
const browserEsm = {
output: [{
file: 'dist/liquid.browser.esm.js',
format: 'esm',
banner
}],
external: ['path', 'fs'],
plugins: [
replace({
include: './src/liquid.ts',
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
}),
@@ -64,9 +85,9 @@ const esm = {
input
}
const umd = {
const browserUmd = {
output: [{
file: 'dist/liquid.js',
file: 'dist/liquid.browser.umd.js',
name: 'liquidjs',
format: 'umd',
sourcemap,
@@ -74,7 +95,7 @@ const umd = {
}],
plugins: [
replace({
include: './src/liquid.ts',
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
}),
@@ -93,16 +114,16 @@ const umd = {
input
}
const min = {
const browserMin = {
output: [{
file: 'dist/liquid.min.js',
file: 'dist/liquid.browser.min.js',
name: 'liquidjs',
format: 'umd',
sourcemap
}],
plugins: [
replace({
include: './src/liquid.ts',
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
}),
@@ -124,10 +145,10 @@ const min = {
const bundles = []
const env = process.env.BUNDLES || ''
if (env.includes('cjs')) bundles.push(cjs)
if (env.includes('esm')) bundles.push(esm)
if (env.includes('umd')) bundles.push(umd)
if (env.includes('min')) bundles.push(min)
if (bundles.length === 0) bundles.push(cjs, umd, min, esm)
if (env.includes('cjs')) bundles.push(nodeCjs)
if (env.includes('esm')) bundles.push(nodeEsm, browserEsm)
if (env.includes('umd')) bundles.push(browserUmd)
if (env.includes('min')) bundles.push(browserMin)
if (bundles.length === 0) bundles.push(nodeCjs, nodeEsm, browserEsm, browserUmd, browserMin)
export default bundles
+6 -2
View File
@@ -1,4 +1,4 @@
import { isArray, last as arrayLast } from '../../util/underscore'
import { isArray, isNil, last as arrayLast } from '../../util/underscore'
import { toArray } from '../../util/collection'
import { isTruthy } from '../../render/boolean'
import { FilterImpl } from '../../template/filter/filter-impl'
@@ -24,6 +24,10 @@ export function map<T1, T2> (this: FilterImpl, arr: Scope[], property: string) {
return toArray(arr).map(obj => this.context.getFromScope(obj, property.split('.')))
}
export function compact<T> (this: FilterImpl, arr: T[]) {
return toArray(arr).filter(x => !isNil(x))
}
export function concat<T1, T2> (v: T1[], arg: T2[] | T2): (T1 | T2)[] {
return toArray(v).concat(arg)
}
@@ -36,7 +40,7 @@ export function slice<T> (v: T[], begin: number, length = 1): T[] {
export function where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): T[] {
return toArray(arr).filter(obj => {
const value = this.context.getFromScope(obj, String(property).split('.'))
return expected === undefined ? isTruthy(value) : value === expected
return expected === undefined ? isTruthy(value, this.context) : value === expected
})
}
+10 -3
View File
@@ -1,14 +1,21 @@
import strftime from '../../util/strftime'
import strftime, { TimezoneDate } from '../../util/strftime'
import { isString, isNumber } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
export function date (v: string | Date, arg: string) {
export function date (this: FilterImpl, v: string | Date, arg: string) {
let date = v
if (v === 'now' || v === 'today') {
date = new Date()
} else if (isNumber(v)) {
date = new Date(v * 1000)
} else if (isString(v)) {
date = /^\d+$/.test(v) ? new Date(+v * 1000) : new Date(v)
if (/^\d+$/.test(v)) {
date = new Date(+v * 1000)
} else if (this.context.opts.preserveTimezones) {
date = new TimezoneDate(v)
} else {
date = new Date(v)
}
}
return isValidDate(date) ? strftime(date, arg) : v
}
+3 -2
View File
@@ -1,9 +1,10 @@
import { isFalsy } from '../../render/boolean'
import { isArray, isString, toValue } from '../../util/underscore'
import { FilterImpl } from '../../template/filter/filter-impl'
export function Default<T1, T2> (v: string | T1, arg: T2): string | T1 | T2 {
export function Default<T1, T2> (this: FilterImpl, v: string | T1, arg: T2): string | T1 | T2 {
if (isArray(v) || isString(v)) return v.length ? v : arg
return isFalsy(toValue(v)) ? arg : v
return isFalsy(toValue(v), this.context) ? arg : v
}
export function json (v: any) {
return JSON.stringify(v)
+5 -5
View File
@@ -29,11 +29,11 @@ export function upcase (str: string) {
}
export function remove (v: string, arg: string) {
return stringify(v).split(arg).join('')
return stringify(v).split(String(arg)).join('')
}
export function removeFirst (v: string, l: string) {
return stringify(v).replace(l, '')
return stringify(v).replace(String(l), '')
}
export function rstrip (str: string) {
@@ -41,7 +41,7 @@ export function rstrip (str: string) {
}
export function split (v: string, arg: string) {
return stringify(v).split(arg)
return stringify(v).split(String(arg))
}
export function strip (v: string) {
@@ -58,11 +58,11 @@ export function capitalize (str: string) {
}
export function replace (v: string, pattern: string, replacement: string) {
return stringify(v).split(pattern).join(replacement)
return stringify(v).split(String(pattern)).join(replacement)
}
export function replaceFirst (v: string, arg1: string, arg2: string) {
return stringify(v).replace(arg1, arg2)
return stringify(v).replace(String(arg1), arg2)
}
export function truncate (v: string, l = 50, o = '...') {
+2 -2
View File
@@ -2,8 +2,8 @@ import { Tokenizer, assert, TagImplOptions, TagToken, Context } from '../../type
export default {
parse: function (token: TagToken) {
const tokenizer = new Tokenizer(token.args)
this.key = tokenizer.readWord().content
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
this.key = tokenizer.readIdentifier().content
tokenizer.skipBlank()
assert(tokenizer.peek() === '=', () => `illegal token ${token.getText()}`)
tokenizer.advance()
+28 -14
View File
@@ -1,8 +1,9 @@
import BlockMode from '../../context/block-mode'
import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImplOptions, Emitter } from '../../types'
import { BlockDrop } from '../../drop/block-drop'
import { ParseStream, TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../../types'
export default {
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) {
const match = /\w+/.exec(token.args)
this.block = match ? match[0] : ''
this.tpls = [] as Template[]
@@ -14,18 +15,31 @@ export default {
})
stream.start()
},
render: function * (ctx: Context, emitter: Emitter) {
const blocks = ctx.getRegister('blocks')
const childDefined = blocks[this.block]
const r = this.liquid.renderer
const html = childDefined !== undefined
? childDefined
: yield r.renderTemplates(this.tpls, ctx)
if (ctx.getRegister('blockMode', BlockMode.OUTPUT) === BlockMode.STORE) {
blocks[this.block] = html
return
* render (this: TagImpl, ctx: Context, emitter: Emitter) {
const blockRender = this.getBlockRender(ctx)
yield this.emitHTML(ctx, emitter, blockRender)
},
getBlockRender (this: TagImpl, ctx: Context) {
const { liquid, tpls } = this
const extendedBlockRender = ctx.getRegister('blocks')[this.block]
const defaultBlockRender = function * (superBlock: BlockDrop) {
ctx.push({ block: superBlock })
const result = yield liquid.renderer.renderTemplates(tpls, ctx)
ctx.pop()
return result
}
return extendedBlockRender
? (superBlock: BlockDrop) => extendedBlockRender(new BlockDrop(() => defaultBlockRender(superBlock)))
: defaultBlockRender
},
* emitHTML (this: TagImpl, ctx: Context, emitter: Emitter, blockRender: (block: BlockDrop) => string) {
if (ctx.getRegister('blockMode', BlockMode.OUTPUT) === BlockMode.STORE) {
ctx.getRegister('blocks')[this.block] = blockRender
} else {
emitter.write(yield blockRender(new BlockDrop()))
}
emitter.write(html)
}
} as TagImplOptions
}
+10 -2
View File
@@ -1,9 +1,10 @@
import { Tokenizer, assert, Template, Context, TagImplOptions, TagToken, TopLevelToken } from '../../types'
import { evalQuotedToken } from '../../render/expression'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
const tokenizer = new Tokenizer(tagToken.args)
this.variable = tokenizer.readWord().content
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
this.variable = readVariableName(tokenizer)
assert(this.variable, () => `${tagToken.args} not valid identifier`)
this.templates = []
@@ -22,3 +23,10 @@ export default {
ctx.bottom()[this.variable] = html
}
} as TagImplOptions
function readVariableName (tokenizer: Tokenizer) {
const word = tokenizer.readIdentifier().content
if (word) return word
const quoted = tokenizer.readQuoted()
if (quoted) return evalQuotedToken(quoted)
}
+6 -7
View File
@@ -1,8 +1,8 @@
import { Expression, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
import { toValue, Value, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.cond = tagToken.args
this.cond = new Value(tagToken.args, this.liquid)
this.cases = []
this.elseTemplates = []
@@ -10,7 +10,7 @@ export default {
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
.on('tag:when', (token: TagToken) => {
this.cases.push({
val: token.args,
val: new Value(token.args, this.liquid),
templates: p = []
})
})
@@ -26,10 +26,9 @@ export default {
render: function * (ctx: Context, emitter: Emitter) {
const r = this.liquid.renderer
const cond = yield new Expression(this.cond).value(ctx)
for (let i = 0; i < this.cases.length; i++) {
const branch = this.cases[i]
const val = yield new Expression(branch.val).value(ctx)
const cond = toValue(yield this.cond.value(ctx, ctx.opts.lenientIf))
for (const branch of this.cases) {
const val = toValue(yield branch.val.value(ctx, ctx.opts.lenientIf))
if (val === cond) {
yield r.renderTemplates(branch.templates, ctx, emitter)
return
+1 -1
View File
@@ -4,7 +4,7 @@ import { Tokenizer } from '../../parser/tokenizer'
export default {
parse: function (tagToken: TagToken) {
const tokenizer = new Tokenizer(tagToken.args)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
const group = tokenizer.readValue()
tokenizer.skipBlank()
+2 -2
View File
@@ -3,8 +3,8 @@ import { isNumber, stringify } from '../../util/underscore'
export default {
parse: function (token: TagToken) {
const tokenizer = new Tokenizer(token.args)
this.variable = tokenizer.readWord().content
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
this.variable = tokenizer.readIdentifier().content
},
render: function (context: Context, emitter: Emitter) {
const scope = context.environments
+4 -4
View File
@@ -6,10 +6,10 @@ import { Hash } from '../../template/tag/hash'
export default {
type: 'block',
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
const toknenizer = new Tokenizer(token.args)
const toknenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
const variable = toknenizer.readWord()
const inStr = toknenizer.readWord()
const variable = toknenizer.readIdentifier()
const inStr = toknenizer.readIdentifier()
const collection = toknenizer.readValue()
assert(
variable.size() && inStr.content === 'in' && collection,
@@ -36,7 +36,7 @@ export default {
},
render: function * (ctx: Context, emitter: Emitter) {
const r = this.liquid.renderer
let collection = toEnumerable(evalToken(this.collection, ctx))
let collection = toEnumerable(yield evalToken(this.collection, ctx))
if (!collection.length) {
yield r.renderTemplates(this.elseTemplates, ctx, emitter)
+5 -5
View File
@@ -1,4 +1,4 @@
import { Emitter, isTruthy, Expression, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
import { Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../../types'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
@@ -8,12 +8,12 @@ export default {
let p
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
.on('start', () => this.branches.push({
cond: tagToken.args,
cond: new Value(tagToken.args, this.liquid),
templates: (p = [])
}))
.on('tag:elsif', (token: TagToken) => {
this.branches.push({
cond: token.args,
cond: new Value(token.args, this.liquid),
templates: p = []
})
})
@@ -31,8 +31,8 @@ export default {
const r = this.liquid.renderer
for (const branch of this.branches) {
const cond = yield new Expression(branch.cond).value(ctx)
if (isTruthy(cond)) {
const cond = yield branch.cond.value(ctx, ctx.opts.lenientIf)
if (isTruthy(cond, ctx)) {
yield r.renderTemplates(branch.templates, ctx, emitter)
return
}
+2 -2
View File
@@ -4,14 +4,14 @@ import BlockMode from '../../context/block-mode'
export default {
parse: function (token: TagToken) {
const args = token.args
const tokenizer = new Tokenizer(args)
const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie)
this.file = this.liquid.options.dynamicPartials
? tokenizer.readValue()
: tokenizer.readFileName()
assert(this.file, () => `illegal argument "${token.args}"`)
const begin = tokenizer.p
const withStr = tokenizer.readWord()
const withStr = tokenizer.readIdentifier()
if (withStr.content === 'with') {
tokenizer.skipBlank()
if (tokenizer.peek() !== ':') {
+2 -2
View File
@@ -3,8 +3,8 @@ import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../../typ
export default {
parse: function (token: TagToken) {
const tokenizer = new Tokenizer(token.args)
this.variable = tokenizer.readWord().content
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
this.variable = tokenizer.readIdentifier().content
},
render: function (context: Context, emitter: Emitter) {
const scope = context.environments
+17 -9
View File
@@ -3,7 +3,7 @@ import BlockMode from '../../context/block-mode'
export default {
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
const tokenizer = new Tokenizer(token.args)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
const file = this.liquid.options.dynamicPartials ? tokenizer.readValue() : tokenizer.readFileName()
assert(file, () => `illegal argument "${token.args}"`)
@@ -14,21 +14,29 @@ export default {
render: function * (ctx: Context, emitter: Emitter) {
const { liquid, hash, file } = this
const { renderer } = liquid
if (file.getText() === 'none') {
ctx.setRegister('blockMode', BlockMode.OUTPUT)
const html = yield renderer.renderTemplates(this.tpls, ctx)
emitter.write(html)
return
}
const filepath = ctx.opts.dynamicPartials
? (TypeGuards.isQuotedToken(file)
? yield renderer.renderTemplates(liquid.parse(evalQuotedToken(file)), ctx)
: evalToken(this.file, ctx))
: file.getText()
assert(filepath, () => `illegal filename "${file.getText()}":"${filepath}"`)
// render the remaining tokens immediately
ctx.setRegister('blockMode', BlockMode.STORE)
const blocks = ctx.getRegister('blocks')
const html = yield renderer.renderTemplates(this.tpls, ctx)
if (blocks[''] === undefined) blocks[''] = html
assert(filepath, () => `file "${file.getText()}"("${filepath}") not available`)
const templates = yield liquid._parseFile(filepath, ctx.opts, ctx.sync)
ctx.push(yield hash.render(ctx))
// render remaining contents and store rendered results
ctx.setRegister('blockMode', BlockMode.STORE)
const html = yield renderer.renderTemplates(this.tpls, ctx)
const blocks = ctx.getRegister('blocks')
if (blocks[''] === undefined) blocks[''] = () => html
ctx.setRegister('blockMode', BlockMode.OUTPUT)
// render the layout file use stored blocks
ctx.push(yield hash.render(ctx))
const partial = yield renderer.renderTemplates(templates, ctx)
ctx.pop()
emitter.write(partial)
+4 -4
View File
@@ -6,7 +6,7 @@ import { evalQuotedToken, TypeGuards, Tokenizer, evalToken, Hash, Emitter, TagTo
export default {
parse: function (token: TagToken) {
const args = token.args
const tokenizer = new Tokenizer(args)
const tokenizer = new Tokenizer(args, this.liquid.options.operatorsTrie)
this.file = this.liquid.options.dynamicPartials
? tokenizer.readValue()
: tokenizer.readFileName()
@@ -15,16 +15,16 @@ export default {
while (!tokenizer.end()) {
tokenizer.skipBlank()
const begin = tokenizer.p
const keyword = tokenizer.readWord()
const keyword = tokenizer.readIdentifier()
if (keyword.content === 'with' || keyword.content === 'for') {
tokenizer.skipBlank()
if (tokenizer.peek() !== ':') {
const value = tokenizer.readValue()
if (value) {
const beforeAs = tokenizer.p
const asStr = tokenizer.readWord()
const asStr = tokenizer.readIdentifier()
let alias
if (asStr.content === 'as') alias = tokenizer.readWord()
if (asStr.content === 'as') alias = tokenizer.readIdentifier()
else tokenizer.p = beforeAs
this[keyword.content] = { value, alias: alias && alias.content }
+4 -4
View File
@@ -5,12 +5,12 @@ import { Tokenizer } from '../../parser/tokenizer'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
const tokenizer = new Tokenizer(tagToken.args)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operatorsTrie)
this.variable = tokenizer.readWord()
this.variable = tokenizer.readIdentifier()
tokenizer.skipBlank()
const tmp = tokenizer.readWord()
const tmp = tokenizer.readIdentifier()
assert(tmp && tmp.content === 'in', () => `illegal tag: ${tagToken.getText()}`)
this.collection = tokenizer.readValue()
@@ -30,7 +30,7 @@ export default {
},
render: function * (ctx: Context, emitter: Emitter) {
let collection = toEnumerable(evalToken(this.collection, ctx))
let collection = toEnumerable(yield evalToken(this.collection, ctx))
const hash = yield this.hash.render(ctx)
const offset = hash.offset || 0
const limit = (hash.limit === undefined) ? collection.length : hash.limit
+25 -6
View File
@@ -1,14 +1,21 @@
import { TopLevelToken, Template, Emitter, Expression, isFalsy, ParseStream, Context, TagImplOptions, Token, TagToken } from '../../types'
import { Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, ParseStream, Context, TagImplOptions, TagToken } from '../../types'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.templates = []
this.branches = []
this.elseTemplates = []
let p
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
.on('start', () => {
p = this.templates
this.cond = tagToken.args
this.cond = new Value(tagToken.args, this.liquid)
})
.on('tag:elsif', (token: TagToken) => {
this.branches.push({
cond: new Value(token.args, this.liquid),
templates: p = []
})
})
.on('tag:else', () => (p = this.elseTemplates))
.on('tag:endunless', () => stream.stop())
@@ -22,9 +29,21 @@ export default {
render: function * (ctx: Context, emitter: Emitter) {
const r = this.liquid.renderer
const cond = yield new Expression(this.cond).value(ctx)
yield (isFalsy(cond)
? r.renderTemplates(this.templates, ctx, emitter)
: r.renderTemplates(this.elseTemplates, ctx, emitter))
const cond = yield this.cond.value(ctx, ctx.opts.lenientIf)
if (isFalsy(cond, ctx)) {
yield r.renderTemplates(this.templates, ctx, emitter)
return
}
for (const branch of this.branches) {
const cond = yield branch.cond.value(ctx, ctx.opts.lenientIf)
if (isTruthy(cond, ctx)) {
yield r.renderTemplates(branch.templates, ctx, emitter)
return
}
}
yield r.renderTemplates(this.elseTemplates, ctx, emitter)
}
} as TagImplOptions
+3 -2
View File
@@ -3,6 +3,7 @@ import { __assign } from 'tslib'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { Scope } from './scope'
import { isArray, isNil, isString, isFunction, toLiquid } from '../util/underscore'
import { InternalUndefinedVariableError } from '../util/error'
export class Context {
private scopes: Scope[] = [{}]
@@ -42,7 +43,7 @@ export class Context {
return paths.reduce((scope, path) => {
scope = readProperty(scope, path)
if (isNil(scope) && this.opts.strictVariables) {
throw new TypeError(`undefined variable: ${path}`)
throw new InternalUndefinedVariableError(path)
}
return scope
}, scope)
@@ -69,8 +70,8 @@ export class Context {
export function readProperty (obj: Scope, key: string) {
if (isNil(obj)) return obj
obj = toLiquid(obj)
if (isFunction(obj[key])) return obj[key]()
if (obj instanceof Drop) {
if (isFunction(obj[key])) return obj[key]()
if (obj.hasOwnProperty(key)) return obj[key]
return obj.liquidMethodMissing(key)
}
+13
View File
@@ -0,0 +1,13 @@
import { Drop } from './drop'
export class BlockDrop extends Drop {
constructor (
// the block render from layout template
private superBlockRender: () => Iterable<string> = () => ''
) {
super()
}
public super () {
return this.superBlockRender()
}
}
+32 -1
View File
@@ -3,6 +3,9 @@ import { Template } from './template/template'
import { Cache } from './cache/cache'
import { LRU } from './cache/lru'
import { FS } from './fs/fs'
import * as fs from './fs/node'
import { defaultOperators, Operators } from './render/operator'
import { createTrie, Trie } from './util/operator-trie'
export interface LiquidOptions {
/** A directory or an array of directories from where to resolve layout and include templates, and the filename passed to `.renderFile()`. If it's an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
@@ -11,12 +14,16 @@ export interface LiquidOptions {
extname?: string;
/** Whether or not to cache resolved templates. Defaults to `false`. */
cache?: boolean | number | Cache<Template[]>;
/** Use Javascript Truthiness. Defaults to `false`. */
jsTruthy?: boolean;
/** If set, treat the `filepath` parameter in `{%include filepath %}` and `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */
dynamicPartials?: boolean;
/** Whether or not to assert filter existence. If set to `false`, undefined filters will be skipped. Otherwise, undefined filters will cause an exception. Defaults to `false`. */
strictFilters?: boolean;
/** Whether or not to assert variable existence. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */
strictVariables?: boolean;
/** Modifies the behavior of `strictVariables`. If set, a single undefined variable will *not* cause an exception in the context of the `if`/`elsif`/`unless` tag and the `default` filter. Instead, it will evaluate to `false` and `null`, respectively. Irrelevant if `strictVariables` is not set. Defaults to `false`. **/
lenientIf?: boolean;
/** Strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */
trimTagRight?: boolean;
/** Similar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
@@ -33,26 +40,36 @@ export interface LiquidOptions {
outputDelimiterLeft?: string;
/** The right delimiter for liquid outputs. **/
outputDelimiterRight?: string;
/** Whether input strings to date filter preserve the given timezone **/
preserveTimezones?: boolean;
/** Whether `trim*Left`/`trim*Right` is greedy. When set to `true`, all consecutive blank characters including `\n` will be trimed regardless of line breaks. Defaults to `true`. */
greedy?: boolean;
/** `fs` is used to override the default file-system module with a custom implementation. */
fs?: FS;
/** the global environment passed down to all partial templates, i.e. templates included by `include`, `layout` and `render` tags. */
globals?: object;
/** Whether or not to keep value type when writing the Output. Defaults to `false`. */
keepOutputType?: boolean;
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
operators?: Operators;
}
interface NormalizedOptions extends LiquidOptions {
root?: string[];
cache?: Cache<Template[]>;
operatorsTrie?: Trie;
}
export interface NormalizedFullOptions extends NormalizedOptions {
root: string[];
extname: string;
cache: undefined | Cache<Template[]>;
jsTruthy: boolean;
dynamicPartials: boolean;
fs: FS;
strictFilters: boolean;
strictVariables: boolean;
lenientIf: boolean;
trimTagRight: boolean;
trimTagLeft: boolean;
trimOutputRight: boolean;
@@ -61,15 +78,21 @@ export interface NormalizedFullOptions extends NormalizedOptions {
tagDelimiterRight: string;
outputDelimiterLeft: string;
outputDelimiterRight: string;
preserveTimezones: boolean;
greedy: boolean;
globals: object;
keepOutputType: boolean;
operators: Operators;
operatorsTrie: Trie;
}
export const defaultOptions: NormalizedFullOptions = {
root: ['.'],
cache: undefined,
extname: '',
fs: fs,
dynamicPartials: true,
jsTruthy: false,
trimTagRight: false,
trimTagLeft: false,
trimOutputRight: false,
@@ -79,9 +102,14 @@ export const defaultOptions: NormalizedFullOptions = {
tagDelimiterRight: '%}',
outputDelimiterLeft: '{{',
outputDelimiterRight: '}}',
preserveTimezones: false,
strictFilters: false,
strictVariables: false,
globals: {}
lenientIf: false,
globals: {},
keepOutputType: false,
operators: defaultOperators,
operatorsTrie: createTrie(defaultOperators)
}
export function normalize (options?: LiquidOptions): NormalizedOptions {
@@ -96,6 +124,9 @@ export function normalize (options?: LiquidOptions): NormalizedOptions {
else cache = options.cache ? new LRU<Template[]>(1024) : undefined
options.cache = cache
}
if (options.hasOwnProperty('operators')) {
(options as NormalizedOptions).operatorsTrie = createTrie(options.operators!)
}
return options as NormalizedOptions
}
+24 -27
View File
@@ -1,5 +1,4 @@
import { Context } from './context/context'
import * as fs from './fs/node'
import { forOwn, snakeCase } from './util/underscore'
import { Template } from './template/template'
import { Tokenizer } from './parser/tokenizer'
@@ -13,8 +12,8 @@ import { TagMap } from './template/tag/tag-map'
import { FilterMap } from './template/filter/filter-map'
import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
import { FilterImplOptions } from './template/filter/filter-impl-options'
import { FS } from './fs/fs'
import { toThenable, toValue } from './util/async'
import { toPromise, toValue } from './util/async'
import { Emitter } from './render/emitter'
export * from './types'
@@ -24,53 +23,52 @@ export class Liquid {
public parser: Parser
public filters: FilterMap
public tags: TagMap
private fs: FS
public constructor (opts: LiquidOptions = {}) {
this.options = applyDefault(normalize(opts))
this.parser = new Parser(this)
this.renderer = new Render()
this.fs = opts.fs || fs
this.filters = new FilterMap(this.options.strictFilters)
this.filters = new FilterMap(this.options.strictFilters, this)
this.tags = new TagMap()
forOwn(builtinTags, (conf: TagImplOptions, name: string) => this.registerTag(snakeCase(name), conf))
forOwn(builtinFilters, (handler: FilterImplOptions, name: string) => this.registerFilter(snakeCase(name), handler))
}
public parse (html: string, filepath?: string): Template[] {
const tokenizer = new Tokenizer(html, filepath)
const tokenizer = new Tokenizer(html, this.options.operatorsTrie, filepath)
const tokens = tokenizer.readTopLevelTokens(this.options)
return this.parser.parse(tokens)
}
public _render (tpl: Template[], scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator<string> {
public _render (tpl: Template[], scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator<any> {
const options = { ...this.options, ...normalize(opts) }
const ctx = new Context(scope, options, sync)
return this.renderer.renderTemplates(tpl, ctx)
const emitter = new Emitter(options.keepOutputType)
return this.renderer.renderTemplates(tpl, ctx, emitter)
}
public async render (tpl: Template[], scope?: object, opts?: LiquidOptions): Promise<string> {
return toThenable(this._render(tpl, scope, opts, false))
public async render (tpl: Template[], scope?: object, opts?: LiquidOptions): Promise<any> {
return toPromise(this._render(tpl, scope, opts, false))
}
public renderSync (tpl: Template[], scope?: object, opts?: LiquidOptions): string {
public renderSync (tpl: Template[], scope?: object, opts?: LiquidOptions): any {
return toValue(this._render(tpl, scope, opts, true))
}
public _parseAndRender (html: string, scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator<string> {
public _parseAndRender (html: string, scope?: object, opts?: LiquidOptions, sync?: boolean): IterableIterator<any> {
const tpl = this.parse(html)
return this._render(tpl, scope, opts, sync)
}
public async parseAndRender (html: string, scope?: object, opts?: LiquidOptions): Promise<string> {
return toThenable(this._parseAndRender(html, scope, opts, false))
public async parseAndRender (html: string, scope?: object, opts?: LiquidOptions): Promise<any> {
return toPromise(this._parseAndRender(html, scope, opts, false))
}
public parseAndRenderSync (html: string, scope?: object, opts?: LiquidOptions): string {
public parseAndRenderSync (html: string, scope?: object, opts?: LiquidOptions): any {
return toValue(this._parseAndRender(html, scope, opts, true))
}
public * _parseFile (file: string, opts?: LiquidOptions, sync?: boolean) {
const options = { ...this.options, ...normalize(opts) }
const paths = options.root.map(root => this.fs.resolve(root, file, options.extname))
if (this.fs.fallback !== undefined) {
const filepath = this.fs.fallback(file)
const paths = options.root.map(root => options.fs.resolve(root, file, options.extname))
if (options.fs.fallback !== undefined) {
const filepath = options.fs.fallback(file)
if (filepath !== undefined) paths.push(filepath)
}
@@ -80,15 +78,15 @@ export class Liquid {
const tpls = yield cache.read(filepath)
if (tpls) return tpls
}
if (!(sync ? this.fs.existsSync(filepath) : yield this.fs.exists(filepath))) continue
const tpl = this.parse(sync ? this.fs.readFileSync(filepath) : yield this.fs.readFile(filepath), filepath)
if (!(sync ? options.fs.existsSync(filepath) : yield options.fs.exists(filepath))) continue
const tpl = this.parse(sync ? options.fs.readFileSync(filepath) : yield options.fs.readFile(filepath), filepath)
if (cache) cache.write(filepath, tpl)
return tpl
}
throw this.lookupError(file, options.root)
}
public async parseFile (file: string, opts?: LiquidOptions): Promise<Template[]> {
return toThenable(this._parseFile(file, opts, false))
return toPromise(this._parseFile(file, opts, false))
}
public parseFileSync (file: string, opts?: LiquidOptions): Template[] {
return toValue(this._parseFile(file, opts, true))
@@ -98,17 +96,16 @@ export class Liquid {
return this.render(templates, ctx, opts)
}
public renderFileSync (file: string, ctx?: object, opts?: LiquidOptions) {
const options = normalize(opts)
const templates = this.parseFileSync(file, options)
const templates = this.parseFileSync(file, opts)
return this.renderSync(templates, ctx, opts)
}
public _evalValue (str: string, ctx: Context): IterableIterator<any> {
const value = new Value(str, this.filters)
return value.value(ctx)
const value = new Value(str, this)
return value.value(ctx, false)
}
public async evalValue (str: string, ctx: Context): Promise<any> {
return toThenable(this._evalValue(str, ctx))
return toPromise(this._evalValue(str, ctx))
}
public evalValueSync (str: string, ctx: Context): any {
return toValue(this._evalValue(str, ctx))
+4 -13
View File
@@ -1,16 +1,7 @@
import { VARIABLE } from '../util/character'
import { IDENTIFIER } from '../util/character'
import { Trie } from '../util/operator-trie'
const trie = {
a: { n: { d: { end: true, needBoundary: true } } },
o: { r: { end: true, needBoundary: true } },
c: { o: { n: { t: { a: { i: { n: { s: { end: true, needBoundary: true } } } } } } } },
'=': { '=': { end: true } },
'!': { '=': { end: true } },
'>': { end: true, '=': { end: true } },
'<': { end: true, '=': { end: true } }
}
export function matchOperator (str: string, begin: number, end = str.length) {
export function matchOperator (str: string, begin: number, trie: Trie, end = str.length) {
let node = trie
let i = begin
let info
@@ -19,6 +10,6 @@ export function matchOperator (str: string, begin: number, end = str.length) {
if (node['end']) info = node
}
if (!info) return -1
if (info['needBoundary'] && str.charCodeAt(i) & VARIABLE) return -1
if (info['needBoundary'] && str.charCodeAt(i) & IDENTIFIER) return -1
return i
}
-1
View File
@@ -47,4 +47,3 @@ export function parseStringLiteral (str: string): string {
}
return ret
}
+1 -1
View File
@@ -29,7 +29,7 @@ export default class Parser {
return new Tag(token, remainTokens, this.liquid)
}
if (isOutputToken(token)) {
return new Output(token as OutputToken, this.liquid.filters)
return new Output(token as OutputToken, this.liquid)
}
return new HTML(token)
} catch (e) {
+101 -34
View File
@@ -1,6 +1,6 @@
import { whiteSpaceCtrl } from './whitespace-ctrl'
import { NumberToken } from '../tokens/number-token'
import { WordToken } from '../tokens/word-token'
import { IdentifierToken } from '../tokens/identifier-token'
import { literalValues } from '../util/literal'
import { LiteralToken } from '../tokens/literal-token'
import { OperatorToken } from '../tokens/operator-token'
@@ -20,20 +20,29 @@ import { ValueToken } from '../tokens/value-token'
import { OutputToken } from '../tokens/output-token'
import { TokenizationError } from '../util/error'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { TYPES, QUOTE, BLANK, VARIABLE } from '../util/character'
import { TYPES, QUOTE, BLANK, IDENTIFIER } from '../util/character'
import { matchOperator } from './match-operator'
import { Trie } from '../util/operator-trie'
import { Expression } from '../render/expression'
export class Tokenizer {
p = 0
N: number
private rawBeginAt = -1
constructor (
private input: string,
private trie: Trie,
private file: string = ''
) {
this.N = input.length
}
* readExpression (): IterableIterator<Token> {
readExpression () {
return new Expression(this.readExpressionTokens())
}
* readExpressionTokens (): IterableIterator<Token> {
const operand = this.readValue()
if (!operand) return
@@ -52,7 +61,7 @@ export class Tokenizer {
}
readOperator (): OperatorToken | undefined {
this.skipBlank()
const end = matchOperator(this.input, this.p, this.p + 8)
const end = matchOperator(this.input, this.p, this.trie, this.p + 8)
if (end === -1) return
return new OperatorToken(this.input, this.p, (this.p = end), this.file)
}
@@ -70,7 +79,7 @@ export class Tokenizer {
assert(this.peek() === '|', () => `unexpected token at ${this.snapshot()}`)
this.p++
const begin = this.p
const name = this.readWord()
const name = this.readIdentifier()
if (!name.size()) return null
const args = []
this.skipBlank()
@@ -107,8 +116,9 @@ export class Tokenizer {
readTopLevelToken (options: NormalizedFullOptions): TopLevelToken {
const { tagDelimiterLeft, outputDelimiterLeft } = options
if (this.matchWord(tagDelimiterLeft)) return this.readTagToken(options)
if (this.matchWord(outputDelimiterLeft)) return this.readOutputToken(options)
if (this.rawBeginAt > -1) return this.readEndrawOrRawContent(options)
if (this.match(tagDelimiterLeft)) return this.readTagToken(options)
if (this.match(outputDelimiterLeft)) return this.readOutputToken(options)
return this.readHTMLToken(options)
}
@@ -116,46 +126,94 @@ export class Tokenizer {
const begin = this.p
while (this.p < this.N) {
const { tagDelimiterLeft, outputDelimiterLeft } = options
if (this.matchWord(tagDelimiterLeft)) break
if (this.matchWord(outputDelimiterLeft)) break
if (this.match(tagDelimiterLeft)) break
if (this.match(outputDelimiterLeft)) break
++this.p
}
return new HTMLToken(this.input, begin, this.p, this.file)
}
readTagToken (options: NormalizedFullOptions): TagToken {
readTagToken (options: NormalizedFullOptions = defaultOptions): TagToken {
const { file, input } = this
const { tagDelimiterRight } = options
const begin = this.p
if (this.readTo(tagDelimiterRight) === -1) {
this.mkError(`tag ${this.snapshot(begin)} not closed`, begin)
if (this.readToDelimiter(options.tagDelimiterRight) === -1) {
throw this.mkError(`tag ${this.snapshot(begin)} not closed`, begin)
}
return new TagToken(input, begin, this.p, options, file)
const token = new TagToken(input, begin, this.p, options, file)
if (token.name === 'raw') this.rawBeginAt = begin
return token
}
readOutputToken (options: NormalizedFullOptions): OutputToken {
readToDelimiter (delimiter: string) {
while (this.p < this.N) {
if ((this.peekType() & QUOTE)) {
this.readQuoted()
continue
}
++this.p
if (this.rmatch(delimiter)) return this.p
}
return -1
}
readOutputToken (options: NormalizedFullOptions = defaultOptions): OutputToken {
const { file, input } = this
const { outputDelimiterRight } = options
const begin = this.p
if (this.readTo(outputDelimiterRight) === -1) {
this.mkError(`output ${this.snapshot(begin)} not closed`, begin)
if (this.readToDelimiter(outputDelimiterRight) === -1) {
throw this.mkError(`output ${this.snapshot(begin)} not closed`, begin)
}
return new OutputToken(input, begin, this.p, options, file)
}
readEndrawOrRawContent (options: NormalizedFullOptions): HTMLToken | TagToken {
const { tagDelimiterLeft, tagDelimiterRight } = options
const begin = this.p
let leftPos = this.readTo(tagDelimiterLeft) - tagDelimiterLeft.length
while (this.p < this.N) {
if (this.readIdentifier().getText() !== 'endraw') {
leftPos = this.readTo(tagDelimiterLeft) - tagDelimiterLeft.length
continue
}
while (this.p <= this.N) {
if (this.rmatch(tagDelimiterRight)) {
const end = this.p
if (begin === leftPos) {
this.rawBeginAt = -1
return new TagToken(this.input, begin, end, options, this.file)
} else {
this.p = leftPos
return new HTMLToken(this.input, begin, leftPos, this.file)
}
}
if (this.rmatch(tagDelimiterLeft)) break
this.p++
}
}
throw this.mkError(`raw ${this.snapshot(this.rawBeginAt)} not closed`, begin)
}
mkError (msg: string, begin: number) {
throw new TokenizationError(msg, new WordToken(this.input, begin, this.N, this.file))
return new TokenizationError(msg, new IdentifierToken(this.input, begin, this.N, this.file))
}
snapshot (begin: number = this.p) {
return JSON.stringify(ellipsis(this.input.slice(begin), 16))
}
readWord (): WordToken { // rename to identifier
/**
* @deprecated
*/
readWord () {
console.warn('Tokenizer#readWord() will be removed, use #readIdentifier instead')
return this.readIdentifier()
}
readIdentifier (): IdentifierToken {
this.skipBlank()
const begin = this.p
while (this.peekType() & VARIABLE) ++this.p
return new WordToken(this.input, begin, this.p, this.file)
while (this.peekType() & IDENTIFIER) ++this.p
return new IdentifierToken(this.input, begin, this.p, this.file)
}
readHashes () {
@@ -171,7 +229,7 @@ export class Tokenizer {
this.skipBlank()
if (this.peek() === ',') ++this.p
const begin = this.p
const name = this.readWord()
const name = this.readIdentifier()
if (!name.size()) return
let value
@@ -198,7 +256,7 @@ export class Tokenizer {
readTo (end: string): number {
while (this.p < this.N) {
++this.p
if (this.reverseMatchWord(end)) return this.p
if (this.rmatch(end)) return this.p
}
return -1
}
@@ -207,21 +265,30 @@ export class Tokenizer {
const value = this.readQuoted() || this.readRange()
if (value) return value
const variable = this.readWord()
if (this.peek() === '[') {
this.p++
const prop = this.readQuoted()
if (!prop) return
if (this.peek() !== ']') return
this.p++
return new PropertyAccessToken(prop, [], this.p)
}
const variable = this.readIdentifier()
if (!variable.size()) return
let isNumber = variable.isNumber(true)
const props: (QuotedToken | WordToken)[] = []
const props: (QuotedToken | IdentifierToken)[] = []
while (true) {
if (this.peek() === '[') {
isNumber = false
this.p++
const prop = this.readValue() || new WordToken(this.input, this.p, this.p, this.file)
const prop = this.readValue() || new IdentifierToken(this.input, this.p, this.p, this.file)
this.readTo(']')
props.push(prop)
} else if (this.peek() === '.' && this.peek(1) !== '.') { // skip range syntax
this.p++
const prop = this.readWord()
const prop = this.readIdentifier()
if (!prop.size()) break
if (!prop.isNumber()) isNumber = false
props.push(prop)
@@ -230,7 +297,7 @@ export class Tokenizer {
if (!props.length && literalValues.hasOwnProperty(variable.content)) {
return new LiteralToken(this.input, variable.begin, variable.end, this.file)
}
if (isNumber) return new NumberToken(variable, props[0] as WordToken)
if (isNumber) return new NumberToken(variable, props[0] as IdentifierToken)
return new PropertyAccessToken(variable, props, this.p)
}
@@ -267,22 +334,22 @@ export class Tokenizer {
return new QuotedToken(this.input, begin, this.p, this.file)
}
readFileName (): WordToken {
readFileName (): IdentifierToken {
const begin = this.p
while (!(this.peekType() & BLANK) && this.peek() !== ',' && this.p < this.N) this.p++
return new WordToken(this.input, begin, this.p, this.file)
return new IdentifierToken(this.input, begin, this.p, this.file)
}
matchWord (word: string) {
match (word: string) {
for (let i = 0; i < word.length; i++) {
if (word[i] !== this.input[this.p + i]) return false
}
return true
}
reverseMatchWord (word: string) {
for (let i = 0; i < word.length; i++) {
if (word[word.length - 1 - i] !== this.input[this.p - 1 - i]) return false
rmatch (pattern: string) {
for (let i = 0; i < pattern.length; i++) {
if (pattern[pattern.length - 1 - i] !== this.input[this.p - 1 - i]) return false
}
return true
}
-1
View File
@@ -4,7 +4,6 @@ import { NormalizedFullOptions } from '../liquid-options'
import { TYPES, INLINE_BLANK, BLANK } from '../util/character'
export function whiteSpaceCtrl (tokens: Token[], options: NormalizedFullOptions) {
options = { greedy: true, ...options }
let inRaw = false
for (let i = 0; i < tokens.length; i++) {
+11 -4
View File
@@ -1,6 +1,13 @@
export function isTruthy (val: any): boolean {
return !isFalsy(val)
import { Context } from '../context/context'
export function isTruthy (val: any, ctx: Context): boolean {
return !isFalsy(val, ctx)
}
export function isFalsy (val: any): boolean {
return val === false || undefined === val || val === null
export function isFalsy (val: any, ctx: Context): boolean {
if (ctx.opts.jsTruthy) {
return !val
} else {
return val === false || undefined === val || val === null
}
}
+23 -3
View File
@@ -1,9 +1,29 @@
import { stringify, toValue } from '../util/underscore'
export class Emitter {
public html = '';
public html: any = '';
public break = false;
public continue = false;
private keepOutputType? = false;
public write (html: string) {
this.html += html
constructor (keepOutputType: boolean|undefined) {
this.keepOutputType = keepOutputType
}
public write (html: any) {
if (this.keepOutputType === true) {
html = toValue(html)
} else {
html = stringify(toValue(html))
}
// This will only preserve the type if the value is isolated.
// I.E:
// {{ my-port }} -> 42
// {{ my-host }}:{{ my-port }} -> 'host:42'
if (this.keepOutputType === true && typeof html !== 'string' && this.html === '') {
this.html = html
} else {
this.html = stringify(this.html) + stringify(html)
}
}
}
+32 -28
View File
@@ -1,4 +1,5 @@
import { QuotedToken } from '../tokens/quoted-token'
import { PropertyAccessToken } from '../tokens/property-access-token'
import { NumberToken } from '../tokens/number-token'
import { assert } from '../util/assert'
import { literalValues } from '../util/literal'
@@ -9,43 +10,35 @@ import { OperatorToken } from '../tokens/operator-token'
import { RangeToken } from '../tokens/range-token'
import { parseStringLiteral } from '../parser/parse-string-literal'
import { Context } from '../context/context'
import { range, toValue } from '../util/underscore'
import { Tokenizer } from '../parser/tokenizer'
import { operatorImpls } from '../render/operator'
import { range } from '../util/underscore'
import { Operators } from '../render/operator'
import { UndefinedVariableError } from '../util/error'
export class Expression {
private operands: any[] = []
private postfix: IterableIterator<Token>
private postfix: Token[]
public constructor (str: string) {
const tokenizer = new Tokenizer(str)
this.postfix = toPostfix(tokenizer.readExpression())
public constructor (tokens: IterableIterator<Token>) {
this.postfix = [...toPostfix(tokens)]
}
public evaluate (ctx: Context): any {
public * evaluate (ctx: Context, lenient: boolean): any {
assert(ctx, () => 'unable to evaluate: context not defined')
const operands: any[] = []
for (const token of this.postfix) {
if (TypeGuards.isOperatorToken(token)) {
const r = this.operands.pop()
const l = this.operands.pop()
const result = evalOperatorToken(token, l, r)
this.operands.push(result)
const r = yield operands.pop()
const l = yield operands.pop()
const result = evalOperatorToken(ctx.opts.operators, token, l, r, ctx)
operands.push(result)
} else {
this.operands.push(evalToken(token, ctx))
operands.push(yield evalToken(token, ctx, lenient && this.postfix.length === 1))
}
}
return this.operands[0]
}
public * value (ctx: Context) {
return toValue(this.evaluate(ctx))
return operands[0]
}
}
export function evalToken (token: Token | undefined, ctx: Context): any {
assert(ctx, () => 'unable to evaluate: context not defined')
if (TypeGuards.isPropertyAccessToken(token)) {
const variable = token.variable.getText()
const props: string[] = token.props.map(prop => evalToken(prop, ctx))
return ctx.get([variable, ...props])
}
export function evalToken (token: Token | undefined, ctx: Context, lenient = false): any {
if (TypeGuards.isPropertyAccessToken(token)) return evalPropertyAccessToken(token, ctx, lenient)
if (TypeGuards.isRangeToken(token)) return evalRangeToken(token, ctx)
if (TypeGuards.isLiteralToken(token)) return evalLiteralToken(token)
if (TypeGuards.isNumberToken(token)) return evalNumberToken(token)
@@ -53,6 +46,17 @@ export function evalToken (token: Token | undefined, ctx: Context): any {
if (TypeGuards.isQuotedToken(token)) return evalQuotedToken(token)
}
function evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean) {
const variable = token.getVariableAsText()
const props: string[] = token.props.map(prop => evalToken(prop, ctx, false))
try {
return ctx.get([variable, ...props])
} catch (e) {
if (lenient && e.name === 'InternalUndefinedVariableError') return null
throw (new UndefinedVariableError(e, token))
}
}
function evalNumberToken (token: NumberToken) {
const str = token.whole.content + '.' + (token.decimal ? token.decimal.content : '')
return Number(str)
@@ -62,9 +66,9 @@ export function evalQuotedToken (token: QuotedToken) {
return parseStringLiteral(token.getText())
}
function evalOperatorToken (token: OperatorToken, lhs: any, rhs: any) {
const impl = operatorImpls[token.operator]
return impl(lhs, rhs)
function evalOperatorToken (operators: Operators, token: OperatorToken, lhs: any, rhs: any, ctx: Context) {
const impl = operators[token.operator]
return impl(lhs, rhs, ctx)
}
function evalLiteralToken (token: LiteralToken) {
+8 -3
View File
@@ -1,8 +1,13 @@
import { isComparable } from '../drop/comparable'
import { Context } from '../context/context'
import { isFunction } from '../util/underscore'
import { isTruthy } from '../render/boolean'
export const operatorImpls: {[key: string]: (lhs: any, rhs: any) => boolean} = {
export interface Operators {
[key: string]: (lhs: any, rhs: any, ctx: Context) => boolean;
}
export const defaultOperators: Operators = {
'==': (l: any, r: any) => {
if (isComparable(l)) return l.equals(r)
if (isComparable(r)) return r.equals(l)
@@ -36,6 +41,6 @@ export const operatorImpls: {[key: string]: (lhs: any, rhs: any) => boolean} = {
'contains': (l: any, r: any) => {
return l && isFunction(l.indexOf) ? l.indexOf(r) > -1 : false
},
'and': (l: any, r: any) => isTruthy(l) && isTruthy(r),
'or': (l: any, r: any) => isTruthy(l) || isTruthy(r)
'and': (l: any, r: any, ctx: Context) => isTruthy(l, ctx) && isTruthy(r, ctx),
'or': (l: any, r: any, ctx: Context) => isTruthy(l, ctx) || isTruthy(r, ctx)
}
+4 -1
View File
@@ -4,7 +4,10 @@ import { Template } from '../template/template'
import { Emitter } from './emitter'
export class Render {
public * renderTemplates (templates: Template[], ctx: Context, emitter = new Emitter()): IterableIterator<string> {
public * renderTemplates (templates: Template[], ctx: Context, emitter?: Emitter): IterableIterator<any> {
if (!emitter) {
emitter = new Emitter(ctx.opts.keepOutputType)
}
for (const tpl of templates) {
try {
const html = yield tpl.render(ctx, emitter)
+2
View File
@@ -1,5 +1,7 @@
import { Context } from '../../context/context'
import { Liquid } from '../../liquid'
export interface FilterImpl {
context: Context;
liquid: Liquid;
}
+6 -2
View File
@@ -2,11 +2,15 @@ import { FilterImplOptions } from './filter-impl-options'
import { Filter } from './filter'
import { FilterArg } from '../../parser/filter-arg'
import { assert } from '../../util/assert'
import { Liquid } from '../../liquid'
export class FilterMap {
private impls: {[key: string]: FilterImplOptions} = {}
constructor (private readonly strictFilters: boolean) {}
constructor (
private readonly strictFilters: boolean,
private readonly liquid: Liquid
) {}
get (name: string) {
const impl = this.impls[name]
@@ -19,6 +23,6 @@ export class FilterMap {
}
create (name: string, args: FilterArg[]) {
return new Filter(name, this.get(name), args)
return new Filter(name, this.get(name), args, this.liquid)
}
}
+8 -5
View File
@@ -3,23 +3,26 @@ import { Context } from '../../context/context'
import { identify } from '../../util/underscore'
import { FilterImplOptions } from './filter-impl-options'
import { FilterArg, isKeyValuePair } from '../../parser/filter-arg'
import { Liquid } from '../../liquid'
export class Filter {
public name: string
public args: FilterArg[]
private impl: FilterImplOptions
private liquid: Liquid
public constructor (name: string, impl: FilterImplOptions, args: FilterArg[]) {
public constructor (name: string, impl: FilterImplOptions, args: FilterArg[], liquid: Liquid) {
this.name = name
this.impl = impl || identify
this.args = args
this.liquid = liquid
}
public * render (value: any, context: Context) {
public render (value: any, context: Context) {
const argv: any[] = []
for (const arg of this.args as FilterArg[]) {
if (isKeyValuePair(arg)) argv.push([arg[0], yield evalToken(arg[1], context)])
else argv.push(yield evalToken(arg, context))
if (isKeyValuePair(arg)) argv.push([arg[0], evalToken(arg[1], context)])
else argv.push(evalToken(arg, context))
}
return this.impl.apply({ context }, [value, ...argv])
return this.impl.apply({ context, liquid: this.liquid }, [value, ...argv])
}
}
+5 -6
View File
@@ -1,20 +1,19 @@
import { Value } from './value'
import { FilterMap } from './filter/filter-map'
import { stringify, toValue } from '../util/underscore'
import { TemplateImpl } from '../template/template-impl'
import { Template } from '../template/template'
import { Context } from '../context/context'
import { Emitter } from '../render/emitter'
import { OutputToken } from '../tokens/output-token'
import { Liquid } from '../liquid'
export class Output extends TemplateImpl<OutputToken> implements Template {
private value: Value
public constructor (token: OutputToken, filters: FilterMap) {
public constructor (token: OutputToken, liquid: Liquid) {
super(token)
this.value = new Value(token.content, filters)
this.value = new Value(token.content, liquid)
}
public * render (ctx: Context, emitter: Emitter) {
const val = yield this.value.value(ctx)
emitter.write(stringify(toValue(val)))
const val = yield this.value.value(ctx, false)
emitter.write(val)
}
}
+2 -2
View File
@@ -13,7 +13,7 @@ import { Tokenizer } from '../../parser/tokenizer'
export class Hash {
hash: { [key: string]: any } = {}
constructor (markup: string) {
const tokenizer = new Tokenizer(markup)
const tokenizer = new Tokenizer(markup, {})
for (const hash of tokenizer.readHashes()) {
this.hash[hash.name.content] = hash.value
}
@@ -21,7 +21,7 @@ export class Hash {
* render (ctx: Context) {
const hash = {}
for (const key of Object.keys(this.hash)) {
hash[key] = evalToken(this.hash[key], ctx)
hash[key] = yield evalToken(this.hash[key], ctx)
}
return hash
}
+1 -2
View File
@@ -1,13 +1,12 @@
import { isFunction } from '../../util/underscore'
import { Liquid } from '../../liquid'
import { TemplateImpl } from '../../template/template-impl'
import { Emitter, Hash, Context, TagImplOptions, TagToken, Template, TopLevelToken } from '../../types'
import { Emitter, Hash, Context, TagToken, Template, TopLevelToken } from '../../types'
import { TagImpl } from './tag-impl'
export class Tag extends TemplateImpl<TagToken> implements Template {
public name: string
private impl: TagImpl
private static impls: { [key: string]: TagImplOptions } = {}
public constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) {
super(token)
+11 -10
View File
@@ -1,24 +1,25 @@
import { evalToken } from '../render/expression'
import { Expression } from '../render/expression'
import { Tokenizer } from '../parser/tokenizer'
import { FilterMap } from '../template/filter/filter-map'
import { Filter } from './filter/filter'
import { Context } from '../context/context'
import { ValueToken } from '../tokens/value-token'
import { Liquid } from '../liquid'
export class Value {
public readonly filters: Filter[] = []
public readonly initial?: ValueToken
public readonly initial: Expression
/**
* @param str the value to be valuated, eg.: "foobar" | truncate: 3
*/
public constructor (str: string, private readonly filterMap: FilterMap) {
const tokenizer = new Tokenizer(str)
this.initial = tokenizer.readValue()
this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, this.filterMap.get(name), args))
public constructor (str: string, liquid: Liquid) {
const tokenizer = new Tokenizer(str, liquid.options.operatorsTrie)
this.initial = tokenizer.readExpression()
this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, liquid.filters.get(name), args, liquid))
}
public * value (ctx: Context) {
let val = yield evalToken(this.initial, ctx)
public * value (ctx: Context, lenient: boolean) {
lenient = lenient || (ctx.opts.lenientIf && this.filters.length > 0 && this.filters[0].name === 'default')
let val = yield this.initial.evaluate(ctx, lenient)
for (const filter of this.filters) {
val = yield filter.render(val, ctx)
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { ValueToken } from './value-token'
import { WordToken } from './word-token'
import { IdentifierToken } from './identifier-token'
import { TokenKind } from '../parser/token-kind'
export class HashToken extends Token {
@@ -8,7 +8,7 @@ export class HashToken extends Token {
public input: string,
public begin: number,
public end: number,
public name: WordToken,
public name: IdentifierToken,
public value?: ValueToken,
public file?: string
) {
@@ -2,8 +2,7 @@ import { Token } from './token'
import { NUMBER, TYPES, SIGN } from '../util/character'
import { TokenKind } from '../parser/token-kind'
// a word can be an identifier, a number, a keyword or a single-word-literal
export class WordToken extends Token {
export class IdentifierToken extends Token {
public content: string
constructor (
public input: string,
+3 -3
View File
@@ -1,11 +1,11 @@
import { Token } from './token'
import { WordToken } from './word-token'
import { IdentifierToken } from './identifier-token'
import { TokenKind } from '../parser/token-kind'
export class NumberToken extends Token {
constructor (
public whole: WordToken,
public decimal?: WordToken
public whole: IdentifierToken,
public decimal?: IdentifierToken
) {
super(TokenKind.Number, whole.input, whole.begin, decimal ? decimal.end : whole.end, whole.file)
}
+2 -1
View File
@@ -25,6 +25,7 @@ export class OperatorToken extends Token {
this.operator = this.getText()
}
getPrecedence () {
return precedence[this.getText()]
const key = this.getText()
return key in precedence ? precedence[key] : 1
}
}
+12 -3
View File
@@ -1,14 +1,23 @@
import { Token } from './token'
import { WordToken } from './word-token'
import { IdentifierToken } from './identifier-token'
import { QuotedToken } from './quoted-token'
import { TokenKind } from '../parser/token-kind'
import { parseStringLiteral } from '../parser/parse-string-literal'
export class PropertyAccessToken extends Token {
constructor (
public variable: WordToken,
public props: (WordToken | QuotedToken | PropertyAccessToken)[],
public variable: IdentifierToken | QuotedToken,
public props: (IdentifierToken | QuotedToken | PropertyAccessToken)[],
end: number
) {
super(TokenKind.PropertyAccess, variable.input, variable.begin, end, variable.file)
}
getVariableAsText () {
if (this.variable instanceof IdentifierToken) {
return this.variable.getText()
} else {
return parseStringLiteral(this.variable.getText())
}
}
}
+5 -7
View File
@@ -1,8 +1,8 @@
import { DelimitedToken } from './delimited-token'
import { BLANK, TYPES, VARIABLE } from '../util/character'
import { TokenizationError } from '../util/error'
import { NormalizedFullOptions } from '../liquid-options'
import { TokenKind } from '../parser/token-kind'
import { Tokenizer } from '../parser/tokenizer'
export class TagToken extends DelimitedToken {
public name: string
@@ -18,13 +18,11 @@ export class TagToken extends DelimitedToken {
const value = input.slice(begin + tagDelimiterLeft.length, end - tagDelimiterRight.length)
super(TokenKind.Tag, value, input, begin, end, trimTagLeft, trimTagRight, file)
let nameEnd = 0
while (TYPES[this.content.charCodeAt(nameEnd)] & VARIABLE) nameEnd++
this.name = this.content.slice(0, nameEnd)
const tokenizer = new Tokenizer(this.content, options.operatorsTrie)
this.name = tokenizer.readIdentifier().getText()
if (!this.name) throw new TokenizationError(`illegal tag syntax`, this)
let argsBegin = nameEnd
while (TYPES[this.content.charCodeAt(argsBegin)] & BLANK) argsBegin++
this.args = this.content.slice(argsBegin)
tokenizer.skipBlank()
this.args = tokenizer.remaining()
}
}
+6
View File
@@ -11,9 +11,15 @@ export { Context } from './context/context'
export { Template } from './template/template'
export { FilterImplOptions } from './template/filter/filter-impl-options'
export { TagImplOptions } from './template/tag/tag-impl-options'
export { TagImpl } from './template/tag/tag-impl'
export { ParseStream } from './parser/parse-stream'
export { Token } from './tokens/token'
export { TopLevelToken } from './tokens/toplevel-token'
export { Tokenizer } from './parser/tokenizer'
export { Hash } from './template/tag/hash'
export { Value } from './template/value'
export { evalToken, evalQuotedToken } from './render/expression'
export { toPromise, toThenable } from './util/async'
export { defaultOperators, Operators } from './render/operator'
export { createTrie, Trie } from './util/operator-trie'
export { toValue } from './util/underscore'
+16 -10
View File
@@ -7,7 +7,7 @@ interface Thenable {
catch (reject: resolver): Thenable;
}
function mkResolve (value: any) {
function createResolvedThenable (value: any): Thenable {
const ret = {
then: (resolve: resolver) => resolve(value),
catch: () => ret
@@ -15,7 +15,7 @@ function mkResolve (value: any) {
return ret
}
function mkReject (err: Error) {
function createRejectedThenable (err: Error): Thenable {
const ret = {
then: (resolve: resolver, reject?: resolver) => {
if (reject) return reject(err)
@@ -30,43 +30,49 @@ function isThenable (val: any): val is Thenable {
return val && isFunction(val.then)
}
function isCustomIterable (val: any): val is IterableIterator<any> {
function isAsyncIterator (val: any): val is IterableIterator<any> {
return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return)
}
// convert an async iterator to a thenable (Promise compatible)
export function toThenable (val: IterableIterator<any> | Thenable | any): Thenable {
if (isThenable(val)) return val
if (isCustomIterable(val)) return reduce()
return mkResolve(val)
if (isAsyncIterator(val)) return reduce()
return createResolvedThenable(val)
function reduce (prev?: any): Thenable {
let state
try {
state = (val as IterableIterator<any>).next(prev)
} catch (err) {
return mkReject(err)
return createRejectedThenable(err)
}
if (state.done) return mkResolve(state.value)
if (state.done) return createResolvedThenable(state.value)
return toThenable(state.value!).then(reduce, err => {
let state
try {
state = (val as IterableIterator<any>).throw!(err)
} catch (e) {
return mkReject(e)
return createRejectedThenable(e)
}
if (state.done) return mkResolve(state.value)
if (state.done) return createResolvedThenable(state.value)
return reduce(state.value)
})
}
}
export function toPromise (val: IterableIterator<any> | Thenable | any): Promise<any> {
return Promise.resolve(toThenable(val))
}
// get the value of async iterator in synchronous manner
export function toValue (val: IterableIterator<any> | Thenable | any) {
let ret: any
toThenable(val)
.then((x: any) => {
ret = x
return mkResolve(ret)
return createResolvedThenable(ret)
})
.catch((err: Error) => {
throw err
+1 -1
View File
@@ -3,7 +3,7 @@
// This file is generated by bin/character-gen.js
// bitmask character types to boost performance
export const TYPES = [0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 4, 4, 4, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 2, 8, 0, 0, 0, 0, 8, 0, 0, 0, 64, 0, 65, 0, 0, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 0, 0, 2, 2, 2, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
export const VARIABLE = 1
export const IDENTIFIER = 1
export const OPERATOR = 2
export const BLANK = 4
export const QUOTE = 8
+22 -1
View File
@@ -44,7 +44,28 @@ export class RenderError extends LiquidError {
super.update()
}
public static is (obj: any): obj is RenderError {
return obj instanceof RenderError
return obj.name === 'RenderError'
}
}
export class UndefinedVariableError extends LiquidError {
public constructor (err: Error, token: Token) {
super(err, token)
this.name = 'UndefinedVariableError'
this.message = err.message
super.update()
}
}
// only used internally; raised where we don't have token information,
// so it can't be an UndefinedVariableError.
export class InternalUndefinedVariableError extends Error {
variableName: string
public constructor (variableName: string) {
super(`undefined variable: ${variableName}`)
this.name = 'InternalUndefinedVariableError'
this.variableName = variableName
}
}
+27
View File
@@ -0,0 +1,27 @@
import { Operators } from '../render/operator'
export interface Trie {
[key: string]: any;
}
export function createTrie (operators: Operators): Trie {
const trie: Trie = {}
for (const [name, handler] of Object.entries(operators)) {
let node = trie
for (let i = 0; i < name.length; i++) {
const c = name[i]
node[c] = node[c] || {}
if (i === name.length - 1 && c !== '=') {
node[c].needBoundary = true
}
node = node[c]
}
node.handler = handler
node.end = true
}
return trie
}
+28 -1
View File
@@ -140,7 +140,12 @@ const formatCodes = {
};
(formatCodes as any).h = formatCodes.b
export default function (d: Date, formatStr: string) {
export default function (inputDate: Date, formatStr: string) {
let d = inputDate
if (d instanceof TimezoneDate) {
d = d.getDisplayDate()
}
let output = ''
let remaining = formatStr
let match
@@ -168,3 +173,25 @@ function format (d: Date, match: RegExpExecArray) {
if (flags['-']) padWidth = 0
return padStart(ret, padWidth, padChar)
}
export class TimezoneDate extends Date {
ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/;
inputTimezoneOffset = 0;
constructor (public dateString: string) {
super(dateString)
const m = dateString.match(this.ISO8601_TIMEZONE_PATTERN)
if (m && m[1] === 'Z') {
this.inputTimezoneOffset = this.getTimezoneOffset()
} else if (m && m[2] && m[3] && m[4]) {
const [, , sign, hours, minutes] = m
const delta = (sign === '+' ? 1 : -1) * (parseInt(hours, 10) * 60 + parseInt(minutes, 10))
this.inputTimezoneOffset = this.getTimezoneOffset() + delta
}
}
getDisplayDate (): Date {
return new Date((+this) + this.inputTimezoneOffset * 60 * 1000)
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { OperatorToken } from '../tokens/operator-token'
import { DelimitedToken } from '../tokens/delimited-token'
import { WordToken } from '../tokens/word-token'
import { IdentifierToken } from '../tokens/identifier-token'
import { TagToken } from '../tokens/tag-token'
import { HTMLToken } from '../tokens/html-token'
import { OutputToken } from '../tokens/output-token'
@@ -47,7 +47,7 @@ export function isPropertyAccessToken (val: any): val is PropertyAccessToken {
return getKind(val) === TokenKind.PropertyAccess
}
export function isWordToken (val: any): val is WordToken {
export function isWordToken (val: any): val is IdentifierToken {
return getKind(val) === TokenKind.Word
}
+2 -2
View File
@@ -5,7 +5,7 @@ describe('#evalValueSync()', function () {
var engine: Liquid
beforeEach(() => { engine = new Liquid() })
it('should throw when scope undefined', async function () {
return expect(() => engine.evalValueSync('foo', null as any)).to.throw(/context not defined/)
it('should eval value syncly', async function () {
return expect(engine.evalValueSync('true', { opts: {} } as any)).to.equal(true)
})
})
+3 -2
View File
@@ -5,7 +5,8 @@ describe('#evalValue()', function () {
var engine: Liquid
beforeEach(() => { engine = new Liquid() })
it('should throw when scope undefined', async function () {
return expect(engine.evalValue('"foo"', null as any)).to.be.rejectedWith(/context not defined/)
it('should eval value', async function () {
const val = await engine.evalValue('true', { opts: {} } as any)
expect(val).to.equal(true)
})
})
+77
View File
@@ -1,4 +1,5 @@
import { Liquid } from '../..'
import { Liquid as LiquidUMD } from '../../dist/liquid.browser.umd.js'
import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised'
@@ -10,4 +11,80 @@ describe('Issues', function () {
const html = engine.parseAndRenderSync('{{huh | truncate: 11}}', { huh: 'fdsafdsafdsafdsaaaaa' })
expect(html).to.equal('fdsafdsa...')
})
it('#252 "Not valid identifier" error for a quotes-containing identifier', async () => {
const template = `{% capture "form_classes" -%}
foo
{%- endcapture %}{{form_classes}}`
const engine = new Liquid()
const html = await engine.parseAndRender(template)
expect(html).to.equal('foo')
})
it('#259 complex property access with braces is not supported', async () => {
const engine = new Liquid()
const html = engine.parseAndRenderSync('{{ ["complex key"] }}', { 'complex key': 'foo' })
expect(html).to.equal('foo')
})
it('#243 Potential for ReDoS through string replace function', async () => {
const engine = new Liquid()
const INPUT = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
const BROKEN_REGEX = /([a-z]+)+$/
// string filters vulnerable to regexp parameter: split, replace, replace_first, remove_first
const parameters = { input: INPUT, regex: BROKEN_REGEX }
const template = `{{ input | replace:regex,'' }}`
const html = engine.parseAndRenderSync(template, parameters)
// should stringify the regexp rather than execute it
expect(html).to.equal(INPUT)
})
it('#263 raw/endraw block not ignoring {% characters', () => {
const template = `{% raw %}This is a code snippet showing how {% breaks the raw block.{% endraw %}`
const engine = new Liquid()
const html = engine.parseAndRenderSync(template)
expect(html).to.equal('This is a code snippet showing how {% breaks the raw block.')
})
it('#268 elsif is not supported for unless', () => {
const template = `{%- unless condition1 -%}
<div>X</div>
{%- elsif condition2 -%}
<div>Y</div>
{%- else %}
<div>Z</div>
{% endunless %}`
const engine = new Liquid()
const html = engine.parseAndRenderSync(template, { condition1: true, condition2: true })
expect(html).to.equal('<div>Y</div>')
})
it('#277 Passing liquid in FilterImpl', () => {
const engine = new Liquid()
engine.registerFilter('render', function (this: any, template: string, name: string) {
return this.liquid.parseAndRenderSync(decodeURIComponent(template), { name })
})
const html = engine.parseAndRenderSync(
`{{ subtemplate | render: "foo" }}`,
{ subtemplate: encodeURIComponent('hello {{ name }}') }
)
expect(html).to.equal('hello foo')
})
it('#288 Unexpected behavior when string literals contain }}', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`)
expect(html).to.equal('{{}}')
})
it('#222 Support function calls', async () => {
const engine = new Liquid()
const html = await engine.parseAndRender(
`{{ obj.property }}`,
{ obj: { property: () => 'BAR' } }
)
expect(html).to.equal('BAR')
})
it('#313 lenientIf not working as expected in umd', async () => {
const engine = new LiquidUMD({
strictVariables: true,
lenientIf: true
})
const html = await engine.parseAndRender(`{{ name | default: "default name" }}`)
expect(html).to.equal('default name')
})
})
+2 -2
View File
@@ -45,13 +45,13 @@ describe('.parseAndRender()', function () {
const result2 = await engine.render(template, ctx)
expect(result2).to.equal('1,2')
})
it('should render filters', async function () {
it('should support the "join" filter', async function () {
var ctx = { names: ['alice', 'bob'] }
var template = engine.parse('<p>{{names | join: ","}}</p>')
const html = await engine.render(template, ctx)
return expect(html).to.equal('<p>alice,bob</p>')
})
it('should render accessive filters', async function () {
it('should support the "first" filter', async function () {
var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}'
const html = await engine.parseAndRender(src)
+1 -1
View File
@@ -1,4 +1,4 @@
import { Liquid } from '../../dist/liquid.js'
import { Liquid } from '../../dist/liquid.browser.umd.js'
import * as sinon from 'sinon'
import { expect, use } from 'chai'
import { JSDOM } from 'jsdom'
@@ -50,6 +50,12 @@ describe('filters/array', function () {
return test(tpl, { arr: [a, b, c] }, 'Alice Bob Carol')
})
})
describe('compact', () => {
it('should compact array', function () {
const posts = [{ category: 'foo' }, { category: 'bar' }, { foo: 'bar' }]
return test('{{posts | map: "category" | compact}}', { posts }, 'foo,bar')
})
})
describe('reverse', function () {
it('should support reverse', () => test(
'{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}',
+19 -2
View File
@@ -1,3 +1,4 @@
import { LiquidOptions } from '../../../../src/liquid-options'
import { test } from '../../../stub/render'
describe('filters/date', function () {
@@ -11,8 +12,24 @@ describe('filters/date', function () {
it('should create a new Date when given "today"', function () {
return test('{{ "today" | date: "%Y"}}', (new Date()).getFullYear().toString())
})
it('should parse as Date when given UTC string', function () {
return test('{{ "1991-02-22T00:00:00" | date: "%Y"}}', '1991')
it('should parse as Date when given a timezoneless string', function () {
return test('{{ "1991-02-22T00:00:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1991-02-22T00:00:00')
})
describe('when preserveTimezones is enabled', function () {
const opts: LiquidOptions = { preserveTimezones: true }
it('should not change the timezone between input and output', function () {
return test('{{ "1990-12-31T23:00:00Z" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
})
it('should apply numeric timezone offset (0)', function () {
return test('{{ "1990-12-31T23:00:00+00:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
})
it('should apply numeric timezone offset (-1)', function () {
return test('{{ "1990-12-31T23:00:00-01:00" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
})
it('should apply numeric timezone offset (+2.30)', function () {
return test('{{ "1990-12-31T23:00:00+02:30" | date: "%Y-%m-%dT%H:%M:%S"}}', '1990-12-31T23:00:00', undefined, opts)
})
})
it('should render string as string if not valid', function () {
return test('{{ "foo" | date: "%Y"}}', 'foo')

Some files were not shown because too many files have changed in this diff Show More