Skip to content

Commit 842b45c

Browse files
committed
feat: jsonify, inspect, to_integer, normalize_whitespace filters
1 parent ea0eab9 commit 842b45c

19 files changed

+258
-17
lines changed

docs/source/_data/sidebar.yml

+4
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,17 @@ filters:
4747
floor: floor.html
4848
group_by: group_by.html
4949
group_by_exp: group_by_exp.html
50+
inspect: inspect.html
5051
join: join.html
5152
json: json.html
53+
jsonify: jsonify.html
5254
last: last.html
5355
lstrip: lstrip.html
5456
map: map.html
5557
minus: minus.html
5658
modulo: modulo.html
5759
newline_to_br: newline_to_br.html
60+
normalize_whitespace: normalize_whitespace.html
5861
plus: plus.html
5962
pop: pop.html
6063
push: push.html
@@ -80,6 +83,7 @@ filters:
8083
strip_newlines: strip_newlines.html
8184
sum: sum.html
8285
times: times.html
86+
to_integer: to_integer.html
8387
truncate: truncate.html
8488
truncatewords: truncatewords.html
8589
uniq: uniq.html

docs/source/filters/inspect.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: inspect
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
Similar with `json`, but `inspect` allows cyclic structure. For the scope below:
8+
9+
```
10+
const foo = {
11+
bar: 'BAR'
12+
}
13+
foo.foo = foo
14+
const scope = { foo }
15+
```
16+
17+
Input
18+
```liquid
19+
{% foo | inspect %}
20+
```
21+
22+
Output
23+
```text
24+
{"bar":"BAR","foo":"[Circular]"}
25+
```
26+
27+
## Formatting
28+
29+
An additional `space` argument can be specified for the indent width.
30+
31+
Input
32+
```liquid
33+
{{ foo | inspect: 4 }}
34+
```
35+
36+
Output
37+
```text
38+
{
39+
"bar": "BAR",
40+
"foo": "[Circular]"
41+
}
42+
```

docs/source/filters/jsonify.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: jsonify
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
See [json][json].
8+
9+
[json]: /filters/json.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: normalize_whitespace
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
Replace any occurrence of whitespace with a single space.
8+
9+
Input
10+
```liquid
11+
{{ "a \n b" | normalize_whitespace }}
12+
```
13+
14+
Output
15+
```html
16+
a b
17+
```

docs/source/filters/overview.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ There's 40+ filters supported by LiquidJS. These filters can be categorized into
1010
Categories | Filters
1111
--- | ---
1212
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
13-
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last,remove, remove_first, remove_last, truncate, truncatewords
13+
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last,remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace
1414
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
1515
Array | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
1616
Date | date
17-
Misc | default, json, raw
17+
Misc | default, json, jsonify, inspect, raw, to_integer
1818

1919
[shopify/liquid]: https://github.com/Shopify/liquid

docs/source/filters/to_integer.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: to_integer
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
Convert values to number.
8+
9+
Input
10+
```liquid
11+
{{ "123" | to_integer | json }}
12+
```
13+
14+
Output
15+
```text
16+
123
17+
```

