mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-12 19:00:39 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7483613b23 | ||
|
|
7762dd9612 | ||
|
|
daa0b574fc | ||
|
|
a084183246 | ||
|
|
eadb6f3711 | ||
|
|
dba26f24e6 | ||
|
|
722da6a7ee | ||
|
|
82d7673554 | ||
|
|
64dd057552 | ||
|
|
45e3c2bb8e | ||
|
|
76345a64c3 | ||
|
|
9e69652655 | ||
|
|
defbb58e66 | ||
|
|
b7b92c63cf | ||
|
|
265534b7b7 | ||
|
|
84ea2c7488 | ||
|
|
64e0c876e9 | ||
|
|
4a8088d4e4 | ||
|
|
ad0930f152 | ||
|
|
998832c772 | ||
|
|
4ba224043b | ||
|
|
5ffc904f80 | ||
|
|
08646f75b0 | ||
|
|
3647305dcf | ||
|
|
cca0306f5b | ||
|
|
eeeacc4492 | ||
|
|
d44e1d727a | ||
|
|
f960904df4 | ||
|
|
b4acdb463a | ||
|
|
21d674e22b | ||
|
|
7bee9fc92d | ||
|
|
b69c3a3203 | ||
|
|
d5b9916b2f | ||
|
|
d0118402e4 | ||
|
|
31bc71d472 | ||
|
|
53a835ab54 | ||
|
|
75b3e15ca9 | ||
|
|
f34573ee21 | ||
|
|
875f71d5f2 | ||
|
|
407ac6d0a4 | ||
|
|
f9f35ebd6c | ||
|
|
88c9e96392 | ||
|
|
54b2adce12 | ||
|
|
542968e133 | ||
|
|
3b7351d9a0 | ||
|
|
e977669118 | ||
|
|
279458c670 | ||
|
|
37d12a14eb | ||
|
|
3cf01603e8 | ||
|
|
56c7992b76 | ||
|
|
5b6100d12b | ||
|
|
51f7e66f60 | ||
|
|
952ab6e73b | ||
|
|
4171b2e023 | ||
|
|
d20a043fbb | ||
|
|
3dbab238ae | ||
|
|
d7d00421e2 | ||
|
|
c33d8f6828 | ||
|
|
c13a16fdd6 | ||
|
|
4bd35de867 | ||
|
|
29f53304bc | ||
|
|
cbebb00ef1 | ||
|
|
64a9eb79c5 | ||
|
|
852c6ad453 | ||
|
|
f432aade6a | ||
|
|
aa49b4f117 | ||
|
|
fc9ebdb90f | ||
|
|
7ee87008c7 | ||
|
|
677e8511e6 | ||
|
|
b51b0dabca | ||
|
|
1cc7249a71 | ||
|
|
a626de632c | ||
|
|
83a8e2d310 | ||
|
|
c37b330751 | ||
|
|
c8de8bcde2 | ||
|
|
2015f683b8 | ||
|
|
d14f18520b | ||
|
|
c0524a83f3 | ||
|
|
dde7b3b389 | ||
|
|
84bbb3432f | ||
|
|
1c5b934250 | ||
|
|
a2caeb559b | ||
|
|
81e6861b73 | ||
|
|
9038d56ce7 | ||
|
|
bad72005fe | ||
|
|
4dffb27e95 | ||
|
|
f38ea63c14 | ||
|
|
57bdf17dea | ||
|
|
0040485188 | ||
|
|
42d799055a | ||
|
|
8a881ac69a | ||
|
|
7a2e036c79 | ||
|
|
a31e0c6275 | ||
|
|
7df4d5d62c | ||
|
|
22b7e55fc9 | ||
|
|
0238cf2c44 | ||
|
|
601f2e48fc | ||
|
|
8cb0137362 | ||
|
|
dee4303fad | ||
|
|
9ba352ef04 | ||
|
|
4793a83bd6 | ||
|
|
af4fe1b330 | ||
|
|
5f5c62a51e | ||
|
|
21f4d6bf08 | ||
|
|
ba54bec82f | ||
|
|
c2e1bc7055 | ||
|
|
2055258193 | ||
|
|
d65ea14b9f | ||
|
|
f1b4c4fe14 | ||
|
|
6af631b77b | ||
|
|
d504c8107e | ||
|
|
c0181aec31 | ||
|
|
22a7a85d50 | ||
|
|
044be8253d | ||
|
|
8a629980d9 | ||
|
|
5412c5a39f | ||
|
|
b7445f26d6 | ||
|
|
3213c6d23a | ||
|
|
829afc24a7 | ||
|
|
0049a56864 | ||
|
|
f8e0587542 | ||
|
|
c3c57a560b | ||
|
|
5378f2709b | ||
|
|
d6876bd9ec | ||
|
|
31c39561c9 | ||
|
|
d828c4021e | ||
|
|
4d4a2d04ec | ||
|
|
bc9d8f92af | ||
|
|
4ece4e208f | ||
|
|
4ad3d2a52b | ||
|
|
fa7cd8f4ff | ||
|
|
87360d5207 | ||
|
|
19891b2df0 | ||
|
|
5f634b0b5e | ||
|
|
9f597843c9 | ||
|
|
887b736f39 | ||
|
|
7a3689067b | ||
|
|
5b43520d78 | ||
|
|
ea38a9e1f5 | ||
|
|
fa5b731daf | ||
|
|
96829b5293 | ||
|
|
4cf7215a67 | ||
|
|
50006db26d | ||
|
|
2d81223d99 | ||
|
|
3038115c21 | ||
|
|
fdbf4b838a | ||
|
|
7ab0e96419 | ||
|
|
48484fe52f | ||
|
|
14d1956232 | ||
|
|
c3e7859cdd | ||
|
|
c3de3fbb19 | ||
|
|
b5cfd2d2c5 | ||
|
|
36f84e4b3c | ||
|
|
63b5658604 | ||
|
|
c79700ef1a | ||
|
|
6755462056 | ||
|
|
02806b2257 | ||
|
|
d0fb5ccece | ||
|
|
b7144f9b75 | ||
|
|
88fc05732f | ||
|
|
44b1aac096 | ||
|
|
42043b6944 | ||
|
|
61f146432d | ||
|
|
220b705b73 | ||
|
|
ef85bd9192 | ||
|
|
8e9e10fdb4 | ||
|
|
f668a5cc41 | ||
|
|
098f5164e7 | ||
|
|
432f878bb6 | ||
|
|
aa54dad36e | ||
|
|
c6ff1f7911 | ||
|
|
11355cbf09 | ||
|
|
7ccd0cb1e5 | ||
|
|
4326c223f3 | ||
|
|
78b6e35fbf | ||
|
|
e275368cb3 | ||
|
|
ca2b16a583 | ||
|
|
86d0529a5e | ||
|
|
400b93c88d | ||
|
|
22b41fd1a8 | ||
|
|
d7c7f32d4b | ||
|
|
925df9a9b5 | ||
|
|
7b696ed0d2 | ||
|
|
c55f0234eb | ||
|
|
eee5649a47 | ||
|
|
1048f4e0ab | ||
|
|
5c6d05092f | ||
|
|
900664a26d | ||
|
|
757a06ee83 | ||
|
|
9f6e9f2f3f | ||
|
|
0f9adfb067 | ||
|
|
dabde0e9e4 | ||
|
|
bd4e851b3c | ||
|
|
1d2aece687 | ||
|
|
ace271c7f0 | ||
|
|
4cbfa6d204 | ||
|
|
3153f1d4bb | ||
|
|
9cd4f3d904 | ||
|
|
8dd1b3ba99 | ||
|
|
11de73a7b1 | ||
|
|
0325eacddf | ||
|
|
a21fcf6f87 | ||
|
|
7bb2ee2213 | ||
|
|
b24582c2c4 | ||
|
|
49cb7a8c36 | ||
|
|
a4126e7f10 | ||
|
|
5196d6ad56 | ||
|
|
6082bf4757 | ||
|
|
8efb41a631 | ||
|
|
d348edac14 | ||
|
|
be00f1d385 | ||
|
|
4e132ca1c5 | ||
|
|
329aea6bd6 | ||
|
|
23e7424f9d | ||
|
|
8241625be4 | ||
|
|
d36ec17621 | ||
|
|
485424cc89 | ||
|
|
a239389044 | ||
|
|
e8fa8d32f5 | ||
|
|
502426f621 | ||
|
|
8c77fc5a3d | ||
|
|
be17d7443c | ||
|
|
118624ca7d | ||
|
|
d08ad6dc97 | ||
|
|
e22870f5d7 | ||
|
|
bd6706cf4d | ||
|
|
b71928ffd4 | ||
|
|
6f69cd774d | ||
|
|
1b662d374d | ||
|
|
6dd6ed176c | ||
|
|
21af8c4d7a | ||
|
|
942a3224a1 | ||
|
|
11259fae62 | ||
|
|
030037eedc | ||
|
|
6d59f34fc8 | ||
|
|
72597c52e5 | ||
|
|
7904f823a8 | ||
|
|
e993118e87 | ||
|
|
4ae63b2899 | ||
|
|
9f446ad024 | ||
|
|
6f82b914cb | ||
|
|
31397a4862 | ||
|
|
a2a076d038 | ||
|
|
3dc37bf980 | ||
|
|
4ee2bc964c | ||
|
|
ea2a5f5c3b | ||
|
|
52e2be68ab | ||
|
|
cbde48971a | ||
|
|
e91cc8c950 | ||
|
|
314aa6b467 | ||
|
|
14e53ed3a9 | ||
|
|
cf6d3e78fe | ||
|
|
fb52c864b1 | ||
|
|
9f82aec569 | ||
|
|
29fe8dcb4b | ||
|
|
2a16800305 | ||
|
|
154cf379a8 | ||
|
|
ed20fe6072 | ||
|
|
2632e3e97e | ||
|
|
82f84d96c1 | ||
|
|
1126dffd7d | ||
|
|
07ec997ffd | ||
|
|
79815784c1 | ||
|
|
2d793a63fe | ||
|
|
cc2b2ea5d1 | ||
|
|
8ff9eeb51d | ||
|
|
3bc7caa387 | ||
|
|
20044c4256 | ||
|
|
e72f295bd0 | ||
|
|
66774865f4 | ||
|
|
2ae6874397 | ||
|
|
b307f6ffd0 | ||
|
|
5b5db616e2 | ||
|
|
551a15f373 | ||
|
|
45e0fb21dc | ||
|
|
4fc977268f | ||
|
|
8231727428 | ||
|
|
0a068ac51c | ||
|
|
6f1d6f3d05 | ||
|
|
aa52a31e90 | ||
|
|
e4056a79ee | ||
|
|
f1d68409b1 | ||
|
|
7ad9ce95e9 | ||
|
|
ea7e334d87 | ||
|
|
4f123c1d6b | ||
|
|
0f4edc5b53 | ||
|
|
48bee428d5 | ||
|
|
2d1f05799f | ||
|
|
dbc18539a0 | ||
|
|
492f56cbfb | ||
|
|
4018ffe68b | ||
|
|
21eefe1d7b | ||
|
|
0c8148c12b | ||
|
|
7f336ded5c | ||
|
|
5ecca1b761 | ||
|
|
af284daf34 | ||
|
|
21a5ac7e08 | ||
|
|
7d22214cb5 | ||
|
|
d6ca12f3bd | ||
|
|
84f7a399c5 | ||
|
|
5b5dfd5c00 | ||
|
|
d032f7f4d4 | ||
|
|
044035b8ed | ||
|
|
564d9cd9f4 | ||
|
|
fbb1dc8a36 | ||
|
|
c99dfd69e7 | ||
|
|
4719544d49 |
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
demo
|
||||
coverage
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"env": {
|
||||
"mocha": true,
|
||||
"es6": true,
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"standard",
|
||||
"@typescript-eslint",
|
||||
"promise"
|
||||
],
|
||||
"rules": {
|
||||
"no-var": 2,
|
||||
"prefer-const": 2,
|
||||
"no-unused-vars": "off",
|
||||
"import/export": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": false }]
|
||||
}
|
||||
}
|
||||
+10
-34
@@ -1,40 +1,16 @@
|
||||
# Logs
|
||||
logs
|
||||
# logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
# cache
|
||||
.rpt2_cache
|
||||
coverage/
|
||||
.nyc_output/
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
# modules
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# vim
|
||||
# editors
|
||||
.*.swp
|
||||
.vscode
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
test/
|
||||
+17
-5
@@ -1,7 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4"
|
||||
before_script:
|
||||
- npm install -g mocha
|
||||
after_script:
|
||||
- NODE_ENV=test ./node_modules/.bin/istanbul cover --report lcovonly ./node_modules/mocha/bin/_mocha -- -R spec --recursive && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
|
||||
- "10"
|
||||
- "8"
|
||||
- "6"
|
||||
jobs:
|
||||
include:
|
||||
- stage: test
|
||||
name: 'Linting'
|
||||
script: npm run lint
|
||||
- name: 'Coverage'
|
||||
script: npm run coverage-coveralls
|
||||
- stage: release
|
||||
if: branch = master
|
||||
script: skip
|
||||
deploy:
|
||||
provider: script
|
||||
skip_cleanup: true
|
||||
script: npx semantic-release
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
# [8.1.0](https://github.com/harttle/liquidjs/compare/v8.0.3...v8.1.0) (2019-04-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* where filter, working on [#118](https://github.com/harttle/liquidjs/issues/118) ([daa0b57](https://github.com/harttle/liquidjs/commit/daa0b57))
|
||||
|
||||
## [8.0.3](https://github.com/harttle/liquidjs/compare/v8.0.2...v8.0.3) (2019-04-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* slice filter on negative `begin`, [#117](https://github.com/harttle/liquidjs/issues/117) ([eadb6f3](https://github.com/harttle/liquidjs/commit/eadb6f3))
|
||||
|
||||
## [8.0.2](https://github.com/harttle/liquidjs/compare/v8.0.1...v8.0.2) (2019-03-25)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* use polymophism instead duck test ([82d7673](https://github.com/harttle/liquidjs/commit/82d7673))
|
||||
|
||||
## [8.0.1](https://github.com/harttle/liquidjs/compare/v8.0.0...v8.0.1) (2019-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* incorrect scope when using assign with for, fixes [#115](https://github.com/harttle/liquidjs/issues/115) ([defbb58](https://github.com/harttle/liquidjs/commit/defbb58))
|
||||
|
||||
# [8.0.0](https://github.com/harttle/liquidjs/compare/v7.5.1...v8.0.0) (2019-03-10)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* use camelCase for JavaScript APIs ([64e0c87](https://github.com/harttle/liquidjs/commit/64e0c87))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* promise support for drops, working on [#65](https://github.com/harttle/liquidjs/issues/65) ([4a8088d](https://github.com/harttle/liquidjs/commit/4a8088d))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Options and method names in JavaScript API are now renamed to cammelCase, for a complete list see #109
|
||||
|
||||
## [7.5.1](https://github.com/harttle/liquidjs/compare/v7.5.0...v7.5.1) (2019-03-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* named params for filters, working on [#113](https://github.com/harttle/liquidjs/issues/113) ([5ffc904](https://github.com/harttle/liquidjs/commit/5ffc904))
|
||||
|
||||
# [7.5.0](https://github.com/harttle/liquidjs/compare/v7.4.0...v7.5.0) (2019-03-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* tablerowloop object ([3647305](https://github.com/harttle/liquidjs/commit/3647305))
|
||||
|
||||
# [7.4.0](https://github.com/harttle/liquidjs/compare/v7.3.1...v7.4.0) (2019-02-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* math filters now return number, resolves [#110](https://github.com/harttle/liquidjs/issues/110) ([b4acdb4](https://github.com/harttle/liquidjs/commit/b4acdb4))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* exported Drop interface for [#107](https://github.com/harttle/liquidjs/issues/107) ([7bee9fc](https://github.com/harttle/liquidjs/commit/7bee9fc)), closes [#109](https://github.com/harttle/liquidjs/issues/109)
|
||||
|
||||
## [7.3.1](https://github.com/harttle/liquidjs/compare/v7.3.0...v7.3.1) (2019-02-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **#108:** remove absolute path in emitted d.ts ([53a835a](https://github.com/harttle/liquidjs/commit/53a835a)), closes [#108](https://github.com/harttle/liquidjs/issues/108)
|
||||
|
||||
# [7.3.0](https://github.com/harttle/liquidjs/compare/v7.2.2...v7.3.0) (2019-02-24)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* nil/null/empty/blank literals, resolves [#102](https://github.com/harttle/liquidjs/issues/102) ([88c9e96](https://github.com/harttle/liquidjs/commit/88c9e96))
|
||||
|
||||
## [7.2.2](https://github.com/harttle/liquidjs/compare/v7.2.1...v7.2.2) (2019-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* filters break when argument contains [()|, fixes [#89](https://github.com/harttle/liquidjs/issues/89) ([e977669](https://github.com/harttle/liquidjs/commit/e977669))
|
||||
|
||||
## [7.2.1](https://github.com/harttle/liquidjs/compare/v7.2.0...v7.2.1) (2019-02-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default length for truncate and truncatewords ([56c7992](https://github.com/harttle/liquidjs/commit/56c7992))
|
||||
|
||||
# [7.2.0](https://github.com/harttle/liquidjs/compare/v7.1.0...v7.2.0) (2019-02-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* override output/tag delimiter, fixes [#54](https://github.com/harttle/liquidjs/issues/54) ([d20a043](https://github.com/harttle/liquidjs/commit/d20a043))
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* `trim_value_left` option renamed to `trim_output_left`, `trim_value_right` option renamed to `trim_output_right`
|
||||
|
||||
# [7.1.0](https://github.com/harttle/liquidjs/compare/v7.0.2...v7.1.0) (2019-02-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* throw an Error if delimiter not matched ([c33d8f6](https://github.com/harttle/liquidjs/commit/c33d8f6))
|
||||
|
||||
# [7.0.0](https://github.com/harttle/liquidjs/compare/v6.4.3...v7.0.0) (2019-02-14)
|
||||
|
||||
|
||||
### chore
|
||||
|
||||
* **TypeScript:** ship Liquid to class ([1cc7249](https://github.com/harttle/liquidjs/commit/1cc7249))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **TypeScript:** calling `Liquid()` without `new` now becomes invalid
|
||||
|
||||
## [6.4.3](https://github.com/harttle/liquidjs/compare/v6.4.2...v6.4.3) (2019-02-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* better index.d.ts and a demo ([2015f68](https://github.com/harttle/liquidjs/commit/2015f68)), closes [#98](https://github.com/harttle/liquidjs/issues/98)
|
||||
|
||||
## [6.4.2](https://github.com/harttle/liquidjs/compare/v6.4.1...v6.4.2) (2019-01-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **CI:** e2e not building cjs ([dde7b3b](https://github.com/harttle/liquidjs/commit/dde7b3b))
|
||||
|
||||
## [6.4.1](https://github.com/harttle/liquidjs/compare/v6.4.0...v6.4.1) (2019-01-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* regenerator undefined ([a2caeb5](https://github.com/harttle/liquidjs/commit/a2caeb5))
|
||||
@@ -1,135 +1,105 @@
|
||||
# shopify-liquid
|
||||
# liquidjs
|
||||
|
||||
[](https://www.npmjs.org/package/shopify-liquid)
|
||||
[](https://travis-ci.org/harttle/shopify-liquid)
|
||||
[](https://coveralls.io/github/harttle/shopify-liquid?branch=master)
|
||||
[](https://david-dm.org/harttle/shopify-liquid)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://www.npmjs.org/package/liquidjs)
|
||||
[](https://travis-ci.org/harttle/liquidjs)
|
||||
[](https://coveralls.io/github/harttle/liquidjs?branch=master)
|
||||
[](https://github.com/harttle/liquidjs/issues)
|
||||
[](https://github.com/harttle/liquidjs/graphs/contributors)
|
||||
[](https://david-dm.org/harttle/liquidjs)
|
||||
[](https://david-dm.org/harttle/liquidjs?type=dev)
|
||||
[](https://github.com/harttle/liquidjs/blob/master/LICENSE)
|
||||
[](http://github.com/harttle/liquidjs)
|
||||
[](https://github.com/harttle/liquidjs)
|
||||
|
||||
A feature-rich Liquid template engine for Node.js and browsers,
|
||||
with compliance with [the Ruby version][shopify-liquid].
|
||||
New to Liquid? Here's a live demo: <http://harttle.com/shopify-liquid/>
|
||||
A [shopify][shopify/liquid] compatible [Liquid][tutorial] template engine in pure JavaScript.
|
||||
|
||||
> The Liquid template engine is implemented in Ruby originally,
|
||||
> which is used by [Jekyll][jekyll] and [Github Pages][gh].
|
||||
|
||||
Features:
|
||||
|
||||
* Pretty much [builtin filters](https://github.com/harttle/shopify-liquid/wiki/Builtin-Filters) and [builtin tags](https://github.com/harttle/shopify-liquid/wiki/Builtin-Tags)
|
||||
* Async rendering, especially for tags
|
||||
* [any-promise][any-promise]
|
||||
|
||||
Installation:
|
||||
Install via npm:
|
||||
|
||||
```bash
|
||||
npm install --save shopify-liquid
|
||||
npm install --save liquidjs
|
||||
```
|
||||
|
||||
Or include the UMD build:
|
||||
|
||||
```html
|
||||
<script src="//unpkg.com/liquidjs/dist/liquid.min.js"></script> <!--for production-->
|
||||
<script src="//unpkg.com/liquidjs/dist/liquid.js"></script> <!--for development-->
|
||||
```
|
||||
|
||||
You may need a [Promise polyfill][pp] for Node.js < 4 and ES5 browsers like [IE and Android UC][caniuse-promises].
|
||||
|
||||
## TOC
|
||||
|
||||
* Get Started
|
||||
* [Render from String](#render-from-string)
|
||||
* [Render from File](#render-from-file)
|
||||
* [Use with Express.js](#use-with-expressjs)
|
||||
* [Include Partials](#include-partials)
|
||||
* [Layout Templates (Extends)](#layout-templates-extends)
|
||||
* Demos
|
||||
* Node.js: [/demo/node/](demo/node/)
|
||||
* Browser: <https://jsfiddle.net/6u40xbzs/>, [/demo/browser/](demo/browser/).
|
||||
* Express.js: [/demo/express/](demo/express/)
|
||||
* TypeScript: [/demo/typescript/](demo/typescript/)
|
||||
* React JS: [/demo/reactjs/](demo/reactjs/)
|
||||
* Advanced
|
||||
* [Options](#options)
|
||||
* [Register Filters](#register-filters), [Builtin Filters](https://github.com/harttle/liquidjs/wiki/Builtin-Filters)
|
||||
* [Register Tags](#register-tags), [Builtin Tags](https://github.com/harttle/liquidjs/wiki/Builtin-Tags)
|
||||
* [Operators](https://github.com/harttle/liquidjs/wiki/Operators)
|
||||
* [Whitespace Control](https://github.com/harttle/liquidjs/wiki/Whitespace-Control)
|
||||
* [Plugin API](#plugin-api)
|
||||
* [Differences With shopify/liquid](#differences-with-shopify%2fliquid)
|
||||
* [Contribute Guidelines](#contribute-guidelines)
|
||||
|
||||
## Render from String
|
||||
|
||||
Parse and Render:
|
||||
Just render a template string with a context:
|
||||
|
||||
```javascript
|
||||
var Liquid = require('shopify-liquid');
|
||||
var engine = Liquid();
|
||||
var Liquid = require('liquidjs');
|
||||
var engine = new Liquid();
|
||||
|
||||
engine.parseAndRender('{{name | capitalize}}', {name: 'alice'})
|
||||
.then(function(html){
|
||||
// html === 'Alice'
|
||||
});
|
||||
engine
|
||||
.parseAndRender('{{name | capitalize}}', {name: 'alice'})
|
||||
.then(console.log); // outputs 'Alice'
|
||||
```
|
||||
|
||||
Caching templates:
|
||||
Caching parsed templates:
|
||||
|
||||
```javascript
|
||||
var tpl = engine.parse('{{name | capitalize}}');
|
||||
engine.render(tpl, {name: 'alice'})
|
||||
.then(function(html){
|
||||
// html === 'Alice'
|
||||
});
|
||||
engine
|
||||
.render(tpl, {name: 'alice'})
|
||||
.then(console.log); // outputs 'Alice'
|
||||
```
|
||||
|
||||
## Render from File
|
||||
|
||||
```javascript
|
||||
var engine = Liquid({
|
||||
root: path.resolve(__dirname, 'views/'), // for layouts and includes
|
||||
extname: '.liquid',
|
||||
cache: false,
|
||||
strict_filters: false, // default: false
|
||||
strict_variables: false // default: false
|
||||
var engine = new Liquid({
|
||||
root: path.resolve(__dirname, 'views/'), // root for layouts/includes lookup
|
||||
extname: '.liquid' // used for layouts/includes, defaults ""
|
||||
});
|
||||
engine.renderFile("hello.liquid", {name: 'alice'})
|
||||
.then(function(html){
|
||||
// html === 'Alice'
|
||||
});
|
||||
// equivalent to:
|
||||
engine.renderFile("hello", {name: 'alice'})
|
||||
.then(function(html){
|
||||
// html === 'Alice'
|
||||
});
|
||||
engine
|
||||
.renderFile("hello", {name: 'alice'}) // will read and render `views/hello.liquid`
|
||||
.then(console.log) // outputs "Alice"
|
||||
```
|
||||
|
||||
* `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`.
|
||||
If an array, the files are looked up in the order they occur in the array.
|
||||
Defaults to `process.cwd()`
|
||||
|
||||
* `extname` is used to lookup the template file when filepath doesn't include an extension name. Defaults to `.liquid`
|
||||
|
||||
* `cache` indicates whether or not to cache resolved templates. Defaults to `false`.
|
||||
|
||||
* `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`.
|
||||
|
||||
* `strict_variables` is used to enable strict variable derivation.
|
||||
If set to `false`, undefined variables will be rendered as empty string.
|
||||
Otherwise, undefined variables will cause an exception. Defaults to `false`.
|
||||
|
||||
## Use with Express.js
|
||||
|
||||
```javascript
|
||||
// register liquid engine
|
||||
app.engine('liquid', engine.express({
|
||||
strict_variables: true, // Default: fasle
|
||||
strict_filters: true // Default: false
|
||||
}));
|
||||
app.engine('liquid', engine.express());
|
||||
app.set('views', './views'); // specify the views directory
|
||||
app.set('view engine', 'liquid'); // set to default
|
||||
```
|
||||
|
||||
> There's an Express demo [here](demo/express/).
|
||||
[`views`][express-views] in express.js will be included when looking up
|
||||
partials(includes and layouts).
|
||||
|
||||
When using with Express.js, partials(includes and layouts) will be looked up in
|
||||
both Liquid `root` and Express `views` directories.
|
||||
|
||||
## Use in Browser
|
||||
|
||||
[Download][releases] the dist files and import into your HTML.
|
||||
And `window.Liquid` is what you want. There's also a [demo](demo/browser/).
|
||||
|
||||
```html
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="dist/liquid.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var engine = window.Liquid();
|
||||
var src = '{{ name | capitalize}}';
|
||||
var ctx = {
|
||||
name: 'welcome to Shopify Liquid'
|
||||
};
|
||||
engine.parseAndRender(src, ctx)
|
||||
.then(function(html) {
|
||||
// html === Welcome to Shopify Liquid
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Note: In [IE and Android UC][caniuse-promises] browser, you need a Promise implementation
|
||||
registered to [any-promise][any-promise].
|
||||
|
||||
## Includes
|
||||
## Include Partials
|
||||
|
||||
```
|
||||
// file: color.liquid
|
||||
@@ -150,7 +120,7 @@ color: 'red' shape: 'circle'
|
||||
color: 'yellow' shape: 'square'
|
||||
```
|
||||
|
||||
## Layouts
|
||||
## Layout Templates (Extends)
|
||||
|
||||
```
|
||||
// file: default-layout.liquid
|
||||
@@ -174,16 +144,55 @@ Footer
|
||||
* It's possible to define multiple blocks.
|
||||
* block name is optional when there's only one block.
|
||||
|
||||
## Options
|
||||
|
||||
The full list of options for `Liquid()` is listed as following:
|
||||
|
||||
* `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`.
|
||||
If an array, the files are looked up in the order they occur in the array.
|
||||
Defaults to `["."]`
|
||||
|
||||
* `extname` is used to lookup the template file when filepath doesn't include an extension name. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`.
|
||||
|
||||
* `cache` indicates whether or not to cache resolved templates. Defaults to `false`.
|
||||
|
||||
* `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`.
|
||||
|
||||
* `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`.
|
||||
|
||||
* `strictVariables` is used to enable strict variable derivation.
|
||||
If set to `false`, undefined variables will be rendered as empty string.
|
||||
Otherwise, undefined variables will cause an exception. Defaults to `false`.
|
||||
|
||||
* `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`.
|
||||
|
||||
* `trimTagLeft` is similiar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.
|
||||
|
||||
* `trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`.
|
||||
|
||||
* `trimOutputLeft` is similiar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See [Whitespace Control][whitespace control] for details.
|
||||
|
||||
* `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags.
|
||||
|
||||
* `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs.
|
||||
|
||||
* `greedy` is used to specify 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`.
|
||||
|
||||
## Register Filters
|
||||
|
||||
```javascript
|
||||
// Usage: {{ name | uppper }}
|
||||
engine.registerFilter('upper', function(v){
|
||||
return v.toUpperCase();
|
||||
});
|
||||
engine.registerFilter('upper', v => v.toUpperCase())
|
||||
```
|
||||
|
||||
> See existing filter implementations: <https://github.com/harttle/shopify-liquid/blob/master/filters.js>
|
||||
Filter arguments will be passed to the registered filter function, for example:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ 1 | add: 2, 3 }}
|
||||
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
|
||||
```
|
||||
|
||||
See existing filter implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>
|
||||
|
||||
## Register Tags
|
||||
|
||||
@@ -193,27 +202,65 @@ engine.registerTag('upper', {
|
||||
parse: function(tagToken, remainTokens) {
|
||||
this.str = tagToken.args; // name
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
var str = Liquid.evalValue(this.str, scope); // 'alice'
|
||||
return Promise.resolve(str.toUpperCase()); // 'Alice'
|
||||
render: async function(scope, hash) {
|
||||
var str = await Liquid.evalValue(this.str, scope); // 'alice'
|
||||
return str.toUpperCase() // 'Alice'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> See existing tag implementations: <https://github.com/harttle/shopify-liquid/blob/master/tags/>
|
||||
* `parse`: Read tokens from `remainTokens` until your end token.
|
||||
* `render`: Combine scope data with your parsed tokens into HTML string.
|
||||
|
||||
## Contribution Guide
|
||||
See existing tag implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/tags>
|
||||
|
||||
1. Write a [test][test] to define the feature you want.
|
||||
2. Just initiate an issue, or optionally:
|
||||
3. Get your test pass and make a pull request.
|
||||
## Plugin API
|
||||
|
||||
A pack of tags or filters can be encapsulated into a **plugin**, which will be typically installed via npm.
|
||||
|
||||
```javascript
|
||||
engine.plugin(require('./some-plugin'));
|
||||
|
||||
// some-plugin.js
|
||||
module.exports = function (Liquid) {
|
||||
// here `this` refers to the engine instance
|
||||
// `Liquid` provides facilities to implement tags and filters
|
||||
this.registerFilter('foo', x => x);
|
||||
}
|
||||
```
|
||||
|
||||
Plugin List:
|
||||
|
||||
* To add your plugin, contact me or simply send a PR.
|
||||
|
||||
## Differences With shopify/liquid
|
||||
|
||||
Though being compatible with [Ruby Liquid](https://github.com/shopify/liquid) is one of our priorities, there're still certain differences. You may need some configuration to get it compatible in these senarios:
|
||||
|
||||
* Dynamic file locating (enabled by default), that means layout/partial names are treated as variables in liquidjs. See [#51](https://github.com/harttle/liquidjs/issues/51).
|
||||
* Truthy and Falsy. All values except `undefined`, `null`, `false` are truthy, whereas in Ruby Liquid all except `nil` and `false` are truthy. See [#26](https://github.com/harttle/liquidjs/pull/26).
|
||||
* Number Rendering. Since JavaScript do not distinguish `float` and `integer`, we cannot either convert between them nor render regarding to their type. See [#59](https://github.com/harttle/liquidjs/issues/59).
|
||||
* [.to_liquid()](https://github.com/Shopify/liquid/wiki/Introduction-to-Drops) is replaced by `.toLiquid()`
|
||||
* [.to_s()](https://www.rubydoc.info/gems/liquid/Liquid/Drop) is replaced by JavaScript `.toString()`
|
||||
|
||||
## Contribute Guidelines
|
||||
|
||||
This repo uses [eslint](https://eslint.org/) to check code style, [semantic-release](https://github.com/semantic-release/semantic-release) to generate changelog and publish to npm and Github Releases.
|
||||
|
||||
* Code Style: <https://github.com/standard/eslint-config-standard>, `npm run lint` to check locally.
|
||||
* Commit Message: <https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#commits>
|
||||
|
||||
[nunjucks]: http://mozilla.github.io/nunjucks/
|
||||
[liquid-node]: https://github.com/sirlantis/liquid-node
|
||||
[shopify-liquid]: https://shopify.github.io/liquid/
|
||||
[shopify/liquid]: https://shopify.github.io/liquid/
|
||||
[jekyll]: http://jekyllrb.com/
|
||||
[gh]: https://pages.github.com/
|
||||
[releases]: https://github.com/harttle/shopify-liquid/releases
|
||||
[any-promise]: https://github.com/kevinbeaty/any-promise
|
||||
[test]: https://github.com/harttle/shopify-liquid/tree/master/test
|
||||
[test]: https://github.com/harttle/liquidjs/tree/master/test
|
||||
[caniuse-promises]: http://caniuse.com/#feat=promises
|
||||
[whitespace control]: https://github.com/harttle/liquidjs/wiki/Whitespace-Control
|
||||
[tags]: https://github.com/harttle/liquidjs/wiki/Builtin-Tags
|
||||
[filters]: https://github.com/harttle/liquidjs/wiki/Builtin-Filters
|
||||
[express-views]: http://expressjs.com/en/guide/using-template-engines.html
|
||||
[pp]: https://github.com/taylorhakes/promise-polyfill
|
||||
[tutorial]: https://shopify.github.io/liquid/basics/introduction/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-unused-expressions": "off"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as Benchmark from 'benchmark'
|
||||
import Liquid from '../src/liquid'
|
||||
import TagToken from '../src/parser/tag-token'
|
||||
import Context from '../src/context/context'
|
||||
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
extname: '.liquid'
|
||||
})
|
||||
|
||||
engine.registerTag('header', {
|
||||
parse: function (token: TagToken) {
|
||||
const [key, val] = token.args.split(':')
|
||||
this[key] = val
|
||||
},
|
||||
render: function (ctx: Context) {
|
||||
const title = this.liquid.evalValue(this.content, ctx)
|
||||
return `<h1>${title}</h1>`
|
||||
}
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request'],
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
const template = `
|
||||
{%header content: "welcome to liquid" | capitalize%}
|
||||
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
<li>{{forloop.index}} - {{todo}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
`
|
||||
|
||||
export default function () {
|
||||
console.log('--- demo ---')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('demo')
|
||||
.add('demo', {
|
||||
defer: true,
|
||||
fn: (d: any) => engine.parseAndRender(template, ctx).then(x => d.resolve(x))
|
||||
})
|
||||
.on('cycle', (event: any) => console.log(String(event.target)))
|
||||
.on('complete', resolve)
|
||||
.run({ 'async': true })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import output from './output'
|
||||
import tag from './tag'
|
||||
import demo from './demo'
|
||||
|
||||
async function main () {
|
||||
await output()
|
||||
await tag()
|
||||
await demo()
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as Benchmark from 'benchmark'
|
||||
import Liquid from '../src/liquid'
|
||||
|
||||
const liquid = new Liquid()
|
||||
|
||||
export default function () {
|
||||
console.log('--- output ---')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('output')
|
||||
.add('literal', test('{{false}}{{"foo"}}{{32.322}}'))
|
||||
.add('truncate', test('{{"foobar" | truncate: 3}}'))
|
||||
.add('date', test('{{"now" | date: "%d%Y%m"}}'))
|
||||
.add('escape', test('{{"1<2" | escape}}'))
|
||||
.add('default', test('{{"" | default: 3}}'))
|
||||
.on('cycle', (event: any) => console.log(String(event.target)))
|
||||
.on('complete', resolve)
|
||||
.run({ 'async': true })
|
||||
})
|
||||
}
|
||||
|
||||
function test (str: string) {
|
||||
return {
|
||||
defer: true,
|
||||
fn: (d: any) => liquid.parseAndRender(str).then(x => d.resolve(x))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import * as Benchmark from 'benchmark'
|
||||
import Liquid from '../src/liquid'
|
||||
|
||||
const liquid = new Liquid()
|
||||
|
||||
export default function () {
|
||||
console.log('--- tag ---')
|
||||
return new Promise(resolve => {
|
||||
new Benchmark.Suite('tag')
|
||||
.add('if', test('{% if "foobar" %}foo{% endif %}'))
|
||||
.add('unless', test('{%unless "foo"%}true{%else%}false{%endunless%}'))
|
||||
.add('for', test('{% for i in (1..3) %}{{fooloop.index}}{% endfor %}'))
|
||||
.add('switch', test('{%case 3%}{% when 1 %}1{% when 2 %}2{% when 3 %}3{%endcase%}'))
|
||||
.add('assign', test('{%assign a="foo bar"%}'))
|
||||
.add('capture', test('{%capture foo%}what is this{%endcapture%}'))
|
||||
.add('increment', test('{%increment a%}'))
|
||||
.add('decrement', test('{%decrement a%}'))
|
||||
.add('tablerow', test('{%tablerow i in (1..10) cols:3%}{%endtablerow%}'))
|
||||
.on('cycle', (event: any) => console.log(String(event.target)))
|
||||
.on('complete', resolve)
|
||||
.run({ 'async': true })
|
||||
})
|
||||
}
|
||||
|
||||
function test (str: string) {
|
||||
return {
|
||||
defer: true,
|
||||
fn: (d: any) => liquid.parseAndRender(str).then(x => d.resolve(x))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
access time: {{date|date: "%Y-%m-%d %H:%M:%S"}}
|
||||
@@ -0,0 +1 @@
|
||||
<h2>Welcome to {{ name | capitalize}}, {% include 'date.html' %}</h2>
|
||||
+13
-10
@@ -1,26 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title></title>
|
||||
<script src="../../dist/liquid.js"></script>
|
||||
<title>liquidjs for the browser</title>
|
||||
<script src="node_modules/liquidjs/dist/liquid.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h2 id="header"></h2>
|
||||
<script>
|
||||
var engine = window.Liquid();
|
||||
|
||||
var src = 'Welcome to {{ name | capitalize}}, ' +
|
||||
'access time: {{date|date: "%Y-%m-%d %H:%M:%S"}}';
|
||||
var engine = new window.Liquid({
|
||||
extname: '.html',
|
||||
cache: true
|
||||
});
|
||||
var src = '<h2>Welcome to {{ name | capitalize}}, ' +
|
||||
'access time: {{date|date: "%Y-%m-%d %H:%M:%S"}}</h2>';
|
||||
var ctx = {
|
||||
name: 'Liquid',
|
||||
date: new Date()
|
||||
};
|
||||
engine.parseAndRender(src, ctx)
|
||||
.then(function(html) {
|
||||
document.getElementById('header').innerHTML = html;
|
||||
return engine.renderFile('hello', ctx);
|
||||
})
|
||||
.then(function(html) {
|
||||
document.body.innerHTML += html
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Generated
+220
@@ -0,0 +1,220 @@
|
||||
{
|
||||
"name": "liquidjs-demo-browser",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
},
|
||||
"colors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
|
||||
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
|
||||
"dev": true
|
||||
},
|
||||
"corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.1"
|
||||
}
|
||||
},
|
||||
"ecstatic": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.1.tgz",
|
||||
"integrity": "sha512-/rrctvxZ78HMI/tPIsqdvFKHHscxR3IJuKrZI2ZoUgkt2SiufyLFBmcco+aqQBIu6P1qBsUNG3drAAGLx80vTQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"he": "1.2.0",
|
||||
"mime": "1.6.0",
|
||||
"minimist": "1.2.0",
|
||||
"url-join": "2.0.5"
|
||||
}
|
||||
},
|
||||
"eventemitter3": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
|
||||
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz",
|
||||
"integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"debug": "3.2.6"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||
"dev": true
|
||||
},
|
||||
"http-proxy": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
|
||||
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter3": "3.1.0",
|
||||
"follow-redirects": "1.7.0",
|
||||
"requires-port": "1.0.0"
|
||||
}
|
||||
},
|
||||
"http-server": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz",
|
||||
"integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"colors": "1.0.3",
|
||||
"corser": "2.0.1",
|
||||
"ecstatic": "3.3.1",
|
||||
"http-proxy": "1.17.0",
|
||||
"opener": "1.4.3",
|
||||
"optimist": "0.6.1",
|
||||
"portfinder": "1.0.20",
|
||||
"union": "0.4.6"
|
||||
}
|
||||
},
|
||||
"liquidjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-7.0.0.tgz",
|
||||
"integrity": "sha512-/JL+VQUI++6iCS7JQ4fPPeji6vyvHCU/iuz2bLSiE0tn0eO3kf8GEyfaXnQUHSroy8SmJu7HQIvjvOErtYEvUg=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz",
|
||||
"integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=",
|
||||
"dev": true
|
||||
},
|
||||
"optimist": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
|
||||
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.10",
|
||||
"wordwrap": "0.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
|
||||
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"portfinder": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.20.tgz",
|
||||
"integrity": "sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "1.5.2",
|
||||
"debug": "2.6.9",
|
||||
"mkdirp": "0.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz",
|
||||
"integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=",
|
||||
"dev": true
|
||||
},
|
||||
"requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||
"dev": true
|
||||
},
|
||||
"union": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz",
|
||||
"integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"qs": "2.3.3"
|
||||
}
|
||||
},
|
||||
"url-join": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
|
||||
"dev": true
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "liquidjs-demo-browser",
|
||||
"version": "1.0.0",
|
||||
"description": "Liquidjs Demo for Browser Usage",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
"start": "http-server -c-1 "
|
||||
},
|
||||
"author": "harttle <[email protected]>",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"http-server": "^0.11.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "^7.0.0"
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -1,22 +1,22 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var Liquid = require('../..');
|
||||
const express = require('express')
|
||||
const Liquid = require('../..')
|
||||
|
||||
var engine = Liquid({
|
||||
root: __dirname, // for layouts and partials
|
||||
extname: '.liquid'
|
||||
});
|
||||
const app = express()
|
||||
const engine = new Liquid({
|
||||
root: __dirname, // for layouts and partials
|
||||
extname: '.liquid'
|
||||
})
|
||||
|
||||
app.engine('liquid', engine.express()); // register liquid engine
|
||||
app.set('views', ['./partials', './views']); // specify the views directory
|
||||
app.set('view engine', 'liquid'); // set to default
|
||||
app.engine('liquid', engine.express()) // register liquid engine
|
||||
app.set('views', ['./partials', './views']) // specify the views directory
|
||||
app.set('view engine', 'liquid') // set to default
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
var todos = ['fork and clone', 'make it better', 'make a pull request'];
|
||||
res.render('todolist', {
|
||||
todos: todos,
|
||||
title: 'Welcome to shopify-liquid!'
|
||||
});
|
||||
});
|
||||
const todos = ['fork and clone', 'make it better', 'make a pull request']
|
||||
res.render('todolist', {
|
||||
todos: todos,
|
||||
title: 'Welcome to liquidjs!'
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const app = require('./app.js');
|
||||
const app = require('./app.js')
|
||||
|
||||
app.listen(3000, function () {
|
||||
console.log('Example app listening on port 3000!');
|
||||
});
|
||||
|
||||
console.log('Express running: http://localhost:3000')
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "express-demo",
|
||||
"version": "1.0.0",
|
||||
"description": "Express Demo Using Shopify-Liquid",
|
||||
"description": "Express Demo Using liquidjs",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "harttle",
|
||||
"license": "ISC",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "^4.14.0"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{% layout 'layout' %}
|
||||
|
||||
<ul>
|
||||
{% for todo in todolist %}
|
||||
<li>{% include 'todo' id=forloop.index %}</li>
|
||||
{% for todo in todos %}
|
||||
<li>{% include 'todo', id:forloop.index %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
const Liquid = require('liquidjs')
|
||||
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
extname: '.liquid'
|
||||
})
|
||||
|
||||
engine.registerTag('header', {
|
||||
parse: function (token) {
|
||||
const [key, val] = token.args.split(':')
|
||||
this[key] = val
|
||||
},
|
||||
render: function (scope, hash) {
|
||||
const title = this.liquid.evalValue(this.content, scope)
|
||||
return `<h1>${title}</h1>`
|
||||
}
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request'],
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
engine.renderFile('todolist', ctx)
|
||||
.then(console.log)
|
||||
.catch(err => console.error(err.stack))
|
||||
Generated
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Node.js demo for liquidjs",
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"liquidjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-7.0.0.tgz",
|
||||
"integrity": "sha512-/JL+VQUI++6iCS7JQ4fPPeji6vyvHCU/iuz2bLSiE0tn0eO3kf8GEyfaXnQUHSroy8SmJu7HQIvjvOErtYEvUg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "liquidjs-demo-nodejs",
|
||||
"description": "Node.js demo for liquidjs",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "^7.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{%header content: "welcome to liquid" | capitalize%}
|
||||
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
<li>{{forloop.index}} - {{todo}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
@@ -0,0 +1,21 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
File diff suppressed because it is too large
Load Diff
Generated
+16737
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "react-liquidjs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"dns": "^0.2.2",
|
||||
"html-react-parser": "^0.4.7",
|
||||
"liquidjs": "^6.0.1",
|
||||
"path": "^0.12.7",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.5.2",
|
||||
"react-promise": "^2.0.3",
|
||||
"react-scripts": "2.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="result"></div>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 40vmin;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import React, { Component } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
let path = require('path')
|
||||
let Liquid = require('liquidjs');
|
||||
|
||||
let config = require('./views/demo.liquid');
|
||||
let pageConfig = config.toString()
|
||||
|
||||
let Parser = require('html-react-parser');
|
||||
|
||||
class App extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
let engine = new Liquid({
|
||||
root: path.resolve(__dirname, 'views/'), // dirs to lookup layouts/includes
|
||||
extname: '.liquid' // the extname used for layouts/includes, defaults
|
||||
});
|
||||
|
||||
engine.registerFilter('image', d => {
|
||||
let img = `<img src="${d}" class="App-logo" alt="logo"></img>`;
|
||||
return img
|
||||
})
|
||||
|
||||
engine.renderFile(pageConfig, {name: 'alice', logo: logo })
|
||||
.then((htmlTemp) => {
|
||||
this.setState({ html: htmlTemp })
|
||||
}) // outputs "Alice"
|
||||
}
|
||||
|
||||
state = {
|
||||
html: []
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
{Parser(`${this.state.html}`)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// Learn more about service workers: http://bit.ly/CRA-PWA
|
||||
serviceWorker.unregister();
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
|
||||
<g fill="#61DAFB">
|
||||
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
|
||||
<circle cx="420.9" cy="296.5" r="45.7"/>
|
||||
<path d="M520.5 78.1z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,127 @@
|
||||
// In production, we register a service worker to serve assets from local cache.
|
||||
|
||||
// This lets the app load faster on subsequent visits in production, and gives
|
||||
// it offline capabilities. However, it also means that developers (and users)
|
||||
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||
// cached resources are updated in the background.
|
||||
|
||||
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||
// This link also includes instructions on opting out of this behavior.
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
|
||||
export function register(config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||
if (publicUrl.origin !== window.location.origin) {
|
||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||
// from what our page is served on. This might happen if a CDN is used to
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||
checkValidServiceWorker(swUrl, config);
|
||||
|
||||
// Add some additional logging to localhost, pointing developers to the
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://goo.gl/SC7cgQ'
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Is not local host. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function registerValidSW(swUrl, config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the old content will have been purged and
|
||||
// the fresh content will have been added to the cache.
|
||||
// It's the perfect time to display a "New content is
|
||||
// available; please refresh." message in your web app.
|
||||
console.log('New content is available; please refresh.');
|
||||
|
||||
// Execute callback
|
||||
if (config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
|
||||
// Execute callback
|
||||
if (config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
if (
|
||||
response.status === 404 ||
|
||||
response.headers.get('content-type').indexOf('javascript') === -1
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<header className="App-header">
|
||||
{{ logo | image }}
|
||||
<h2>Welcome {{name | capitalize}}</h2>
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<p>
|
||||
You are using liquidjs & Reactjs
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import Liquid from 'liquidjs'
|
||||
|
||||
const engine = new Liquid({
|
||||
root: __dirname,
|
||||
extname: '.liquid'
|
||||
})
|
||||
|
||||
engine.registerTag('header', {
|
||||
parse: function (token) {
|
||||
const [key, val] = token.args.split(':')
|
||||
this[key] = val
|
||||
},
|
||||
render: function (scope, hash) {
|
||||
const title = this.liquid.evalValue(this['content'], scope)
|
||||
return `<h1>${title}</h1>`
|
||||
}
|
||||
})
|
||||
|
||||
const ctx = {
|
||||
todos: ['fork and clone', 'make it better', 'make a pull request'],
|
||||
title: 'Welcome to liquidjs!'
|
||||
}
|
||||
|
||||
// console.log('isTruthy:', isTruthy('a string here'));
|
||||
engine.renderFile('todolist', ctx).then(console.log)
|
||||
Generated
+8701
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "liquidjs-demo-typescript",
|
||||
"description": "TypeScript demo for liquidjs",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"start": "ts-node index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"liquidjs": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^8.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{%header content: "welcome to liquid" | capitalize%}
|
||||
|
||||
<ul>
|
||||
{% for todo in todos %}
|
||||
<li>{{forloop.index}} - {{todo}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
Vendored
-2498
File diff suppressed because it is too large
Load Diff
Vendored
-2
File diff suppressed because one or more lines are too long
-127
@@ -1,127 +0,0 @@
|
||||
const strftime = require('./src/util/strftime.js');
|
||||
const _ = require('./src/util/underscore.js');
|
||||
|
||||
var escapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
};
|
||||
var unescapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'",
|
||||
};
|
||||
|
||||
var filters = {
|
||||
'abs': v => Math.abs(v),
|
||||
'append': (v, arg) => v + arg,
|
||||
'capitalize': str => stringify(str).charAt(0).toUpperCase() + str.slice(1),
|
||||
'ceil': v => Math.ceil(v),
|
||||
'date': (v, arg) => {
|
||||
if (v === 'now') v = new Date();
|
||||
return v instanceof Date ? strftime(v, arg) : '';
|
||||
},
|
||||
'default': (v, arg) => v || arg,
|
||||
'divided_by': (v, arg) => Math.floor(v / arg),
|
||||
'downcase': v => v.toLowerCase(),
|
||||
'escape': escape,
|
||||
|
||||
'escape_once': str => escape(unescape(str)),
|
||||
'first': v => v[0],
|
||||
'floor': v => Math.floor(v),
|
||||
'join': (v, arg) => v.join(arg),
|
||||
'last': v => v[v.length - 1],
|
||||
'lstrip': v => stringify(v).replace(/^\s+/, ''),
|
||||
'map': (arr, arg) => arr.map(v => v[arg]),
|
||||
'minus': bindFixed((v, arg) => v - arg),
|
||||
'modulo': bindFixed((v, arg) => v % arg),
|
||||
'newline_to_br': v => v.replace(/\n/g, '<br />'),
|
||||
'plus': bindFixed((v, arg) => v + arg),
|
||||
'prepend': (v, arg) => arg + v,
|
||||
'remove': (v, arg) => v.split(arg).join(''),
|
||||
'remove_first': (v, l) => v.replace(l, ''),
|
||||
'replace': (v, pattern, replacement) =>
|
||||
stringify(v).split(pattern).join(replacement),
|
||||
'replace_first': (v, arg1, arg2) => stringify(v).replace(arg1, arg2),
|
||||
'reverse': v => v.reverse(),
|
||||
'round': (v, arg) => {
|
||||
var amp = Math.pow(10, arg || 0);
|
||||
return Math.round(v * amp, arg) / amp;
|
||||
},
|
||||
'rstrip': str => stringify(str).replace(/\s+$/, ''),
|
||||
'size': v => v.length,
|
||||
'slice': (v, begin, length) =>
|
||||
v.substr(begin, length === undefined ? 1 : length),
|
||||
'sort': (v, arg) => v.sort(arg),
|
||||
'split': (v, arg) => stringify(v).split(arg),
|
||||
'strip': (v) => stringify(v).trim(),
|
||||
'strip_html': v => stringify(v).replace(/<\/?\s*\w+\s*\/?>/g, ''),
|
||||
'strip_newlines': v => stringify(v).replace(/\n/g, ''),
|
||||
'times': (v, arg) => v * arg,
|
||||
'truncate': (v, l, o) => {
|
||||
v = stringify(v);
|
||||
o = (o === undefined) ? '...' : o;
|
||||
l = l || 16;
|
||||
if (v.length <= l) return v;
|
||||
return v.substr(0, l - o.length) + o;
|
||||
},
|
||||
'truncatewords': (v, l, o) => {
|
||||
if (o === undefined) o = '...';
|
||||
var arr = v.split(' ');
|
||||
var ret = arr.slice(0, l).join(' ');
|
||||
if (arr.length > l) ret += o;
|
||||
return ret;
|
||||
},
|
||||
'uniq': function(arr) {
|
||||
var u = {};
|
||||
return (arr || []).filter(val => {
|
||||
if (u.hasOwnProperty(val)) {
|
||||
return false;
|
||||
}
|
||||
u[val] = true;
|
||||
return true;
|
||||
});
|
||||
},
|
||||
'upcase': str => stringify(str).toUpperCase(),
|
||||
'url_encode': encodeURIComponent
|
||||
};
|
||||
|
||||
function escape(str) {
|
||||
return stringify(str).replace(/&|<|>|"|'/g, m => escapeMap[m]);
|
||||
}
|
||||
|
||||
function unescape(str) {
|
||||
return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]);
|
||||
}
|
||||
|
||||
function getFixed(v) {
|
||||
var p = (v + "").split(".");
|
||||
return (p.length > 1) ? p[1].length : 0;
|
||||
}
|
||||
|
||||
function getMaxFixed(l, r) {
|
||||
return Math.max(getFixed(l), getFixed(r));
|
||||
}
|
||||
|
||||
function stringify(obj) {
|
||||
obj = obj || "";
|
||||
return obj + '';
|
||||
}
|
||||
|
||||
function bindFixed(cb) {
|
||||
return (l, r) => {
|
||||
var f = getMaxFixed(l, r);
|
||||
return cb(l, r).toFixed(f);
|
||||
};
|
||||
}
|
||||
|
||||
function registerAll(liquid) {
|
||||
return _.forOwn(filters, (func, name) => liquid.registerFilter(name, func));
|
||||
}
|
||||
|
||||
registerAll.filters = filters;
|
||||
module.exports = registerAll;
|
||||
@@ -1,148 +0,0 @@
|
||||
const Scope = require('./src/scope');
|
||||
const _ = require('./src/util/underscore.js');
|
||||
const assert = require('./src/util/assert.js');
|
||||
const tokenizer = require('./src/tokenizer.js');
|
||||
const statFileAsync = require('./src/util/fs.js').statFileAsync;
|
||||
const readFileAsync = require('./src/util/fs.js').readFileAsync;
|
||||
const path = require('path');
|
||||
const Render = require('./src/render.js');
|
||||
const lexical = require('./src/lexical.js');
|
||||
const Tag = require('./src/tag.js');
|
||||
const Filter = require('./src/filter.js');
|
||||
const Parser = require('./src/parser');
|
||||
const Syntax = require('./src/syntax.js');
|
||||
const tags = require('./tags');
|
||||
const filters = require('./filters');
|
||||
const Promise = require('any-promise');
|
||||
const anySeries = require('./src/util/promise.js').anySeries;
|
||||
const Errors = require('./src/util/error.js');
|
||||
|
||||
var _engine = {
|
||||
init: function(tag, filter, options) {
|
||||
if (options.cache) {
|
||||
this.cache = {};
|
||||
}
|
||||
this.options = options;
|
||||
this.tag = tag;
|
||||
this.filter = filter;
|
||||
this.parser = Parser(tag, filter);
|
||||
this.renderer = Render();
|
||||
|
||||
tags(this);
|
||||
filters(this);
|
||||
|
||||
return this;
|
||||
},
|
||||
parse: function(html, filepath) {
|
||||
var tokens = tokenizer.parse(html, filepath);
|
||||
return this.parser.parse(tokens);
|
||||
},
|
||||
render: function(tpl, ctx, opts) {
|
||||
opts = _.assign({}, this.options, opts);
|
||||
var scope = Scope.factory(ctx, opts);
|
||||
return this.renderer.renderTemplates(tpl, scope);
|
||||
},
|
||||
parseAndRender: function(html, ctx, opts) {
|
||||
return Promise.resolve()
|
||||
.then(() => this.parse(html))
|
||||
.then(tpl => this.render(tpl, ctx, opts))
|
||||
.catch(e => {
|
||||
if (e instanceof Errors.RenderBreakError) {
|
||||
return e.html;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
renderFile: function(filepath, ctx, opts) {
|
||||
opts = _.assign({}, opts);
|
||||
return this.getTemplate(filepath, opts.root)
|
||||
.then(templates => this.render(templates, ctx, opts));
|
||||
},
|
||||
evalOutput: function(str, scope) {
|
||||
var tpl = this.parser.parseOutput(str.trim());
|
||||
return this.renderer.evalOutput(tpl, scope);
|
||||
},
|
||||
registerFilter: function(name, filter) {
|
||||
return this.filter.register(name, filter);
|
||||
},
|
||||
registerTag: function(name, tag) {
|
||||
return this.tag.register(name, tag);
|
||||
},
|
||||
lookup: function(filepath, root) {
|
||||
root = this.options.root.concat(root || []);
|
||||
root = _.uniq(root);
|
||||
var paths = root.map(root => path.resolve(root, filepath));
|
||||
return anySeries(paths, path => statFileAsync(path).then(() => path))
|
||||
.catch((e) => {
|
||||
if (e.code === 'ENOENT') {
|
||||
e.message = `Failed to lookup ${filepath} in: ${root}`;
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
getTemplate: function(filepath, root) {
|
||||
if(!path.extname(filepath)){
|
||||
filepath += this.options.extname;
|
||||
}
|
||||
return this
|
||||
.lookup(filepath, root)
|
||||
.then(filepath => {
|
||||
if (this.options.cache) {
|
||||
var tpl = this.cache[filepath];
|
||||
if (tpl) {
|
||||
return Promise.resolve(tpl);
|
||||
}
|
||||
return readFileAsync(filepath)
|
||||
.then(str => this.parse(str))
|
||||
.then(tpl => this.cache[filepath] = tpl);
|
||||
} else {
|
||||
return readFileAsync(filepath).then(str => this.parse(str, filepath));
|
||||
}
|
||||
});
|
||||
},
|
||||
express: function(opts) {
|
||||
opts = opts || {};
|
||||
var self = this;
|
||||
return function(filePath, ctx, callback) {
|
||||
assert(_.isArray(this.root) || _.isString(this.root),
|
||||
'illegal views root, are you using express.js?');
|
||||
opts.root = this.root;
|
||||
self.renderFile(filePath, ctx, opts)
|
||||
.then(html => callback(null, html))
|
||||
.catch(e => callback(e));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function factory(options) {
|
||||
options = _.assign({}, options);
|
||||
options.root = normalizeStringArray(options.root);
|
||||
if (!options.root.length) options.root = ['.'];
|
||||
|
||||
options.extname = options.extname || '.liquid';
|
||||
|
||||
var engine = Object.create(_engine);
|
||||
|
||||
engine.init(Tag(), Filter(options), options);
|
||||
return engine;
|
||||
}
|
||||
|
||||
function normalizeStringArray(value) {
|
||||
if (_.isArray(value)) return value;
|
||||
if (_.isString(value)) return [value];
|
||||
return [];
|
||||
}
|
||||
|
||||
factory.lexical = lexical;
|
||||
factory.isTruthy = Syntax.isTruthy;
|
||||
factory.isFalsy = Syntax.isFalsy;
|
||||
factory.evalExp = Syntax.evalExp;
|
||||
factory.evalValue = Syntax.evalValue;
|
||||
factory.Types = {
|
||||
ParseError: Errors.ParseError,
|
||||
TokenizationEroor: Errors.TokenizationError,
|
||||
RenderBreakError: Errors.RenderBreakError,
|
||||
AssertionError: Errors.AssertionError
|
||||
};
|
||||
|
||||
module.exports = factory;
|
||||
@@ -0,0 +1,6 @@
|
||||
declare module 'rollup-plugin-uglify' {
|
||||
export function uglify (): any
|
||||
}
|
||||
declare module 'rollup-plugin-replace' {
|
||||
export default function replace(options: any): any
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
MINIFY = ./node_modules/.bin/uglifyjs
|
||||
BROWSERIFY = ./node_modules/.bin/browserify
|
||||
|
||||
.PHONY: dist default clean
|
||||
|
||||
default:
|
||||
|
||||
dist:
|
||||
[ -d dist/ ] || mkdir dist/
|
||||
$(BROWSERIFY) index.js -s Liquid \
|
||||
--ignore path \
|
||||
-t [ babelify --global true --presets [ es2015 ] ] \
|
||||
> dist/liquid.js
|
||||
$(MINIFY) dist/liquid.js --compress warnings=false --mangle --output dist/liquid.min.js
|
||||
ls -lh dist/
|
||||
|
||||
clean:
|
||||
rm -rf dist/
|
||||
Generated
+10775
File diff suppressed because it is too large
Load Diff
+87
-30
@@ -1,18 +1,33 @@
|
||||
{
|
||||
"name": "shopify-liquid",
|
||||
"version": "1.5.3",
|
||||
"description": "A feature-rich Liquid template engine for Node.js and browsers, with compliance with the Ruby version.",
|
||||
"main": "index.js",
|
||||
"name": "liquidjs",
|
||||
"version": "8.1.0",
|
||||
"description": "Liquid template engine by pure JavaScript: compatible to shopify, easy to extend.",
|
||||
"main": "dist/liquid.common.js",
|
||||
"types": "dist/liquid.d.ts",
|
||||
"browser": "dist/liquid.js",
|
||||
"scripts": {
|
||||
"test": "mocha --recursive",
|
||||
"dist": "make dist",
|
||||
"preversion": "npm test",
|
||||
"version": "npm run dist && git add -A dist",
|
||||
"postversion": "git push && git push --tags"
|
||||
"lint": "eslint . --ext .ts",
|
||||
"unit": "mocha \"test/unit/**/*.ts\"",
|
||||
"integration": "mocha \"test/integration/**/*.ts\"",
|
||||
"e2e": "npm run build && mocha \"test/e2e/**/*.ts\"",
|
||||
"test": "npm run build && mocha \"test/**/*.ts\"",
|
||||
"benchmark": "ts-node benchmark",
|
||||
"coverage-html": "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",
|
||||
"version": "npm run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/harttle/shopify-liquid.git"
|
||||
"url": "git+https://github.com/harttle/liquidjs.git"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.8.7"
|
||||
},
|
||||
"keywords": [
|
||||
"liquid",
|
||||
@@ -24,26 +39,68 @@
|
||||
"author": "Harttle",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/harttle/shopify-liquid/issues"
|
||||
},
|
||||
"homepage": "https://github.com/harttle/shopify-liquid#readme",
|
||||
"dependencies": {
|
||||
"any-promise": "^1.3.0"
|
||||
"url": "https://github.com/harttle/liquidjs/issues"
|
||||
},
|
||||
"homepage": "https://github.com/harttle/liquidjs#readme",
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.1.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^5.3.0",
|
||||
"coveralls": "^2.11.9",
|
||||
"express": "^4.14.0",
|
||||
"istanbul": "^0.4.3",
|
||||
"mocha": "^3.0.2",
|
||||
"mock-fs": "^3.9.0",
|
||||
"sinon": "^1.17.4",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"supertest": "^2.0.0",
|
||||
"uglifyjs": "^2.4.10"
|
||||
}
|
||||
"@semantic-release/changelog": "^3.0.2",
|
||||
"@semantic-release/commit-analyzer": "^6.1.0",
|
||||
"@semantic-release/git": "^7.0.8",
|
||||
"@semantic-release/npm": "^5.1.4",
|
||||
"@semantic-release/release-notes-generator": "^7.1.4",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chai-as-promised": "^7.1.0",
|
||||
"@types/express": "^4.16.1",
|
||||
"@types/jsdom": "^12.2.2",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/sinon": "^7.0.6",
|
||||
"@types/sinon-chai": "^3.2.2",
|
||||
"@types/supertest": "^2.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^1.3.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"coveralls": "^3.0.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"eslint": "^5.12.1",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.15.0",
|
||||
"eslint-plugin-mocha": "^5.3.0",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"express": "^4.16.4",
|
||||
"jsdom": "^13.2.0",
|
||||
"mocha": "^5.2.0",
|
||||
"nyc": "^13.1.0",
|
||||
"regenerator-runtime": "^0.12.1",
|
||||
"rollup": "^1.1.2",
|
||||
"rollup-plugin-replace": "^2.1.0",
|
||||
"rollup-plugin-typescript2": "^0.19.2",
|
||||
"rollup-plugin-uglify": "^6.0.2",
|
||||
"semantic-release": "^15.13.3",
|
||||
"sinon": "^7.2.3",
|
||||
"sinon-chai": "^3.3.0",
|
||||
"supertest": "^3.4.2",
|
||||
"ts-node": "^8.0.2",
|
||||
"tslib": "^1.9.3",
|
||||
"typescript": "^3.3.3"
|
||||
},
|
||||
"release": {
|
||||
"branch": "master",
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"@semantic-release/npm",
|
||||
"@semantic-release/git"
|
||||
]
|
||||
},
|
||||
"nyc": {
|
||||
"extension": [
|
||||
".ts"
|
||||
]
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { uglify } from 'rollup-plugin-uglify'
|
||||
import pkg from './package.json'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
|
||||
const version = process.env.VERSION || pkg.version
|
||||
const sourcemap = true
|
||||
const banner = `/*
|
||||
* liquidjs@${version}, https://github.com/harttle/liquidjs
|
||||
* (c) 2016-${new Date().getFullYear()} harttle
|
||||
* Released under the MIT License.
|
||||
*/`
|
||||
const treeshake = {
|
||||
propertyReadSideEffects: false
|
||||
}
|
||||
const input = './src/liquid.ts'
|
||||
|
||||
export default [{
|
||||
output: [{
|
||||
file: 'dist/liquid.common.js',
|
||||
name: 'Liquid',
|
||||
format: 'cjs',
|
||||
sourcemap,
|
||||
banner
|
||||
}],
|
||||
external: ['path', 'fs'],
|
||||
plugins: [typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})],
|
||||
treeshake,
|
||||
input
|
||||
}, {
|
||||
output: [{
|
||||
file: 'dist/liquid.js',
|
||||
name: 'Liquid',
|
||||
format: 'umd',
|
||||
sourcemap,
|
||||
banner
|
||||
}],
|
||||
plugins: [
|
||||
replace({
|
||||
include: './src/liquid.ts',
|
||||
delimiters: ['', ''],
|
||||
'./fs/node': './fs/browser'
|
||||
}),
|
||||
typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
})
|
||||
],
|
||||
treeshake,
|
||||
input
|
||||
}, {
|
||||
output: [{
|
||||
file: 'dist/liquid.min.js',
|
||||
name: 'Liquid',
|
||||
format: 'umd',
|
||||
sourcemap
|
||||
}],
|
||||
plugins: [
|
||||
replace({
|
||||
include: './src/liquid.ts',
|
||||
delimiters: ['', ''],
|
||||
'./fs/node': './fs/browser'
|
||||
}),
|
||||
typescript({
|
||||
tsconfigOverride: {
|
||||
include: [ 'src' ],
|
||||
exclude: [ 'test', 'benchmark' ],
|
||||
compilerOptions: {
|
||||
target: 'es5',
|
||||
module: 'ES2015'
|
||||
}
|
||||
}
|
||||
}),
|
||||
uglify()
|
||||
],
|
||||
treeshake,
|
||||
input
|
||||
}]
|
||||
@@ -0,0 +1,28 @@
|
||||
import { last } from '../../util/underscore'
|
||||
import { isTruthy } from '../../render/syntax'
|
||||
|
||||
export default {
|
||||
'join': (v: any[], arg: string) => v.join(arg === undefined ? ' ' : arg),
|
||||
'last': <T>(v: T[]): T => last(v),
|
||||
'first': <T>(v: T[]): T => v[0],
|
||||
'map': <T1, T2>(arr: {[key: string]: T1}[], arg: string): T1[] => arr.map(v => v[arg]),
|
||||
'reverse': (v: any[]) => v.reverse(),
|
||||
'sort': <T>(v: T[], arg: (lhs: T, rhs: T) => number) => v.sort(arg),
|
||||
'size': (v: string | any[]) => v.length,
|
||||
'concat': <T1, T2>(v: T1[], arg: T2[] | T2): Array<T1 | T2> => Array.prototype.concat.call(v, arg),
|
||||
'slice': <T>(v: T[], begin: number, length: number = 1): T[] => {
|
||||
begin = begin < 0 ? v.length + begin : begin
|
||||
return v.slice(begin, begin + length)
|
||||
},
|
||||
'uniq': function<T> (arr: T[]): T[] {
|
||||
const u = {}
|
||||
return (arr || []).filter(val => {
|
||||
if (u.hasOwnProperty(String(val))) return false
|
||||
u[String(val)] = true
|
||||
return true
|
||||
})
|
||||
},
|
||||
'where': function<T> (arr: T[], property: string, value?: any): T[] {
|
||||
return arr.filter(obj => value === undefined ? isTruthy(obj[property]) : obj[property] === value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import strftime from '../../util/strftime'
|
||||
import { isString } from '../../util/underscore'
|
||||
|
||||
export default {
|
||||
'date': (v: string | Date, arg: string) => {
|
||||
let date = v
|
||||
if (v === 'now') {
|
||||
date = new Date()
|
||||
} else if (isString(v)) {
|
||||
date = new Date(v)
|
||||
}
|
||||
return isValidDate(date) ? strftime(date, arg) : v
|
||||
}
|
||||
}
|
||||
|
||||
function isValidDate (date: any): date is Date {
|
||||
return date instanceof Date && !isNaN(date.getTime())
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const escapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
}
|
||||
const unescapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
''': "'"
|
||||
}
|
||||
|
||||
function escape (str: string) {
|
||||
return String(str).replace(/&|<|>|"|'/g, m => escapeMap[m])
|
||||
}
|
||||
|
||||
function unescape (str: string) {
|
||||
return String(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m])
|
||||
}
|
||||
|
||||
export default {
|
||||
'escape': escape,
|
||||
'escape_once': (str: string) => escape(unescape(str)),
|
||||
'newline_to_br': (v: string) => v.replace(/\n/g, '<br />'),
|
||||
'strip_html': (v: string) => v.replace(/<script.*?<\/script>|<!--.*?-->|<style.*?<\/style>|<.*?>/g, '')
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import html from './html'
|
||||
import str from './string'
|
||||
import math from './math'
|
||||
import url from './url'
|
||||
import array from './array'
|
||||
import date from './date'
|
||||
import obj from './object'
|
||||
|
||||
export default { ...html, ...str, ...math, ...url, ...date, ...obj, ...array }
|
||||
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
'abs': (v: number) => Math.abs(v),
|
||||
'ceil': (v: number) => Math.ceil(v),
|
||||
'divided_by': (v: number, arg: number) => v / arg,
|
||||
'floor': (v: number) => Math.floor(v),
|
||||
'minus': (v: number, arg: number) => v - arg,
|
||||
'modulo': (v: number, arg: number) => v % arg,
|
||||
'round': (v: number, arg: number = 0) => {
|
||||
const amp = Math.pow(10, arg)
|
||||
return Math.round(v * amp) / amp
|
||||
},
|
||||
'plus': (v: number, arg: number) => Number(v) + Number(arg),
|
||||
'times': (v: number, arg: number) => v * arg
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { isTruthy } from '../../render/syntax'
|
||||
|
||||
export default {
|
||||
'default': <T1, T2>(v: T1, arg: T2): T1 | T2 => isTruthy(v) ? v : arg
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { FilterImpl } from '../../template/filter/filter-impl'
|
||||
|
||||
export default {
|
||||
'append': (v: string, arg: string) => v + arg,
|
||||
'prepend': (v: string, arg: string) => arg + v,
|
||||
'capitalize': (str: string) => String(str).charAt(0).toUpperCase() + str.slice(1),
|
||||
'lstrip': (v: string) => String(v).replace(/^\s+/, ''),
|
||||
'downcase': (v: string) => v.toLowerCase(),
|
||||
'upcase': (str: string) => String(str).toUpperCase(),
|
||||
'remove': (v: string, arg: string) => v.split(arg).join(''),
|
||||
'remove_first': (v: string, l: string) => v.replace(l, ''),
|
||||
'replace': (v: string, pattern: string, replacement: string) =>
|
||||
String(v).split(pattern).join(replacement),
|
||||
'replace_first': (v: string, arg1: string, arg2: string) => String(v).replace(arg1, arg2),
|
||||
'rstrip': (str: string) => String(str).replace(/\s+$/, ''),
|
||||
'split': (v: string, arg: string) => String(v).split(arg),
|
||||
'strip': (v: string) => String(v).trim(),
|
||||
'strip_newlines': (v: string) => String(v).replace(/\n/g, ''),
|
||||
'truncate': (v: string, l: number = 50, o: string = '...') => {
|
||||
v = String(v)
|
||||
if (v.length <= l) return v
|
||||
return v.substr(0, l - o.length) + o
|
||||
},
|
||||
'truncatewords': (v: string, l: number = 15, o: string = '...') => {
|
||||
const arr = v.split(/\s+/)
|
||||
let ret = arr.slice(0, l).join(' ')
|
||||
if (arr.length >= l) ret += o
|
||||
return ret
|
||||
}
|
||||
} as {[key: string]: FilterImpl}
|
||||
@@ -0,0 +1,4 @@
|
||||
export default {
|
||||
'url_decode': (x: string) => x.split('+').map(decodeURIComponent).join(' '),
|
||||
'url_encode': (x: string) => x.split(' ').map(encodeURIComponent).join('+')
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import assert from '../../util/assert'
|
||||
import { identifier } from '../../parser/lexical'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const re = new RegExp(`(${identifier.source})\\s*=([^]*)`)
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const match = token.args.match(re) as RegExpMatchArray
|
||||
assert(match, `illegal token ${token.raw}`)
|
||||
this.key = match[1]
|
||||
this.value = match[2]
|
||||
},
|
||||
render: async function (ctx: Context) {
|
||||
ctx.scopes[0][this.key] = await this.liquid.evalValue(this.value, ctx)
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,34 @@
|
||||
import BlockMode from '../../context/block-mode'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import ITemplate from '../../template/itemplate'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken, remainTokens: Token[]) {
|
||||
const match = /\w+/.exec(token.args)
|
||||
this.block = match ? match[0] : ''
|
||||
this.tpls = [] as ITemplate[]
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('tag:endblock', () => stream.stop())
|
||||
.on('template', (tpl: ITemplate) => this.tpls.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${token.raw} not closed`)
|
||||
})
|
||||
stream.start()
|
||||
},
|
||||
render: async function (ctx: Context) {
|
||||
const childDefined = ctx.blocks[this.block]
|
||||
const html = childDefined !== undefined
|
||||
? childDefined
|
||||
: await this.liquid.renderer.renderTemplates(this.tpls, ctx)
|
||||
|
||||
if (ctx.blockMode === BlockMode.STORE) {
|
||||
ctx.blocks[this.block] = html
|
||||
return ''
|
||||
}
|
||||
return html
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,7 @@
|
||||
import { RenderBreakError } from '../../util/error'
|
||||
|
||||
export default {
|
||||
render: async function () {
|
||||
throw new RenderBreakError('break')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import assert from '../../util/assert'
|
||||
import { identifier } from '../../parser/lexical'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const re = new RegExp(`(${identifier.source})`)
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
const match = tagToken.args.match(re) as RegExpMatchArray
|
||||
assert(match, `${tagToken.args} not valid identifier`)
|
||||
|
||||
this.variable = match[1]
|
||||
this.templates = []
|
||||
|
||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||
stream.on('tag:endcapture', () => stream.stop())
|
||||
.on('template', (tpl) => this.templates.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
stream.start()
|
||||
},
|
||||
render: async function (ctx: Context) {
|
||||
const html = await this.liquid.renderer.renderTemplates(this.templates, ctx)
|
||||
ctx.scopes[0][this.variable] = html
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,44 @@
|
||||
import { evalExp } from '../../render/syntax'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import ITemplate from '../../template/itemplate'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
this.cond = tagToken.args
|
||||
this.cases = []
|
||||
this.elseTemplates = []
|
||||
|
||||
let p: ITemplate[] = []
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('tag:when', (token: TagToken) => {
|
||||
this.cases.push({
|
||||
val: token.args,
|
||||
templates: p = []
|
||||
})
|
||||
})
|
||||
.on('tag:else', () => (p = this.elseTemplates))
|
||||
.on('tag:endcase', () => stream.stop())
|
||||
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
|
||||
stream.start()
|
||||
},
|
||||
|
||||
render: async function (ctx: Context) {
|
||||
for (let i = 0; i < this.cases.length; i++) {
|
||||
const branch = this.cases[i]
|
||||
const val = await evalExp(branch.val, ctx)
|
||||
const cond = await evalExp(this.cond, ctx)
|
||||
if (val === cond) {
|
||||
return this.liquid.renderer.renderTemplates(branch.templates, ctx)
|
||||
}
|
||||
}
|
||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, ctx)
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,17 @@
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||
stream
|
||||
.on('token', (token: TagToken) => {
|
||||
if (token.name === 'endcomment') stream.stop()
|
||||
})
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
stream.start()
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,7 @@
|
||||
import { RenderBreakError } from '../../util/error'
|
||||
|
||||
export default {
|
||||
render: async function () {
|
||||
throw new RenderBreakError('continue')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import assert from '../../util/assert'
|
||||
import { value as rValue } from '../../parser/lexical'
|
||||
import { evalValue } from '../../render/syntax'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const groupRE = new RegExp(`^(?:(${rValue.source})\\s*:\\s*)?(.*)$`)
|
||||
const candidatesRE = new RegExp(rValue.source, 'g')
|
||||
|
||||
export default <ITagImplOptions>{
|
||||
parse: function (tagToken: TagToken) {
|
||||
let match: RegExpExecArray | null = groupRE.exec(tagToken.args) as RegExpExecArray
|
||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||
|
||||
this.group = match[1] || ''
|
||||
const candidates = match[2]
|
||||
|
||||
this.candidates = []
|
||||
|
||||
while ((match = candidatesRE.exec(candidates))) {
|
||||
this.candidates.push(match[0])
|
||||
}
|
||||
assert(this.candidates.length, `empty candidates: ${tagToken.raw}`)
|
||||
},
|
||||
|
||||
render: async function (ctx: Context) {
|
||||
const group = await evalValue(this.group, ctx)
|
||||
const fingerprint = `cycle:${group}:` + this.candidates.join(',')
|
||||
const groups = ctx.groups
|
||||
let idx = groups[fingerprint]
|
||||
|
||||
if (idx === undefined) {
|
||||
idx = groups[fingerprint] = 0
|
||||
}
|
||||
|
||||
const candidate = this.candidates[idx]
|
||||
idx = (idx + 1) % this.candidates.length
|
||||
groups[fingerprint] = idx
|
||||
|
||||
return evalValue(candidate, ctx)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import assert from '../../util/assert'
|
||||
import { identifier } from '../../parser/lexical'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken) {
|
||||
const match = token.args.match(identifier) as RegExpMatchArray
|
||||
assert(match, `illegal identifier ${token.args}`)
|
||||
this.variable = match[0]
|
||||
},
|
||||
render: function (context: Context) {
|
||||
const scope = context.environments
|
||||
if (typeof scope[this.variable] !== 'number') {
|
||||
scope[this.variable] = 0
|
||||
}
|
||||
return --scope[this.variable]
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,82 @@
|
||||
import { isString, isObject, isArray } from '../../util/underscore'
|
||||
import { evalExp } from '../../render/syntax'
|
||||
import assert from '../../util/assert'
|
||||
import { identifier, value, hash } from '../../parser/lexical'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import Hash from '../../template/tag/hash'
|
||||
import ITemplate from '../../template/itemplate'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
import { ForloopDrop } from '../../drop/forloop-drop'
|
||||
|
||||
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
||||
`(${value.source})` +
|
||||
`(?:\\s+${hash.source})*` +
|
||||
`(?:\\s+(reversed))?` +
|
||||
`(?:\\s+${hash.source})*$`)
|
||||
|
||||
export default <ITagImplOptions>{
|
||||
type: 'block',
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
const match = re.exec(tagToken.args) as RegExpExecArray
|
||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||
this.variable = match[1]
|
||||
this.collection = match[2]
|
||||
this.reversed = !!match[3]
|
||||
|
||||
this.templates = []
|
||||
this.elseTemplates = []
|
||||
|
||||
let p
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('start', () => (p = this.templates))
|
||||
.on('tag:else', () => (p = this.elseTemplates))
|
||||
.on('tag:endfor', () => stream.stop())
|
||||
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
|
||||
stream.start()
|
||||
},
|
||||
render: async function (ctx: Context, hash: Hash) {
|
||||
let collection = await evalExp(this.collection, ctx)
|
||||
|
||||
if (!isArray(collection)) {
|
||||
if (isString(collection) && collection.length > 0) {
|
||||
collection = [collection] as string[]
|
||||
} else if (isObject(collection)) {
|
||||
collection = Object.keys(collection).map((key) => [key, collection[key]])
|
||||
}
|
||||
}
|
||||
if (!isArray(collection) || !collection.length) {
|
||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, ctx)
|
||||
}
|
||||
|
||||
const offset = hash.offset || 0
|
||||
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
||||
|
||||
collection = collection.slice(offset, offset + limit)
|
||||
if (this.reversed) collection.reverse()
|
||||
|
||||
const context = { forloop: new ForloopDrop(collection.length) }
|
||||
ctx.push(context)
|
||||
let html = ''
|
||||
for (const item of collection) {
|
||||
context[this.variable] = item
|
||||
try {
|
||||
html += await this.liquid.renderer.renderTemplates(this.templates, ctx)
|
||||
} catch (e) {
|
||||
if (e.name === 'RenderBreakError') {
|
||||
html += e.resolvedHTML
|
||||
if (e.message === 'break') break
|
||||
} else throw e
|
||||
}
|
||||
context.forloop.next()
|
||||
}
|
||||
ctx.pop()
|
||||
return html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { evalExp, isTruthy } from '../../render/syntax'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import ITemplate from '../../template/itemplate'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
this.branches = []
|
||||
this.elseTemplates = []
|
||||
|
||||
let p
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('start', () => this.branches.push({
|
||||
cond: tagToken.args,
|
||||
templates: (p = [])
|
||||
}))
|
||||
.on('tag:elsif', (token: TagToken) => {
|
||||
this.branches.push({
|
||||
cond: token.args,
|
||||
templates: p = []
|
||||
})
|
||||
})
|
||||
.on('tag:else', () => (p = this.elseTemplates))
|
||||
.on('tag:endif', () => stream.stop())
|
||||
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
|
||||
stream.start()
|
||||
},
|
||||
|
||||
render: async function (ctx: Context) {
|
||||
for (const branch of this.branches) {
|
||||
const cond = await evalExp(branch.cond, ctx)
|
||||
if (isTruthy(cond)) {
|
||||
return this.liquid.renderer.renderTemplates(branch.templates, ctx)
|
||||
}
|
||||
}
|
||||
return this.liquid.renderer.renderTemplates(this.elseTemplates, ctx)
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,60 @@
|
||||
import assert from '../../util/assert'
|
||||
import { value, quotedLine } from '../../parser/lexical'
|
||||
import { evalValue } from '../../render/syntax'
|
||||
import BlockMode from '../../context/block-mode'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Context from '../../context/context'
|
||||
import Hash from '../../template/tag/hash'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const staticFileRE = /[^\s,]+/
|
||||
const withRE = new RegExp(`with\\s+(${value.source})`)
|
||||
|
||||
export default <ITagImplOptions>{
|
||||
parse: function (token: TagToken) {
|
||||
let match = staticFileRE.exec(token.args)
|
||||
if (match) {
|
||||
this.staticValue = match[0]
|
||||
}
|
||||
|
||||
match = value.exec(token.args)
|
||||
if (match) {
|
||||
this.value = match[0]
|
||||
}
|
||||
|
||||
match = withRE.exec(token.args)
|
||||
if (match) {
|
||||
this.with = match[1]
|
||||
}
|
||||
},
|
||||
render: async function (ctx: Context, hash: Hash) {
|
||||
let filepath
|
||||
if (ctx.opts.dynamicPartials) {
|
||||
if (quotedLine.exec(this.value)) {
|
||||
const template = this.value.slice(1, -1)
|
||||
filepath = await this.liquid.parseAndRender(template, ctx.getAll(), ctx.opts)
|
||||
} else {
|
||||
filepath = await evalValue(this.value, ctx)
|
||||
}
|
||||
} else {
|
||||
filepath = this.staticValue
|
||||
}
|
||||
assert(filepath, `cannot include with empty filename`)
|
||||
|
||||
const originBlocks = ctx.blocks
|
||||
const originBlockMode = ctx.blockMode
|
||||
|
||||
ctx.blocks = {}
|
||||
ctx.blockMode = BlockMode.OUTPUT
|
||||
if (this.with) {
|
||||
hash[filepath] = await evalValue(this.with, ctx)
|
||||
}
|
||||
const templates = await this.liquid.getTemplate(filepath, ctx.opts)
|
||||
ctx.push(hash)
|
||||
const html = await this.liquid.renderer.renderTemplates(templates, ctx)
|
||||
ctx.pop(hash)
|
||||
ctx.blocks = originBlocks
|
||||
ctx.blockMode = originBlockMode
|
||||
return html
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import assert from '../../util/assert'
|
||||
import { identifier } from '../../parser/lexical'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
export default {
|
||||
parse: function (token) {
|
||||
const match = token.args.match(identifier)
|
||||
assert(match, `illegal identifier ${token.args}`)
|
||||
this.variable = match![0]
|
||||
},
|
||||
render: function (context) {
|
||||
const scope = context.environments
|
||||
if (typeof scope[this.variable] !== 'number') {
|
||||
scope[this.variable] = 0
|
||||
}
|
||||
const val = scope[this.variable]
|
||||
scope[this.variable]++
|
||||
return val
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,24 @@
|
||||
import assign from './assign'
|
||||
import For from './for'
|
||||
import capture from './capture'
|
||||
import Case from './case'
|
||||
import comment from './comment'
|
||||
import include from './include'
|
||||
import decrement from './decrement'
|
||||
import cycle from './cycle'
|
||||
import If from './if'
|
||||
import increment from './increment'
|
||||
import layout from './layout'
|
||||
import block from './block'
|
||||
import raw from './raw'
|
||||
import tablerow from './tablerow'
|
||||
import unless from './unless'
|
||||
import Break from './break'
|
||||
import Continue from './continue'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const tags: { [key: string]: ITagImplOptions } = {
|
||||
assign, 'for': For, capture, 'case': Case, comment, include, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue
|
||||
}
|
||||
|
||||
export default tags
|
||||
@@ -0,0 +1,46 @@
|
||||
import assert from '../../util/assert'
|
||||
import { value as rValue } from '../../parser/lexical'
|
||||
import { evalValue } from '../../render/syntax'
|
||||
import BlockMode from '../../context/block-mode'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import Hash from '../../template/tag/hash'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
const staticFileRE = /\S+/
|
||||
|
||||
export default {
|
||||
parse: function (token: TagToken, remainTokens: Token[]) {
|
||||
let match = staticFileRE.exec(token.args)
|
||||
if (match) {
|
||||
this.staticLayout = match[0]
|
||||
}
|
||||
|
||||
match = rValue.exec(token.args)
|
||||
if (match) {
|
||||
this.layout = match[0]
|
||||
}
|
||||
|
||||
this.tpls = this.liquid.parser.parse(remainTokens)
|
||||
},
|
||||
render: async function (ctx: Context, hash: Hash) {
|
||||
const layout = ctx.opts.dynamicPartials
|
||||
? await evalValue(this.layout, ctx)
|
||||
: this.staticLayout
|
||||
assert(layout, `cannot apply layout with empty filename`)
|
||||
|
||||
// render the remaining tokens immediately
|
||||
ctx.blockMode = BlockMode.STORE
|
||||
const html = await this.liquid.renderer.renderTemplates(this.tpls, ctx)
|
||||
if (ctx.blocks[''] === undefined) {
|
||||
ctx.blocks[''] = html
|
||||
}
|
||||
const templates = await this.liquid.getTemplate(layout, ctx.opts)
|
||||
ctx.push(hash)
|
||||
ctx.blockMode = BlockMode.OUTPUT
|
||||
const partial = await this.liquid.renderer.renderTemplates(templates, ctx)
|
||||
ctx.pop(hash)
|
||||
return partial
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,23 @@
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
|
||||
export default <ITagImplOptions>{
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
this.tokens = []
|
||||
|
||||
const stream = this.liquid.parser.parseStream(remainTokens)
|
||||
stream
|
||||
.on('token', (token: TagToken) => {
|
||||
if (token.name === 'endraw') stream.stop()
|
||||
else this.tokens.push(token)
|
||||
})
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
stream.start()
|
||||
},
|
||||
render: function () {
|
||||
return this.tokens.map((token: Token) => token.raw).join('')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import assert from '../../util/assert'
|
||||
import { evalExp } from '../../render/syntax'
|
||||
import { identifier, value, hash } from '../../parser/lexical'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import ITemplate from '../../template/itemplate'
|
||||
import Context from '../../context/context'
|
||||
import Hash from '../../template/tag/hash'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
import { TablerowloopDrop } from '../../drop/tablerowloop-drop'
|
||||
|
||||
const re = new RegExp(`^(${identifier.source})\\s+in\\s+` +
|
||||
`(${value.source})` +
|
||||
`(?:\\s+${hash.source})*$`)
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
const match = re.exec(tagToken.args) as RegExpExecArray
|
||||
assert(match, `illegal tag: ${tagToken.raw}`)
|
||||
|
||||
this.variable = match[1]
|
||||
this.collection = match[2]
|
||||
this.templates = []
|
||||
|
||||
let p
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('start', () => (p = this.templates))
|
||||
.on('tag:endtablerow', () => stream.stop())
|
||||
.on('template', (tpl: ITemplate) => p.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
|
||||
stream.start()
|
||||
},
|
||||
|
||||
render: async function (ctx: Context, hash: Hash) {
|
||||
let collection = await evalExp(this.collection, ctx) || []
|
||||
const offset = hash.offset || 0
|
||||
const limit = (hash.limit === undefined) ? collection.length : hash.limit
|
||||
|
||||
collection = collection.slice(offset, offset + limit)
|
||||
const cols = hash.cols || collection.length
|
||||
|
||||
const tablerowloop = new TablerowloopDrop(collection.length, cols)
|
||||
const scope = { tablerowloop }
|
||||
ctx.push(scope)
|
||||
|
||||
let html = ''
|
||||
for (let idx = 0; idx < collection.length; idx++, tablerowloop.next()) {
|
||||
scope[this.variable] = collection[idx]
|
||||
if (tablerowloop.col0() === 0) {
|
||||
if (tablerowloop.row() !== 1) html += '</tr>'
|
||||
html += `<tr class="row${tablerowloop.row()}">`
|
||||
}
|
||||
html += `<td class="col${tablerowloop.col()}">`
|
||||
html += await this.liquid.renderer.renderTemplates(this.templates, ctx)
|
||||
html += '</td>'
|
||||
}
|
||||
if (collection.length) html += '</tr>'
|
||||
ctx.pop(scope)
|
||||
return html
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,34 @@
|
||||
import { evalExp, isFalsy } from '../../render/syntax'
|
||||
import TagToken from '../../parser/tag-token'
|
||||
import Token from '../../parser/token'
|
||||
import Context from '../../context/context'
|
||||
import ITagImplOptions from '../../template/tag/itag-impl-options'
|
||||
import ParseStream from '../../parser/parse-stream'
|
||||
|
||||
export default {
|
||||
parse: function (tagToken: TagToken, remainTokens: Token[]) {
|
||||
this.templates = []
|
||||
this.elseTemplates = []
|
||||
let p
|
||||
const stream: ParseStream = this.liquid.parser.parseStream(remainTokens)
|
||||
.on('start', () => {
|
||||
p = this.templates
|
||||
this.cond = tagToken.args
|
||||
})
|
||||
.on('tag:else', () => (p = this.elseTemplates))
|
||||
.on('tag:endunless', () => stream.stop())
|
||||
.on('template', tpl => p.push(tpl))
|
||||
.on('end', () => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`)
|
||||
})
|
||||
|
||||
stream.start()
|
||||
},
|
||||
|
||||
render: async function (ctx: Context) {
|
||||
const cond = await evalExp(this.cond, ctx)
|
||||
return isFalsy(cond)
|
||||
? this.liquid.renderer.renderTemplates(this.templates, ctx)
|
||||
: this.liquid.renderer.renderTemplates(this.elseTemplates, ctx)
|
||||
}
|
||||
} as ITagImplOptions
|
||||
@@ -0,0 +1,8 @@
|
||||
enum BlockMode {
|
||||
/* store rendered html into blocks */
|
||||
OUTPUT,
|
||||
/* output rendered html directly */
|
||||
STORE
|
||||
}
|
||||
|
||||
export default BlockMode
|
||||
@@ -0,0 +1,147 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import { Drop } from '../drop/drop'
|
||||
import { __assign } from 'tslib'
|
||||
import assert from '../util/assert'
|
||||
import { NormalizedFullOptions, applyDefault } from '../liquid-options'
|
||||
import BlockMode from './block-mode'
|
||||
import { Scope } from './scope'
|
||||
|
||||
export default class Context {
|
||||
opts: NormalizedFullOptions
|
||||
scopes: Array<Scope> = [{}]
|
||||
environments: Scope
|
||||
blocks: object = {}
|
||||
groups: {[key: string]: number} = {}
|
||||
blockMode: BlockMode = BlockMode.OUTPUT
|
||||
constructor (ctx: object = {}, opts?: NormalizedFullOptions) {
|
||||
this.opts = applyDefault(opts)
|
||||
this.environments = ctx
|
||||
}
|
||||
getAll () {
|
||||
return [this.environments, ...this.scopes]
|
||||
.reduce((ctx, val) => __assign(ctx, val), {})
|
||||
}
|
||||
async get (path: string) {
|
||||
const paths = await this.parseProp(path)
|
||||
let ctx = this.findScope(paths[0]) || this.environments
|
||||
for (const path of paths) {
|
||||
ctx = this.readProperty(ctx, path)
|
||||
if (_.isNil(ctx) && this.opts.strictVariables) {
|
||||
throw new TypeError(`undefined variable: ${path}`)
|
||||
}
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
push (ctx: object) {
|
||||
return this.scopes.push(ctx)
|
||||
}
|
||||
pop (ctx?: object): object | undefined {
|
||||
if (!arguments.length) {
|
||||
return this.scopes.pop()
|
||||
}
|
||||
const i = this.scopes.findIndex(scope => scope === ctx)
|
||||
if (i === -1) {
|
||||
throw new TypeError('scope not found, cannot pop')
|
||||
}
|
||||
return this.scopes.splice(i, 1)[0]
|
||||
}
|
||||
private findScope (key: string) {
|
||||
for (let i = this.scopes.length - 1; i >= 0; i--) {
|
||||
const candidate = this.scopes[i]
|
||||
if (key in candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
private readProperty (obj: Scope, key: string) {
|
||||
if (_.isNil(obj)) return obj
|
||||
obj = _.toLiquid(obj)
|
||||
if (obj instanceof Drop) {
|
||||
if (_.isFunction(obj[key])) return obj[key]()
|
||||
if (obj.hasOwnProperty(key)) return obj[key]
|
||||
return obj.liquidMethodMissing(key)
|
||||
}
|
||||
return key === 'size' ? readSize(obj) : obj[key]
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse property access sequence from access string
|
||||
* @example
|
||||
* accessSeq("foo.bar") // ['foo', 'bar']
|
||||
* accessSeq("foo['bar']") // ['foo', 'bar']
|
||||
* accessSeq("foo['b]r']") // ['foo', 'b]r']
|
||||
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
||||
*/
|
||||
private async parseProp (str: string) {
|
||||
str = String(str)
|
||||
const seq: string[] = []
|
||||
let name = ''
|
||||
let j
|
||||
let i = 0
|
||||
while (i < str.length) {
|
||||
switch (str[i]) {
|
||||
case '[':
|
||||
push()
|
||||
|
||||
const delemiter = str[i + 1]
|
||||
if (/['"]/.test(delemiter)) { // foo["bar"]
|
||||
j = str.indexOf(delemiter, i + 2)
|
||||
assert(j !== -1, `unbalanced ${delemiter}: ${str}`)
|
||||
name = str.slice(i + 2, j)
|
||||
push()
|
||||
i = j + 2
|
||||
} else { // foo[bar.coo]
|
||||
j = matchRightBracket(str, i + 1)
|
||||
assert(j !== -1, `unbalanced []: ${str}`)
|
||||
name = str.slice(i + 1, j)
|
||||
if (!/^[+-]?\d+$/.test(name)) { // foo[bar] vs. foo[1]
|
||||
name = String(await this.get(name))
|
||||
}
|
||||
push()
|
||||
i = j + 1
|
||||
}
|
||||
break
|
||||
case '.':// foo.bar, foo[0].bar
|
||||
push()
|
||||
i++
|
||||
break
|
||||
default:// foo.bar
|
||||
name += str[i++]
|
||||
}
|
||||
}
|
||||
push()
|
||||
|
||||
if (!seq.length) {
|
||||
throw new TypeError(`invalid path:"${str}"`)
|
||||
}
|
||||
return seq
|
||||
|
||||
function push () {
|
||||
if (name.length) seq.push(name)
|
||||
name = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readSize (obj: Scope) {
|
||||
if (!_.isNil(obj['size'])) return obj['size']
|
||||
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
||||
return obj['size']
|
||||
}
|
||||
|
||||
function matchRightBracket (str: string, begin: number) {
|
||||
let stack = 1 // count of '[' - count of ']'
|
||||
for (let i = begin; i < str.length; i++) {
|
||||
if (str[i] === '[') {
|
||||
stack++
|
||||
}
|
||||
if (str[i] === ']') {
|
||||
stack--
|
||||
if (stack === 0) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Drop } from '../drop/drop'
|
||||
|
||||
type PlainObject = {
|
||||
[key: string]: any
|
||||
toLiquid?: () => any
|
||||
}
|
||||
|
||||
export type Scope = PlainObject | Drop
|
||||
@@ -0,0 +1,12 @@
|
||||
import { isNil, isString } from '../util/underscore'
|
||||
import { Drop } from '../drop/drop'
|
||||
import { EmptyDrop } from '../drop/empty-drop'
|
||||
|
||||
export class BlankDrop extends EmptyDrop {
|
||||
equals (value: any) {
|
||||
if (value === false) return true
|
||||
if (isNil(value instanceof Drop ? value.valueOf() : value)) return true
|
||||
if (isString(value)) return /^\s*$/.test(value)
|
||||
return super.equals(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
export abstract class Drop {
|
||||
valueOf (): any {
|
||||
return undefined
|
||||
}
|
||||
|
||||
liquidMethodMissing (key: string): Promise<string | undefined> | string | undefined {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Drop } from './drop'
|
||||
import { IComparable } from './icomparable'
|
||||
import { isObject, isString, isArray } from '../util/underscore'
|
||||
|
||||
export class EmptyDrop extends Drop implements IComparable {
|
||||
equals (value: any) {
|
||||
if (isString(value) || isArray(value)) return value.length === 0
|
||||
if (isObject(value)) return Object.keys(value).length === 0
|
||||
return false
|
||||
}
|
||||
gt () {
|
||||
return false
|
||||
}
|
||||
geq () {
|
||||
return false
|
||||
}
|
||||
lt () {
|
||||
return false
|
||||
}
|
||||
leq () {
|
||||
return false
|
||||
}
|
||||
valueOf () {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Drop } from './drop'
|
||||
|
||||
export class ForloopDrop extends Drop {
|
||||
protected i: number = 0
|
||||
length: number
|
||||
constructor (length: number) {
|
||||
super()
|
||||
this.length = length
|
||||
}
|
||||
next () {
|
||||
this.i++
|
||||
}
|
||||
index0 () {
|
||||
return this.i
|
||||
}
|
||||
index () {
|
||||
return this.i + 1
|
||||
}
|
||||
first () {
|
||||
return this.i === 0
|
||||
}
|
||||
last () {
|
||||
return this.i === this.length - 1
|
||||
}
|
||||
rindex () {
|
||||
return this.length - this.i
|
||||
}
|
||||
rindex0 () {
|
||||
return this.length - this.i - 1
|
||||
}
|
||||
valueOf () {
|
||||
return JSON.stringify(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { isFunction } from '../util/underscore'
|
||||
|
||||
export interface IComparable {
|
||||
equals: (rhs: any) => boolean
|
||||
gt: (rhs: any) => boolean
|
||||
geq: (rhs: any) => boolean
|
||||
lt: (rhs: any) => boolean
|
||||
leq: (rhs: any) => boolean
|
||||
}
|
||||
|
||||
export function isComparable (arg: any): arg is IComparable {
|
||||
return arg && isFunction(arg.equals)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Drop } from './drop'
|
||||
import { IComparable } from './icomparable'
|
||||
import { isNil } from '../util/underscore'
|
||||
import { BlankDrop } from '../drop/blank-drop'
|
||||
|
||||
export class NullDrop extends Drop implements IComparable {
|
||||
equals (value: any) {
|
||||
return isNil(value instanceof Drop ? value.valueOf() : value) || value instanceof BlankDrop
|
||||
}
|
||||
gt () {
|
||||
return false
|
||||
}
|
||||
geq () {
|
||||
return false
|
||||
}
|
||||
lt () {
|
||||
return false
|
||||
}
|
||||
leq () {
|
||||
return false
|
||||
}
|
||||
valueOf () {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ForloopDrop } from './forloop-drop'
|
||||
|
||||
export class TablerowloopDrop extends ForloopDrop {
|
||||
private cols: number
|
||||
constructor (length: number, cols: number) {
|
||||
super(length)
|
||||
this.length = length
|
||||
this.cols = cols
|
||||
}
|
||||
row () {
|
||||
return Math.floor(this.i / this.cols) + 1
|
||||
}
|
||||
col0 () {
|
||||
return (this.i % this.cols)
|
||||
}
|
||||
col () {
|
||||
return this.col0() + 1
|
||||
}
|
||||
col_first () { // eslint-disable-line
|
||||
return this.col0() === 0
|
||||
}
|
||||
col_last () { // eslint-disable-line
|
||||
return this.col() === this.cols
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
const lexical = require('./lexical.js');
|
||||
const Syntax = require('./syntax.js');
|
||||
const assert = require('./util/assert.js');
|
||||
const _ = require('./util/underscore.js');
|
||||
|
||||
var valueRE = new RegExp(`${lexical.value.source}`, 'g');
|
||||
|
||||
module.exports = function(options) {
|
||||
options = _.assign({}, options);
|
||||
var filters = {};
|
||||
|
||||
var _filterInstance = {
|
||||
render: function(output, scope) {
|
||||
var args = this.args.map(arg => Syntax.evalValue(arg, scope));
|
||||
args.unshift(output);
|
||||
return this.filter.apply(null, args);
|
||||
},
|
||||
parse: function(str) {
|
||||
var match = lexical.filterLine.exec(str);
|
||||
assert(match, 'illegal filter: ' + str);
|
||||
|
||||
var name = match[1], argList = match[2] || '', filter = filters[name];
|
||||
if (typeof filter !== 'function'){
|
||||
if(options.strict_filters){
|
||||
throw new TypeError(`undefined filter: ${name}`);
|
||||
}
|
||||
this.name= name;
|
||||
this.filter= x => x;
|
||||
this.args= [];
|
||||
return this;
|
||||
//return {
|
||||
//name: name,
|
||||
//error: new TypeError(`undefined filter: ${name}`)
|
||||
//};
|
||||
}
|
||||
|
||||
var args = [];
|
||||
while(match = valueRE.exec(argList.trim())){
|
||||
args.push(match[0]);
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.filter = filter;
|
||||
this.args = args;
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
function construct(str) {
|
||||
var instance = Object.create(_filterInstance);
|
||||
return instance.parse(str);
|
||||
}
|
||||
|
||||
function register(name, filter) {
|
||||
filters[name] = filter;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
filters = {};
|
||||
}
|
||||
|
||||
return {
|
||||
construct, register, clear
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { last } from '../util/underscore'
|
||||
import IFS from './ifs'
|
||||
|
||||
function domResolve (root: string, path: string) {
|
||||
const base = document.createElement('base')
|
||||
base.href = root
|
||||
|
||||
const head = document.getElementsByTagName('head')[0]
|
||||
head.insertBefore(base, head.firstChild)
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = path
|
||||
const resolved = a.href
|
||||
head.removeChild(base)
|
||||
|
||||
return resolved
|
||||
}
|
||||
|
||||
function resolve (root: string, filepath: string, ext: string) {
|
||||
if (root.length && last(root) !== '/') root += '/'
|
||||
const url = domResolve(root, filepath)
|
||||
return url.replace(/^(\w+:\/\/[^/]+)(\/[^?]+)/, (str, origin, path) => {
|
||||
const last = path.split('/').pop()
|
||||
if (/\.\w+$/.test(last)) return str
|
||||
return origin + path + ext
|
||||
})
|
||||
}
|
||||
|
||||
async function readFile (url: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.responseText as string)
|
||||
} else {
|
||||
reject(new Error(xhr.statusText))
|
||||
}
|
||||
}
|
||||
xhr.onerror = () => {
|
||||
reject(new Error('An error occurred whilst receiving the response.'))
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
})
|
||||
}
|
||||
|
||||
async function exists () {
|
||||
return true
|
||||
}
|
||||
|
||||
export default { readFile, resolve, exists } as IFS
|
||||
@@ -0,0 +1,5 @@
|
||||
export default interface IFS {
|
||||
exists: (filepath: string) => Promise<boolean>
|
||||
readFile: (filepath:string) => Promise<string>
|
||||
resolve: (root: string, file: string, ext: string) => string
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import { resolve, extname } from 'path'
|
||||
import { stat, readFile } from 'fs'
|
||||
import IFS from './ifs'
|
||||
|
||||
const statAsync = _.promisify(stat)
|
||||
const readFileAsync = _.promisify<string, string, string>(readFile)
|
||||
|
||||
const fs: IFS = {
|
||||
exists: (filepath: string) => {
|
||||
return statAsync(filepath).then(() => true).catch(() => false)
|
||||
},
|
||||
readFile: filepath => {
|
||||
return readFileAsync(filepath, 'utf8')
|
||||
},
|
||||
resolve: (root: string, file: string, ext: string) => {
|
||||
if (!extname(file)) file += ext
|
||||
return resolve(root, file)
|
||||
}
|
||||
}
|
||||
|
||||
export default fs
|
||||
@@ -1,91 +0,0 @@
|
||||
// quote related
|
||||
var singleQuoted = /'[^']*'/;
|
||||
var doubleQuoted = /"[^"]*"/;
|
||||
var quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
|
||||
var quoteBalanced = new RegExp(`(?:${quoted.source}|[^'"])*`);
|
||||
|
||||
// basic types
|
||||
var integer = /-?\d+/;
|
||||
var number = /-?\d+\.?\d*|\.?\d+/;
|
||||
var bool = /true|false/;
|
||||
|
||||
// peoperty access
|
||||
var identifier = /[\w-]+/;
|
||||
var subscript = new RegExp(`\\[(?:${quoted.source}|[\\w-\\.]+)\\]`);
|
||||
var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`);
|
||||
var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`);
|
||||
|
||||
// range related
|
||||
var rangeLimit = new RegExp(`(?:${variable.source}|${number.source})`);
|
||||
var range = new RegExp(`\\(${rangeLimit.source}\\.\\.${rangeLimit.source}\\)`);
|
||||
var rangeCapture = new RegExp(`\\((${rangeLimit.source})\\.\\.(${rangeLimit.source})\\)`);
|
||||
|
||||
var value = new RegExp(`(?:${variable.source}|${literal.source}|${range.source})`);
|
||||
|
||||
// hash related
|
||||
var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`);
|
||||
var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
|
||||
|
||||
// full match
|
||||
var tagLine = new RegExp(`^\\s*(${identifier.source})\\s*(.*)\\s*$`);
|
||||
var literalLine = new RegExp(`^${literal.source}$`, 'i');
|
||||
var variableLine = new RegExp(`^${variable.source}$`);
|
||||
var numberLine = new RegExp(`^${number.source}$`);
|
||||
var boolLine = new RegExp(`^${bool.source}$`, 'i');
|
||||
var quotedLine = new RegExp(`^${quoted.source}$`);
|
||||
var rangeLine = new RegExp(`^${rangeCapture.source}$`);
|
||||
var integerLine = new RegExp(`^${integer.source}$`);
|
||||
|
||||
// filter related
|
||||
var valueList = new RegExp(`${value.source}(\\s*,\\s*${value.source})*`);
|
||||
var filter = new RegExp(`${identifier.source}(?:\\s*:\\s*${valueList.source})?`, 'g');
|
||||
var filterCapture = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${valueList.source}))?`);
|
||||
var filterLine = new RegExp(`^${filterCapture.source}$`);
|
||||
|
||||
var operators = [
|
||||
/\s+or\s+/,
|
||||
/\s+and\s+/,
|
||||
/==|!=|<=|>=|<|>|\s+contains\s+/
|
||||
];
|
||||
|
||||
function isInteger(str){
|
||||
return integerLine.test(str);
|
||||
}
|
||||
|
||||
function isLiteral(str) {
|
||||
return literalLine.test(str);
|
||||
}
|
||||
|
||||
function isRange(str) {
|
||||
return rangeLine.test(str);
|
||||
}
|
||||
|
||||
function isVariable(str) {
|
||||
return variableLine.test(str);
|
||||
}
|
||||
|
||||
function matchValue(str) {
|
||||
return value.exec(str);
|
||||
}
|
||||
|
||||
function parseLiteral(str) {
|
||||
var res;
|
||||
if (res = str.match(numberLine)) {
|
||||
return Number(str);
|
||||
}
|
||||
if (res = str.match(boolLine)) {
|
||||
return str.toLowerCase() === 'true';
|
||||
}
|
||||
if (res = str.match(quotedLine)) {
|
||||
return str.slice(1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
quoted, number, bool, literal, filter, integer,
|
||||
hash, hashCapture,
|
||||
range, rangeCapture,
|
||||
identifier, value, quoteBalanced, operators,
|
||||
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine,
|
||||
isLiteral, isVariable, parseLiteral, isRange, matchValue, isInteger
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import * as _ from './util/underscore'
|
||||
|
||||
export interface LiquidOptions {
|
||||
/** `root` is a directory or an array of directories to resolve layouts and includes, as well as the filename passed in when calling `.renderFile()`. If an array, the files are looked up in the order they occur in the array. Defaults to `["."]` */
|
||||
root?: string | string[]
|
||||
/** `extname` is used to lookup the template file when filepath doesn't include an extension name. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
|
||||
extname?: string
|
||||
/** `cache` indicates whether or not to cache resolved templates. Defaults to `false`. */
|
||||
cache?: boolean
|
||||
/** `dynamicPartials`: if set, treat `<filepath>` parameter in `{%include filepath %}`, `{%layout filepath%}` as a variable, otherwise as a literal value. Defaults to `true`. */
|
||||
dynamicPartials?: boolean
|
||||
/** `strictFilters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`. */
|
||||
strictFilters?: boolean
|
||||
/** `strictVariables` is used to enable strict variable derivation. If set to `false`, undefined variables will be rendered as empty string. Otherwise, undefined variables will cause an exception. Defaults to `false`. */
|
||||
strictVariables?: boolean
|
||||
/** `trimTagRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of tags (`{% %}`) until `\n` (inclusive). Defaults to `false`. */
|
||||
trimTagRight?: boolean
|
||||
/** `trimTagLeft` is similar to `trimTagRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
|
||||
trimTagLeft?: boolean
|
||||
/** ``trimOutputRight` is used to strip blank characters (including ` `, `\t`, and `\r`) from the right of values (`{{ }}`) until `\n` (inclusive). Defaults to `false`. */
|
||||
trimOutputRight?: boolean
|
||||
/** `trimOutputLeft` is similar to `trimOutputRight`, whereas the `\n` is exclusive. Defaults to `false`. See Whitespace Control for details. */
|
||||
trimOutputLeft?: boolean
|
||||
/** `tagDelimiterLeft` and `tagDelimiterRight` are used to override the delimiter for liquid tags **/
|
||||
tagDelimiterLeft?: string,
|
||||
tagDelimiterRight?: string,
|
||||
/** `outputDelimiterLeft` and `outputDelimiterRight` are used to override the delimiter for liquid outputs **/
|
||||
outputDelimiterLeft?: string,
|
||||
outputDelimiterRight?: string,
|
||||
/** `greedy` is used to specify 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
|
||||
}
|
||||
|
||||
interface NormalizedOptions extends LiquidOptions {
|
||||
root?: string[]
|
||||
}
|
||||
|
||||
export interface NormalizedFullOptions extends NormalizedOptions {
|
||||
root: string[]
|
||||
extname: string
|
||||
cache: boolean
|
||||
dynamicPartials: boolean
|
||||
strictFilters: boolean
|
||||
strictVariables: boolean
|
||||
trimTagRight: boolean
|
||||
trimTagLeft: boolean
|
||||
trimOutputRight: boolean
|
||||
trimOutputLeft: boolean
|
||||
tagDelimiterLeft: string,
|
||||
tagDelimiterRight: string,
|
||||
outputDelimiterLeft: string,
|
||||
outputDelimiterRight: string,
|
||||
greedy: boolean
|
||||
}
|
||||
|
||||
const defaultOptions: NormalizedFullOptions = {
|
||||
root: ['.'],
|
||||
cache: false,
|
||||
extname: '',
|
||||
dynamicPartials: true,
|
||||
trimTagRight: false,
|
||||
trimTagLeft: false,
|
||||
trimOutputRight: false,
|
||||
trimOutputLeft: false,
|
||||
greedy: true,
|
||||
tagDelimiterLeft: '{%',
|
||||
tagDelimiterRight: '%}',
|
||||
outputDelimiterLeft: '{{',
|
||||
outputDelimiterRight: '}}',
|
||||
strictFilters: false,
|
||||
strictVariables: false
|
||||
}
|
||||
|
||||
export function normalize (options?: LiquidOptions): NormalizedOptions {
|
||||
options = options || {}
|
||||
if (options.hasOwnProperty('root')) {
|
||||
options.root = normalizeStringArray(options.root)
|
||||
}
|
||||
return options as NormalizedOptions
|
||||
}
|
||||
|
||||
export function applyDefault (options?: NormalizedOptions): NormalizedFullOptions {
|
||||
return { ...defaultOptions, ...options }
|
||||
}
|
||||
|
||||
function normalizeStringArray (value: any): string[] {
|
||||
if (_.isArray(value)) return value as string[]
|
||||
if (_.isString(value)) return [value as string]
|
||||
return []
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import Context from './context/context'
|
||||
import * as Types from './types'
|
||||
import fs from './fs/node'
|
||||
import * as _ from './util/underscore'
|
||||
import ITemplate from './template/itemplate'
|
||||
import Tokenizer from './parser/tokenizer'
|
||||
import Render from './render/render'
|
||||
import Tag from './template/tag/tag'
|
||||
import { Filter } from './template/filter/filter'
|
||||
import Parser from './parser/parser'
|
||||
import ITagImplOptions from './template/tag/itag-impl-options'
|
||||
import Value from './template/value'
|
||||
import { isTruthy, isFalsy, evalExp, evalValue } from './render/syntax'
|
||||
import builtinTags from './builtin/tags'
|
||||
import builtinFilters from './builtin/filters'
|
||||
import { LiquidOptions, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
|
||||
import { FilterImpl } from './template/filter/filter-impl'
|
||||
|
||||
export default class Liquid {
|
||||
public options: NormalizedFullOptions
|
||||
public renderer: Render
|
||||
public parser: Parser
|
||||
private cache: object = {}
|
||||
private tokenizer: Tokenizer
|
||||
|
||||
constructor (opts: LiquidOptions = {}) {
|
||||
this.options = applyDefault(normalize(opts))
|
||||
this.parser = new Parser(this)
|
||||
this.renderer = new Render()
|
||||
this.tokenizer = new Tokenizer(this.options)
|
||||
|
||||
_.forOwn(builtinTags, (conf, name) => this.registerTag(name, conf))
|
||||
_.forOwn(builtinFilters, (handler, name) => this.registerFilter(name, handler))
|
||||
}
|
||||
parse (html: string, filepath?: string) {
|
||||
const tokens = this.tokenizer.tokenize(html, filepath)
|
||||
return this.parser.parse(tokens)
|
||||
}
|
||||
render (tpl: Array<ITemplate>, ctx?: object, opts?: LiquidOptions) {
|
||||
const options = { ...this.options, ...normalize(opts) }
|
||||
const scope = new Context(ctx, options)
|
||||
return this.renderer.renderTemplates(tpl, scope)
|
||||
}
|
||||
async parseAndRender (html: string, ctx?: object, opts?: LiquidOptions) {
|
||||
const tpl = await this.parse(html)
|
||||
return this.render(tpl, ctx, opts)
|
||||
}
|
||||
async getTemplate (file: string, opts?: LiquidOptions) {
|
||||
const options = normalize(opts)
|
||||
const roots = options.root ? [...options.root, ...this.options.root] : this.options.root
|
||||
const paths = roots.map(root => fs.resolve(root, file, this.options.extname))
|
||||
|
||||
for (const filepath of paths) {
|
||||
if (!(await fs.exists(filepath))) continue
|
||||
|
||||
if (this.options.cache && this.cache[filepath]) return this.cache[filepath]
|
||||
const value = this.parse(await fs.readFile(filepath), filepath)
|
||||
if (this.options.cache) this.cache[filepath] = value
|
||||
return value
|
||||
}
|
||||
|
||||
const err = new Error('ENOENT') as any
|
||||
err.message = `ENOENT: Failed to lookup "${file}" in "${roots}"`
|
||||
err.code = 'ENOENT'
|
||||
throw err
|
||||
}
|
||||
async renderFile (file: string, ctx?: object, opts?: LiquidOptions) {
|
||||
const options = normalize(opts)
|
||||
const templates = await this.getTemplate(file, options)
|
||||
return this.render(templates, ctx, opts)
|
||||
}
|
||||
evalValue (str: string, ctx: Context) {
|
||||
return new Value(str, this.options.strictFilters).value(ctx)
|
||||
}
|
||||
registerFilter (name: string, filter: FilterImpl) {
|
||||
return Filter.register(name, filter)
|
||||
}
|
||||
registerTag (name: string, tag: ITagImplOptions) {
|
||||
return Tag.register(name, tag)
|
||||
}
|
||||
plugin (plugin: (this: Liquid, L: typeof Liquid) => void) {
|
||||
return plugin.call(this, Liquid)
|
||||
}
|
||||
express () {
|
||||
const self = this
|
||||
return function (this: any, filePath: string, ctx: object, cb: (err: Error | null, html?: string) => void) {
|
||||
const opts = { root: this.root }
|
||||
self.renderFile(filePath, ctx, opts).then(html => cb(null, html), cb)
|
||||
}
|
||||
}
|
||||
static default = Liquid
|
||||
static isTruthy = isTruthy
|
||||
static isFalsy = isFalsy
|
||||
static evalExp = evalExp
|
||||
static evalValue = evalValue
|
||||
static Types = Types
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
var operators = {
|
||||
'==': (l, r) => l == r,
|
||||
'!=': (l, r) => l != r,
|
||||
'>': (l, r) => l > r,
|
||||
'<': (l, r) => l < r,
|
||||
'>=': (l, r) => l >= r,
|
||||
'<=': (l, r) => l <= r,
|
||||
'contains': (l, r) => l.indexOf(r) > -1,
|
||||
'and': (l, r) => l && r,
|
||||
'or': (l, r) => l || r
|
||||
};
|
||||
|
||||
module.exports = operators;
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
const lexical = require('./lexical.js');
|
||||
const ParseError = require('./util/error.js').ParseError;
|
||||
const assert = require('./util/assert.js');
|
||||
|
||||
module.exports = function(Tag, Filter) {
|
||||
|
||||
var stream = {
|
||||
init: function(tokens) {
|
||||
this.tokens = tokens;
|
||||
this.handlers = {};
|
||||
return this;
|
||||
},
|
||||
on: function(name, cb) {
|
||||
this.handlers[name] = cb;
|
||||
return this;
|
||||
},
|
||||
trigger: function(event, arg) {
|
||||
var h = this.handlers[event];
|
||||
if (typeof h === 'function') {
|
||||
h(arg);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
start: function() {
|
||||
this.trigger('start');
|
||||
var token;
|
||||
while (!this.stopRequested && (token = this.tokens.shift())) {
|
||||
if (this.trigger('token', token)) continue;
|
||||
if (token.type == 'tag' &&
|
||||
this.trigger(`tag:${token.name}`, token)) {
|
||||
continue;
|
||||
}
|
||||
var template = parseToken(token, this.tokens);
|
||||
this.trigger('template', template);
|
||||
}
|
||||
if (!this.stopRequested) this.trigger('end');
|
||||
return this;
|
||||
},
|
||||
stop: function() {
|
||||
this.stopRequested = true;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
function parse(tokens) {
|
||||
var token, templates = [];
|
||||
while (token = tokens.shift()) {
|
||||
templates.push(parseToken(token, tokens));
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
function parseToken(token, tokens) {
|
||||
try {
|
||||
var tpl = null;
|
||||
if (token.type === 'tag') {
|
||||
tpl = parseTag(token, tokens);
|
||||
} else if (token.type === 'output') {
|
||||
tpl = parseOutput(token.value);
|
||||
} else { // token.type === 'html'
|
||||
tpl = token;
|
||||
}
|
||||
tpl.token = token;
|
||||
return tpl;
|
||||
} catch (e) {
|
||||
throw new ParseError(e, token);
|
||||
}
|
||||
}
|
||||
|
||||
function parseTag(token, tokens) {
|
||||
if (token.name === 'continue' || token.name === 'break') return token;
|
||||
return Tag.construct(token, tokens);
|
||||
}
|
||||
|
||||
function parseOutput(str) {
|
||||
var match = lexical.matchValue(str);
|
||||
assert(match, `illegal output string: ${str}`);
|
||||
|
||||
var initial = match[0];
|
||||
str = str.substr(match.index + match[0].length);
|
||||
|
||||
var filters = [];
|
||||
while (match = lexical.filter.exec(str)) {
|
||||
filters.push([match[0].trim()]);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'output',
|
||||
initial: initial,
|
||||
filters: filters.map(str => Filter.construct(str))
|
||||
};
|
||||
}
|
||||
|
||||
function parseStream(tokens) {
|
||||
var s = Object.create(stream);
|
||||
return s.init(tokens);
|
||||
}
|
||||
|
||||
return {
|
||||
parse,
|
||||
parseTag,
|
||||
parseStream,
|
||||
parseOutput
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import Token from './token'
|
||||
import { last } from '../util/underscore'
|
||||
|
||||
export default class DelimitedToken extends Token {
|
||||
constructor (
|
||||
raw: string,
|
||||
value: string,
|
||||
input: string,
|
||||
line: number,
|
||||
pos: number,
|
||||
trimLeft: boolean,
|
||||
trimRight: boolean,
|
||||
file?: string
|
||||
) {
|
||||
super(raw, input, line, pos, file)
|
||||
const tl = value[0] === '-'
|
||||
const tr = last(value) === '-'
|
||||
this.value = value
|
||||
.slice(
|
||||
tl ? 1 : 0,
|
||||
tr ? -1 : value.length
|
||||
)
|
||||
.trim()
|
||||
this.trimLeft = tl || trimLeft
|
||||
this.trimRight = tr || trimRight
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user