Compare commits

...
307 Commits
Author SHA1 Message Date
semantic-release-bot 7483613b23 chore(release): 8.1.0 [skip ci]
# [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))
2019-04-02 04:55:18 +00:00
harttle 7762dd9612 chore: fix linting 2019-04-02 12:10:35 +08:00
Jun Yang daa0b574fc feat: where filter, working on #118
doc: https://shopify.github.io/liquid/filters/where/
ruby impl: https://github.com/Shopify/liquid/blob/14cd011cb5eef042af192ad5294c8f0e1ae89f06/lib/liquid/standardfilters.rb
2019-04-02 00:46:32 +08:00
semantic-release-bot a084183246 chore(release): 8.0.3 [skip ci]
## [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))
2019-04-01 03:04:24 +00:00
harttle eadb6f3711 fix: slice filter on negative begin, #117 2019-04-01 10:57:08 +08:00
semantic-release-bot dba26f24e6 chore(release): 8.0.2 [skip ci]
## [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))
2019-03-25 15:15:04 +00:00
Jun Yang 722da6a7ee doc: update README 2019-03-25 23:01:04 +08:00
harttle 82d7673554 perf: use polymophism instead duck test 2019-03-25 20:11:23 +08:00
harttle 64dd057552 refactor: Context#propertyAccessSeq => parseProp 2019-03-25 10:36:23 +08:00
harttle 45e3c2bb8e refactor: switch Context <-> Scope concepts 2019-03-25 10:36:23 +08:00
Jun Yang 76345a64c3 doc: update README.md 2019-03-23 22:28:33 +08:00
semantic-release-bot 9e69652655 chore(release): 8.0.1 [skip ci]
## [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))
2019-03-22 12:48:41 +00:00
harttle defbb58e66 fix: incorrect scope when using assign with for, fixes #115
* remove AssignScope, CaptureScope, IncrementScope, DecrementScope concepts
* introduce Scope.environments for decrement/increment
* assign to scopes[0] for assign/capture
2019-03-22 20:38:54 +08:00
ssendevandJun Yang b7b92c63cf async Register Tags example 2019-03-21 20:46:21 +08:00
ssendevandJun Yang 265534b7b7 Fix Register Tags example 2019-03-21 20:46:21 +08:00
semantic-release-bot 84ea2c7488 chore(release): 8.0.0 [skip ci]
# [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
2019-03-10 11:28:39 +00:00
Jun Yang 64e0c876e9 refactor: use camelCase for JavaScript APIs
BREAKING CHANGE: Options and method names in JavaScript API are now renamed to cammelCase, for a complete list see #109
2019-03-10 19:21:09 +08:00
Jun Yang 4a8088d4e4 feat: promise support for drops, working on #65 2019-03-10 18:05:52 +08:00
harttle ad0930f152 test: cover parseFilters in template/value.ts 2019-03-06 01:46:35 +08:00
harttle 998832c772 refactor: mv custom filter test 2019-03-06 01:40:31 +08:00
semantic-release-bot 4ba224043b chore(release): 7.5.1 [skip ci]
## [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))
2019-03-05 17:39:56 +00:00
harttle 5ffc904f80 fix: named params for filters, working on #113 2019-03-06 01:30:47 +08:00
semantic-release-bot 08646f75b0 chore(release): 7.5.0 [skip ci]
# [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))
2019-03-01 22:06:31 +00:00
Jun Yang 3647305dcf feat: tablerowloop object
See: https://www.rubydoc.info/gems/liquid/Liquid/TablerowloopDrop
2019-03-02 06:00:30 +08:00
Jun Yang cca0306f5b chore: fix mocha pattern for windows
See: https://github.com/mochajs/mocha/issues/2895
2019-03-02 03:57:38 +08:00
Jun Yang eeeacc4492 chore: fix rollup.config.ts 2019-03-02 03:42:57 +08:00
Jun Yang d44e1d727a chore: fix test on windows 10 2019-03-02 03:36:33 +08:00
semantic-release-bot f960904df4 chore(release): 7.4.0 [skip ci]
# [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)
2019-02-28 05:13:55 +00:00
harttle b4acdb463a fix: math filters now return number, resolves #110
* fix linting
* numbers are no longer fixed:
    i.e. {{0.3 | minus 0.1}} now renders as 0.19999
    Stick to JavaScript number behavior, see: 8.0.0
