feat: orderedFilterParameters, closes #312

This commit is contained in:
Harttle
2021-09-30 22:15:09 +08:00
committed by Jun Yang
parent fb787e8847
commit 10e8c8ff7e
5 changed files with 47 additions and 14 deletions
+28 -11
View File
@@ -3,14 +3,18 @@ import { toEnumerable } from '../../util/collection'
import { ForloopDrop } from '../../drop/forloop-drop' import { ForloopDrop } from '../../drop/forloop-drop'
import { Hash } from '../../template/tag/hash' import { Hash } from '../../template/tag/hash'
const MODIFIERS = ['offset', 'limit', 'reversed']
type valueof<T> = T[keyof T]
export default { export default {
type: 'block', type: 'block',
parse: function (token: TagToken, remainTokens: TopLevelToken[]) { parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
const toknenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie) const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
const variable = toknenizer.readIdentifier() const variable = tokenizer.readIdentifier()
const inStr = toknenizer.readIdentifier() const inStr = tokenizer.readIdentifier()
const collection = toknenizer.readValue() const collection = tokenizer.readValue()
assert( assert(
variable.size() && inStr.content === 'in' && collection, variable.size() && inStr.content === 'in' && collection,
() => `illegal tag: ${token.getText()}` () => `illegal tag: ${token.getText()}`
@@ -44,14 +48,15 @@ export default {
} }
const hash = yield this.hash.render(ctx) const hash = yield this.hash.render(ctx)
const offset = hash.offset || 0 const modifiers = this.liquid.options.orderedFilterParameters
const limit = (hash.limit === undefined) ? collection.length : hash.limit ? Object.keys(hash).filter(x => MODIFIERS.includes(x))
const reversedIndex = Reflect.ownKeys(hash).indexOf('reversed') : MODIFIERS.filter(x => hash[x] !== undefined)
// reverse collection before slicing if 'reversed' is 1st parameter collection = modifiers.reduce((collection, modifier: valueof<typeof MODIFIERS>) => {
if (reversedIndex === 0) collection.reverse() if (modifier === 'offset') return offset(collection, hash['offset'])
collection = collection.slice(offset, offset + limit) if (modifier === 'limit') return limit(collection, hash['limit'])
if (reversedIndex > 0) collection.reverse() return reversed(collection)
}, collection)
const scope = { forloop: new ForloopDrop(collection.length) } const scope = { forloop: new ForloopDrop(collection.length) }
ctx.push(scope) ctx.push(scope)
@@ -68,3 +73,15 @@ export default {
ctx.pop() ctx.pop()
} }
} as TagImplOptions } as TagImplOptions
function reversed<T> (arr: Array<T>) {
return [...arr].reverse()
}
function offset<T> (arr: Array<T>, count: number) {
return arr.slice(count)
}
function limit<T> (arr: Array<T>, count: number) {
return arr.slice(0, count)
}
+2
View File
@@ -55,6 +55,8 @@ export interface LiquidOptions {
keepOutputType?: boolean; keepOutputType?: boolean;
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */ /** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
operators?: Operators; operators?: Operators;
/** Respect parameter order when using filters like "for ... reversed limit", Defaults to `false`. */
orderedFilterParameters?: boolean;
} }
interface NormalizedOptions extends LiquidOptions { interface NormalizedOptions extends LiquidOptions {
+1 -1
View File
@@ -21,7 +21,7 @@ export class Hash {
* render (ctx: Context) { * render (ctx: Context) {
const hash = {} const hash = {}
for (const key of Object.keys(this.hash)) { for (const key of Object.keys(this.hash)) {
hash[key] = yield evalToken(this.hash[key], ctx) hash[key] = this.hash[key] === undefined ? true : yield evalToken(this.hash[key], ctx)
} }
return hash return hash
} }
+14
View File
@@ -215,6 +215,13 @@ describe('tags/for', function () {
}) })
it('should support for reversed in the first position', async function () { it('should support for reversed in the first position', async function () {
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('21')
})
it('should support for reversed in the first position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}' const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src, scope) const html = await liquid.parseAndRender(src, scope)
return expect(html).to.equal('87') return expect(html).to.equal('87')
@@ -225,6 +232,13 @@ describe('tags/for', function () {
const html = await liquid.parseAndRender(src) const html = await liquid.parseAndRender(src)
return expect(html).to.equal('543') return expect(html).to.equal('543')
}) })
it('should support for reversed in the middle position with orderedFilterParameters=true', async function () {
const liquid = new Liquid({ orderedFilterParameters: true })
const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
const html = await liquid.parseAndRender(src)
return expect(html).to.equal('876')
})
}) })
describe('sync', function () { describe('sync', function () {
+2 -2
View File
@@ -9,7 +9,7 @@ describe('Hash', function () {
it('should parse "reverse"', async function () { it('should parse "reverse"', async function () {
const hash = await toThenable(new Hash('reverse').render(new Context({ foo: 3 }))) const hash = await toThenable(new Hash('reverse').render(new Context({ foo: 3 })))
expect(hash).to.haveOwnProperty('reverse') expect(hash).to.haveOwnProperty('reverse')
expect(hash.reverse).to.be.undefined expect(hash.reverse).to.be.true
}) })
it('should parse "num:foo"', async function () { it('should parse "num:foo"', async function () {
const hash = await toThenable(new Hash('num:foo').render(new Context({ foo: 3 }))) const hash = await toThenable(new Hash('num:foo').render(new Context({ foo: 3 })))
@@ -37,7 +37,7 @@ describe('Hash', function () {
const hash = await toThenable(new Hash('num1:2.3 reverse,num2:bar.coo\n num3: arr[0]').render(ctx)) const hash = await toThenable(new Hash('num1:2.3 reverse,num2:bar.coo\n num3: arr[0]').render(ctx))
expect(hash).to.deep.equal({ expect(hash).to.deep.equal({
num1: 2.3, num1: 2.3,
reverse: undefined, reverse: true,
num2: 3, num2: 3,
num3: 4 num3: 4
}) })