diff --git a/src/liquid-options.ts b/src/liquid-options.ts index 95143c5cda..c0ff85671f 100644 --- a/src/liquid-options.ts +++ b/src/liquid-options.ts @@ -1,6 +1,6 @@ import { assert, isArray, isString, isFunction } from './util' import { LRU, LiquidCache } from './cache' -import { FS } from './fs/fs' +import { FS, LookupType } from './fs' import * as fs from './fs/node' import { defaultOperators, Operators } from './render' import { json } from './filters/misc' @@ -91,6 +91,10 @@ export interface RenderOptions { ownPropertyOnly?: boolean; } +export interface RenderFileOptions extends RenderOptions { + lookupType?: LookupType; +} + interface NormalizedOptions extends LiquidOptions { root?: string[]; partials?: string[]; diff --git a/src/liquid.ts b/src/liquid.ts index 30f199632e..0336fa3c48 100644 --- a/src/liquid.ts +++ b/src/liquid.ts @@ -6,7 +6,7 @@ import { Render } from './render' import { Parser } from './parser' import { tags } from './tags' 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 { public readonly options: NormalizedFullOptions @@ -25,8 +25,8 @@ export class Liquid { return this.parser.parse(html, filepath) } - public _render (tpl: Template[], scope: object | undefined, renderOptions: RenderOptions): IterableIterator { - const ctx = new Context(scope, this.options, renderOptions) + public _render (tpl: Template[], scope: Context | object | undefined, renderOptions: RenderOptions): IterableIterator { + const ctx = scope instanceof Context ? scope : new Context(scope, this.options, renderOptions) return this.renderer.renderTemplates(tpl, ctx) } public async render (tpl: Template[], scope?: object, renderOptions?: RenderOptions): Promise { @@ -40,14 +40,14 @@ export class Liquid { return this.renderer.renderTemplatesToNodeStream(tpl, ctx) } - public _parseAndRender (html: string, scope: object | undefined, renderOptions: RenderOptions): IterableIterator { + public _parseAndRender (html: string, scope: Context | object | undefined, renderOptions: RenderOptions): IterableIterator { const tpl = this.parse(html) return this._render(tpl, scope, renderOptions) } - public async parseAndRender (html: string, scope?: object, renderOptions?: RenderOptions): Promise { + public async parseAndRender (html: string, scope?: Context | object, renderOptions?: RenderOptions): Promise { 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 })) } @@ -57,19 +57,24 @@ export class Liquid { public _parseLayoutFile (file: string, sync?: boolean, currentFile?: string) { return this.parser.parseFile(file, sync, LookupType.Layouts, currentFile) } - public async parseFile (file: string): Promise { - return toPromise(this.parser.parseFile(file, false)) + public _parseFile (file: string, sync?: boolean, lookupType?: LookupType, currentFile?: string): Generator { + return this.parser.parseFile(file, sync, lookupType, currentFile) } - public parseFileSync (file: string): Template[] { - return toValueSync(this.parser.parseFile(file, true)) + public async parseFile (file: string, lookupType?: LookupType): Promise { + return toPromise(this.parser.parseFile(file, false, lookupType)) } - public async renderFile (file: string, ctx?: object, renderOptions?: RenderOptions) { - const templates = await this.parseFile(file) - return this.render(templates, ctx, renderOptions) + public parseFileSync (file: string, lookupType?: LookupType): Template[] { + return toValueSync(this.parser.parseFile(file, true, lookupType)) + } + public * _renderFile (file: string, ctx: Context | object | undefined, renderFileOptions: RenderFileOptions): Generator { + const templates = (yield this._parseFile(file, renderFileOptions.sync, renderFileOptions.lookupType)) as Template[] + 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?: object, renderOptions?: RenderOptions) { - const templates = this.parseFileSync(file) - return this.renderSync(templates, ctx, renderOptions) + 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) { const templates = await this.parseFile(file) diff --git a/src/template/tag-options-adapter.ts b/src/template/tag-options-adapter.ts index 4bb23043ed..cb4478afd9 100644 --- a/src/template/tag-options-adapter.ts +++ b/src/template/tag-options-adapter.ts @@ -7,8 +7,9 @@ import { Context } from '../context' import type { Liquid } from '../liquid' export interface TagImplOptions { - parse?: (this: Tag, token: TagToken, remainingTokens: TopLevelToken[]) => void; - render: (this: Tag, ctx: Context, emitter: Emitter, hash: Record) => TagRenderReturn; + [key: string]: any + parse?: (this: Tag & TagImplOptions, token: TagToken, remainingTokens: TopLevelToken[]) => void; + render: (this: Tag & TagImplOptions, ctx: Context, emitter: Emitter, hash: Record) => TagRenderReturn; } export function createTagClass (options: TagImplOptions): TagClass { diff --git a/test/e2e/issues.ts b/test/e2e/issues.ts index 734655fbd1..ba6af67a23 100644 --- a/test/e2e/issues.ts +++ b/test/e2e/issues.ts @@ -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 * as chaiAsPromised from 'chai-as-promised' import * as sinon from 'sinon' @@ -346,4 +346,20 @@ describe('Issues', function () { 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*$/) }) + 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') + }) }) diff --git a/test/integration/liquid/liquid.ts b/test/integration/liquid/liquid.ts index 8a50b4fb8f..5a020c7b2f 100644 --- a/test/integration/liquid/liquid.ts +++ b/test/integration/liquid/liquid.ts @@ -57,6 +57,10 @@ describe('Liquid', function () { const html = await engine.parseAndRender(src, { foo: Promise.resolve('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 () { const engine = new Liquid() @@ -145,6 +149,11 @@ describe('Liquid', function () { const str = await engine.evalValue('"foo"', ctx) 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 () { it('should eval string literal', function () {