2019-02-28 13:02:51 +08:00
harttle 21d674e22b chore: fix benchmark npm script 2019-02-28 00:05:37 +08:00
harttle 7bee9fc92d feat: exported Drop interface for #107
Deprecate snake_cased options and APIs, sed #109
2019-02-28 00:05:08 +08:00
harttle b69c3a3203 refactor: IDrop interface => abstract method 2019-02-28 00:05:08 +08:00
harttle d5b9916b2f refactor: Drop.prototype.value => valueOf 2019-02-28 00:05:08 +08:00
semantic-release-bot d0118402e4 chore(release): 7.3.1 [skip ci]
## [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)
2019-02-25 17:03:07 +00:00
harttle 31bc71d472 chore: fix linting 2019-02-26 00:56:12 +08:00
harttle 53a835ab54 fix(#108): remove absolute path in emitted d.ts 2019-02-26 00:43:30 +08:00
harttle 75b3e15ca9 chore: fix rollup.config.ts types 2019-02-25 00:12:31 +08:00
harttle f34573ee21 chore: stick with es6 for testing 2019-02-24 23:42:44 +08:00
harttle 875f71d5f2 chore: a simple liquid_method_missing case for #97 2019-02-24 23:08:53 +08:00
harttle 407ac6d0a4 refactor: remove /dist from repo 2019-02-24 23:03:12 +08:00
semantic-release-bot f9f35ebd6c chore(release): 7.3.0 [skip ci]
# [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))
2019-02-24 13:55:31 +00:00
harttle 88c9e96392 feat: nil/null/empty/blank literals, resolves #102 2019-02-24 21:50:01 +08:00
semantic-release-bot 54b2adce12 chore(release): 7.2.2 [skip ci]
## [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))
2019-02-23 22:32:04 +00:00
harttle 542968e133 refactor: remove unused lexical regexps 2019-02-24 06:26:33 +08:00
harttle 3b7351d9a0 chore: fix linting 2019-02-24 05:03:59 +08:00
harttle e977669118 fix: filters break when argument contains [()|, fixes #89 2019-02-24 04:48:42 +08:00
harttle 279458c670 test: add benchmark 2019-02-23 21:57:02 +08:00
semantic-release-bot 37d12a14eb chore(release): 7.2.1 [skip ci]
## [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))
2019-02-22 19:56:16 +00:00
harttle 3cf01603e8 chore: fix linting 2019-02-23 03:50:45 +08:00
harttle 56c7992b76 fix: default length for truncate and truncatewords 2019-02-23 03:11:07 +08:00
harttle 5b6100d12b refactor: strictly typed 2019-02-23 00:49:54 +08:00
Jun YangandGitHub 51f7e66f60 Update CHANGELOG.md 2019-02-21 01:19:25 +08:00
Jun YangandGitHub 952ab6e73b Update CHANGELOG.md 2019-02-21 01:16:32 +08:00
semantic-release-bot 4171b2e023 chore(release): 7.2.0 [skip ci]
# [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))
2019-02-20 17:12:01 +00:00
harttle d20a043fbb feat: override output/tag delimiter, fixes #54 2019-02-21 01:07:32 +08:00
harttle 3dbab238ae BREAKING CHANGE: trim_value_* -> trim_output_* 2019-02-21 01:07:32 +08:00
semantic-release-bot d7d00421e2 chore(release): 7.1.0 [skip ci]
# [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))
2019-02-20 14:45:44 +00:00
harttle c33d8f6828 feat: throw an Error if delimiter not matched 2019-02-20 22:40:09 +08:00
harttle c13a16fdd6 7.0.2 2019-02-20 10:02:10 +08:00
harttle 4bd35de867 chore: use white list to publish npm 2019-02-20 10:01:09 +08:00
harttle 29f53304bc 7.0.1 2019-02-19 23:55:38 +08:00
harttle cbebb00ef1 fix: unit test on Node.js 6 2019-02-19 23:25:08 +08:00
harttle 64a9eb79c5 style: fix unused vars in unit test 2019-02-19 23:01:59 +08:00
harttle 852c6ad453 refactor: remove mock-fs from dev dependency 2019-02-19 22:28:27 +08:00
harttle f432aade6a chore(TypeScript): remove mock-fs 2019-02-17 21:37:13 +08:00
harttle aa49b4f117 chore(TypeScript): fix unit tests and coverage 2019-02-17 20:46:33 +08:00
harttle fc9ebdb90f chore(TypeScript): fix linting and generate .d.ts 2019-02-17 18:20:04 +08:00
harttle 7ee87008c7 chore(TypeScript): demo for typescript 2019-02-17 14:22:13 +08:00
harttle 677e8511e6 chore(TypeScript): refactor objects into classes
fix: `Nil`(null, undefined) now renders as empty string
change: `parser.parseValue()` renamed to `parser.parseOutput`
change: registered tags/filters become static and shared across different liquid instances
2019-02-17 04:55:30 +08:00
semantic-release-bot b51b0dabca chore(release): 7.0.0 [skip ci]
# [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
2019-02-14 16:02:23 +00:00
harttle 1cc7249a71 chore(TypeScript): ship Liquid to class
BREAKING CHANGE: calling `Liquid()` without `new` now becomes invalid
2019-02-14 23:53:40 +08:00
harttle a626de632c chore(CI): remove node_js 4 2019-02-14 23:15:29 +08:00
harttle 83a8e2d310 chore(CI): skip unit test on node_js 4 2019-02-14 23:11:13 +08:00
harttle c37b330751 chore(typescript): ship to TypeScript 2019-02-14 22:33:45 +08:00
semantic-release-bot c8de8bcde2 chore(release): 6.4.3 [skip ci]
## [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)
2019-02-13 12:13:27 +00:00
harttle 2015f683b8 fix: better index.d.ts and a demo
The demo is located at demo/typescript/index.ts , working on #98
2019-02-13 20:07:13 +08:00
semantic-release-bot d14f18520b chore(release): 6.4.2 [skip ci]
## [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))
2019-01-28 17:34:02 +00:00
Jun Yang c0524a83f3 chore: remove build from e2e, check es5 after published 2019-01-29 01:28:02 +08:00
Jun Yang dde7b3b389 fix(CI): e2e not building cjs 2019-01-29 01:08:52 +08:00
semantic-release-bot 84bbb3432f chore(release): 6.4.1 [skip ci]
## [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))
2019-01-28 16:44:56 +00:00
Jun Yang 1c5b934250 chore(CI): update package-lock.json 2019-01-29 00:39:19 +08:00
Jun Yang a2caeb559b fix: regenerator undefined 2019-01-29 00:26:08 +08:00
harttle 81e6861b73 chore(Changelog): add semantic release plugins 2019-01-28 20:33:33 +08:00
harttle 9038d56ce7 chore(Changelog): add @semantic-release/changelog as dev dependency 2019-01-28 20:26:50 +08:00
harttle bad72005fe chore(CI): add @semantic-release/changelog plugin
working on #100
2019-01-28 20:21:16 +08:00
harttle 4dffb27e95 feat(Drop): liquid_method_missing and to_s
close #97
2019-01-28 19:53:04 +08:00
harttle f38ea63c14 chore(CI): mv lint and covereage to test phase 2019-01-28 18:57:25 +08:00
harttle 57bdf17dea chore(CI): downgrade rollup-plugin-babel 2019-01-28 18:46:41 +08:00
harttle 0040485188 chore(CI): skip_cleanup for node_modules 2019-01-28 18:22:20 +08:00
harttle 42d799055a chore(CI): use nodejs_8 for linting 2019-01-28 18:11:25 +08:00
harttle 8a881ac69a style: align to [email protected] 2019-01-28 18:06:47 +08:00
harttle 7a2e036c79 chore(package): update package-lock.json 2019-01-28 17:30:34 +08:00
harttle a31e0c6275 chore(CI): test use build matrix 2019-01-28 17:27:00 +08:00
harttle 7df4d5d62c chore(CI): adjust build matrix 2019-01-28 16:29:59 +08:00
harttle 22b7e55fc9 chore(CI): assign node_js for each script 2019-01-28 15:40:32 +08:00
harttle 0238cf2c44 fix(CI): scirpt for the first stage not defined 2019-01-28 15:26:40 +08:00
harttle 601f2e48fc chore(CI): enable semantic release 2019-01-28 15:18:21 +08:00
harttle 8cb0137362 6.3.0 2019-01-21 15:25:11 +08:00
harttle dee4303fad feature: plugin API, working on #86 2019-01-21 15:24:13 +08:00
harttle 9ba352ef04 6.2.1 2019-01-21 14:54:07 +08:00
harttle 4793a83bd6 fix #101: Slice filter does not work on arrays 2019-01-21 14:52:08 +08:00
harttle af4fe1b330 6.2.0 2019-01-10 13:30:05 +08:00
harttle 5f5c62a51e fix banner link 2019-01-10 13:29:39 +08:00
oott123andJun Yang 21f4d6bf08 export types variable 2019-01-10 13:28:13 +08:00
oott123andJun Yang ba54bec82f feat: add typescript definitions 2019-01-10 13:28:13 +08:00
Jun Yang c2e1bc7055 6.1.1 2018-11-22 09:36:18 +08:00
Andrew BarclayandJun Yang 2055258193 fix assign tag regex to include linebreaks in value 2018-11-22 09:31:22 +08:00
harttle d65ea14b9f 6.1.0 2018-10-14 00:53:43 +08:00
steven anthonyandJun Yang f1b4c4fe14 Added react-liquidjs-demo 2018-10-10 19:59:43 +08:00
harttle 6af631b77b feature: url_decode, working on #94 2018-10-08 10:37:24 +08:00
Jun Yang d504c8107e 6.0.1 2018-10-06 12:20:13 +08:00
Jun Yang c0181aec31 remove dependency from 'util' 2018-10-01 23:08:39 +08:00
Jun Yang 22a7a85d50 test case for Regexp rendering 2018-10-01 23:00:49 +08:00
Jun Yang 044be8253d fixes #91 2018-10-01 22:51:40 +08:00
Jun Yang 8a629980d9 refactor: move fs util into template.js 2018-10-01 22:06:03 +08:00
harttle 5412c5a39f fixes #88 2018-09-29 16:45:11 +08:00
harttle b7445f26d6 6.0.0 2018-08-28 00:37:00 +08:00
harttle 3213c6d23a fix: ci 2018-08-28 00:27:44 +08:00
harttle 829afc24a7 fix: ci 2018-08-28 00:17:26 +08:00
harttle 0049a56864 fix: ci 2018-08-28 00:05:50 +08:00
harttle f8e0587542 fix: ci 2018-08-27 23:54:43 +08:00
harttle c3c57a560b fix: travis 2018-08-27 23:49:11 +08:00
harttle 5378f2709b test: cover all 2018-08-27 23:27:42 +08:00
harttle d6876bd9ec refactor: bundle template.js respectively 2018-08-27 21:37:39 +08:00
harttle 31c39561c9 feature: get nyc, babel and coveralls working 2018-08-26 21:54:35 +08:00
harttle d828c4021e fix: unit test on node6.x 2018-08-21 01:21:30 +08:00
harttle 4d4a2d04ec refactor 2018-08-21 01:18:13 +08:00
harttle bc9d8f92af refactor: import rollup 2018-08-20 23:58:41 +08:00
harttle 4ece4e208f 5.3.0-0 2018-08-17 01:34:57 +08:00
harttle 4ad3d2a52b modified: .eslintignore 2018-08-17 01:34:07 +08:00
harttle fa7cd8f4ff refactor: import babelify 2018-08-17 01:32:16 +08:00
harttle 87360d5207 refactor: mv es5 files to build/ 2018-08-16 23:20:13 +08:00
harttle 19891b2df0 refactor: es6 building and linting 2018-08-16 23:14:12 +08:00
harttle 5f634b0b5e refactor: moving all ES6 files to src/, working on #83 2018-08-16 00:22:46 +08:00
harttle 9f597843c9 5.2.0 2018-08-14 00:12:42 +08:00
harttle 887b736f39 test cases for stringify 2018-08-14 00:11:50 +08:00
harttle 7a3689067b feature: support toLiquid, working on #81 2018-08-14 00:11:50 +08:00
harttle 5b43520d78 feature: variable lookup respects to to_liquid, #81 2018-08-14 00:11:50 +08:00
harttle ea38a9e1f5 refactor: test/render.js use local scope 2018-08-14 00:11:50 +08:00
harttle fa5b731daf fix string type checking and circular reference, working on #81 2018-08-14 00:11:50 +08:00
harttle 96829b5293 fix: express demo, fixes #79 2018-07-25 21:51:42 +08:00
harttle 4cf7215a67 5.1.1 2018-07-24 19:39:12 +08:00
harttle 50006db26d fix: test/util/strftime.js 2018-07-24 19:38:47 +08:00
harttle 2d81223d99 update outdated packages 2018-07-24 19:37:21 +08:00
harttle 3038115c21 5.1.0 2018-07-24 14:53:47 +08:00
harttle fdbf4b838a add package.lock 2018-07-24 14:53:28 +08:00
harttle 7ab0e96419 feature: template string for {%include%} argument, #72 2018-07-24 14:49:23 +08:00
harttle 48484fe52f 5.0.2 2018-07-21 23:42:18 +08:00
harttle 14d1956232 test case for _.forOwn 2018-07-21 23:23:25 +08:00
harttle c3e7859cdd fix: increment/decrement independent from capture, #76 2018-07-21 23:19:28 +08:00
harttle c3de3fbb19 fix: post-increment, pre-decrement working on #76 2018-07-21 22:59:52 +08:00
harttle b5cfd2d2c5 fix: {%assign%} now adds a shading scope 2018-07-21 21:30:40 +08:00
harttle 36f84e4b3c refactor: pop the same context as the pushed one 2018-07-21 17:12:46 +08:00
harttle 63b5658604 refactor: src/scope.js 2018-07-21 17:02:18 +08:00
Ryan KennedyandJun Yang c79700ef1a fix: correctly understand variables with question marks
Fixes #77
2018-07-18 10:15:43 +08:00
harttle 6755462056 5.0.1 2018-07-16 15:04:40 +08:00
harttle 02806b2257 fix #75 2018-07-16 15:02:31 +08:00
harttle d0fb5ccece 5.0.0 2018-07-16 13:41:57 +08:00
harttle b7144f9b75 fix: linting 2018-07-07 01:22:40 +08:00
harttle 88fc05732f fix: tablerow cols defaults to 0, #74 2018-07-07 01:18:11 +08:00
Jun Yang 44b1aac096 remove any-promise, working on #73 2018-06-23 21:55:23 +08:00
Jun Yang 42043b6944 4.0.0 2018-05-12 23:10:36 +08:00
Jun Yang 61f146432d fix #59, divide numbers as it is 2018-05-12 22:58:43 +08:00
Jun Yang 220b705b73 3.1.3 2018-05-12 22:06:35 +08:00
Jun Yang ef85bd9192 fix #67: forloop.last, forloop.rindex, forloop.rindex0 incorrect if loop has a limit 2018-05-12 22:05:16 +08:00
Paul Robert LloydandJun Yang 8e9e10fdb4 Ensure strip_html filter uses same regex as Ruby Liquid 2018-05-09 10:41:25 +08:00
Jun Yang f668a5cc41 3.1.2 2018-04-07 19:49:46 +08:00
Jun Yang 098f5164e7 fix: linting 2018-04-07 19:48:46 +08:00
Jun Yang 432f878bb6 fix: support comma-separated arguments for static partials, fixes #62 2018-04-07 19:45:57 +08:00
Cory MawhorterandJun Yang aa54dad36e fix dupe name bug 2018-04-07 19:36:50 +08:00
Jun Yang c6ff1f7911 3.1.1 2018-03-05 01:34:00 +08:00
Jun Yang 11355cbf09 refactor: remove makefile 2018-03-05 01:31:12 +08:00
Jun Yang 7ccd0cb1e5 fix: branch coverage 2018-03-05 01:20:43 +08:00
Jun Yang 4326c223f3 fix #61: static filename not parsed for subdirectories 2018-03-05 00:58:26 +08:00
Jun Yang 78b6e35fbf fix: file path asserts for windows 2018-03-03 20:01:24 +08:00
Jun YangandGitHub e275368cb3 Update README.md 2018-01-17 11:01:58 +08:00
Jun YangandGitHub ca2b16a583 Update README.md 2018-01-17 10:48:05 +08:00
Jun YangandGitHub 86d0529a5e Update README.md 2018-01-17 10:47:11 +08:00
Jun YangandGitHub 400b93c88d Update README.md 2018-01-17 10:44:11 +08:00
harttle 22b41fd1a8 3.1.0 2018-01-12 10:55:38 +08:00
ChenLandJun Yang d7c7f32d4b make dist 2018-01-12 10:53:43 +08:00
ChenLandJun Yang 925df9a9b5 100% coverage 2018-01-12 10:53:43 +08:00
ChenLandJun Yang 7b696ed0d2 low version, skip tests 2018-01-12 10:53:43 +08:00
ChenLandJun Yang c55f0234eb Add xhr support for render file in browser. 2018-01-12 10:53:43 +08:00
harttle eee5649a47 fix package.json syntax 2017-12-31 12:32:13 +08:00
harttle 1048f4e0ab add engine spec 2017-12-31 12:29:32 +08:00
harttle 5c6d05092f fix: remove let for nodejs 4.x 2017-12-29 12:33:10 +08:00
harttle 900664a26d test cases for #53 2017-12-29 12:17:52 +08:00
harttle 757a06ee83 fix linting 2017-12-29 12:14:01 +08:00
harttle 9f6e9f2f3f 3.0.0 2017-12-23 21:36:37 +08:00
harttle 0f9adfb067 change: remove deprecated features, coverall 2017-12-23 21:31:55 +08:00
harttle dabde0e9e4 2.2.1 2017-12-23 13:01:28 +08:00
harttle bd4e851b3c update babelify 2017-12-23 12:57:19 +08:00
harttle 1d2aece687 2.2.0 2017-12-21 14:10:42 +08:00
harttle ace271c7f0 feature: support static filepath resolving by options.dynamicPartials,
fix #51
2017-12-21 14:09:30 +08:00
Sami KukkonenandJun Yang 4cbfa6d204 Ignore "fs" module in browser environments 2017-12-21 13:09:21 +08:00
harttle 3153f1d4bb 2.1.4 2017-12-10 01:35:21 +08:00
harttle 9cd4f3d904 modified: README.md 2017-12-10 01:35:06 +08:00
harttle 8dd1b3ba99 2.1.3 2017-12-10 01:26:46 +08:00
harttle 11de73a7b1 update doc 2017-12-10 00:30:13 +08:00
harttle 0325eacddf 2.1.2 2017-12-08 10:42:27 +08:00
harttle a21fcf6f87 fix: compatible to nodejs<=4 2017-12-08 10:42:00 +08:00
harttle 7bb2ee2213 2.1.1 2017-12-08 00:29:09 +08:00
harttle b24582c2c4 remove debug message, working on #50 2017-12-08 00:28:51 +08:00
harttle 49cb7a8c36 2.1.0 2017-12-08 00:25:44 +08:00
harttle a4126e7f10 fix: typo in scope.get("liquid") error message, fixes #50 2017-12-08 00:24:23 +08:00
harttle 5196d6ad56 feature: date filter supports utc string now, fixes #49 2017-12-08 00:22:56 +08:00
harttle 6082bf4757 2.0.3 2017-11-27 00:00:09 +08:00
harttle 8efb41a631 use isTruthy for and/or operators, fixes #47 2017-11-26 23:59:50 +08:00
harttle d348edac14 2.0.2 2017-11-06 00:16:51 +08:00
harttle be00f1d385 feature: treat top-level content as anonymous block, fixes #45 2017-11-06 00:14:33 +08:00
harttle 4e132ca1c5 tags: use scope.opts insteadof scope.get('liquid') 2017-11-04 10:46:15 +08:00
harttle 329aea6bd6 2.0.1 2017-10-31 00:11:35 +08:00
harttle 23e7424f9d change: default extname to "", working for #41 2017-10-31 00:10:45 +08:00
harttle 8241625be4 remove publish from version script 2017-10-30 02:24:08 +08:00
harttle d36ec17621 2.0.0 2017-10-30 02:23:36 +08:00
harttle 485424cc89 feature: concat filter 2017-10-30 02:22:56 +08:00
harttle a239389044 docs for #41 2017-10-30 02:07:07 +08:00
harttle e8fa8d32f5 feature: separate options for value/tag trimming, working on #17 2017-10-30 01:46:31 +08:00
harttle 502426f621 mv whiteSpaceCtrl to a separate file 2017-10-30 00:36:21 +08:00
harttle 8c77fc5a3d fix: whiteSpace Ctrl with compliance to shopify/liquid, working on #17 2017-10-30 00:09:29 +08:00
harttle be17d7443c 1.9.7 2017-10-21 10:50:44 +08:00
Mehdi JafferyandJun Yang 118624ca7d Fix to make for tag iterate objects like Shopify liquid.
Ref: https://github.com/Shopify/liquid/wiki/Liquid-for-Designers#allowed-collection-types
2017-10-18 05:41:43 -05:00
harttle d08ad6dc97 fix: error.stack assertion in v8.5 2017-10-06 12:32:06 +08:00
harttle e22870f5d7 fix: broken unit test by UTC time parsing behaviour change 2017-10-06 12:16:03 +08:00
harttle bd6706cf4d update outdated 2017-10-05 08:32:32 +08:00
harttle b71928ffd4 refactor: format 2017-10-05 08:21:32 +08:00
harttle 6f69cd774d 1.9.6 2017-09-15 10:39:18 +08:00
harttle 1b662d374d update outdated dependencies 2017-09-15 10:38:54 +08:00
harttle 6dd6ed176c 1.9.5 2017-08-26 17:58:14 +08:00
harttle 21af8c4d7a fix read/write variable in parent's scope, fix #39 2017-08-26 17:57:43 +08:00
harttle 942a3224a1 1.9.4 2017-08-16 00:07:47 +08:00
harttle 11259fae62 fix stack matching 2017-08-16 00:04:31 +08:00
harttle 030037eedc cover all 2017-08-15 23:52:05 +08:00
harttle 6d59f34fc8 cover all 2017-08-14 22:43:49 +08:00
harttle 72597c52e5 cover all 2017-08-14 22:07:08 +08:00
harttle 7904f823a8 1.9.3 2017-08-04 11:28:02 +08:00
harttle e993118e87 Merge branch 'jaswrks-patch-1' 2017-08-04 11:27:37 +08:00
harttle 4ae63b2899 test cases for #36 2017-08-04 11:27:12 +08:00
jaswrksandGitHub 9f446ad024 Support {{- and -}} for whitespace control.
With this PR it now matches the intended functionality; i.e., https://github.com/harttle/liquidjs/wiki/Whitespace-Control
2017-08-03 06:06:35 -08:00
harttle 6f82b914cb 1.9.2 2017-08-03 13:56:09 +08:00
harttle 31397a4862 update uglify-js 2017-08-03 13:55:46 +08:00
harttle a2a076d038 refactor access seq parsing 2017-08-03 13:46:00 +08:00
jaswrksandGitHub 3dc37bf980 Bug fix for foo.bar[0].foo that is not working.
Corrects a bug where `foo.bar[0].foo` results in error: 'Cannot read property `foo` of undefined', because the sequence contains an empty string; i.e., `seq.push('')` should be avoided.

Another possible solution would be to strip empty strings from the final `seq` variable, but this PR fixes the immediate problem.
2017-08-02 10:37:11 -08:00
harttle 4ee2bc964c upgrade outdated dependencies 2017-07-28 21:25:24 +08:00
wojtask9andJun Yang ea2a5f5c3b add identation value to token (#34)
* add identation value to token

* fix lint problems

* use var instead of let
2017-07-21 12:44:27 +08:00
harttle 52e2be68ab 1.9.1 2017-07-17 01:09:22 +08:00
harttle cbde48971a rename to liquidjs 2017-07-17 01:08:51 +08:00
harttle e91cc8c950 update: auto publish after version 2017-07-14 10:42:01 +08:00
harttle 314aa6b467 1.9.0 2017-07-14 00:37:08 +08:00
harttle 14e53ed3a9 test cases for #32 2017-07-14 00:34:12 +08:00
harttle cf6d3e78fe Merge branch 'object-for-loop' of https://github.com/aleclarson/liquid-node 2017-07-13 23:24:48 +08:00
aleclarson fb52c864b1 Add isPlainObject to 'util/underscore.js' 2017-07-13 10:33:01 -04:00
harttle 9f82aec569 add test case for truncate 2017-07-13 16:28:32 +08:00
Jun YangandGitHub 29fe8dcb4b Merge pull request #31 from aleclarson/remove-stop-skip
Remove unused `stop` and `skip` from `forloop` object
2017-07-13 16:10:10 +08:00
aleclarson 2a16800305 Iterate keys of object when used with for...in loop 2017-07-12 15:33:48 -04:00
aleclarson 154cf379a8 Remove unused stop and skip from forloop object 2017-07-12 15:33:09 -04:00
harttle ed20fe6072 1.8.0 2017-04-24 21:31:56 +08:00
harttle 2632e3e97e eslint 2017-04-24 21:31:30 +08:00
harttle 82f84d96c1 1.7.9 2017-04-20 10:33:49 +08:00
harttle 1126dffd7d fix default filter use isTruthy, working on #30 2017-04-20 10:33:10 +08:00
harttle 07ec997ffd tests for truthy 2017-04-20 10:31:02 +08:00
harttle 79815784c1 1.7.8 2017-04-17 22:56:16 +08:00
harttle 2d793a63fe fix #29 2017-04-17 22:49:53 +08:00
harttle cc2b2ea5d1 1.7.7 2017-04-10 19:07:51 +08:00
Jun YangandGitHub 8ff9eeb51d Merge pull request #28 from wojtask9/master
support multiline tags
2017-04-10 19:06:16 +08:00
wojtask9 3bc7caa387 support multiline tags 2017-04-10 11:58:45 +02:00
harttle 20044c4256 1.7.6 2017-03-21 18:00:27 +08:00
chenosandJun Yang e72f295bd0 Comparison on NULL is always false (#27)
* All values in Liquid are truthy except nil and false.

* The falsy values are null, undefined and false.

* comparison on null
2017-03-21 14:54:26 +08:00
chenosandJun Yang 66774865f4 All values in Liquid are truthy except nil and false. (#26)
* All values in Liquid are truthy except nil and false.

* The falsy values are null, undefined and false.
2017-03-21 12:25:11 +08:00
harttle 2ae6874397 1.7.5 2017-03-20 15:07:10 +08:00
chenosandJun Yang b307f6ffd0 Size can also be used with dot notation (#25)
* Size can also be used with dot notation (for example, {{ my_string.size }}).

* Fixed test failed.

* Optimization
2017-03-20 15:06:32 +08:00
harttle 5b5db616e2 1.7.4 2017-03-16 19:29:16 +08:00
harttle 551a15f373 import value declaration syntax 2017-03-16 19:27:54 +08:00
ChenL 45e0fb21dc Fixed array arguments bug 2017-03-16 19:02:30 +08:00
ChenL 4fc977268f Support create variables on the filter line. 2017-03-16 18:26:10 +08:00
harttle 8231727428 update README 2017-03-16 10:44:21 +08:00
harttle 0a068ac51c 1.7.3 2017-03-16 10:40:27 +08:00
harttle 6f1d6f3d05 Merge branch 'chenos-master' 2017-03-16 10:40:09 +08:00
harttle aa52a31e90 fix: contains return false when left side equals null 2017-03-16 10:39:10 +08:00
ChenL e4056a79ee Test if an array contains a specific number/string. 2017-03-16 10:05:50 +08:00
ChenL f1d68409b1 Do not throw error in contains when variable is illegal. 2017-03-15 21:20:00 +08:00
ChenL 7ad9ce95e9 Illegal expression in contains 2017-03-15 20:38:08 +08:00
harttle ea7e334d87 1.7.2 2017-03-15 17:14:21 +08:00
harttle 4f123c1d6b Merge branch 'chenos-master' 2017-03-15 17:13:58 +08:00
harttle 0f4edc5b53 add unit test for #21 2017-03-15 17:13:05 +08:00
chenosandGitHub 48bee428d5 reversed position
eg:

- reversed limit:4 offset:2
- limit:4 reversed offset:2
- limit:4 offset:2 reversed
2017-03-15 16:59:24 +08:00
Jun YangandGitHub 2d1f05799f Update README.md 2017-02-18 17:14:51 +08:00
harttle dbc18539a0 modified: README.md 2017-02-14 10:57:57 +08:00
harttle 492f56cbfb unit test: multiple hash 2017-02-01 13:49:01 +08:00
harttle 4018ffe68b 1.7.1 2017-01-31 23:17:08 +08:00
harttle 21eefe1d7b do not trim output tags and previous \n, #19 2017-01-31 23:15:23 +08:00
harttle 0c8148c12b 1.7.0 2017-01-31 00:48:26 +08:00
harttle 7f336ded5c fix badges 2017-01-31 00:48:11 +08:00
harttle 5ecca1b761 feature: greedy trim disabled by default, working on #19 2017-01-31 00:07:46 +08:00
harttle af284daf34 1.6.2 2017-01-21 19:41:59 +08:00
harttle 21a5ac7e08 RenderError extends original error 2017-01-21 19:41:19 +08:00
harttle 7d22214cb5 remove strict_* from express arguments 2017-01-02 13:44:33 +08:00
harttle d6ca12f3bd 1.6.1 2016-12-27 00:46:53 +08:00
harttle 84f7a399c5 update README and unit test 2016-12-27 00:33:11 +08:00
harttle 5b5dfd5c00 documents for whitespace control (#17) 2016-12-27 00:09:39 +08:00
harttle d032f7f4d4 1.6.0 2016-12-26 23:38:15 +08:00
harttle 044035b8ed unit tests for #17 2016-12-26 23:37:33 +08:00
harttle 564d9cd9f4 feature: whitespace control, fixes #17 2016-12-26 23:23:58 +08:00
harttle fbb1dc8a36 test cases for filters: string->number convertion, #18 2016-12-15 23:14:49 +08:00
harttle c99dfd69e7 1.5.4 2016-12-15 22:39:52 +08:00
harttle 4719544d49 fix: number convertion for plus filter, closes #18 2016-12-15 22:38:48 +08:00
249 changed files with 55525 additions and 7565 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
dist
demo
coverage
+23
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
test/
+17 -5
View File
@@ -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
View File
@@ -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))
+161 -114
View File
@@ -1,135 +1,105 @@
# shopify-liquid
# liquidjs
[![NPM version](https://img.shields.io/npm/v/shopify-liquid.svg?style=flat)](https://www.npmjs.org/package/shopify-liquid)
[![Build Status](https://travis-ci.org/harttle/shopify-liquid.svg?branch=master)](https://travis-ci.org/harttle/shopify-liquid)
[![Coverage Status](https://img.shields.io/coveralls/harttle/shopify-liquid/master.svg)](https://coveralls.io/github/harttle/shopify-liquid?branch=master)
[![Dependency manager](https://david-dm.org/harttle/shopify-liquid.svg?style=flat)](https://david-dm.org/harttle/shopify-liquid)
[![npm](https://img.shields.io/npm/v/liquidjs.svg)](https://www.npmjs.org/package/liquidjs)
[![npm](https://img.shields.io/npm/dm/liquidjs.svg)](https://www.npmjs.org/package/liquidjs)
[![Build Status](https://travis-ci.org/harttle/liquidjs.svg?branch=master)](https://travis-ci.org/harttle/liquidjs)
[![Coveralls](https://img.shields.io/coveralls/harttle/liquidjs.svg)](https://coveralls.io/github/harttle/liquidjs?branch=master)
[![GitHub issues](https://img.shields.io/github/issues-closed/harttle/liquidjs.svg)](https://github.com/harttle/liquidjs/issues)
[![GitHub contributors](https://img.shields.io/github/contributors/harttle/liquidjs.svg)](https://github.com/harttle/liquidjs/graphs/contributors)
[![David](https://img.shields.io/david/harttle/liquidjs.svg)](https://david-dm.org/harttle/liquidjs)
[![David Dev](https://img.shields.io/david/dev/harttle/liquidjs.svg)](https://david-dm.org/harttle/liquidjs?type=dev)
[![DUB](https://img.shields.io/dub/l/vibe-d.svg)](https://github.com/harttle/liquidjs/blob/master/LICENSE)
[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://github.com/harttle/liquidjs)
[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](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/
+5
View File
@@ -0,0 +1,5 @@
{
"rules": {
"no-unused-expressions": "off"
}
}
+49
View File
@@ -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 })
})
}
+11
View File
@@ -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()
+26
View File
@@ -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))
}
}
+30
View File
@@ -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))
}
}
+1
View File
@@ -0,0 +1 @@
access time: {{date|date: "%Y-%m-%d %H:%M:%S"}}
+1
View File
@@ -0,0 +1 @@
<h2>Welcome to {{ name | capitalize}}, {% include 'date.html' %}</h2>
+13 -10
View File
@@ -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"}}&lt;/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>
+220
View File
@@ -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
}
}
}
+17
View File
@@ -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
View File
@@ -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
+3 -4
View File
@@ -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')
})
+2 -2
View File
@@ -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"
}
+2 -2
View File
@@ -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>
+26
View File
@@ -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))
+12
View File
@@ -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=="
}
}
}
+11
View File
@@ -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"
}
}
+7
View File
@@ -0,0 +1,7 @@
{%header content: "welcome to liquid" | capitalize%}
<ul>
{% for todo in todos %}
<li>{{forloop.index}} - {{todo}}</li>
{% endfor %}
</ul>
+21
View File
@@ -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
+16737
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -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

+41
View File
@@ -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>
+15
View File
@@ -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"
}
+32
View File
@@ -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);
}
}
+46
View File
@@ -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;
+9
View File
@@ -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);
});
+14
View File
@@ -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;
}
+12
View File
@@ -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();
+7
View File
@@ -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

