feat: add find_index, has, and reject filters (#799)

* feat: add find_index, has, and reject filters

* Minor tweaks

* Change semantics of jekyllStyle, add more tests

* Some docs improvements
This commit is contained in:
Bruno Carvalho
2025-02-23 22:57:17 +08:00
committed by GitHub
parent b0facc71f7
commit 0deb93eeae
10 changed files with 595 additions and 23 deletions
+70 -18
View File
@@ -117,7 +117,17 @@ export function slice<T> (this: FilterImpl, v: T[] | string, begin: number, leng
return v.slice(begin, begin + length)
}
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
function expectedMatcher (this: FilterImpl, expected: any): (v: any) => boolean {
if (this.context.opts.jekyllWhere) {
return (v: any) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
} else if (expected === undefined) {
return (v: any) => isTruthy(v, this.context)
} else {
return (v: any) => equals(v, expected)
}
}
function * filter<T extends object> (this: FilterImpl, include: boolean, arr: T[], property: string, expected: any): IterableIterator<unknown> {
const values: unknown[] = []
arr = toArray(arr)
this.context.memoryLimit.use(arr.length)
@@ -125,16 +135,11 @@ 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 matcher(values[i])
})
const matcher = expectedMatcher.call(this, expected)
return arr.filter((_, i) => matcher(values[i]) === include)
}
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
function * filter_exp<T extends object> (this: FilterImpl, include: boolean, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const filtered: unknown[] = []
const keyTemplate = new Value(stringify(exp), this.liquid)
const array = toArray(arr)
@@ -143,11 +148,27 @@ export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemN
this.context.push({ [itemName]: item })
const value = yield keyTemplate.value(this.context)
this.context.pop()
if (value) filtered.push(item)
if (value === include) filtered.push(item)
}
return filtered
}
export function * where<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
return yield * filter.call(this, true, arr, property, expected)
}
export function * reject<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
return yield * filter.call(this, false, arr, property, expected)
}
export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
return yield * filter_exp.call(this, true, arr, itemName, exp)
}
export function * reject_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
return yield * filter_exp.call(this, false, arr, itemName, exp)
}
export function * group_by<T extends object> (this: FilterImpl, arr: T[], property: string): IterableIterator<unknown> {
const map = new Map()
arr = toEnumerable(arr)
@@ -176,26 +197,57 @@ export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], it
return [...map.entries()].map(([name, items]) => ({ name, items }))
}
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
function * search<T extends object> (this: FilterImpl, arr: T[], property: string, expected: string): IterableIterator<unknown> {
const token = new Tokenizer(stringify(property)).readScopeValue()
const array = toArray(arr)
for (const item of array) {
const value = yield evalToken(token, this.context.spawn(item))
if (equals(value, expected)) return item
const matcher = expectedMatcher.call(this, expected)
for (let index = 0; index < array.length; index++) {
const value = yield evalToken(token, this.context.spawn(array[index]))
if (matcher(value)) return [index, array[index]]
}
}
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
function * search_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const predicate = new Value(stringify(exp), this.liquid)
const array = toArray(arr)
for (const item of array) {
this.context.push({ [itemName]: item })
for (let index = 0; index < array.length; index++) {
this.context.push({ [itemName]: array[index] })
const value = yield predicate.value(this.context)
this.context.pop()
if (value) return item
if (value) return [index, array[index]]
}
}
export function * has<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
const result = yield * search.call(this, arr, property, expected)
return !!result
}
export function * has_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const result = yield * search_exp.call(this, arr, itemName, exp)
return !!result
}
export function * find_index<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
const result = yield * search.call(this, arr, property, expected)
return result ? result[0] : undefined
}
export function * find_index_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const result = yield * search_exp.call(this, arr, itemName, exp)
return result ? result[0] : undefined
}
export function * find<T extends object> (this: FilterImpl, arr: T[], property: string, expected?: any): IterableIterator<unknown> {
const result = yield * search.call(this, arr, property, expected)
return result ? result[1] : undefined
}
export function * find_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const result = yield * search_exp.call(this, arr, itemName, exp)
return result ? result[1] : undefined
}
export function uniq<T> (this: FilterImpl, arr: T[]): T[] {
arr = toArray(arr)
this.context.memoryLimit.use(arr.length)