docs/source/tutorials/differences.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ In the meantime, it's now implemented in JavaScript, that means it has to be mor
1616
* **Async as first-class citizen**. Filters and tags can be implemented asynchronously by return a `Promise`.
1717
* **Also can be sync**. For scenarios that are not I/O intensive, render synchronously can be much faster. You can call synchronous APIs like `.renderSync()` as long as all the filters and tags in template support to be rendered synchronously. All builtin filters/tags support both sync and async render.
1818
* **[Abstract file system][afs]**. Along with async feature, LiquidJS can be used to serve templates stored in Databases [#414][#414], on remote HTTP server [#485][#485], and so on.
19-
* **Additional tags and filters** like `layout` and `json`, see below for details.
19+
* **Additional tags and filters** like `layout` and `json`, `inspect`, `where_exp`, `group_by`, etc., see below for details.
2020

2121
## Differences
2222

@@ -31,7 +31,7 @@ Though we're trying to be compatible with the Ruby version, there are still some
3131
* Trailing unmatched characters inside filters are allowed in shopify/liquid but not in LiquidJS. It means filter arguments without a colon like `{%raw%}{{ "a b" | split " "}}{%endraw%}` will throw an error in LiquidJS. This is intended to improve Liquid usability, see [#208][#208] and [#212][#212].
3232
* LiquidJS has more tags/filters than [the Liquid language][liquid]:
3333
* LiquidJS-defined tags: [layout][layout], [render][render] and corresponding `block` tag.
34-
* LiquidJS-defined filters: [json][json].
34+
* LiquidJS-defined filters: [json][json], group_by, group_by_exp, where_exp, jsonify, inspect, etc.
3535
* Tags/filters that don't depend on Shopify platform are borrowed from [Shopify][shopify-tags].
3636
* Tags/filters that don't depend on Jekyll framework are borrowed from [Jekyll][jekyll-filters].
3737
* Some tags/filters behave differently: [date][date] filter.

docs/source/zh-cn/filters/inspect.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: inspect
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
类似于 `json`,但可以处理循环引用的情况。例如对于上下文:
8+
9+
```
10+
const foo = {
11+
bar: 'BAR'
12+
}
13+
foo.foo = foo
14+
const scope = { foo }
15+
```
16+
17+
输入
18+
```liquid
19+
{% foo | inspect %}
20+
```
21+
22+
输出
23+
```text
24+
{"bar":"BAR","foo":"[Circular]"}
25+
```
26+
27+
## 格式化
28+
29+
可以指定一个 `space` 参数来缩进长度。
30+
31+
输入
32+
```liquid
33+
{{ foo | inspect: 4 }}
34+
```
35+
36+
输出
37+
```text
38+
{
39+
"bar": "BAR",
40+
"foo": "[Circular]"
41+
}
42+
```

docs/source/zh-cn/filters/json.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ title: json
2323

2424
可以指定一个 `space` 参数来格式化 JSON。
2525

26-
Input
26+
输入
2727
```liquid
2828
{% assign arr = "foo bar coo" | split: " " %}
2929
{{ arr | json: 4 }}
3030
```
3131

32-
Output
32+
输出
3333
```text
3434
[
3535
"foo",

docs/source/zh-cn/filters/jsonify.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: jsonify
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
[json][json]
8+
9+
[json]: ./json.html
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: normalize_whitespace
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
把连续的空白字符替换为单个空格。
8+
9+
输入
10+
```liquid
11+
{{ "a \n b" | normalize_whitespace }}
12+
```
13+
14+
输出
15+
```html
16+
a b
17+
```

docs/source/zh-cn/filters/overview.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
1010
类别 | 过滤器
1111
--- | ---
1212
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
13-
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last, remove, remove_first, remove_last, truncate, truncatewords
13+
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last, remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace
1414
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
1515
数组 | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
1616
日期 | date
17-
其他 | default, json
17+
其他 | default, json, jsonify, inspect, raw, to_integer
1818

1919
[shopify/liquid]: https://github.com/Shopify/liquid
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
title: to_integer
3+
---
4+
5+
{% since %}v10.13.0{% endsince %}
6+
7+
转换为数字类型。
8+
9+
输入
10+
```liquid
11+
{{ "123" | to_integer | json }}
12+
```
13+
14+
输出
15+
```text
16+
123
17+
```

src/filters/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as urlFilters from './url'
44
import * as arrayFilters from './array'
55
import * as dateFilters from './date'
66
import * as stringFilters from './string'
7-
import { Default, json, raw } from './misc'
7+
import misc from './misc'
88
import { FilterImplOptions } from '../template'
99

1010
export const filters: Record<string, FilterImplOptions> = {
@@ -14,7 +14,5 @@ export const filters: Record<string, FilterImplOptions> = {
1414
...arrayFilters,
1515
...dateFilters,
1616
...stringFilters,
17-
json,
18-
raw,
19-
default: Default
17+
...misc
2018
}

src/filters/misc.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,43 @@ import { isFalsy } from '../render/boolean'
22
import { identify, isArray, isString, toValue } from '../util/underscore'
33
import { FilterImpl } from '../template'
44

5-
export function Default<T1 extends boolean, T2> (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 {
5+
function defaultFilter<T1 extends boolean, T2> (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 {
66
value = toValue(value)
77
if (isArray(value) || isString(value)) return value.length ? value : defaultValue
88
if (value === false && (new Map(args)).get('allow_false')) return false as T1
99
return isFalsy(value, this.context) ? defaultValue : value
1010
}
1111

12-
export function json (value: any, space = 0) {
12+
function json (value: any, space = 0) {
1313
return JSON.stringify(value, null, space)
1414
}
1515

16-
export const raw = {
16+
function inspect (value: any, space = 0) {
17+
const ancestors: object[] = []
18+
return JSON.stringify(value, function (this: unknown, _key: unknown, value: any) {
19+
if (typeof value !== 'object' || value === null) return value
20+
// `this` is the object that value is contained in, i.e., its direct parent.
21+
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop()
22+
if (ancestors.includes(value)) return '[Circular]'
23+
ancestors.push(value)
24+
return value
25+
}, space)
26+
}
27+
28+
function to_integer (value: any) {
29+
return Number(value)
30+
}
31+
32+
const raw = {
1733
raw: true,
1834
handler: identify
1935
}
36+
37+
export default {
38+
default: defaultFilter,
39+
raw,
40+
jsonify: json,
41+
to_integer,
42+
json,
43+
inspect
44+
}

src/filters/string.ts

+5
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,8 @@ export function truncatewords (v: string, words = 15, o = '...') {
112112
if (arr.length >= words) ret += o
113113
return ret
114114
}
115+
116+
export function normalize_whitespace (v: string) {
117+
v = stringify(v)
118+
return v.replace(/\s+/g, ' ')
119+
}

src/liquid-options.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { LRU, LiquidCache } from './cache'
33
import { FS, LookupType } from './fs'
44
import * as fs from './fs/fs-impl'
55
import { defaultOperators, Operators } from './render'
6-
import { json } from './filters/misc'
6+
import misc from './filters/misc'
77
import { escape } from './filters/html'
88

99
type OutputEscape = (value: any) => string
@@ -193,7 +193,7 @@ export function normalize (options: LiquidOptions): NormalizedFullOptions {
193193

194194
function getOutputEscapeFunction (nameOrFunction: OutputEscapeOption): OutputEscape {
195195
if (nameOrFunction === 'escape') return escape
196-
if (nameOrFunction === 'json') return json
196+
if (nameOrFunction === 'json') return misc.json
197197
assert(isFunction(nameOrFunction), '`outputEscape` need to be of type string or function')
198198
return nameOrFunction
199199
}

test/integration/filters/misc.spec.ts

+29
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,33 @@ describe('filters/object', function () {
3030
expect(liquid.parseAndRenderSync('{{obj | json: 4}}', scope)).toBe(result)
3131
})
3232
})
33+
describe('jsonify', function () {
34+
it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | jsonify}}')).toBe('"foo"'))
35+
})
36+
describe('inspect', function () {
37+
it('should inspect string', async () => expect(await liquid.parseAndRender('{{"foo" | inspect}}')).toBe('"foo"'))
38+
it('should inspect object', () => {
39+
const text = '{{foo | inspect}}'
40+
const foo = { bar: 'bar' }
41+
const expected = '{"bar":"bar"}'
42+
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
43+
})
44+
it('should inspect cyclic object', () => {
45+
const text = '{{foo | inspect}}'
46+
const foo: any = { bar: 'bar' }
47+
foo.foo = foo
48+
const expected = '{"bar":"bar","foo":"[Circular]"}'
49+
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
50+
})
51+
it('should support space argument', () => {
52+
const text = '{{foo | inspect: 4}}'
53+
const foo: any = { bar: 'bar' }
54+
foo.foo = foo
55+
const expected = '{\n "bar": "bar",\n "foo": "[Circular]"\n}'
56+
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
57+
})
58+
})
59+
describe('to_integer', function () {
60+
it('should stringify string', () => expect(liquid.parseAndRenderSync('{{ "123" | to_integer | json }}')).toBe('123'))
61+
})
3362
})

0 commit comments

Comments
 (0)