Skip to content

Commit

Permalink
feat: jsonify, inspect, to_integer, normalize_whitespace filters
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed May 6, 2024
1 parent ea0eab9 commit 842b45c
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 17 deletions.
4 changes: 4 additions & 0 deletions docs/source/_data/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,17 @@ filters:
floor: floor.html
group_by: group_by.html
group_by_exp: group_by_exp.html
inspect: inspect.html
join: join.html
json: json.html
jsonify: jsonify.html
last: last.html
lstrip: lstrip.html
map: map.html
minus: minus.html
modulo: modulo.html
newline_to_br: newline_to_br.html
normalize_whitespace: normalize_whitespace.html
plus: plus.html
pop: pop.html
push: push.html
Expand All @@ -80,6 +83,7 @@ filters:
strip_newlines: strip_newlines.html
sum: sum.html
times: times.html
to_integer: to_integer.html
truncate: truncate.html
truncatewords: truncatewords.html
uniq: uniq.html
Expand Down
42 changes: 42 additions & 0 deletions docs/source/filters/inspect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: inspect
---

{% since %}v10.13.0{% endsince %}

Similar with `json`, but `inspect` allows cyclic structure. For the scope below:

```
const foo = {
bar: 'BAR'
}
foo.foo = foo
const scope = { foo }
```

Input
```liquid
{% foo | inspect %}
```

Output
```text
{"bar":"BAR","foo":"[Circular]"}
```

## Formatting

An additional `space` argument can be specified for the indent width.

Input
```liquid
{{ foo | inspect: 4 }}
```

Output
```text
{
"bar": "BAR",
"foo": "[Circular]"
}
```
9 changes: 9 additions & 0 deletions docs/source/filters/jsonify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: jsonify
---

{% since %}v10.13.0{% endsince %}

See [json][json].

[json]: /filters/json.html
17 changes: 17 additions & 0 deletions docs/source/filters/normalize_whitespace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: normalize_whitespace
---

{% since %}v10.13.0{% endsince %}

Replace any occurrence of whitespace with a single space.

Input
```liquid
{{ "a \n b" | normalize_whitespace }}
```

Output
```html
a b
```
4 changes: 2 additions & 2 deletions docs/source/filters/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ There's 40+ filters supported by LiquidJS. These filters can be categorized into
Categories | Filters
--- | ---
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last,remove, remove_first, remove_last, truncate, truncatewords
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
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
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
Date | date
Misc | default, json, raw
Misc | default, json, jsonify, inspect, raw, to_integer

[shopify/liquid]: https://github.com/Shopify/liquid
17 changes: 17 additions & 0 deletions docs/source/filters/to_integer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: to_integer
---

{% since %}v10.13.0{% endsince %}

Convert values to number.

Input
```liquid
{{ "123" | to_integer | json }}
```

Output
```text
123
```
4 changes: 2 additions & 2 deletions docs/source/tutorials/differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ In the meantime, it's now implemented in JavaScript, that means it has to be mor
* **Async as first-class citizen**. Filters and tags can be implemented asynchronously by return a `Promise`.
* **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.
* **[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.
* **Additional tags and filters** like `layout` and `json`, see below for details.
* **Additional tags and filters** like `layout` and `json`, `inspect`, `where_exp`, `group_by`, etc., see below for details.

## Differences

Expand All @@ -31,7 +31,7 @@ Though we're trying to be compatible with the Ruby version, there are still some
* 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].
* LiquidJS has more tags/filters than [the Liquid language][liquid]:
* LiquidJS-defined tags: [layout][layout], [render][render] and corresponding `block` tag.
* LiquidJS-defined filters: [json][json].
* LiquidJS-defined filters: [json][json], group_by, group_by_exp, where_exp, jsonify, inspect, etc.
* Tags/filters that don't depend on Shopify platform are borrowed from [Shopify][shopify-tags].
* Tags/filters that don't depend on Jekyll framework are borrowed from [Jekyll][jekyll-filters].
* Some tags/filters behave differently: [date][date] filter.
Expand Down
42 changes: 42 additions & 0 deletions docs/source/zh-cn/filters/inspect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: inspect
---

{% since %}v10.13.0{% endsince %}

类似于 `json`,但可以处理循环引用的情况。例如对于上下文:

```
const foo = {
bar: 'BAR'
}
foo.foo = foo
const scope = { foo }
```

输入
```liquid
{% foo | inspect %}
```

输出
```text
{"bar":"BAR","foo":"[Circular]"}
```

## 格式化

可以指定一个 `space` 参数来缩进长度。

输入
```liquid
{{ foo | inspect: 4 }}
```

输出
```text
{
"bar": "BAR",
"foo": "[Circular]"
}
```
4 changes: 2 additions & 2 deletions docs/source/zh-cn/filters/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ title: json

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

Input
输入
```liquid
{% assign arr = "foo bar coo" | split: " " %}
{{ arr | json: 4 }}
```

