fix: type compatible with v9 tag definition, support Context as scope in various render APIs, #570

This commit is contained in:
Jun Yang
2022-12-18 12:48:06 +08:00
parent 7526d4062d
commit fb6a9f8717
5 changed files with 55 additions and 20 deletions
+5 -1
View File
@@ -1,6 +1,6 @@
import { assert, isArray, isString, isFunction } from './util' import { assert, isArray, isString, isFunction } from './util'
import { LRU, LiquidCache } from './cache' import { LRU, LiquidCache } from './cache'
import { FS } from './fs/fs' import { FS, LookupType } from './fs'
import * as fs from './fs/node' import * as fs from './fs/node'
import { defaultOperators, Operators } from './render' import { defaultOperators, Operators } from './render'
import { json } from './filters/misc' import { json } from './filters/misc'
@@ -91,6 +91,10 @@ export interface RenderOptions {
ownPropertyOnly?: boolean; ownPropertyOnly?: boolean;
} }
export interface RenderFileOptions extends RenderOptions {
lookupType?: LookupType;
}
interface NormalizedOptions extends LiquidOptions { interface NormalizedOptions extends LiquidOptions {
root?: string[]; root?: string[];
partials?: string[]; partials?: string[];
+21 -16
View File
@@ -6,7 +6,7 @@ import { Render } from './render'
import { Parser } from './parser' import { Parser } from './parser'
import { tags } from './tags' import { tags } from './tags'
import { filters } from './filters' import { filters } from './filters'
import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize, RenderOptions } from './liquid-options' import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize, RenderOptions, RenderFileOptions } from './liquid-options'
export class Liquid { export class Liquid {
public readonly options: NormalizedFullOptions public readonly options: NormalizedFullOptions
@@ -25,8 +25,8 @@ export class Liquid {
return this.parser.parse(html, filepath) return this.parser.parse(html, filepath)
} }
public _render (tpl: Template[], scope: object | undefined, renderOptions: RenderOptions): IterableIterator<any> { public _render (tpl: Template[], scope: Context | object | undefined, renderOptions: RenderOptions): IterableIterator<any> {
const ctx = new Context(scope, this.options, renderOptions) const ctx = scope instanceof Context ? scope : new Context(scope, this.options, renderOptions)
return this.renderer.renderTemplates(tpl, ctx) return this.renderer.renderTemplates(tpl, ctx)
} }
public async render (tpl: Template[], scope?: object, renderOptions?: RenderOptions): Promise<any> { public async render (tpl: Template[], scope?: object, renderOptions?: RenderOptions): Promise<any> {
@@ -40,14 +40,14 @@ export class Liquid {
return this.renderer.renderTemplatesToNodeStream(tpl, ctx) return this.renderer.renderTemplatesToNodeStream(tpl, ctx)
} }
public _parseAndRender (html: string, scope: object | undefined, renderOptions: RenderOptions): IterableIterator<any> { public _parseAndRender (html: string, scope: Context | object | undefined, renderOptions: RenderOptions): IterableIterator<any> {
const tpl = this.parse(html) const tpl = this.parse(html)
return this._render(tpl, scope, renderOptions) return this._render(tpl, scope, renderOptions)
} }
public async parseAndRender (html: string, scope?: object, renderOptions?: RenderOptions): Promise<any> { public async parseAndRender (html: string, scope?: Context | object, renderOptions?: RenderOptions): Promise<any> {
return toPromise(this._parseAndRender(html, scope, { ...renderOptions, sync: false })) return toPromise(this._parseAndRender(html, scope, { ...renderOptions, sync: false }))
} }
public parseAndRenderSync (html: string, scope?: object, renderOptions?: RenderOptions): any { public parseAndRenderSync (html: string, scope?: Context | object, renderOptions?: RenderOptions): any {
return toValueSync(this._parseAndRender(html, scope, { ...renderOptions, sync: true })) return toValueSync(this._parseAndRender(html, scope, { ...renderOptions, sync: true }))
} }
@@ -57,19 +57,24 @@ export class Liquid {
public _parseLayoutFile (file: string, sync?: boolean, currentFile?: string) { public _parseLayoutFile (file: string, sync?: boolean, currentFile?: string) {
return this.parser.parseFile(file, sync, LookupType.Layouts, currentFile) return this.parser.parseFile(file, sync, LookupType.Layouts, currentFile)
} }
public async parseFile (file: string): Promise<Template[]> { public _parseFile (file: string, sync?: boolean, lookupType?: LookupType, currentFile?: string): Generator<unknown, Template[]> {
return toPromise<Template[]>(this.parser.parseFile(file, false)) return this.parser.parseFile(file, sync, lookupType, currentFile)
} }
public parseFileSync (file: string): Template[] { public async parseFile (file: string, lookupType?: LookupType): Promise<Template[]> {
return toValueSync<Template[]>(this.parser.parseFile(file, true)) return toPromise<Template[]>(this.parser.parseFile(file, false, lookupType))
} }
public async renderFile (file: string, ctx?: object, renderOptions?: RenderOptions) { public parseFileSync (file: string, lookupType?: LookupType): Template[] {
const templates = await this.parseFile(file) return toValueSync<Template[]>(this.parser.parseFile(file, true, lookupType))
return this.render(templates, ctx, renderOptions)
} }
public renderFileSync (file: string, ctx?: object, renderOptions?: RenderOptions) { public * _renderFile (file: string, ctx: Context | object | undefined, renderFileOptions: RenderFileOptions): Generator<any> {
const templates = this.parseFileSync(file) const templates = (yield this._parseFile(file, renderFileOptions.sync, renderFileOptions.lookupType)) as Template[]
return this.renderSync(templates, ctx, renderOptions) return yield this._render(templates, ctx, renderFileOptions)
}
public async renderFile (file: string, ctx?: Context | object, renderFileOptions?: RenderFileOptions) {
return toPromise(this._renderFile(file, ctx, { ...renderFileOptions, sync: false }))
}
public renderFileSync (file: string, ctx?: Context | object, renderFileOptions?: RenderFileOptions) {
return toValueSync(this._renderFile(file, ctx, { ...renderFileOptions, sync: true }))
} }
public async renderFileToNodeStream (file: string, scope?: object, renderOptions?: RenderOptions) { public async renderFileToNodeStream (file: string, scope?: object, renderOptions?: RenderOptions) {
const templates = await this.parseFile(file) const templates = await this.parseFile(file)
+3 -2
View File
@@ -7,8 +7,9 @@ import { Context } from '../context'
import type { Liquid } from '../liquid' import type { Liquid } from '../liquid'
export interface TagImplOptions { export interface TagImplOptions {
parse?: (this: Tag, token: TagToken, remainingTokens: TopLevelToken[]) => void; [key: string]: any
render: (this: Tag, ctx: Context, emitter: Emitter, hash: Record<string, any>) => TagRenderReturn; parse?: (this: Tag & TagImplOptions, token: TagToken, remainingTokens: TopLevelToken[]) => void;
render: (this: Tag & TagImplOptions, ctx: Context, emitter: Emitter, hash: Record<string, any>) => TagRenderReturn;
} }
export function createTagClass (options: TagImplOptions): TagClass { export function createTagClass (options: TagImplOptions): TagClass {
+17 -1
View File
@@ -1,4 +1,4 @@
import { Tokenizer, Context, Liquid, Drop, toValueSync } from '../..' import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync } from '../..'
import { expect, use } from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised' import * as chaiAsPromised from 'chai-as-promised'
import * as sinon from 'sinon' import * as sinon from 'sinon'
@@ -346,4 +346,20 @@ describe('Issues', function () {
const html = await liquid.parseAndRender(tpl) const html = await liquid.parseAndRender(tpl)
expect(html).to.match(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/) expect(html).to.match(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
}) })
it('#570 tag registration compatible to v9', async () => {
const liquid = new Liquid()
liquid.registerTag('metadata_file', {
parse (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args
},
async render (ctx: Context) {
const content = await Promise.resolve(`{{${this.str}}}`)
return this.liquid.parseAndRender(content.toString(), ctx)
}
})
const tpl = '{% metadata_file foo %}'
const ctx = { foo: 'FOO' }
const html = await liquid.parseAndRender(tpl, ctx)
expect(html).to.equal('FOO')
})
}) })
+9
View File
@@ -57,6 +57,10 @@ describe('Liquid', function () {
const html = await engine.parseAndRender(src, { foo: Promise.resolve('FOO') }) const html = await engine.parseAndRender(src, { foo: Promise.resolve('FOO') })
expect(html).to.equal('FOO') expect(html).to.equal('FOO')
}) })
it('should parse and render with Context', async function () {
const html = await engine.parseAndRender('{{foo}}', new Context({ foo: 'FOO' }))
expect(html).to.equal('FOO')
})
}) })
describe('#parseAndRenderSync', function () { describe('#parseAndRenderSync', function () {
const engine = new Liquid() const engine = new Liquid()
@@ -145,6 +149,11 @@ describe('Liquid', function () {
const str = await engine.evalValue('"foo"', ctx) const str = await engine.evalValue('"foo"', ctx)
expect(str).to.equal('foo') expect(str).to.equal('foo')
}) })
it('should support plain scope', async function () {
const engine = new Liquid()
const str = await engine.evalValue('foo', { foo: 'FOO' })
expect(str).to.equal('FOO')
})
}) })
describe('#evalValueSync', function () { describe('#evalValueSync', function () {
it('should eval string literal', function () { it('should eval string literal', function () {