mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 04:40:39 -07:00
feat: pop/shift/unshift filters from Jekyll
This commit is contained in:
@@ -51,6 +51,7 @@ filters:
|
|||||||
modulo: modulo.html
|
modulo: modulo.html
|
||||||
newline_to_br: newline_to_br.html
|
newline_to_br: newline_to_br.html
|
||||||
plus: plus.html
|
plus: plus.html
|
||||||
|
pop: pop.html
|
||||||
push: push.html
|
push: push.html
|
||||||
prepend: prepend.html
|
prepend: prepend.html
|
||||||
raw: raw.html
|
raw: raw.html
|
||||||
@@ -63,6 +64,7 @@ filters:
|
|||||||
reverse: reverse.html
|
reverse: reverse.html
|
||||||
round: round.html
|
round: round.html
|
||||||
rstrip: rstrip.html
|
rstrip: rstrip.html
|
||||||
|
shift: shift.html
|
||||||
size: size.html
|
size: size.html
|
||||||
slice: slice.html
|
slice: slice.html
|
||||||
sort: sort.html
|
sort: sort.html
|
||||||
@@ -76,6 +78,7 @@ filters:
|
|||||||
truncate: truncate.html
|
truncate: truncate.html
|
||||||
truncatewords: truncatewords.html
|
truncatewords: truncatewords.html
|
||||||
uniq: uniq.html
|
uniq: uniq.html
|
||||||
|
unshift: unshift.html
|
||||||
upcase: upcase.html
|
upcase: upcase.html
|
||||||
url_decode: url_decode.html
|
url_decode: url_decode.html
|
||||||
url_encode: url_encode.html
|
url_encode: url_encode.html
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Categories | Filters
|
|||||||
Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
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
|
||||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||||
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat, compact, size, push
|
Array | slice, map, sort, sort_natural, uniq, where, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
|
||||||
Date | date
|
Date | date
|
||||||
Misc | default, json, raw
|
Misc | default, json, raw
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: pop
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
Pop an element from the array. It's NON-DESTRUCTIVE, i.e. it does not mutate the array, but rather make a copy and mutate that.
|
||||||
|
|
||||||
|
Input
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "apples, oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | pop %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
```text
|
||||||
|
- apples
|
||||||
|
- oranges
|
||||||
|
```
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: shift
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
Shift an element from the array. It's NON-DESTRUCTIVE, i.e. it does not mutate the array, but rather make a copy and mutate that.
|
||||||
|
|
||||||
|
Input
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "apples, oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | shift %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
```text
|
||||||
|
- oranges
|
||||||
|
- peaches
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: unshift
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
Unshift an element to the front of the array. It's NON-DESTRUCTIVE, i.e. it does not mutate the array, but rather make a copy and mutate that.
|
||||||
|
|
||||||
|
Input
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | unshift: "apples" %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output
|
||||||
|
```text
|
||||||
|
- apples
|
||||||
|
- oranges
|
||||||
|
- peaches
|
||||||
|
```
|
||||||
@@ -12,7 +12,7 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类:
|
|||||||
数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most
|
数学 | 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
|
||||||
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br
|
||||||
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse, concat, compact, size, push
|
数组 | slice, map, sort, sort_natural, uniq, wheres, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift
|
||||||
日期 | date
|
日期 | date
|
||||||
其他 | default, json
|
其他 | default, json
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: pop
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
从数组末尾弹出一个元素。注意该操作不会改变原数组,而是在一份拷贝上操作。
|
||||||
|
|
||||||
|
输入
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "apples, oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | pop %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
输出
|
||||||
|
```text
|
||||||
|
- apples
|
||||||
|
- oranges
|
||||||
|
```
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
title: shift
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
从数组头部弹出一个元素。注意该操作不会改变原数组,而是在一份拷贝上操作。
|
||||||
|
|
||||||
|
输入
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "apples, oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | shift %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
输出
|
||||||
|
```text
|
||||||
|
- oranges
|
||||||
|
- peaches
|
||||||
|
```
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: unshift
|
||||||
|
---
|
||||||
|
|
||||||
|
{% since %}v10.11.0{% endsince %}
|
||||||
|
|
||||||
|
往数组头部添加一个元素。注意该操作不会改变原数组,而是在一份拷贝上操作。
|
||||||
|
|
||||||
|
输入
|
||||||
|
```liquid
|
||||||
|
{% assign fruits = "oranges, peaches" | split: ", " %}
|
||||||
|
|
||||||
|
{% assign everything = fruits | unshift: "apples" %}
|
||||||
|
|
||||||
|
{% for item in everything %}
|
||||||
|
- {{ item }}
|
||||||
|
{% endfor %}
|
||||||
|
```
|
||||||
|
|
||||||
|
输出
|
||||||
|
```text
|
||||||
|
- apples
|
||||||
|
- oranges
|
||||||
|
- peaches
|
||||||
|
```
|
||||||
+23
-9
@@ -11,7 +11,7 @@ export const reverse = argumentsToValue((v: any[]) => [...toArray(v)].reverse())
|
|||||||
|
|
||||||
export function * sort<T> (this: FilterImpl, arr: T[], property?: string): IterableIterator<unknown> {
|
export function * sort<T> (this: FilterImpl, arr: T[], property?: string): IterableIterator<unknown> {
|
||||||
const values: [T, string | number][] = []
|
const values: [T, string | number][] = []
|
||||||
for (const item of toArray(toValue(arr))) {
|
for (const item of toArray(arr)) {
|
||||||
values.push([
|
values.push([
|
||||||
item,
|
item,
|
||||||
property ? yield this.context._getFromScope(item, stringify(property).split('.'), false) : item
|
property ? yield this.context._getFromScope(item, stringify(property).split('.'), false) : item
|
||||||
@@ -25,7 +25,6 @@ export function * sort<T> (this: FilterImpl, arr: T[], property?: string): Itera
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sort_natural<T> (input: T[], property?: string) {
|
export function sort_natural<T> (input: T[], property?: string) {
|
||||||
input = toValue(input)
|
|
||||||
const propertyString = stringify(property)
|
const propertyString = stringify(property)
|
||||||
const compare = property === undefined
|
const compare = property === undefined
|
||||||
? caseInsensitiveCompare
|
? caseInsensitiveCompare
|
||||||
@@ -37,7 +36,7 @@ export const size = (v: string | any[]) => (v && v.length) || 0
|
|||||||
|
|
||||||
export function * map (this: FilterImpl, arr: Scope[], property: string): IterableIterator<unknown> {
|
export function * map (this: FilterImpl, arr: Scope[], property: string): IterableIterator<unknown> {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of toArray(toValue(arr))) {
|
for (const item of toArray(arr)) {
|
||||||
results.push(yield this.context._getFromScope(item, stringify(property), false))
|
results.push(yield this.context._getFromScope(item, stringify(property), false))
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
@@ -45,7 +44,7 @@ export function * map (this: FilterImpl, arr: Scope[], property: string): Iterab
|
|||||||
|
|
||||||
export function * sum (this: FilterImpl, arr: Scope[], property?: string): IterableIterator<unknown> {
|
export function * sum (this: FilterImpl, arr: Scope[], property?: string): IterableIterator<unknown> {
|
||||||
let sum = 0
|
let sum = 0
|
||||||
for (const item of toArray(toValue(arr))) {
|
for (const item of toArray(arr)) {
|
||||||
const data = Number(property ? yield this.context._getFromScope(item, stringify(property), false) : item)
|
const data = Number(property ? yield this.context._getFromScope(item, stringify(property), false) : item)
|
||||||
sum += Number.isNaN(data) ? 0 : data
|
sum += Number.isNaN(data) ? 0 : data
|
||||||
}
|
}
|
||||||
@@ -53,20 +52,35 @@ export function * sum (this: FilterImpl, arr: Scope[], property?: string): Itera
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function compact<T> (this: FilterImpl, arr: T[]) {
|
export function compact<T> (this: FilterImpl, arr: T[]) {
|
||||||
arr = toValue(arr)
|
|
||||||
return toArray(arr).filter(x => !isNil(toValue(x)))
|
return toArray(arr).filter(x => !isNil(toValue(x)))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function concat<T1, T2> (v: T1[], arg: T2[] = []): (T1 | T2)[] {
|
export function concat<T1, T2> (v: T1[], arg: T2[] = []): (T1 | T2)[] {
|
||||||
v = toValue(v)
|
return toArray(v).concat(toArray(arg))
|
||||||
arg = toArray(arg).map(v => toValue(v))
|
|
||||||
return toArray(v).concat(arg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function push<T> (v: T[], arg: T): T[] {
|
export function push<T> (v: T[], arg: T): T[] {
|
||||||
return concat(v, [arg])
|
return concat(v, [arg])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function unshift<T> (v: T[], arg: T): T[] {
|
||||||
|
const clone = [...toArray(v)]
|
||||||
|
clone.unshift(arg)
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pop<T> (v: T[]): T[] {
|
||||||
|
const clone = [...toArray(v)]
|
||||||
|
clone.pop()
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shift<T> (v: T[]): T[] {
|
||||||
|
const clone = [...toArray(v)]
|
||||||
|
clone.shift()
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
export function slice<T> (v: T[] | string, begin: number, length = 1): T[] | string {
|
export function slice<T> (v: T[] | string, begin: number, length = 1): T[] | string {
|
||||||
v = toValue(v)
|
v = toValue(v)
|
||||||
if (isNil(v)) return []
|
if (isNil(v)) return []
|
||||||
@@ -77,7 +91,7 @@ export function slice<T> (v: T[] | string, begin: number, length = 1): T[] | str
|
|||||||
|
|
||||||
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
|
||||||
const values: unknown[] = []
|
const values: unknown[] = []
|
||||||
arr = toArray(toValue(arr))
|
arr = toArray(arr)
|
||||||
for (const item of arr) {
|
for (const item of arr) {
|
||||||
values.push(yield this.context._getFromScope(item, stringify(property).split('.'), false))
|
values.push(yield this.context._getFromScope(item, stringify(property).split('.'), false))
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Hash, ValueToken, Liquid, Tag, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
import { Hash, ValueToken, Liquid, Tag, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
||||||
import { toEnumerable } from '../util/collection'
|
import { toEnumerable } from '../util'
|
||||||
import { ForloopDrop } from '../drop/forloop-drop'
|
import { ForloopDrop } from '../drop/forloop-drop'
|
||||||
|
|
||||||
const MODIFIERS = ['offset', 'limit', 'reversed']
|
const MODIFIERS = ['offset', 'limit', 'reversed']
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { toEnumerable } from '../util/collection'
|
import { toEnumerable } from '../util'
|
||||||
import { ValueToken, Liquid, Tag, evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
import { ValueToken, Liquid, Tag, evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
||||||
import { TablerowloopDrop } from '../drop/tablerowloop-drop'
|
import { TablerowloopDrop } from '../drop/tablerowloop-drop'
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { isNil, isString, isObject, isArray, isIterable, toValue } from './underscore'
|
|
||||||
|
|
||||||
export function toEnumerable<T = unknown> (val: any): T[] {
|
|
||||||
val = toValue(val)
|
|
||||||
if (isArray(val)) return val
|
|
||||||
if (isString(val) && val.length > 0) return [val] as unknown as T[]
|
|
||||||
if (isIterable(val)) return Array.from(val)
|
|
||||||
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]]) as unknown as T[]
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
export function toArray (val: any) {
|
|
||||||
if (isNil(val)) return []
|
|
||||||
if (isArray(val)) return val
|
|
||||||
return [ val ]
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ export * from './underscore'
|
|||||||
export * from './operator-trie'
|
export * from './operator-trie'
|
||||||
export * from './type-guards'
|
export * from './type-guards'
|
||||||
export * from './async'
|
export * from './async'
|
||||||
export * from './collection'
|
|
||||||
export * from './strftime'
|
export * from './strftime'
|
||||||
export * from './liquid-date'
|
export * from './liquid-date'
|
||||||
export * from './timezone-date'
|
export * from './timezone-date'
|
||||||
|
|||||||
@@ -46,6 +46,22 @@ export function stringify (value: any): string {
|
|||||||
return String(value)
|
return String(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toEnumerable<T = unknown> (val: any): T[] {
|
||||||
|
val = toValue(val)
|
||||||
|
if (isArray(val)) return val
|
||||||
|
if (isString(val) && val.length > 0) return [val] as unknown as T[]
|
||||||
|
if (isIterable(val)) return Array.from(val)
|
||||||
|
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]]) as unknown as T[]
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toArray (val: any) {
|
||||||
|
val = toValue(val)
|
||||||
|
if (isNil(val)) return []
|
||||||
|
if (isArray(val)) return val
|
||||||
|
return [ val ]
|
||||||
|
}
|
||||||
|
|
||||||
export function toValue (value: any): any {
|
export function toValue (value: any): any {
|
||||||
return (value instanceof Drop && isFunction(value.valueOf)) ? value.valueOf() : value
|
return (value instanceof Drop && isFunction(value.valueOf)) ? value.valueOf() : value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,10 +158,53 @@ describe('filters/array', function () {
|
|||||||
await test('{{ undefinedValue | push: arg | join: "," }}', scope, 'foo')
|
await test('{{ undefinedValue | push: arg | join: "," }}', scope, 'foo')
|
||||||
await test('{{ nullValue | push: arg | join: "," }}', scope, 'foo')
|
await test('{{ nullValue | push: arg | join: "," }}', scope, 'foo')
|
||||||
})
|
})
|
||||||
it('should ignore nil right value', async () => {
|
it('should support nil right value', async () => {
|
||||||
const scope = { nullValue: null }
|
const scope = { nullValue: [] }
|
||||||
await test('{{ nullValue | push | join: "," }}', scope, '')
|
await test('{{ nullValue | push | size }}', scope, '1')
|
||||||
await test('{{ nullValue | push: nil | join: "," }}', scope, '')
|
await test('{{ nullValue | push: nil | size }}', scope, '1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('pop', () => {
|
||||||
|
it('should support pop', async () => {
|
||||||
|
const scope = { val: ['hey', 'you'] }
|
||||||
|
await test('{{ val | pop | join: "," }}', scope, 'hey')
|
||||||
|
})
|
||||||
|
it('should not change original array', async () => {
|
||||||
|
const scope = { val: ['hey', 'you'] }
|
||||||
|
await test('{{ val | pop | join: "," }} {{ val| join: "," }}', scope, 'hey hey,you')
|
||||||
|
})
|
||||||
|
it('should support nil left value', async () => {
|
||||||
|
await test('{{ notDefined | pop | join: "," }}', {}, '')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unshift', () => {
|
||||||
|
it('should support unshift', async () => {
|
||||||
|
const scope = { val: ['you'] }
|
||||||
|
await test('{{ val | unshift: "hey" | join: ", " }}', scope, 'hey, you')
|
||||||
|
})
|
||||||
|
it('should not change original array', async () => {
|
||||||
|
const scope = { val: ['you'] }
|
||||||
|
await test('{{ val | unshift: "hey" | join: "," }} {{ val | join: "," }}', scope, 'hey,you you')
|
||||||
|
})
|
||||||
|
it('should support nil right value', async () => {
|
||||||
|
const scope = { val: [] }
|
||||||
|
await test('{{ val | unshift: nil | size }}', scope, '1')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('shift', () => {
|
||||||
|
it('should support pop', async () => {
|
||||||
|
const scope = { val: ['hey', 'you'] }
|
||||||
|
await test('{{ val | shift }}', scope, 'you')
|
||||||
|
})
|
||||||
|
it('should not change original array', async () => {
|
||||||
|
const scope = { val: ['hey', 'you'] }
|
||||||
|
await test('{{ val | shift }} {{ val | join: ","}}', scope, 'you hey,you')
|
||||||
|
})
|
||||||
|
it('should support nil left value', async () => {
|
||||||
|
await test('{{ notDefined | pop }}', {}, '')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe('tags/for', function () {
|
|||||||
const engine = new Liquid({ strictVariables: true })
|
const engine = new Liquid({ strictVariables: true })
|
||||||
const src = '{% assign hello = "hello,world" | split: "," | concat: null %}{% for i in hello %}{{ i }},{% endfor %}'
|
const src = '{% assign hello = "hello,world" | split: "," | concat: null %}{% for i in hello %}{{ i }},{% endfor %}'
|
||||||
const html = await engine.parseAndRender(src, scope)
|
const html = await engine.parseAndRender(src, scope)
|
||||||
return expect(html).toBe('hello,world,,')
|
return expect(html).toBe('hello,world,')
|
||||||
})
|
})
|
||||||
describe('illegal', function () {
|
describe('illegal', function () {
|
||||||
it('should reject when for not closed', function () {
|
it('should reject when for not closed', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user