Output
输出
```text
[
"foo",
Expand Down
9 changes: 9 additions & 0 deletions docs/source/zh-cn/filters/jsonify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: jsonify
---

{% since %}v10.13.0{% endsince %}

[json][json]

[json]: ./json.html
17 changes: 17 additions & 0 deletions docs/source/zh-cn/filters/normalize_whitespace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: normalize_whitespace
---

{% since %}v10.13.0{% endsince %}

把连续的空白字符替换为单个空格。

输入
```liquid
{{ "a \n b" | normalize_whitespace }}
```

输出
```html
a b
```
4 changes: 2 additions & 2 deletions docs/source/zh-cn/filters/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
类别 | 过滤器
--- | ---
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last, remove, remove_first, remove_last, truncate, truncatewords
字符串 | 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
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
数组 | 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
日期 | date
其他 | default, json
其他 | default, json, jsonify, inspect, raw, to_integer

[shopify/liquid]: https://github.com/Shopify/liquid
17 changes: 17 additions & 0 deletions docs/source/zh-cn/filters/to_integer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: to_integer
---

{% since %}v10.13.0{% endsince %}

转换为数字类型。

输入
```liquid
{{ "123" | to_integer | json }}
```

输出
```text
123
```
6 changes: 2 additions & 4 deletions src/filters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as urlFilters from './url'
import * as arrayFilters from './array'
import * as dateFilters from './date'
import * as stringFilters from './string'
import { Default, json, raw } from './misc'
import misc from './misc'
import { FilterImplOptions } from '../template'

export const filters: Record<string, FilterImplOptions> = {
Expand All @@ -14,7 +14,5 @@ export const filters: Record<string, FilterImplOptions> = {
...arrayFilters,
...dateFilters,
...stringFilters,
json,
raw,
default: Default
...misc
}
31 changes: 28 additions & 3 deletions src/filters/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,43 @@ import { isFalsy } from '../render/boolean'
import { identify, isArray, isString, toValue } from '../util/underscore'
import { FilterImpl } from '../template'

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

export function json (value: any, space = 0) {
function json (value: any, space = 0) {
return JSON.stringify(value, null, space)
}

export const raw = {
function inspect (value: any, space = 0) {
const ancestors: object[] = []
return JSON.stringify(value, function (this: unknown, _key: unknown, value: any) {
if (typeof value !== 'object' || value === null) return value
// `this` is the object that value is contained in, i.e., its direct parent.
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop()
if (ancestors.includes(value)) return '[Circular]'
ancestors.push(value)
return value
}, space)
}

function to_integer (value: any) {
return Number(value)
}

const raw = {
raw: true,
handler: identify
}

export default {
default: defaultFilter,
raw,
jsonify: json,
to_integer,
json,
inspect
}
5 changes: 5 additions & 0 deletions src/filters/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,8 @@ export function truncatewords (v: string, words = 15, o = '...') {
if (arr.length >= words) ret += o
return ret
}

export function normalize_whitespace (v: string) {
v = stringify(v)
return v.replace(/\s+/g, ' ')
}
4 changes: 2 additions & 2 deletions src/liquid-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { LRU, LiquidCache } from './cache'
import { FS, LookupType } from './fs'
import * as fs from './fs/fs-impl'
import { defaultOperators, Operators } from './render'
import { json } from './filters/misc'
import misc from './filters/misc'
import { escape } from './filters/html'

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

function getOutputEscapeFunction (nameOrFunction: OutputEscapeOption): OutputEscape {
if (nameOrFunction === 'escape') return escape
if (nameOrFunction === 'json') return json
if (nameOrFunction === 'json') return misc.json
assert(isFunction(nameOrFunction), '`outputEscape` need to be of type string or function')
return nameOrFunction
}
Expand Down
29 changes: 29 additions & 0 deletions test/integration/filters/misc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,33 @@ describe('filters/object', function () {
expect(liquid.parseAndRenderSync('{{obj | json: 4}}', scope)).toBe(result)
})
})
describe('jsonify', function () {
it('should stringify string', async () => expect(await liquid.parseAndRender('{{"foo" | jsonify}}')).toBe('"foo"'))
})
describe('inspect', function () {
it('should inspect string', async () => expect(await liquid.parseAndRender('{{"foo" | inspect}}')).toBe('"foo"'))
it('should inspect object', () => {
const text = '{{foo | inspect}}'
const foo = { bar: 'bar' }
const expected = '{"bar":"bar"}'
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
})
it('should inspect cyclic object', () => {
const text = '{{foo | inspect}}'
const foo: any = { bar: 'bar' }
foo.foo = foo
const expected = '{"bar":"bar","foo":"[Circular]"}'
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
})
it('should support space argument', () => {
const text = '{{foo | inspect: 4}}'
const foo: any = { bar: 'bar' }
foo.foo = foo
const expected = '{\n "bar": "bar",\n "foo": "[Circular]"\n}'
return expect(liquid.parseAndRenderSync(text, { foo })).toBe(expected)
})
})
describe('to_integer', function () {
it('should stringify string', () => expect(liquid.parseAndRenderSync('{{ "123" | to_integer | json }}')).toBe('123'))
})
})
Loading

0 comments on commit 842b45c

Please sign in to comment.