+127
View File
@@ -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();
});
}
}
+18
View File
@@ -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
+25
View File
@@ -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)
+8701
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -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"
}
}
+7
View File
@@ -0,0 +1,7 @@
{%header content: "welcome to liquid" | capitalize%}
<ul>
{% for todo in todos %}
<li>{{forloop.index}} - {{todo}}</li>
{% endfor %}
</ul>
-2498
View File
File diff suppressed because it is too large Load Diff
-2
View File
File diff suppressed because one or more lines are too long
-127
View File
@@ -1,127 +0,0 @@
const strftime = require('./src/util/strftime.js');
const _ = require('./src/util/underscore.js');
var escapeMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;',
};
var unescapeMap = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&#34;': '"',
'&#39;': "'",
};
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;
-148
View File
@@ -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;
Vendored
+6
View File
@@ -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
}
-18
View File
@@ -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/
+10775
View File
File diff suppressed because it is too large Load Diff
+87 -30
View File
@@ -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": {}
}
+93
View File
@@ -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
}]
+28
View File
@@ -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)
}
}
+18
View File
@@ -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())
}
+29
View File
@@ -0,0 +1,29 @@
const escapeMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
}
const unescapeMap = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
'&#34;': '"',
'&#39;': "'"
}
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, '')
}
+9
View File
@@ -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 }
+14
View File
@@ -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
}
+5
View File
@@ -0,0 +1,5 @@
import { isTruthy } from '../../render/syntax'
export default {
'default': <T1, T2>(v: T1, arg: T2): T1 | T2 => isTruthy(v) ? v : arg
}
+30
View File
@@ -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}
+4
View File
@@ -0,0 +1,4 @@
export default {
'url_decode': (x: string) => x.split('+').map(decodeURIComponent).join(' '),
'url_encode': (x: string) => x.split(' ').map(encodeURIComponent).join('+')
}
+19
View File
@@ -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
+34
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
import { RenderBreakError } from '../../util/error'
export default {
render: async function () {
throw new RenderBreakError('break')
}
}
+30
View File
@@ -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
+44
View File
@@ -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
+17
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
import { RenderBreakError } from '../../util/error'
export default {
render: async function () {
throw new RenderBreakError('continue')
}
}
+43
View File
@@ -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)
}
}
+20
View File
@@ -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
+82
View File
@@ -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
}
}
+45
View File
@@ -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
+60
View File
@@ -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
}
}
+20
View File
@@ -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
+24
View File
@@ -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
+46
View File
@@ -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
+23
View File
@@ -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('')
}
}
+65
View File
@@ -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
+34
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
enum BlockMode {
/* store rendered html into blocks */
OUTPUT,
/* output rendered html directly */
STORE
}
export default BlockMode
+147
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
import { Drop } from '../drop/drop'
type PlainObject = {
[key: string]: any
toLiquid?: () => any
}
export type Scope = PlainObject | Drop
+12
View File
@@ -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)
}
}
+9
View File
@@ -0,0 +1,9 @@
export abstract class Drop {
valueOf (): any {
return undefined
}
liquidMethodMissing (key: string): Promise<string | undefined> | string | undefined {
return undefined
}
}
+26
View File
@@ -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 ''
}
}
+34
View File
@@ -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)
}
}
+13
View File
@@ -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)
}
+25
View File
@@ -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
}
}
+25
View File
@@ -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
}
}
-66
View File
@@ -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
};
};
+51
View File
@@ -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
+5
View File
@@ -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
}
+22
View File
@@ -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
-91
View File
@@ -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
};
+90
View File
@@ -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 []
}
+97
View File
@@ -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
}
-13
View File
@@ -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
View File
@@ -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
};
};
+27
View File
@@ -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