Skip to content

Commit

Permalink
feat: customize globals & strictVariables when calling render, see #432
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Dec 11, 2021
1 parent 6023d9b commit 6801552
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/builtin/tags/render.ts
Expand Up @@ -51,7 +51,7 @@ export default {
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
assert(filepath, () => `illegal filename "${filepath}"`)

const childCtx = new Context({}, ctx.opts, ctx.sync)
const childCtx = new Context({}, ctx.opts, { sync: ctx.sync, globals: ctx.globals, strictVariables: ctx.strictVariables })
const scope = childCtx.bottom()
__assign(scope, yield hash.render(ctx))
if (this['with']) {
Expand Down
18 changes: 13 additions & 5 deletions src/context/context.ts
@@ -1,6 +1,6 @@
import { Drop } from '../drop/drop'
import { __assign } from 'tslib'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
import { Scope } from './scope'
import { isArray, isNil, isString, isFunction, toLiquid } from '../util/underscore'
import { InternalUndefinedVariableError } from '../util/error'
Expand All @@ -23,12 +23,20 @@ export class Context {
*/
public globals: Scope
public sync: boolean
/**
* The normalized liquid options object
*/
public opts: NormalizedFullOptions
public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, sync = false) {
this.sync = sync
/**
* Throw when accessing undefined variable?
*/
public strictVariables: boolean;
public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}) {
this.sync = !!renderOptions.sync
this.opts = opts
this.globals = opts.globals
this.globals = renderOptions.globals ?? opts.globals
this.environments = env
this.strictVariables = renderOptions.strictVariables ?? this.opts.strictVariables
}
public getRegister (key: string) {
return (this.registers[key] = this.registers[key] || {})
Expand All @@ -54,7 +62,7 @@ export class Context {
if (typeof paths === 'string') paths = paths.split('.')
return paths.reduce((scope, path) => {
scope = readProperty(scope, path)
if (isNil(scope) && this.opts.strictVariables) {
if (isNil(scope) && this.strictVariables) {
throw new InternalUndefinedVariableError(path)
}
return scope
Expand Down
15 changes: 15 additions & 0 deletions src/liquid-options.ts
Expand Up @@ -65,6 +65,21 @@ export interface LiquidOptions {
orderedFilterParameters?: boolean;
}

export interface RenderOptions {
/**
* This call is sync or async? It's used by Liquid internal methods, you'll not need this.
*/
sync?: boolean;
/**
* Same as `globals` on LiquidOptions, but only for current render() call
*/
globals?: object;
/**
* Same as `strictVariables` on LiquidOptions, but only for current render() call
*/
strictVariables?: boolean;
}

interface NormalizedOptions extends LiquidOptions {
root?: string[];
partials?: string[];
Expand Down
42 changes: 21 additions & 21 deletions src/liquid.ts
Expand Up @@ -10,7 +10,7 @@ import builtinTags from './builtin/tags'
import * as builtinFilters from './builtin/filters'
import { TagMap } from './template/tag/tag-map'
import { FilterMap } from './template/filter/filter-map'
import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize } from './liquid-options'
import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize, RenderOptions } from './liquid-options'
import { FilterImplOptions } from './template/filter/filter-impl-options'
import { toPromise, toValue } from './util/async'

Expand Down Expand Up @@ -39,30 +39,30 @@ export class Liquid {
return this.parser.parse(html, filepath)
}

public _render (tpl: Template[], scope?: object, sync?: boolean): IterableIterator<any> {
const ctx = new Context(scope, this.options, sync)
public _render (tpl: Template[], scope: object | undefined, renderOptions: RenderOptions): IterableIterator<any> {
const ctx = new Context(scope, this.options, renderOptions)
return this.renderer.renderTemplates(tpl, ctx)
}
public async render (tpl: Template[], scope?: object): Promise<any> {
return toPromise(this._render(tpl, scope, false))
public async render (tpl: Template[], scope?: object, renderOptions?: RenderOptions): Promise<any> {
return toPromise(this._render(tpl, scope, { ...renderOptions, sync: false }))
}
public renderSync (tpl: Template[], scope?: object): any {
return toValue(this._render(tpl, scope, true))
public renderSync (tpl: Template[], scope?: object, renderOptions?: RenderOptions): any {
return toValue(this._render(tpl, scope, { ...renderOptions, sync: true }))
}
public renderToNodeStream (tpl: Template[], scope?: object): NodeJS.ReadableStream {
const ctx = new Context(scope, this.options)
public renderToNodeStream (tpl: Template[], scope?: object, renderOptions: RenderOptions = {}): NodeJS.ReadableStream {
const ctx = new Context(scope, this.options, renderOptions)
return this.renderer.renderTemplatesToNodeStream(tpl, ctx)
}

public _parseAndRender (html: string, scope?: object, sync?: boolean): IterableIterator<any> {
public _parseAndRender (html: string, scope: object | undefined, renderOptions: RenderOptions): IterableIterator<any> {
const tpl = this.parse(html)
return this._render(tpl, scope, sync)
return this._render(tpl, scope, renderOptions)
}
public async parseAndRender (html: string, scope?: object): Promise<any> {
return toPromise(this._parseAndRender(html, scope, false))
public async parseAndRender (html: string, scope?: object, renderOptions?: RenderOptions): Promise<any> {
return toPromise(this._parseAndRender(html, scope, { ...renderOptions, sync: false }))
}
public parseAndRenderSync (html: string, scope?: object): any {
return toValue(this._parseAndRender(html, scope, true))
public parseAndRenderSync (html: string, scope?: object, renderOptions?: RenderOptions): any {
return toValue(this._parseAndRender(html, scope, { ...renderOptions, sync: true }))
}

public _parsePartialFile (file: string, sync?: boolean, currentFile?: string) {
Expand All @@ -77,17 +77,17 @@ export class Liquid {
public parseFileSync (file: string): Template[] {
return toValue<Template[]>(this.parser.parseFile(file, true))
}
public async renderFile (file: string, ctx?: object) {
public async renderFile (file: string, ctx?: object, renderOptions?: RenderOptions) {
const templates = await this.parseFile(file)
return this.render(templates, ctx)
return this.render(templates, ctx, renderOptions)
}
public renderFileSync (file: string, ctx?: object) {
public renderFileSync (file: string, ctx?: object, renderOptions?: RenderOptions) {
const templates = this.parseFileSync(file)
return this.renderSync(templates, ctx)
return this.renderSync(templates, ctx, renderOptions)
}
public async renderFileToNodeStream (file: string, scope?: object) {
public async renderFileToNodeStream (file: string, scope?: object, renderOptions?: RenderOptions) {
const templates = await this.parseFile(file)
return this.renderToNodeStream(templates, scope)
return this.renderToNodeStream(templates, scope, renderOptions)
}

public _evalValue (str: string, ctx: Context): IterableIterator<any> {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
@@ -1,3 +1,4 @@
/* istanbul ignore file */
import * as TypeGuards from './util/type-guards'
export { TypeGuards }
export { ParseError, TokenizationError, AssertionError } from './util/error'
Expand Down
9 changes: 9 additions & 0 deletions test/integration/liquid/liquid.ts
Expand Up @@ -43,6 +43,15 @@ describe('Liquid', function () {
const html = await engine.parseAndRender(src, {})
return expect(html).to.equal('12')
})
it('should support `globals` render option', async function () {
const src = '{{ foo }}'
const html = await engine.parseAndRender(src, {}, { globals: { foo: 'FOO' } })
return expect(html).to.equal('FOO')
})
it('should support `strictVariables` render option', function () {
const src = '{{ foo }}'
return expect(engine.parseAndRender(src, {}, { strictVariables: true })).rejectedWith(/undefined variable/)
})
})
describe('#express()', function () {
const liquid = new Liquid({ root: '/root' })
Expand Down

0 comments on commit 6801552

Please sign in to comment.