Skip to content

Commit 05c1ca8

Browse files
Harttleharttle
Harttle
authored andcommitted
feat: orderedFilterParameters, closes #312
1 parent 98aa541 commit 05c1ca8

File tree

5 files changed

+47
-14
lines changed

5 files changed

+47
-14
lines changed

src/builtin/tags/for.ts

+28-11
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import { toEnumerable } from '../../util/collection'
33
import { ForloopDrop } from '../../drop/forloop-drop'
44
import { Hash } from '../../template/tag/hash'
55

6+
const MODIFIERS = ['offset', 'limit', 'reversed']
7+
8+
type valueof<T> = T[keyof T]
9+
610
export default {
711
type: 'block',
812
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
9-
const toknenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
13+
const tokenizer = new Tokenizer(token.args, this.liquid.options.operatorsTrie)
1014

11-
const variable = toknenizer.readIdentifier()
12-
const inStr = toknenizer.readIdentifier()
13-
const collection = toknenizer.readValue()
15+
const variable = tokenizer.readIdentifier()
16+
const inStr = tokenizer.readIdentifier()
17+
const collection = tokenizer.readValue()
1418
assert(
1519
variable.size() && inStr.content === 'in' && collection,
1620
() => `illegal tag: ${token.getText()}`
@@ -44,14 +48,15 @@ export default {
4448
}
4549

4650
const hash = yield this.hash.render(ctx)
47-
const offset = hash.offset || 0
48-
const limit = (hash.limit === undefined) ? collection.length : hash.limit
49-
const reversedIndex = Reflect.ownKeys(hash).indexOf('reversed')
51+
const modifiers = this.liquid.options.orderedFilterParameters
52+
? Object.keys(hash).filter(x => MODIFIERS.includes(x))
53+
: MODIFIERS.filter(x => hash[x] !== undefined)
5054

51-
// reverse collection before slicing if 'reversed' is 1st parameter
52-
if (reversedIndex === 0) collection.reverse()
53-
collection = collection.slice(offset, offset + limit)
54-
if (reversedIndex > 0) collection.reverse()
55+
collection = modifiers.reduce((collection, modifier: valueof<typeof MODIFIERS>) => {
56+
if (modifier === 'offset') return offset(collection, hash['offset'])
57+
if (modifier === 'limit') return limit(collection, hash['limit'])
58+
return reversed(collection)
59+
}, collection)
5560

5661
const scope = { forloop: new ForloopDrop(collection.length) }
5762
ctx.push(scope)
@@ -68,3 +73,15 @@ export default {
6873
ctx.pop()
6974
}
7075
} as TagImplOptions
76+
77+
function reversed<T> (arr: Array<T>) {
78+
return [...arr].reverse()
79+
}
80+
81+
function offset<T> (arr: Array<T>, count: number) {
82+
return arr.slice(count)
83+
}
84+
85+
function limit<T> (arr: Array<T>, count: number) {
86+
return arr.slice(0, count)
87+
}

src/liquid-options.ts

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ export interface LiquidOptions {
5555
keepOutputType?: boolean;
5656
/** An object of operators for conditional statements. Defaults to the regular Liquid operators. */
5757
operators?: Operators;
58+
/** Respect parameter order when using filters like "for ... reversed limit", Defaults to `false`. */
59+
orderedFilterParameters?: boolean;
5860
}
5961

6062
interface NormalizedOptions extends LiquidOptions {

src/template/tag/hash.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class Hash {
2121
* render (ctx: Context) {
2222
const hash = {}
2323
for (const key of Object.keys(this.hash)) {
24-
hash[key] = yield evalToken(this.hash[key], ctx)
24+
hash[key] = this.hash[key] === undefined ? true : yield evalToken(this.hash[key], ctx)
2525
}
2626
return hash
2727
}

test/integration/builtin/tags/for.ts

+14
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ describe('tags/for', function () {
215215
})
216216

217217
it('should support for reversed in the first position', async function () {
218+
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
219+
const html = await liquid.parseAndRender(src, scope)
220+
return expect(html).to.equal('21')
221+
})
222+
223+
it('should support for reversed in the first position with orderedFilterParameters=true', async function () {
224+
const liquid = new Liquid({ orderedFilterParameters: true })
218225
const src = '{% for i in (1..8) reversed limit:2 %}{{ i }}{% endfor %}'
219226
const html = await liquid.parseAndRender(src, scope)
220227
return expect(html).to.equal('87')
@@ -225,6 +232,13 @@ describe('tags/for', function () {
225232
const html = await liquid.parseAndRender(src)
226233
return expect(html).to.equal('543')
227234
})
235+
236+
it('should support for reversed in the middle position with orderedFilterParameters=true', async function () {
237+
const liquid = new Liquid({ orderedFilterParameters: true })
238+
const src = '{% for i in (1..8) offset:2 reversed limit:3 %}{{ i }}{% endfor %}'
239+
const html = await liquid.parseAndRender(src)
240+
return expect(html).to.equal('876')
241+
})
228242
})
229243

230244
describe('sync', function () {

test/unit/template/hash.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('Hash', function () {
99
it('should parse "reverse"', async function () {
1010
const hash = await toThenable(new Hash('reverse').render(new Context({ foo: 3 })))
1111
expect(hash).to.haveOwnProperty('reverse')
12-
expect(hash.reverse).to.be.undefined
12+
expect(hash.reverse).to.be.true
1313
})
1414
it('should parse "num:foo"', async function () {
1515
const hash = await toThenable(new Hash('num:foo').render(new Context({ foo: 3 })))
@@ -37,7 +37,7 @@ describe('Hash', function () {
3737
const hash = await toThenable(new Hash('num1:2.3 reverse,num2:bar.coo\n num3: arr[0]').render(ctx))
3838
expect(hash).to.deep.equal({
3939
num1: 2.3,
40-
reverse: undefined,
40+
reverse: true,
4141
num2: 3,
4242
num3: 4
4343
})

0 commit comments

Comments
 (0)