Skip to content

Commit

Permalink
fix: named params for filters, working on #113
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Mar 5, 2019
1 parent 08646f7 commit 5ffc904
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 132 deletions.
15 changes: 0 additions & 15 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/liquid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ITemplate from './template/itemplate'
import Tokenizer from './parser/tokenizer'
import Render from './render/render'
import Tag from './template/tag/tag'
import Filter from './template/filter/filter'
import { Filter } from './template/filter/filter'
import Parser from './parser/parser'
import ITagImplOptions from './template/tag/itag-impl-options'
import Value from './template/value'
Expand Down
4 changes: 2 additions & 2 deletions src/render/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function evalExp (str: string, scope: Scope): any {
return value instanceof Drop ? value.valueOf() : value
}

function parseValue (str: string, scope: Scope): any {
function parseValue (str: string | undefined, scope: Scope): any {
if (!str) return null
str = str.trim()

Expand All @@ -91,7 +91,7 @@ function parseValue (str: string, scope: Scope): any {
return scope.get(str)
}

export function evalValue (str: string, scope: Scope): any {
export function evalValue (str: string | undefined, scope: Scope): any {
const value = parseValue(str, scope)
return value instanceof Drop ? value.valueOf() : value
}
Expand Down
11 changes: 7 additions & 4 deletions src/template/filter/filter.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { evalValue } from '../../render/syntax'
import Scope from '../../scope/scope'
import { isArray } from '../../util/underscore'
import { FilterImpl } from './filter-impl'

export default class Filter {
export type FilterArgs = Array<string|[string?, string?]>

export class Filter {
name: string
impl: FilterImpl
args: string[]
args: FilterArgs
private static impls: {[key: string]: FilterImpl} = {}

constructor (name: string, args: string[], strictFilters: boolean) {
constructor (name: string, args: FilterArgs, strictFilters: boolean) {
const impl = Filter.impls[name]
if (!impl && strictFilters) throw new TypeError(`undefined filter: ${name}`)

Expand All @@ -17,7 +20,7 @@ export default class Filter {
this.args = args
}
render (value: any, scope: Scope): any {
const args = this.args.map(arg => evalValue(arg, scope))
const args = this.args.map(arg => isArray(arg) ? [arg[0], evalValue(arg[1], scope)] : evalValue(arg, scope))
return this.impl.apply(null, [value, ...args])
}
static register (name: string, filter: FilterImpl) {
Expand Down
116 changes: 56 additions & 60 deletions src/template/value.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,76 @@
import { evalExp } from '../render/syntax'
import Filter from './filter/filter'
import { FilterArgs, Filter } from './filter/filter'
import Scope from '../scope/scope'

enum ParseState {
INIT = 0,
FILTER_NAME = 1,
FILTER_ARG = 2
}

export default class {
initial: any
export default class Value {
private strictFilters: boolean
initial: string
filters: Array<Filter> = []

/**
* @param str value string, like: "i have a dream | truncate: 3
*/
constructor (str: string, strictFilters: boolean) {
let buffer = ''
let quoted = ''
let state = ParseState.INIT
let sealed = false

let filterName = ''
let filterArgs: string[] = []

for (let i = 0; i < str.length; i++) {
if (quoted) {
if (str[i] === quoted) {
quoted = ''
sealed = true
}
buffer += str[i]
} else if (/\s/.test(str[i])) {
if (!buffer) continue
else sealed = true
} else if (str[i] === '|') {
if (state === ParseState.INIT) {
this.initial = buffer
} else {
if (state === ParseState.FILTER_NAME) filterName = buffer
else filterArgs.push(buffer)
this.filters.push(new Filter(filterName, filterArgs, strictFilters))
filterName = ''
filterArgs = []
}
state = ParseState.FILTER_NAME
buffer = ''
sealed = false
} else if (state === ParseState.FILTER_NAME && str[i] === ':') {
filterName = buffer
state = ParseState.FILTER_ARG
buffer = ''
sealed = false
} else if (state === ParseState.FILTER_ARG && str[i] === ',') {
filterArgs.push(buffer)
buffer = ''
sealed = false
} else if (sealed) continue
else {
if ((str[i] === '"' || str[i] === "'") && !quoted) quoted = str[i]
buffer += str[i]
const tokens = Value.tokenize(str)
this.strictFilters = strictFilters
this.initial = tokens[0]
this.parseFilters(tokens, 1)
}
private parseFilters (tokens: string[], begin: number) {
let i = begin
while (i < tokens.length) {
if (tokens[i] !== '|') {
i++
continue
}
const j = ++i
while (i < tokens.length && tokens[i] !== '|') i++
this.parseFilter(tokens, j, i)
}

if (buffer) {
if (state === ParseState.INIT) this.initial = buffer
else if (state === ParseState.FILTER_NAME) this.filters.push(new Filter(buffer, [], strictFilters))
else {
filterArgs.push(buffer)
this.filters.push(new Filter(filterName, filterArgs, strictFilters))
}
private parseFilter (tokens: string[], begin: number, end: number) {
const name = tokens[begin]
const args: FilterArgs = []
let argName, argValue
for (let i = begin + 1; i < end + 1; i++) {
if (i === end || tokens[i] === ',') {
if (argName || argValue) {
args.push(argName ? [argName, argValue] : <string>argValue)
}
argValue = argName = undefined
} else if (tokens[i] === ':') {
argName = argValue
argValue = undefined
} else if (argValue === undefined) {
argValue = tokens[i]
}
}
this.filters.push(new Filter(name, args, this.strictFilters))
}
value (scope: Scope) {
return this.filters.reduce(
(prev, filter) => filter.render(prev, scope),
evalExp(this.initial, scope))
}
static tokenize (str: string): Array<'|' | ',' | ':' | string> {
const tokens = []
let i = 0
while (i < str.length) {
const ch = str[i]
if (ch === '"' || ch === "'") {
const j = i
for (i += 2; i < str.length && str[i - 1] !== ch; ++i);
tokens.push(str.slice(j, i))
} else if (/\s/.test(ch)) {
i++
} else if (/[|,:]/.test(ch)) {
tokens.push(str[i++])
} else {
const j = i++
for (; i < str.length && !/[|,:\s]/.test(str[i]); ++i);
tokens.push(str.slice(j, i))
}
}
return tokens
}
}
15 changes: 15 additions & 0 deletions test/integration/builtin/filters/custom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { test, liquid } from '../../../stub/render'

describe('filters/custom', function () {
liquid.registerFilter('obj_test', function () {
return JSON.stringify(arguments)
})
it('should support object', () => test(
`{{ "a" | obj_test: k1: "v1", k2: foo }}`,
'{"0":"a","1":["k1","v1"],"2":["k2","bar"]}'
))
it('should support mixed object', () => test(
`{{ "a" | obj_test: "something", k1: "v1", k2: foo }}`,
'{"0":"a","1":"something","2":["k1","v1"],"3":["k2","bar"]}'
))
})
2 changes: 1 addition & 1 deletion test/unit/render/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect } from 'chai'
import Scope from '../../../src/scope/scope'
import Token from '../../../src/parser/token'
import Tag from '../../../src/template/tag/tag'
import Filter from '../../../src/template/filter/filter'
import { Filter } from '../../../src/template/filter/filter'
import Render from '../../../src/render/render'
import HTML from '../../../src/template/html'

Expand Down
2 changes: 1 addition & 1 deletion test/unit/template/filter/filter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as chai from 'chai'
import * as sinon from 'sinon'
import * as sinonChai from 'sinon-chai'
import Filter from '../../../../src/template/filter/filter'
import { Filter } from '../../../../src/template/filter/filter'
import Scope from '../../../../src/scope/scope'

chai.use(sinonChai)
Expand Down
2 changes: 1 addition & 1 deletion test/unit/template/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as chai from 'chai'
import Scope from '../../../src/scope/scope'
import Output from '../../../src/template/output'
import OutputToken from '../../../src/parser/output-token'
import Filter from '../../../src/template/filter/filter'
import { Filter } from '../../../src/template/filter/filter'

const expect = chai.expect

Expand Down
Loading

0 comments on commit 5ffc904

Please sign in to comment.