mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
fix: named params for filters, working on #113
This commit is contained in:
Generated
-15
@@ -9145,21 +9145,6 @@
|
|||||||
"acorn": "^6.0.5"
|
"acorn": "^6.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rollup-plugin-alias": {
|
|
||||||
"version": "1.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rollup-plugin-alias/-/rollup-plugin-alias-1.5.1.tgz",
|
|
||||||
"integrity": "sha512-pQTYBRNfLedoVOO7AYHNegIavEIp4jKTga5jUi1r//KYgHKGWgG4qJXYhbcWKt2k1FwGlR5wCYoY+IFkme0t4A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"slash": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rollup-plugin-import-alias": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "http://registry.npm.taobao.org/rollup-plugin-import-alias/download/rollup-plugin-import-alias-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-iywc3ujjnuRmOXcrAI3gppRzyT8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"rollup-plugin-replace": {
|
"rollup-plugin-replace": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "http://registry.npm.taobao.org/rollup-plugin-replace/download/rollup-plugin-replace-2.1.0.tgz",
|
"resolved": "http://registry.npm.taobao.org/rollup-plugin-replace/download/rollup-plugin-replace-2.1.0.tgz",
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import ITemplate from './template/itemplate'
|
|||||||
import Tokenizer from './parser/tokenizer'
|
import Tokenizer from './parser/tokenizer'
|
||||||
import Render from './render/render'
|
import Render from './render/render'
|
||||||
import Tag from './template/tag/tag'
|
import Tag from './template/tag/tag'
|
||||||
import Filter from './template/filter/filter'
|
import { Filter } from './template/filter/filter'
|
||||||
import Parser from './parser/parser'
|
import Parser from './parser/parser'
|
||||||
import ITagImplOptions from './template/tag/itag-impl-options'
|
import ITagImplOptions from './template/tag/itag-impl-options'
|
||||||
import Value from './template/value'
|
import Value from './template/value'
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function evalExp (str: string, scope: Scope): any {
|
|||||||
return value instanceof Drop ? value.valueOf() : value
|
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
|
if (!str) return null
|
||||||
str = str.trim()
|
str = str.trim()
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ function parseValue (str: string, scope: Scope): any {
|
|||||||
return scope.get(str)
|
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)
|
const value = parseValue(str, scope)
|
||||||
return value instanceof Drop ? value.valueOf() : value
|
return value instanceof Drop ? value.valueOf() : value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import { evalValue } from '../../render/syntax'
|
import { evalValue } from '../../render/syntax'
|
||||||
import Scope from '../../scope/scope'
|
import Scope from '../../scope/scope'
|
||||||
|
import { isArray } from '../../util/underscore'
|
||||||
import { FilterImpl } from './filter-impl'
|
import { FilterImpl } from './filter-impl'
|
||||||
|
|
||||||
export default class Filter {
|
export type FilterArgs = Array<string|[string?, string?]>
|
||||||
|
|
||||||
|
export class Filter {
|
||||||
name: string
|
name: string
|
||||||
impl: FilterImpl
|
impl: FilterImpl
|
||||||
args: string[]
|
args: FilterArgs
|
||||||
private static impls: {[key: string]: FilterImpl} = {}
|
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]
|
const impl = Filter.impls[name]
|
||||||
if (!impl && strictFilters) throw new TypeError(`undefined filter: ${name}`)
|
if (!impl && strictFilters) throw new TypeError(`undefined filter: ${name}`)
|
||||||
|
|
||||||
@@ -17,7 +20,7 @@ export default class Filter {
|
|||||||
this.args = args
|
this.args = args
|
||||||
}
|
}
|
||||||
render (value: any, scope: Scope): any {
|
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])
|
return this.impl.apply(null, [value, ...args])
|
||||||
}
|
}
|
||||||
static register (name: string, filter: FilterImpl) {
|
static register (name: string, filter: FilterImpl) {
|
||||||
|
|||||||
+57
-61
@@ -1,80 +1,76 @@
|
|||||||
import { evalExp } from '../render/syntax'
|
import { evalExp } from '../render/syntax'
|
||||||
import Filter from './filter/filter'
|
import { FilterArgs, Filter } from './filter/filter'
|
||||||
import Scope from '../scope/scope'
|
import Scope from '../scope/scope'
|
||||||
|
|
||||||
enum ParseState {
|
export default class Value {
|
||||||
INIT = 0,
|
private strictFilters: boolean
|
||||||
FILTER_NAME = 1,
|
initial: string
|
||||||
FILTER_ARG = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class {
|
|
||||||
initial: any
|
|
||||||
filters: Array<Filter> = []
|
filters: Array<Filter> = []
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param str value string, like: "i have a dream | truncate: 3
|
* @param str value string, like: "i have a dream | truncate: 3
|
||||||
*/
|
*/
|
||||||
constructor (str: string, strictFilters: boolean) {
|
constructor (str: string, strictFilters: boolean) {
|
||||||
let buffer = ''
|
const tokens = Value.tokenize(str)
|
||||||
let quoted = ''
|
this.strictFilters = strictFilters
|
||||||
let state = ParseState.INIT
|
this.initial = tokens[0]
|
||||||
let sealed = false
|
this.parseFilters(tokens, 1)
|
||||||
|
}
|
||||||
let filterName = ''
|
private parseFilters (tokens: string[], begin: number) {
|
||||||
let filterArgs: string[] = []
|
let i = begin
|
||||||
|
while (i < tokens.length) {
|
||||||
for (let i = 0; i < str.length; i++) {
|
if (tokens[i] !== '|') {
|
||||||
if (quoted) {
|
i++
|
||||||
if (str[i] === quoted) {
|
continue
|
||||||
quoted = ''
|
}
|
||||||
sealed = true
|
const j = ++i
|
||||||
|
while (i < tokens.length && tokens[i] !== '|') i++
|
||||||
|
this.parseFilter(tokens, j, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
buffer += str[i]
|
argValue = argName = undefined
|
||||||
} else if (/\s/.test(str[i])) {
|
} else if (tokens[i] === ':') {
|
||||||
if (!buffer) continue
|
argName = argValue
|
||||||
else sealed = true
|
argValue = undefined
|
||||||
} else if (str[i] === '|') {
|
} else if (argValue === undefined) {
|
||||||
if (state === ParseState.INIT) {
|
argValue = tokens[i]
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.filters.push(new Filter(name, args, this.strictFilters))
|
||||||
}
|
}
|
||||||
value (scope: Scope) {
|
value (scope: Scope) {
|
||||||
return this.filters.reduce(
|
return this.filters.reduce(
|
||||||
(prev, filter) => filter.render(prev, scope),
|
(prev, filter) => filter.render(prev, scope),
|
||||||
evalExp(this.initial, 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +2,7 @@ import { expect } from 'chai'
|
|||||||
import Scope from '../../../src/scope/scope'
|
import Scope from '../../../src/scope/scope'
|
||||||
import Token from '../../../src/parser/token'
|
import Token from '../../../src/parser/token'
|
||||||
import Tag from '../../../src/template/tag/tag'
|
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 Render from '../../../src/render/render'
|
||||||
import HTML from '../../../src/template/html'
|
import HTML from '../../../src/template/html'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import * as sinonChai from 'sinon-chai'
|
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'
|
import Scope from '../../../../src/scope/scope'
|
||||||
|
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import * as chai from 'chai'
|
|||||||
import Scope from '../../../src/scope/scope'
|
import Scope from '../../../src/scope/scope'
|
||||||
import Output from '../../../src/template/output'
|
import Output from '../../../src/template/output'
|
||||||
import OutputToken from '../../../src/parser/output-token'
|
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
|
const expect = chai.expect
|
||||||
|
|
||||||
|
|||||||
+94
-51
@@ -2,7 +2,7 @@ import * as chai from 'chai'
|
|||||||
import * as sinonChai from 'sinon-chai'
|
import * as sinonChai from 'sinon-chai'
|
||||||
import * as sinon from 'sinon'
|
import * as sinon from 'sinon'
|
||||||
import Scope from '../../../src/scope/scope'
|
import Scope from '../../../src/scope/scope'
|
||||||
import Filter from '../../../src/template/filter/filter'
|
import { Filter } from '../../../src/template/filter/filter'
|
||||||
import Value from '../../../src/template/value'
|
import Value from '../../../src/template/value'
|
||||||
|
|
||||||
chai.use(sinonChai)
|
chai.use(sinonChai)
|
||||||
@@ -12,56 +12,99 @@ const expect = chai.expect
|
|||||||
describe('Value', function () {
|
describe('Value', function () {
|
||||||
beforeEach(() => Filter.clear())
|
beforeEach(() => Filter.clear())
|
||||||
|
|
||||||
it('should parse "foo', function () {
|
describe('#constructor()', function () {
|
||||||
const tpl: any = new Value('foo', false)
|
it('should parse "foo', function () {
|
||||||
expect(tpl.initial).to.equal('foo')
|
const tpl = new Value('foo', false)
|
||||||
expect(tpl.filters).to.deep.equal([])
|
expect(tpl.initial).to.equal('foo')
|
||||||
})
|
expect(tpl.filters).to.deep.equal([])
|
||||||
|
})
|
||||||
it('should parse "foo | add"', function () {
|
|
||||||
const tpl: any = new Value('foo | add', false)
|
it('should parse "foo | add"', function () {
|
||||||
expect(tpl.initial).to.equal('foo')
|
const tpl = new Value('foo | add', false)
|
||||||
expect(tpl.filters.length).to.equal(1)
|
expect(tpl.initial).to.equal('foo')
|
||||||
expect(tpl.filters[0].args).to.eql([])
|
expect(tpl.filters.length).to.equal(1)
|
||||||
})
|
expect(tpl.filters[0].args).to.eql([])
|
||||||
it('should parse "foo | add: "foo" bar, 3"', function () {
|
})
|
||||||
const tpl: any = new Value('foo | add: "foo" bar, 3', false)
|
it('should parse "foo | add: 3, false"', function () {
|
||||||
expect(tpl.initial).to.equal('foo')
|
const tpl = new Value('foo | add: 3, "foo"', false)
|
||||||
expect(tpl.filters.length).to.equal(1)
|
expect(tpl.initial).to.equal('foo')
|
||||||
expect(tpl.filters[0].name).to.eql('add')
|
expect(tpl.filters.length).to.equal(1)
|
||||||
expect(tpl.filters[0].args).to.eql(['"foo"', '3'])
|
expect(tpl.filters[0].args).to.eql(['3', '"foo"'])
|
||||||
})
|
})
|
||||||
it('should parse "foo | add: 3, false"', function () {
|
it('should parse "foo | add: "foo" bar, 3"', function () {
|
||||||
const tpl: any = new Value('foo | add: 3, "foo"', false)
|
const tpl = new Value('foo | add: "foo" bar, 3', false)
|
||||||
expect(tpl.initial).to.equal('foo')
|
expect(tpl.initial).to.equal('foo')
|
||||||
expect(tpl.filters.length).to.equal(1)
|
expect(tpl.filters.length).to.equal(1)
|
||||||
expect(tpl.filters[0].args).to.eql(['3', '"foo"'])
|
expect(tpl.filters[0].name).to.eql('add')
|
||||||
})
|
expect(tpl.filters[0].args).to.eql(['"foo"', '3'])
|
||||||
it('should parse "foo | add: "|", 3', function () {
|
})
|
||||||
const tpl: any = new Value('foo | add: "|", 3', false)
|
it('should parse "foo | add: "|", 3', function () {
|
||||||
expect(tpl.initial).to.equal('foo')
|
const tpl = new Value('foo | add: "|", 3', false)
|
||||||
expect(tpl.filters.length).to.equal(1)
|
expect(tpl.initial).to.equal('foo')
|
||||||
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
|
expect(tpl.filters.length).to.equal(1)
|
||||||
})
|
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
|
||||||
|
})
|
||||||
it('should parse "foo | add: "|", 3', function () {
|
it('should parse "foo | add: "|", 3', function () {
|
||||||
const tpl: any = new Value('foo | add: "|", 3', false)
|
const tpl = new Value('foo | add: "|", 3', false)
|
||||||
expect(tpl.initial).to.equal('foo')
|
expect(tpl.initial).to.equal('foo')
|
||||||
expect(tpl.filters.length).to.equal(1)
|
expect(tpl.filters.length).to.equal(1)
|
||||||
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
|
expect(tpl.filters[0].args).to.eql(['"|"', '3'])
|
||||||
})
|
})
|
||||||
|
it('should support arguments as named key/values', function () {
|
||||||
it('should call chained filters correctly', function () {
|
const f = new Value('o | foo: key1: "literal1", key2: value2', false)
|
||||||
const date = sinon.stub().returns('y')
|
expect(f.filters[0].name).to.equal('foo')
|
||||||
const time = sinon.spy()
|
expect(f.filters[0].args).to.eql([['key1', '"literal1"'], ['key2', 'value2']])
|
||||||
Filter.register('date', date)
|
})
|
||||||
Filter.register('time', time)
|
it('should support arguments as named key/values with inline literals', function () {
|
||||||
const tpl = new Value('foo.bar | date: "b" | time:2', false)
|
const f = new Value('o | foo: "test0", key1: "literal1", key2: value2', false)
|
||||||
const scope = new Scope({
|
expect(f.filters[0].name).to.equal('foo')
|
||||||
foo: { bar: 'bar' }
|
expect(f.filters[0].args).to.deep.equal(['"test0"', ['key1', '"literal1"'], ['key2', 'value2']])
|
||||||
|
})
|
||||||
|
it('should support arguments as named key/values with inline values', function () {
|
||||||
|
const f = new Value('o | foo: test0, key1: "literal1", key2: value2', false)
|
||||||
|
expect(f.filters[0].name).to.equal('foo')
|
||||||
|
expect(f.filters[0].args).to.deep.equal(['test0', ['key1', '"literal1"'], ['key2', 'value2']])
|
||||||
|
})
|
||||||
|
it('should support argument values named same as keys', function () {
|
||||||
|
const f = new Value('o | foo: a: a', false)
|
||||||
|
expect(f.filters[0].name).to.equal('foo')
|
||||||
|
expect(f.filters[0].args).to.deep.equal([['a', 'a']])
|
||||||
|
})
|
||||||
|
it('should support argument literals named same as keys', function () {
|
||||||
|
const f = new Value('o | foo: a: "a"', false)
|
||||||
|
expect(f.filters[0].name).to.equal('foo')
|
||||||
|
expect(f.filters[0].args).to.deep.equal([['a', '"a"']])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#tokenize()', function () {
|
||||||
|
it('should tokenize a simple value', function () {
|
||||||
|
expect(Value.tokenize('foo')).to.eql(['foo'])
|
||||||
|
})
|
||||||
|
it('should tokenize a value with spaces', function () {
|
||||||
|
expect(Value.tokenize(' foo \t')).to.eql(['foo'])
|
||||||
|
})
|
||||||
|
it('should tokenize a simple filter', function () {
|
||||||
|
expect(Value.tokenize('foo | add')).to.eql(['foo', '|', 'add'])
|
||||||
|
})
|
||||||
|
it('should tokenize a filter with a single argument', function () {
|
||||||
|
expect(Value.tokenize('foo | add: 1')).to.eql(['foo', '|', 'add', ':', '1'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#value()', function () {
|
||||||
|
it('should call chained filters correctly', function () {
|
||||||
|
const date = sinon.stub().returns('y')
|
||||||
|
const time = sinon.spy()
|
||||||
|
Filter.register('date', date)
|
||||||
|
Filter.register('time', time)
|
||||||
|
const tpl = new Value('foo.bar | date: "b" | time:2', false)
|
||||||
|
const scope = new Scope({
|
||||||
|
foo: { bar: 'bar' }
|
||||||
|
})
|
||||||
|
tpl.value(scope)
|
||||||
|
expect(date).to.have.been.calledWith('bar', 'b')
|
||||||
|
expect(time).to.have.been.calledWith('y', 2)
|
||||||
})
|
})
|
||||||
tpl.value(scope)
|
|
||||||
expect(date).to.have.been.calledWith('bar', 'b')
|
|
||||||
expect(time).to.have.been.calledWith('y', 2)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user