mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 20:30:39 -07:00
fix: pass drops directly to filters/tags
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"unit": "mocha \"test/unit/**/*.ts\"",
|
"unit": "mocha \"test/unit/**/*.ts\"",
|
||||||
"integration": "mocha \"test/integration/**/*.ts\"",
|
"integration": "mocha \"test/integration/**/*.ts\"",
|
||||||
"e2e": "npm run build && mocha \"test/e2e/**/*.ts\"",
|
"e2e": "npm run build && mocha \"test/e2e/**/*.ts\"",
|
||||||
"test": "npm run build && mocha \"test/**/*.ts\"",
|
"test": "BUNDLES=cjs npm run build && mocha \"test/**/*.ts\"",
|
||||||
"benchmark": "ts-node benchmark",
|
"benchmark": "ts-node benchmark",
|
||||||
"coverage-html": "nyc --reporter=html mocha \"test/{unit,integration}/**/*.ts\"",
|
"coverage-html": "nyc --reporter=html mocha \"test/{unit,integration}/**/*.ts\"",
|
||||||
"coverage-coveralls": "nyc mocha \"test/{unit,integration}/**/*.ts\" && nyc report --reporter=text-lcov | coveralls",
|
"coverage-coveralls": "nyc mocha \"test/{unit,integration}/**/*.ts\" && nyc report --reporter=text-lcov | coveralls",
|
||||||
|
|||||||
+17
-4
@@ -15,7 +15,7 @@ const treeshake = {
|
|||||||
}
|
}
|
||||||
const input = './src/liquid.ts'
|
const input = './src/liquid.ts'
|
||||||
|
|
||||||
export default [{
|
const cjs = {
|
||||||
output: [{
|
output: [{
|
||||||
file: 'dist/liquid.common.js',
|
file: 'dist/liquid.common.js',
|
||||||
name: 'Liquid',
|
name: 'Liquid',
|
||||||
@@ -36,7 +36,9 @@ export default [{
|
|||||||
})],
|
})],
|
||||||
treeshake,
|
treeshake,
|
||||||
input
|
input
|
||||||
}, {
|
}
|
||||||
|
|
||||||
|
const umd = {
|
||||||
output: [{
|
output: [{
|
||||||
file: 'dist/liquid.js',
|
file: 'dist/liquid.js',
|
||||||
name: 'Liquid',
|
name: 'Liquid',
|
||||||
@@ -63,7 +65,9 @@ export default [{
|
|||||||
],
|
],
|
||||||
treeshake,
|
treeshake,
|
||||||
input
|
input
|
||||||
}, {
|
}
|
||||||
|
|
||||||
|
const min = {
|
||||||
output: [{
|
output: [{
|
||||||
file: 'dist/liquid.min.js',
|
file: 'dist/liquid.min.js',
|
||||||
name: 'Liquid',
|
name: 'Liquid',
|
||||||
@@ -90,4 +94,13 @@ export default [{
|
|||||||
],
|
],
|
||||||
treeshake,
|
treeshake,
|
||||||
input
|
input
|
||||||
}]
|
}
|
||||||
|
|
||||||
|
const bundles = []
|
||||||
|
const env = process.env.BUNDLES || ''
|
||||||
|
if (env.includes('cjs')) bundles.push(cjs)
|
||||||
|
if (env.includes('umd')) bundles.push(umd)
|
||||||
|
if (env.includes('min')) bundles.push(min)
|
||||||
|
if (bundles.length === 0) bundles.push(cjs, umd, min)
|
||||||
|
|
||||||
|
export default bundles
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { isFalsy } from '../../render/syntax'
|
import { isFalsy } from '../../render/syntax'
|
||||||
|
import { toValue } from '../../util/underscore'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
'default': <T1, T2>(v: string | T1, arg: T2): string | T1 | T2 => isFalsy(v) || v === '' ? arg : v
|
'default': function<T1, T2> (v: string | T1, arg: T2): string | T1 | T2 {
|
||||||
|
return isFalsy(toValue(v)) || v === '' ? arg : v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { isString, isObject, isArray } from '../../util/underscore'
|
import { isString, isObject, isArray } from '../../util/underscore'
|
||||||
import { evalExp } from '../../render/syntax'
|
import { parseExp } from '../../render/syntax'
|
||||||
import assert from '../../util/assert'
|
import assert from '../../util/assert'
|
||||||
import { identifier, value, hash } from '../../parser/lexical'
|
import { identifier, value, hash } from '../../parser/lexical'
|
||||||
import TagToken from '../../parser/tag-token'
|
import TagToken from '../../parser/tag-token'
|
||||||
@@ -42,7 +42,7 @@ export default <ITagImplOptions>{
|
|||||||
stream.start()
|
stream.start()
|
||||||
},
|
},
|
||||||
render: async function (ctx: Context, hash: Hash) {
|
render: async function (ctx: Context, hash: Hash) {
|
||||||
let collection = await evalExp(this.collection, ctx)
|
let collection = await parseExp(this.collection, ctx)
|
||||||
|
|
||||||
if (!isArray(collection)) {
|
if (!isArray(collection)) {
|
||||||
if (isString(collection) && collection.length > 0) {
|
if (isString(collection) && collection.length > 0) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import assert from '../../util/assert'
|
import assert from '../../util/assert'
|
||||||
import { value, quotedLine } from '../../parser/lexical'
|
import { value, quotedLine } from '../../parser/lexical'
|
||||||
import { evalValue } from '../../render/syntax'
|
import { evalValue, parseValue } from '../../render/syntax'
|
||||||
import BlockMode from '../../context/block-mode'
|
import BlockMode from '../../context/block-mode'
|
||||||
import TagToken from '../../parser/tag-token'
|
import TagToken from '../../parser/tag-token'
|
||||||
import Context from '../../context/context'
|
import Context from '../../context/context'
|
||||||
@@ -13,19 +13,13 @@ const withRE = new RegExp(`with\\s+(${value.source})`)
|
|||||||
export default <ITagImplOptions>{
|
export default <ITagImplOptions>{
|
||||||
parse: function (token: TagToken) {
|
parse: function (token: TagToken) {
|
||||||
let match = staticFileRE.exec(token.args)
|
let match = staticFileRE.exec(token.args)
|
||||||
if (match) {
|
if (match) this.staticValue = match[0]
|
||||||
this.staticValue = match[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
match = value.exec(token.args)
|
match = value.exec(token.args)
|
||||||
if (match) {
|
if (match) this.value = match[0]
|
||||||
this.value = match[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
match = withRE.exec(token.args)
|
match = withRE.exec(token.args)
|
||||||
if (match) {
|
if (match) this.with = match[1]
|
||||||
this.with = match[1]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
render: async function (ctx: Context, hash: Hash) {
|
render: async function (ctx: Context, hash: Hash) {
|
||||||
let filepath
|
let filepath
|
||||||
@@ -47,7 +41,7 @@ export default <ITagImplOptions>{
|
|||||||
ctx.setRegister('blocks', {})
|
ctx.setRegister('blocks', {})
|
||||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||||
if (this.with) {
|
if (this.with) {
|
||||||
hash[filepath] = await evalValue(this.with, ctx)
|
hash[filepath] = await parseValue(this.with, ctx)
|
||||||
}
|
}
|
||||||
const templates = await this.liquid.getTemplate(filepath, ctx.opts)
|
const templates = await this.liquid.getTemplate(filepath, ctx.opts)
|
||||||
ctx.push(hash)
|
ctx.push(hash)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { isNil, isString } from '../util/underscore'
|
import { isNil, isString, toValue } from '../util/underscore'
|
||||||
import { Drop } from '../drop/drop'
|
|
||||||
import { EmptyDrop } from '../drop/empty-drop'
|
import { EmptyDrop } from '../drop/empty-drop'
|
||||||
|
|
||||||
export class BlankDrop extends EmptyDrop {
|
export class BlankDrop extends EmptyDrop {
|
||||||
equals (value: any) {
|
equals (value: any) {
|
||||||
if (value === false) return true
|
if (value === false) return true
|
||||||
if (isNil(value instanceof Drop ? value.valueOf() : value)) return true
|
if (isNil(toValue(value))) return true
|
||||||
if (isString(value)) return /^\s*$/.test(value)
|
if (isString(value)) return /^\s*$/.test(value)
|
||||||
return super.equals(value)
|
return super.equals(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Drop } from './drop'
|
import { Drop } from './drop'
|
||||||
import { IComparable } from './icomparable'
|
import { IComparable } from './icomparable'
|
||||||
import { isNil } from '../util/underscore'
|
import { isNil, toValue } from '../util/underscore'
|
||||||
import { BlankDrop } from '../drop/blank-drop'
|
import { BlankDrop } from '../drop/blank-drop'
|
||||||
|
|
||||||
export class NullDrop extends Drop implements IComparable {
|
export class NullDrop extends Drop implements IComparable {
|
||||||
equals (value: any) {
|
equals (value: any) {
|
||||||
return isNil(value instanceof Drop ? value.valueOf() : value) || value instanceof BlankDrop
|
return isNil(toValue(value)) || value instanceof BlankDrop
|
||||||
}
|
}
|
||||||
gt () {
|
gt () {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import * as lexical from '../parser/lexical'
|
import * as lexical from '../parser/lexical'
|
||||||
import assert from '../util/assert'
|
import assert from '../util/assert'
|
||||||
import Context from '../context/context'
|
import Context from '../context/context'
|
||||||
import { range, last, isFunction } from '../util/underscore'
|
import { range, last, isFunction, toValue } from '../util/underscore'
|
||||||
import { isComparable } from '../drop/icomparable'
|
import { isComparable } from '../drop/icomparable'
|
||||||
import { NullDrop } from '../drop/null-drop'
|
import { NullDrop } from '../drop/null-drop'
|
||||||
import { EmptyDrop } from '../drop/empty-drop'
|
import { EmptyDrop } from '../drop/empty-drop'
|
||||||
import { BlankDrop } from '../drop/blank-drop'
|
import { BlankDrop } from '../drop/blank-drop'
|
||||||
import { Drop } from '../drop/drop'
|
|
||||||
|
|
||||||
const binaryOperators: {[key: string]: (lhs: any, rhs: any) => boolean} = {
|
const binaryOperators: {[key: string]: (lhs: any, rhs: any) => boolean} = {
|
||||||
'==': (l: any, r: any) => {
|
'==': (l: any, r: any) => {
|
||||||
@@ -71,11 +70,10 @@ export async function parseExp (exp: string, ctx: Context): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function evalExp (str: string, ctx: Context): Promise<any> {
|
export async function evalExp (str: string, ctx: Context): Promise<any> {
|
||||||
const value = await parseExp(str, ctx)
|
return toValue(await parseExp(str, ctx))
|
||||||
return value instanceof Drop ? value.valueOf() : value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function parseValue (str: string | undefined, ctx: Context): Promise<any> {
|
export async function parseValue (str: string | undefined, ctx: Context): Promise<any> {
|
||||||
if (!str) return null
|
if (!str) return null
|
||||||
str = str.trim()
|
str = str.trim()
|
||||||
|
|
||||||
@@ -90,8 +88,7 @@ async function parseValue (str: string | undefined, ctx: Context): Promise<any>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function evalValue (str: string | undefined, ctx: Context) {
|
export async function evalValue (str: string | undefined, ctx: Context) {
|
||||||
const value = await parseValue(str, ctx)
|
return toValue(await parseValue(str, ctx))
|
||||||
return value instanceof Drop ? value.valueOf() : value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isTruthy (val: any): boolean {
|
export function isTruthy (val: any): boolean {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { evalValue } from '../../render/syntax'
|
import { parseValue } from '../../render/syntax'
|
||||||
import Context from '../../context/context'
|
import Context from '../../context/context'
|
||||||
import { isArray } from '../../util/underscore'
|
import { isArray } from '../../util/underscore'
|
||||||
import { FilterImplOptions } from './filter-impl-options'
|
import { FilterImplOptions } from './filter-impl-options'
|
||||||
|
|
||||||
export type FilterArgs = Array<string|[string?, string?]>
|
type KeyValuePair = [string?, string?]
|
||||||
|
type FilterArg = string|KeyValuePair
|
||||||
|
export type FilterArgs = Array<FilterArg>
|
||||||
|
|
||||||
export class Filter {
|
export class Filter {
|
||||||
name: string
|
name: string
|
||||||
@@ -22,8 +24,8 @@ export class Filter {
|
|||||||
async render (value: any, context: Context) {
|
async render (value: any, context: Context) {
|
||||||
const argv: any[] = []
|
const argv: any[] = []
|
||||||
for (const arg of this.args) {
|
for (const arg of this.args) {
|
||||||
if (isArray(arg)) argv.push([arg[0], await evalValue(arg[1], context)])
|
if (isKeyValuePair(arg)) argv.push([arg[0], await parseValue(arg[1], context)])
|
||||||
else argv.push(await evalValue(arg, context))
|
else argv.push(await parseValue(arg, context))
|
||||||
}
|
}
|
||||||
return this.impl.apply({ context }, [value, ...argv])
|
return this.impl.apply({ context }, [value, ...argv])
|
||||||
}
|
}
|
||||||
@@ -34,3 +36,7 @@ export class Filter {
|
|||||||
Filter.impls = {}
|
Filter.impls = {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isKeyValuePair (arr: FilterArg): arr is KeyValuePair {
|
||||||
|
return isArray(arr)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Value from './value'
|
import Value from './value'
|
||||||
import { stringify } from '../util/underscore'
|
import { stringify, toValue } from '../util/underscore'
|
||||||
import Template from '../template/template'
|
import Template from '../template/template'
|
||||||
import ITemplate from '../template/itemplate'
|
import ITemplate from '../template/itemplate'
|
||||||
import Context from '../context/context'
|
import Context from '../context/context'
|
||||||
@@ -12,7 +12,7 @@ export default class Output extends Template<OutputToken> implements ITemplate {
|
|||||||
this.value = new Value(token.value, strictFilters)
|
this.value = new Value(token.value, strictFilters)
|
||||||
}
|
}
|
||||||
async render (ctx: Context): Promise<string> {
|
async render (ctx: Context): Promise<string> {
|
||||||
const html = await this.value.value(ctx)
|
const val = await this.value.value(ctx)
|
||||||
return stringify(html)
|
return stringify(toValue(val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { hashCapture } from '../../parser/lexical'
|
import { hashCapture } from '../../parser/lexical'
|
||||||
import { evalValue } from '../../render/syntax'
|
import { parseValue } from '../../render/syntax'
|
||||||
import Context from '../../context/context'
|
import Context from '../../context/context'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -17,7 +17,7 @@ export default class Hash {
|
|||||||
while ((match = hashCapture.exec(markup))) {
|
while ((match = hashCapture.exec(markup))) {
|
||||||
const k = match[1]
|
const k = match[1]
|
||||||
const v = match[2]
|
const v = match[2]
|
||||||
instance[k] = await evalValue(v, ctx)
|
instance[k] = await parseValue(v, ctx)
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { evalExp } from '../render/syntax'
|
import { parseExp } from '../render/syntax'
|
||||||
import { FilterArgs, Filter } from './filter/filter'
|
import { FilterArgs, Filter } from './filter/filter'
|
||||||
import Context from '../context/context'
|
import Context from '../context/context'
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export default class Value {
|
|||||||
this.filters.push(new Filter(name, args, this.strictFilters))
|
this.filters.push(new Filter(name, args, this.strictFilters))
|
||||||
}
|
}
|
||||||
async value (ctx: Context) {
|
async value (ctx: Context) {
|
||||||
let val = await evalExp(this.initial, ctx)
|
let val = await parseExp(this.initial, ctx)
|
||||||
for (const filter of this.filters) {
|
for (const filter of this.filters) {
|
||||||
val = await filter.render(val, ctx)
|
val = await filter.render(val, ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Drop } from '../drop/drop'
|
||||||
|
|
||||||
const toStr = Object.prototype.toString
|
const toStr = Object.prototype.toString
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -31,6 +33,10 @@ export function stringify (value: any): string {
|
|||||||
return String(value)
|
return String(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toValue (value: any): any {
|
||||||
|
return value instanceof Drop ? value.valueOf() : value
|
||||||
|
}
|
||||||
|
|
||||||
export function toLiquid (value: any): any {
|
export function toLiquid (value: any): any {
|
||||||
if (isFunction(value.toLiquid)) return toLiquid(value.toLiquid())
|
if (isFunction(value.toLiquid)) return toLiquid(value.toLiquid())
|
||||||
return value
|
return value
|
||||||
|
|||||||
@@ -83,6 +83,33 @@ describe('tags/include', function () {
|
|||||||
const html = await liquid.renderFile('with.html')
|
const html = await liquid.renderFile('with.html')
|
||||||
return expect(html).to.equal('color:red, shape:rect')
|
return expect(html).to.equal('color:red, shape:rect')
|
||||||
})
|
})
|
||||||
|
it('should support include: with as Liquid Drop', async function () {
|
||||||
|
class ColorDrop extends Liquid.Types.Drop {
|
||||||
|
valueOf (): string {
|
||||||
|
return 'red!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mock({
|
||||||
|
'/with.html': '{% include "color" with color %}',
|
||||||
|
'/color.html': 'color:{{color}}'
|
||||||
|
})
|
||||||
|
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
|
||||||
|
expect(html).to.equal('color:red!')
|
||||||
|
})
|
||||||
|
it('should support include: with passed as Liquid Drop', async function () {
|
||||||
|
class ColorDrop extends Liquid.Types.Drop {
|
||||||
|
valueOf (): string {
|
||||||
|
return 'red!'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liquid.registerFilter('name', x => x.constructor.name)
|
||||||
|
mock({
|
||||||
|
'/with.html': '{% include "color" with color %}',
|
||||||
|
'/color.html': '{{color | name}}'
|
||||||
|
})
|
||||||
|
const html = await liquid.renderFile('with.html', { color: new ColorDrop() })
|
||||||
|
expect(html).to.equal('ColorDrop')
|
||||||
|
})
|
||||||
|
|
||||||
it('should support nested includes', async function () {
|
it('should support nested includes', async function () {
|
||||||
mock({
|
mock({
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ describe('filter', function () {
|
|||||||
expect(await new Filter('add', ['2', '"c"'], false).render(3, ctx)).to.equal('5c')
|
expect(await new Filter('add', ['2', '"c"'], false).render(3, ctx)).to.equal('5c')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should pass Objects/Drops as it is', async function () {
|
||||||
|
Filter.register('name', a => a.constructor.name)
|
||||||
|
class Foo {}
|
||||||
|
expect(await new Filter('name', [], false).render(new Foo(), ctx)).to.equal('Foo')
|
||||||
|
})
|
||||||
|
|
||||||
it('should not throw when filter name illegal', function () {
|
it('should not throw when filter name illegal', function () {
|
||||||
expect(function () {
|
expect(function () {
|
||||||
new Filter('/', [], false)
|
new Filter('/', [], false)
|
||||||
|
|||||||
Reference in New Issue
Block a user