mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
151ff4cbf3 | ||
|
|
2e66e8b02e | ||
|
|
24306722ec | ||
|
|
2f059f6a25 | ||
|
|
4e82da689e | ||
|
|
e37824fd8a | ||
|
|
81e11bb9c4 | ||
|
|
71d5d24813 | ||
|
|
20f559e545 | ||
|
|
dce75a1a22 | ||
|
|
93ec151374 | ||
|
|
29db1bb0e2 | ||
|
|
f42c217fc0 | ||
|
|
ec4a711e00 | ||
|
|
03893d585e | ||
|
|
3b97c38ee5 | ||
|
|
48a876db49 | ||
|
|
befc33c4eb | ||
|
|
8734e2e6ce | ||
|
|
18055c8855 | ||
|
|
6a7c280f04 | ||
|
|
bc87e19e3e | ||
|
|
75591cdbfc | ||
|
|
5901611af5 | ||
|
|
9c40da7369 | ||
|
|
5e7598ce45 | ||
|
|
42591a04d8 | ||
|
|
a3af44dc10 | ||
|
|
0e0dc2adf0 | ||
|
|
7b909c22a1 | ||
|
|
626f911503 | ||
|
|
de32259bb9 | ||
|
|
d70cd2a1ea | ||
|
|
26b217542b | ||
|
|
e3ecfe3adc | ||
|
|
c16c787861 | ||
|
|
13f5f95d01 | ||
|
|
c8db5e6259 | ||
|
|
9e511ecd14 | ||
|
|
45d95bb8d8 | ||
|
|
8d3eca6ee2 | ||
|
|
6a0ad102a8 | ||
|
|
b8e4b3337b | ||
|
|
cd92e77726 | ||
|
|
e84fbf32ad | ||
|
|
0266b27838 | ||
|
|
090c88eb92 | ||
|
|
e15803fae4 | ||
|
|
117673a725 | ||
|
|
ef43120d6e | ||
|
|
f9f595f0f4 | ||
|
|
2bbf50171b | ||
|
|
a492d8e23d | ||
|
|
c8afa39a01 | ||
|
|
d8f9091a12 | ||
|
|
ca18a579a1 | ||
|
|
6f2b24f7ca | ||
|
|
87d7dd0d37 | ||
|
|
768fb79e32 | ||
|
|
c920ebb282 | ||
|
|
9706e8b411 | ||
|
|
75698eefec | ||
|
|
dfd358c0a2 | ||
|
|
4c70946fa1 | ||
|
|
9d9c0c1ca7 | ||
|
|
a9d3a5a402 | ||
|
|
30dffb675e | ||
|
|
e67c3c6924 | ||
|
|
e2e410f334 | ||
|
|
27ce44238e | ||
|
|
abfaf8a042 | ||
|
|
18a807ea2e | ||
|
|
a6126c3bea | ||
|
|
a96d772963 | ||
|
|
732ef2605e | ||
|
|
72ee7b4c1e | ||
|
|
f807383f2a | ||
|
|
02bfed1c57 | ||
|
|
b11df79b91 | ||
|
|
8ee9bd7ba8 | ||
|
|
5b3f4195f0 | ||
|
|
236d971705 | ||
|
|
0cd4d87d46 | ||
|
|
1fdfd4a68a | ||
|
|
c1f73a9f03 | ||
|
|
d8f550e447 | ||
|
|
2e5ab98bfc | ||
|
|
0dbb7792d7 | ||
|
|
e10ce657dd | ||
|
|
3713fa08b2 | ||
|
|
c5002d8cad | ||
|
|
00b0a5708c | ||
|
|
f82ee8a18c | ||
|
|
632bb6b077 | ||
|
|
0b6ee9bca1 | ||
|
|
941dd668fe | ||
|
|
11ffd65503 | ||
|
|
ce0280ca9f | ||
|
|
1066ef0f76 | ||
|
|
7b38c28d52 | ||
|
|
97e7983bd8 | ||
|
|
6cf6ffae90 | ||
|
|
04873a4e9c | ||
|
|
1a08efd402 | ||
|
|
e36f3ff291 | ||
|
|
f3f017bd37 | ||
|
|
0d20a344b6 | ||
|
|
3acbdfc1b7 | ||
|
|
ed994bdbd5 | ||
|
|
27a4c7666b | ||
|
|
064ea99f49 | ||
|
|
71fb8a8b7c | ||
|
|
acdd121a61 |
+84
-2
@@ -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,
|
||||
|
||||
@@ -8,7 +8,6 @@ jobs:
|
||||
name: 'Linting'
|
||||
script:
|
||||
- npm run lint
|
||||
- commitlint-travis
|
||||
- name: 'Coverage'
|
||||
script: npm run coverage-coveralls
|
||||
- stage: release
|
||||
|
||||
+150
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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/"/\"/g' \
|
||||
-e '1 s/</\</g' \
|
||||
-e '1 s/>/\>/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
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<small>{{title}}</small>
|
||||
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head><meta name="title" content="{{title}}"></head>
|
||||
<body>
|
||||
{% block %}{% endblock %}
|
||||
{% render "footer.liquid" %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
Generated
+9
-9
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ≤ 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 ≤ 6. ESM (dist/liquid.node.esm.js) and UMD (dist/liquid.browser.umd.js, dist/liquid.browser.min.js) bundles are not affected.
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 ≤ 6 了,ESM(dist/liquid.esm.js)和 UMD(dist/liquid.js, dist/liquid.min.js)包不受影响。
|
||||
* 支持的最低目标平台为 Node.js 8,CJS 包(Node.js 下的主入口)不再支持 Node.js ≤ 6 了,ESM(dist/liquid.browser.esm.js)和 UMD(dist/liquid.browser.umd.js, dist/liquid.browser.min.js)包不受影响。
|
||||
@@ -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]。
|
||||
|
||||
@@ -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
|
||||
Vendored
+3
-3
@@ -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 {{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 {{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
@@ -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 %}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+17
-2
@@ -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) {
|
||||
|
||||
Generated
+5099
-473
File diff suppressed because it is too large
Load Diff
+13
-9
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() !== ':') {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -47,4 +47,3 @@ export function parseStringLiteral (str: string): string {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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,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)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Context } from '../../context/context'
|
||||
import { Liquid } from '../../liquid'
|
||||
|
||||
export interface FilterImpl {
|
||||
context: Context;
|
||||
liquid: Liquid;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
@@ -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: "" }}',
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user