mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-13 11:20:40 -07:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a96b49ec2d | ||
|
|
2af297f81a | ||
|
|
d61855bf72 | ||
|
|
734eb52b98 | ||
|
|
2980260fc1 | ||
|
|
062b351598 | ||
|
|
9107eb1b93 | ||
|
|
11f013bf24 | ||
|
|
ad039abea0 | ||
|
|
0b195b21eb |
+11
-1
@@ -483,7 +483,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1557563?v=4",
|
||||
"profile": "http://santialbo.com",
|
||||
"contributions": [
|
||||
"doc"
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -711,6 +712,15 @@
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "timvandam",
|
||||
"name": "Tim van Dam",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/35376389?v=4",
|
||||
"profile": "https://tovd.dev",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# [10.19.0](https://github.com/harttle/liquidjs/compare/v10.18.0...v10.19.0) (2024-11-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* allow drops in property access ([#769](https://github.com/harttle/liquidjs/issues/769)) ([11f013b](https://github.com/harttle/liquidjs/commit/11f013bf249f1366306c1f0d728d45a95eb2df1a))
|
||||
* support Jekyll style `where`, [#768](https://github.com/harttle/liquidjs/issues/768) ([9107eb1](https://github.com/harttle/liquidjs/commit/9107eb1b93f5c11dca0ee0f0d2c1c8243849c3ab))
|
||||
|
||||
# [10.18.0](https://github.com/harttle/liquidjs/compare/v10.17.0...v10.18.0) (2024-10-16)
|
||||
|
||||
|
||||
|
||||
@@ -75,24 +75,26 @@ If you personally love LiquidJS or it's benefiting your business, please conside
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.opensense.com/"><img src="https://images.opencollective.com/opensense-inc/bf840ae/logo/256.png?height=100" width="100px;" alt="Opensense Inc."/><br /><sub><b>Opensense</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.11ty.dev/"><img src="https://avatars.githubusercontent.com/u/35147177?v=4?s=100" width="100px;" alt="Eleventy"/><br /><sub><b>Eleventy</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://about.me/peterdehaan"><img src="https://avatars2.githubusercontent.com/u/557895?v=4?s=100" width="100px;" alt="Peter deHaan"/><br /><sub><b>Peter deHaan</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.11ty.dev/"><img src="https://avatars.githubusercontent.com/u/35147177?v=4&s=100" width="100px;" alt="Eleventy"/><br /><sub><b>Eleventy</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://about.me/peterdehaan"><img src="https://avatars2.githubusercontent.com/u/557895?v=4&s=100" width="100px;" alt="Peter deHaan"/><br /><sub><b>Peter deHaan</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/touchless"><img src="https://images.opencollective.com/touchless/273bc74/logo/256.png?height=100" width="100px;" alt="Touchless"/><br /><sub><b>Touchless</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.dropkiq.com/"><img src="https://images.opencollective.com/1bertlol/43a8ea8/logo/256.png?height=100" width="100px;" alt="Adam Darrah"/><br /><sub><b>Dropkiq</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://dailycontributors.com/"><img src="https://images.opencollective.com/dailycontributors/3c2e057/logo/256.png?height=100&width=100" width="100px;" alt="Dailycontributors"/><br /><sub><b>Dailycontributors</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coni2k"><img src="https://avatars0.githubusercontent.com/u/1284601?v=4?s=100" width="100px;" alt="coni2k"/><br /><sub><b>Serkan Holat</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/coni2k"><img src="https://avatars0.githubusercontent.com/u/1284601?v=4&s=100" width="100px;" alt="coni2k"/><br /><sub><b>Serkan Holat</b></sub></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amit777"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4?s=100" width="100px;" alt="amit777"/><br /><sub><b>amit777</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/amit777"><img src="https://avatars0.githubusercontent.com/u/2703309?v=4&s=100" width="100px;" alt="amit777"/><br /><sub><b>amit777</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/khaled-salem"><img src="https://images.opencollective.com/khaled-salem/avatar/256.png?height=256" width="100px;" alt="Khaled Salem"/><br /><sub><b>Khaled Salem</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://sentry.io/"><img src="https://avatars.githubusercontent.com/u/1396951?v=4?s=100" width="100px;" alt="Sentry"/><br /><sub><b>Sentry</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.checkoutblocks.com/"><img src="https://avatars.githubusercontent.com/u/114603307?v=4?s=100" width="100px;" alt="Checkout Blocks"/><br /><sub><b>Checkout Blocks</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://customer.io/"><img src="https://avatars.githubusercontent.com/u/1152079?v=4?s=100" width="100px;" alt="Customer IO"/><br /><sub><b>Customer IO</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15fathoms"><img src="https://avatars.githubusercontent.com/u/79156039?v=4?s=100" width="100px;" alt="Emmanuel Cartelli"/><br /><sub><b>Emmanuel Cartelli</b></sub></a><br /></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/microsoft"><img src="https://avatars.githubusercontent.com/u/6154722?v=4?s=100" width="100px;" alt="Microsoft"/><br /><sub><b>Microsoft</b></sub></a><br /></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://sentry.io/"><img src="https://avatars.githubusercontent.com/u/1396951?v=4&s=100" width="100px;" alt="Sentry"/><br /><sub><b>Sentry</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.checkoutblocks.com/"><img src="https://avatars.githubusercontent.com/u/114603307?v=4&s=100" width="100px;" alt="Checkout Blocks"/><br /><sub><b>Checkout Blocks</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://customer.io/"><img src="https://avatars.githubusercontent.com/u/1152079?v=4&s=100" width="100px;" alt="Customer IO"/><br /><sub><b>Customer IO</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/15fathoms"><img src="https://avatars.githubusercontent.com/u/79156039?v=4&s=100" width="100px;" alt="Emmanuel Cartelli"/><br /><sub><b>Emmanuel Cartelli</b></sub></a><br /></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/microsoft"><img src="https://avatars.githubusercontent.com/u/6154722?v=4&s=100" width="100px;" alt="Microsoft"/><br /><sub><b>Microsoft</b></sub></a><br /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.pakstyle.pk/"><img src="https://images.opencollective.com/pakstyle/2b81605/logo/256.png?height=100" width="100px;" alt="PakStyle.pk"/><br /><sub><b>PakStyle.pk</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://syntax.fm/"><img src="https://avatars.githubusercontent.com/u/130389858?v=4&s=100" width="100px;" alt="Syntax Podcast"/><br /><sub><b>Syntax Podcast</b></sub></a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://opencollective.com/cartelli-emmanuel"><img src="https://images.opencollective.com/cartelli-emmanuel/avatar/256.png?height=100" width="100px;" alt="Cartelli Emmanuel"/><br /><sub><b>Cartelli Emmanuel</b></sub></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -173,7 +175,7 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ameyaapte1"><img src="https://avatars.githubusercontent.com/u/16054747?v=4?s=100" width="100px;" alt="Ameya Apte"/><br /><sub><b>Ameya Apte</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=ameyaapte1" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tbdrz"><img src="https://avatars.githubusercontent.com/u/50599116?v=4?s=100" width="100px;" alt="tbdrz"/><br /><sub><b>tbdrz</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=tbdrz" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://santialbo.com"><img src="https://avatars.githubusercontent.com/u/1557563?v=4?s=100" width="100px;" alt="Santi Albo"/><br /><sub><b>Santi Albo</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=santialbo" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="http://santialbo.com"><img src="https://avatars.githubusercontent.com/u/1557563?v=4?s=100" width="100px;" alt="Santi Albo"/><br /><sub><b>Santi Albo</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=santialbo" title="Documentation">📖</a> <a href="https://github.com/harttle/liquidjs/commits?author=santialbo" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/YahangWu"><img src="https://avatars.githubusercontent.com/u/12295975?v=4?s=100" width="100px;" alt="Yahang Wu"/><br /><sub><b>Yahang Wu</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=YahangWu" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/hongl-1"><img src="https://avatars.githubusercontent.com/u/101576612?v=4?s=100" width="100px;" alt="hongl"/><br /><sub><b>hongl</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=hongl-1" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zxx-457"><img src="https://avatars.githubusercontent.com/u/114141362?v=4?s=100" width="100px;" alt="zxx-457"/><br /><sub><b>zxx-457</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=zxx-457" title="Documentation">📖</a></td>
|
||||
@@ -206,6 +208,9 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://vrugtehagel.nl"><img src="https://avatars.githubusercontent.com/u/41021050?v=4?s=100" width="100px;" alt="Koen"/><br /><sub><b>Koen</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=vrugtehagel" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://neamar.fr"><img src="https://avatars.githubusercontent.com/u/536844?v=4?s=100" width="100px;" alt="Matthieu Bacconnier"/><br /><sub><b>Matthieu Bacconnier</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=Neamar" title="Documentation">📖</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://tovd.dev"><img src="https://avatars.githubusercontent.com/u/35376389?v=4?s=100" width="100px;" alt="Tim van Dam"/><br /><sub><b>Tim van Dam</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=timvandam" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
+1
-1
@@ -34,4 +34,4 @@
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,4 +103,30 @@ Output
|
||||
- 3
|
||||
```
|
||||
|
||||
## Jekyll style
|
||||
|
||||
{% since %}v10.19.0{% endsince %}
|
||||
|
||||
For Liquid users migrating from Jekyll, there's a `jekyllWhere` option to mimic the behavior of Jekyll's `where` filter. This option is set to `false` by default. When enabled, if `property` is an array, the target value is matched using `Array.includes` instead of `==`, which is particularly useful for filtering tags.
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||
]
|
||||
```
|
||||
|
||||
Input
|
||||
```liquid
|
||||
{% assign selected = pages | where: 'tags', "cat" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
Output
|
||||
```text
|
||||
Cat Food
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
@@ -103,5 +103,32 @@ const products = [
|
||||
- 3
|
||||
```
|
||||
|
||||
## Jekyll 风格
|
||||
|
||||
{% since %}v10.19.0{% endsince %}
|
||||
|
||||
对于从 Jekyll 迁移到 Liquid 的用户,有一个 `jekyllWhere` 选项可以模拟 Jekyll 的 `where` 过滤器的行为。该选项默认设置为 `false`。启用后,如果 `property` 是一个数组,目标值将使用 `Array.includes` 而不是 `==` 进行匹配,这在过滤标签时特别有用。
|
||||
|
||||
例如,以下代码:
|
||||
|
||||
```javascript
|
||||
const pages = [
|
||||
{ tags: ["cat", "food"], title: 'Cat Food' },
|
||||
{ tags: ["dog", "food"], title: 'Dog Food' },
|
||||
]
|
||||
```
|
||||
|
||||
输入
|
||||
```liquid
|
||||
{% assign selected = pages | where: 'tags', "cat" %}
|
||||
{% for item in selected -%}
|
||||
- {{ item.title }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
输出
|
||||
```text
|
||||
Cat Food
|
||||
```
|
||||
|
||||
[truthy]: ../tutorials/truthy-and-falsy.html
|
||||
|
||||
Vendored
+4
-1
@@ -60,7 +60,10 @@
|
||||
/* global liquidjs, ace */
|
||||
if (!location.pathname.match(/playground.html$/)) return;
|
||||
updateVersion(liquidjs.version);
|
||||
const engine = new liquidjs.Liquid();
|
||||
const engine = new liquidjs.Liquid({
|
||||
memoryLimit: 1e5,
|
||||
renderLimit: 1e5
|
||||
});
|
||||
const editor = createEditor('editorEl', 'liquid');
|
||||
const dataEditor = createEditor('dataEl', 'json');
|
||||
const preview = createEditor('previewEl', 'html');
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "liquidjs",
|
||||
"version": "10.18.0",
|
||||
"version": "10.19.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "liquidjs",
|
||||
"version": "10.18.0",
|
||||
"version": "10.19.0",
|
||||
"sideEffects": false,
|
||||
"description": "A simple, expressive and safe Shopify / Github Pages compatible template engine in pure JavaScript.",
|
||||
"main": "dist/liquid.node.js",
|
||||
"module": "dist/liquid.node.mjs",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Drop } from '../drop/drop'
|
||||
import { __assign } from 'tslib'
|
||||
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
|
||||
import { Scope } from './scope'
|
||||
import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter } from '../util'
|
||||
import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue } from '../util'
|
||||
|
||||
type PropertyKey = string | number;
|
||||
|
||||
@@ -25,6 +25,8 @@ export class Context {
|
||||
*/
|
||||
public globals: Scope
|
||||
public sync: boolean
|
||||
public breakCalled = false
|
||||
public continueCalled = false
|
||||
/**
|
||||
* The normalized liquid options object
|
||||
*/
|
||||
@@ -71,8 +73,8 @@ export class Context {
|
||||
public getSync (paths: PropertyKey[]): unknown {
|
||||
return toValueSync(this._get(paths))
|
||||
}
|
||||
public * _get (paths: PropertyKey[]): IterableIterator<unknown> {
|
||||
const scope = this.findScope(paths[0])
|
||||
public * _get (paths: (PropertyKey | Drop)[]): IterableIterator<unknown> {
|
||||
const scope = this.findScope(paths[0] as string) // first prop should always be a string
|
||||
return yield this._getFromScope(scope, paths)
|
||||
}
|
||||
/**
|
||||
@@ -81,7 +83,7 @@ export class Context {
|
||||
public getFromScope (scope: unknown, paths: PropertyKey[] | string): IterableIterator<unknown> {
|
||||
return toValueSync(this._getFromScope(scope, paths))
|
||||
}
|
||||
public * _getFromScope (scope: unknown, paths: PropertyKey[] | string, strictVariables = this.strictVariables): IterableIterator<unknown> {
|
||||
public * _getFromScope (scope: unknown, paths: (PropertyKey | Drop)[] | string, strictVariables = this.strictVariables): IterableIterator<unknown> {
|
||||
if (isString(paths)) paths = paths.split('.')
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
scope = yield readProperty(scope as object, paths[i], this.ownPropertyOnly)
|
||||
@@ -120,8 +122,9 @@ export class Context {
|
||||
}
|
||||
}
|
||||
|
||||
export function readProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) {
|
||||
export function readProperty (obj: Scope, key: (PropertyKey | Drop), ownPropertyOnly: boolean) {
|
||||
obj = toLiquid(obj)
|
||||
key = toValue(key) as PropertyKey
|
||||
if (isNil(obj)) return obj
|
||||
if (isArray(obj) && (key as number) < 0) return obj[obj.length + +key]
|
||||
const value = readJSProperty(obj, key, ownPropertyOnly)
|
||||
|
||||
@@ -8,4 +8,7 @@ export class BlankDrop extends EmptyDrop {
|
||||
if (isString(value)) return /^\s*$/.test(value)
|
||||
return super.equals(value)
|
||||
}
|
||||
static is (value: unknown) {
|
||||
return value instanceof BlankDrop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,7 @@ export class EmptyDrop extends Drop implements Comparable {
|
||||
public valueOf () {
|
||||
return ''
|
||||
}
|
||||
static is (value: unknown) {
|
||||
return value instanceof EmptyDrop
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast } from '../util'
|
||||
import { equals, evalToken, isTruthy } from '../render'
|
||||
import { arrayIncludes, equals, evalToken, isTruthy } from '../render'
|
||||
import { Value, FilterImpl } from '../template'
|
||||
import { Tokenizer } from '../parser'
|
||||
import type { Scope } from '../context'
|
||||
import { EmptyDrop } from '../drop'
|
||||
|
||||
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
|
||||
const array = toArray(v)
|
||||
@@ -124,9 +125,12 @@ export function * where<T extends object> (this: FilterImpl, arr: T[], property:
|
||||
for (const item of arr) {
|
||||
values.push(yield evalToken(token, this.context.spawn(item)))
|
||||
}
|
||||
const matcher = this.context.opts.jekyllWhere
|
||||
? (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
|
||||
: (v: any) => equals(v, expected)
|
||||
return arr.filter((_, i) => {
|
||||
if (expected === undefined) return isTruthy(values[i], this.context)
|
||||
return equals(values[i], expected)
|
||||
return matcher(values[i])
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface LiquidOptions {
|
||||
relativeReference?: boolean;
|
||||
/** Use jekyll style include, pass parameters to `include` variable of current scope. Defaults to `false`. */
|
||||
jekyllInclude?: boolean;
|
||||
/** Use jekyll style where filter, enables array item match. Defaults to `false`. */
|
||||
jekyllWhere?: boolean;
|
||||
/** Add a extname (if filepath doesn't include one) before template file lookup. Eg: setting to `".html"` will allow including file by basename. Defaults to `""`. */
|
||||
extname?: string;
|
||||
/** Whether or not to cache resolved templates. Defaults to `false`. */
|
||||
|
||||
@@ -50,6 +50,16 @@ describe('Expression', function () {
|
||||
expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).toBe('BAR')
|
||||
expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).toBe('bar')
|
||||
})
|
||||
it('should support drops in property access', async function () {
|
||||
class TemplateDrop extends Drop {
|
||||
valueOf () { return 'bar' }
|
||||
}
|
||||
const ctx = new Context({
|
||||
foo: { bar: 'BAR' },
|
||||
coo: new TemplateDrop()
|
||||
})
|
||||
expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).toBe('BAR')
|
||||
})
|
||||
})
|
||||
|
||||
describe('simple expression', function () {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { QuotedToken, RangeToken, OperatorToken, Token, PropertyAccessToken, Ope
|
||||
import { isRangeToken, isPropertyAccessToken, UndefinedVariableError, range, isOperatorToken, assert } from '../util'
|
||||
import type { Context } from '../context'
|
||||
import type { UnaryOperatorHandler } from '../render'
|
||||
import { Drop } from '../drop'
|
||||
|
||||
export class Expression {
|
||||
private postfix: Token[]
|
||||
@@ -42,9 +43,9 @@ export function * evalToken (token: Token | undefined, ctx: Context, lenient = f
|
||||
}
|
||||
|
||||
function * evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean): IterableIterator<unknown> {
|
||||
const props: string[] = []
|
||||
const props: (string | number | Drop)[] = []
|
||||
for (const prop of token.props) {
|
||||
props.push((yield evalToken(prop, ctx, false)) as unknown as string)
|
||||
props.push((yield evalToken(prop, ctx, false)) as unknown as string | number | Drop)
|
||||
}
|
||||
try {
|
||||
if (token.variable) {
|
||||
@@ -66,6 +67,7 @@ export function evalQuotedToken (token: QuotedToken) {
|
||||
function * evalRangeToken (token: RangeToken, ctx: Context) {
|
||||
const low: number = yield evalToken(token.lhs, ctx)
|
||||
const high: number = yield evalToken(token.rhs, ctx)
|
||||
ctx.memoryLimit.use(high - low + 1)
|
||||
return range(+low, +high + 1)
|
||||
}
|
||||
|
||||
|
||||
@@ -58,3 +58,7 @@ function arrayEquals (lhs: any[], rhs: any[]): boolean {
|
||||
if (lhs.length !== rhs.length) return false
|
||||
return !lhs.some((value, i) => !equals(value, rhs[i]))
|
||||
}
|
||||
|
||||
export function arrayIncludes (arr: any[], item: any): boolean {
|
||||
return arr.some(value => equals(value, item))
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export class Render {
|
||||
const html = yield tpl.render(ctx, emitter)
|
||||
// if not, it'll return an `html`, write to the emitter for it
|
||||
html && emitter.write(html)
|
||||
if (emitter['break'] || emitter['continue']) break
|
||||
if (ctx.breakCalled || ctx.continueCalled) break
|
||||
} catch (e) {
|
||||
const err = LiquidError.is(e) ? e : new RenderError(e as Error, tpl)
|
||||
if (ctx.opts.catchAllErrors) errors.push(err)
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Context, Emitter, Tag } from '..'
|
||||
|
||||
export default class extends Tag {
|
||||
render (ctx: Context, emitter: Emitter) {
|
||||
emitter['break'] = true
|
||||
render (ctx: Context, _emitter: Emitter) {
|
||||
ctx.breakCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Tag, Emitter, Context } from '..'
|
||||
|
||||
export default class extends Tag {
|
||||
render (ctx: Context, emitter: Emitter) {
|
||||
emitter['continue'] = true
|
||||
render (ctx: Context, _emitter: Emitter) {
|
||||
ctx.continueCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -68,12 +68,9 @@ export default class extends Tag {
|
||||
ctx.push(scope)
|
||||
for (const item of collection) {
|
||||
scope[this.variable] = item
|
||||
ctx.continueCalled = ctx.breakCalled = false
|
||||
yield r.renderTemplates(this.templates, ctx, emitter)
|
||||
if (emitter['break']) {
|
||||
emitter['break'] = false
|
||||
break
|
||||
}
|
||||
emitter['continue'] = false
|
||||
if (ctx.breakCalled) break
|
||||
scope.forloop.next()
|
||||
}
|
||||
ctx.pop()
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('express()', function () {
|
||||
it('should respect express views(string)', function (done) {
|
||||
app.set('views', views)
|
||||
request(app).get('/include/bar')
|
||||
.expect('bar')
|
||||
.expect('BAR')
|
||||
.expect(200, done)
|
||||
})
|
||||
it('should pass error when file not found', function (done) {
|
||||
@@ -63,7 +63,7 @@ describe('express()', function () {
|
||||
it('should respect express views (Array) when lookup', function (done) {
|
||||
app.set('views', [views, partials])
|
||||
request(app).get('/include/bar')
|
||||
.expect('bar')
|
||||
.expect('BAR')
|
||||
.expect(200, done)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -524,4 +524,11 @@ describe('Issues', function () {
|
||||
const result = engine.parseAndRenderSync(`\n{{ "foo" | pos }}`)
|
||||
expect(result).toEqual('\n[2,12] foo')
|
||||
})
|
||||
it("memoryLimit doesn't work in for tag #776", () => {
|
||||
const engine = new Liquid({
|
||||
memoryLimit: 1e5
|
||||
})
|
||||
const tpl = `{% for i in (1..1000000000) %} {{'a'}} {% endfor %}`
|
||||
expect(() => engine.parseAndRenderSync(tpl)).toThrow('memory alloc limit exceeded, line:1, col:1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,4 +52,14 @@ describe('#renderFile()', function () {
|
||||
})
|
||||
return expect(engine.renderFile('/not/exist.html')).rejects.toThrow(/Failed to lookup "\/not\/exist.html" in "\/boo,\/root\/"/)
|
||||
})
|
||||
it('should handle "." as cwd', async () => {
|
||||
engine = new Liquid({
|
||||
root: ['.'],
|
||||
extname: '.html'
|
||||
})
|
||||
process.chdir(resolve(__dirname, '../stub/views'))
|
||||
await expect(engine.renderFile('bar')).resolves.toEqual('BAR')
|
||||
process.chdir(resolve(__dirname, '../stub/partials'))
|
||||
await expect(engine.renderFile('bar')).resolves.toEqual('bar')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -484,6 +484,28 @@ describe('filters/array', function () {
|
||||
Kitchen products:
|
||||
`)
|
||||
})
|
||||
it('should support nil as target', () => {
|
||||
const scope = { list: [{ foo: 'FOO' }, { bar: 'BAR', type: 2 }] }
|
||||
return test('{{list | where: "type", nil | json}}', scope, '[{"foo":"FOO"}]')
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]')
|
||||
})
|
||||
it('should not match string with array', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[]')
|
||||
})
|
||||
describe('jekyll style', () => {
|
||||
it('should not match string with array', async () => {
|
||||
const scope = { objs: [{ foo: ['FOO', 'bar'] }] }
|
||||
await test('{{objs | where: "foo", "FOO" | json}}', scope, '[{"foo":["FOO","bar"]}]', { jekyllWhere: true })
|
||||
})
|
||||
it('should support empty as target', async () => {
|
||||
const scope = { pages: [{ tags: ['FOO'] }, { tags: [] }, { title: 'foo' }] }
|
||||
await test('{{pages | where: "tags", empty | json}}', scope, '[{"tags":[]}]', { jekyllWhere: true })
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('where_exp', function () {
|
||||
const products = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { normalize } from '../../../src/liquid-options'
|
||||
|
||||
describe('LiquidOptions#root', function () {
|
||||
describe('#normalize ()', function () {
|
||||
describe('#normalize()', function () {
|
||||
it('should normalize string typed root array', function () {
|
||||
const options = normalize({ root: 'foo' })
|
||||
expect(options.root).toEqual(['foo'])
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Liquid } from '../../../src/liquid'
|
||||
import { Drop } from '../../../src/drop/drop'
|
||||
import { Scope } from '../../../src/context/scope'
|
||||
import { mock, restore } from '../../stub/mockfs'
|
||||
|
||||
describe('tags/for', function () {
|
||||
let liquid: Liquid, scope: Scope
|
||||
@@ -139,6 +140,7 @@ describe('tags/for', function () {
|
||||
})
|
||||
|
||||
describe('continue', function () {
|
||||
afterEach(restore)
|
||||
it('should support for with continue', async function () {
|
||||
const src = '{% for i in (1..5) %}' +
|
||||
'{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' +
|
||||
@@ -154,6 +156,28 @@ describe('tags/for', function () {
|
||||
const html = await liquid.parseAndRender(src, scope)
|
||||
return expect(html).toBe('123continue5')
|
||||
})
|
||||
it('should skip snippet for rendered continue', async function () {
|
||||
mock({
|
||||
'snippet.liquid': ' before{% continue %}skipped'
|
||||
})
|
||||
const src = '{% for i in (1..2) %}' +
|
||||
'{% render "snippet.liquid" %}' +
|
||||
' after' +
|
||||
'{% endfor %}'
|
||||
const html = await liquid.parseAndRender(src, scope)
|
||||
return expect(html).toBe(' before after before after')
|
||||
})
|
||||
it('should skip `for` body for included continue', async function () {
|
||||
mock({
|
||||
'snippet.liquid': ' before{% continue %}skipped'
|
||||
})
|
||||
const src = '{% for i in (1..2) %}' +
|
||||
'{% include "snippet.liquid" %}' +
|
||||
' after' +
|
||||
'{% endfor %}'
|
||||
const html = await liquid.parseAndRender(src, scope)
|
||||
return expect(html).toBe(' before before')
|
||||
})
|
||||
})
|
||||
describe('break', function () {
|
||||
it('should support break', async function () {
|
||||
|
||||
@@ -1 +1 @@
|
||||
bar
|
||||
BAR
|
||||
Reference in New Issue
Block a user