Skip to content

Commit

Permalink
refactor: Tag class support in registerTag()
Browse files Browse the repository at this point in the history
  • Loading branch information
harttle committed Nov 27, 2022
1 parent 1f6ce7c commit 9299268
Show file tree
Hide file tree
Showing 145 changed files with 693 additions and 767 deletions.
23 changes: 21 additions & 2 deletions docs/source/tutorials/register-filters-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ engine.registerTag('upper', {
parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.str = tagToken.args; // name
},
render: async function(ctx: Context) {
var str = await this.liquid.evalValue(this.str, ctx); // 'alice'
render: function*(ctx: Context) {
const str = yield this.liquid.evalValue(this.str, ctx); // 'alice'
return str.toUpperCase() // 'ALICE'
}
});
Expand All @@ -22,6 +22,25 @@ engine.registerTag('upper', {
* `parse`: Read tokens from `remainTokens` until your end token.
* `render`: Combine scope data with your parsed tokens into HTML string.

For complex tag implementation, you can also provide a tag class:

```typescript
// Usage: {% upper name:"alice" %}
import { Hash, Tag, TagToken, Context, Emitter, TopLevelToken, Liquid } from 'liquidjs'

engine.registerTag('upper', class UpperTag extends Tag {
private hash: Hash
constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
this.hash = new Hash(tagToken.args)
}
* render(ctx: Context) {
const hash = yield this.hash.render();
return hash.name.toUpperCase() // 'ALICE'
}
});
```

See existing tag implementations here: <https://github.com/harttle/liquidjs/tree/master/src/builtin/tags>
See demo example here: https://github.com/harttle/liquidjs/blob/master/demo/typescript/index.ts

Expand Down
19 changes: 18 additions & 1 deletion docs/source/zh-cn/tutorials/register-filters-tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,24 @@ engine.registerFilter('upper', v => v.toUpperCase())
engine.registerFilter('add', (initial, arg1, arg2) => initial + arg1 + arg2)
```

查看已有的过滤器实现:<https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>
查看已有的过滤器实现:<https://github.com/harttle/liquidjs/tree/master/src/builtin/filters>。对于复杂的标签,也可以用一个类来实现:

```typescript
// Usage: {% upper name:"alice" %}
import { Hash, Tag, TagToken, Context, Emitter, TopLevelToken, Liquid } from 'liquidjs'

engine.registerTag('upper', class UpperTag extends Tag {
private hash: Hash
constructor(tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
this.hash = new Hash(tagToken.args)
}
* render(ctx: Context) {
const hash = yield this.hash.render();
return hash.name.toUpperCase() // 'ALICE'
}
});
```

## 反注册标签/过滤器

Expand Down
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"./dist/liquid.node.cjs.js": "./dist/liquid.browser.umd.js",
"./dist/liquid.node.esm.js": "./dist/liquid.browser.esm.js"
},
"types": "dist/liquid.d.ts",
"types": "dist/index.d.ts",
"engines": {
"node": ">=14"
},
Expand Down Expand Up @@ -44,9 +44,6 @@
"LICENSE",
"README.md"
],
"engines": {
"node": ">=4.8.7"
},
"keywords": [
"liquid",
"template engine",
Expand Down
6 changes: 3 additions & 3 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ const versionInjection = versionInjector({
logger: console,
exclude: []
})
const input = './src/liquid.ts'
const input = './src/index.ts'
const browserFS = {
include: './src/liquid-options.ts',
delimiters: ['', ''],
'./fs/node': './fs/browser'
}
const browserStream = {
include: './src/render/render.ts',
include: './src/emitters/index.ts',
delimiters: ['', ''],
'../emitters/streamed-emitter': '../emitters/streamed-emitter-browser'
'./streamed-emitter': './streamed-emitter-browser'
}
const esmRequire = {
include: './src/fs/node.ts',
Expand Down
2 changes: 2 additions & 0 deletions src/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './cache'
export * from './lru'
2 changes: 1 addition & 1 deletion src/cache/lru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Node<T> {
}

export class LRU<T> implements Cache<T> {
private cache: { [key: string]: Node<T> } = {}
private cache: Record<string, Node<T>> = {}
private head: Node<T>
private tail: Node<T>

Expand Down
4 changes: 1 addition & 3 deletions src/context/block-mode.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
enum BlockMode {
export enum BlockMode {
/* store rendered html into blocks */
OUTPUT,
/* output rendered html directly */
STORE
}

export default BlockMode
4 changes: 1 addition & 3 deletions src/context/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { Drop } from '../drop/drop'
import { __assign } from 'tslib'
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'
import { toValueSync } from '../util/async'
import { isArray, isNil, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync } from '../util'

type PropertyKey = string | number;

Expand Down
3 changes: 3 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './context'
export * from './scope'
export * from './block-mode'
5 changes: 2 additions & 3 deletions src/context/scope.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Drop } from '../drop/drop'

export interface PlainObject {
[key: string]: any;
interface ScopeObject extends Record<string, any> {
toLiquid?: () => any;
}

export type Scope = PlainObject | Drop
export type Scope = ScopeObject | Drop
4 changes: 2 additions & 2 deletions src/drop/blank-drop.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isNil, isString, toValue } from '../util/underscore'
import { EmptyDrop } from '../drop/empty-drop'
import { isNil, isString, toValue } from '../util'
import { EmptyDrop } from '../drop'

export class BlankDrop extends EmptyDrop {
public equals (value: any) {
Expand Down
2 changes: 1 addition & 1 deletion src/drop/comparable.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isFunction } from '../util/underscore'
import { isFunction } from '../util'

export interface Comparable {
equals: (rhs: any) => boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/drop/empty-drop.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Drop } from './drop'
import { Comparable } from './comparable'
import { isObject, isString, isArray, toValue } from '../util/underscore'
import { isObject, isString, isArray, toValue } from '../util'

export class EmptyDrop extends Drop implements Comparable {
public equals (value: any) {
Expand Down
7 changes: 7 additions & 0 deletions src/drop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export * from './drop'
export * from './null-drop'
export * from './empty-drop'
export * from './blank-drop'
export * from './forloop-drop'
export * from './block-drop'
export * from './comparable'
2 changes: 1 addition & 1 deletion src/drop/null-drop.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Drop } from './drop'
import { Comparable } from './comparable'
import { isNil, toValue } from '../util/underscore'
import { isNil, toValue } from '../util'

export class NullDrop extends Drop implements Comparable {
public equals (value: any) {
Expand Down
4 changes: 4 additions & 0 deletions src/emitters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './emitter'
export * from './simple-emitter'
export * from './streamed-emitter'
export * from './keeping-type-emitter'
4 changes: 2 additions & 2 deletions src/emitters/keeping-type-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { stringify, toValue } from '../util/underscore'
import { Emitter } from '../types'
import { stringify, toValue } from '../util'
import { Emitter } from './emitter'

export class KeepingTypeEmitter implements Emitter {
public buffer: any = '';
Expand Down
2 changes: 1 addition & 1 deletion src/emitters/simple-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { stringify } from '../util/underscore'
import { stringify } from '../util'
import { Emitter } from './emitter'

export class SimpleEmitter implements Emitter {
Expand Down
2 changes: 1 addition & 1 deletion src/emitters/streamed-emitter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { stringify } from '../util/underscore'
import { stringify } from '../util'
import { Emitter } from './emitter'
import { PassThrough } from 'stream'

Expand Down
11 changes: 5 additions & 6 deletions src/filters/array.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../util/underscore'
import { toArray } from '../util/collection'
import { isTruthy } from '../render/boolean'
import { FilterImpl } from '../template/filter/filter-impl'
import { Scope } from '../context/scope'
import { isComparable } from '../drop/comparable'
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, hasOwnProperty } from '../util'
import { isTruthy } from '../render'
import { FilterImpl } from '../template'
import { Scope } from '../context'
import { isComparable } from '../drop'

export const join = argumentsToValue((v: any[], arg: string) => toArray(v).join(arg === undefined ? ' ' : arg))
export const last = argumentsToValue((v: any) => isArray(v) ? arrayLast(v) : '')
Expand Down
7 changes: 2 additions & 5 deletions src/filters/date.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import strftime from '../util/strftime'
import { LiquidDate } from '../util/liquid-date'
import { toValue, stringify, isString, isNumber } from '../util/underscore'
import { FilterImpl } from '../template/filter/filter-impl'
import { TimezoneDate } from '../util/timezone-date'
import { toValue, stringify, isString, isNumber, TimezoneDate, LiquidDate, strftime } from '../util'
import { FilterImpl } from '../template'

export function date (this: FilterImpl, v: string | Date, format: string, timeZoneOffset?: number) {
const opts = this.context.opts
Expand Down
4 changes: 2 additions & 2 deletions src/filters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import * as arrayFilters from './array'
import * as dateFilters from './date'
import * as stringFilters from './string'
import { Default, json } from './misc'
import { FilterImplOptions } from '../template/filter/filter-impl-options'
import { FilterImplOptions } from '../template'

export const filters: { [key: string]: FilterImplOptions } = {
export const filters: Record<string, FilterImplOptions> = {
...htmlFilters,
...mathFilters,
...urlFilters,
Expand Down
2 changes: 1 addition & 1 deletion src/filters/misc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isFalsy } from '../render/boolean'
import { identify, isArray, isString, toValue } from '../util/underscore'
import { FilterImpl } from '../template/filter/filter-impl'
import { FilterImpl } from '../template'

export function Default<T1 extends boolean, T2> (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 {
value = toValue(value)
Expand Down
3 changes: 1 addition & 2 deletions src/filters/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
*
* * prefer stringify() to String() since `undefined`, `null` should eval ''
*/
import { escapeRegExp, stringify } from '../util/underscore'
import { assert } from '../util/assert'
import { assert, escapeRegExp, stringify } from '../util'

export function append (v: string, arg: string) {
assert(arguments.length === 2, 'append expect 2 arguments')
Expand Down
2 changes: 1 addition & 1 deletion src/fs/browser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { last } from '../util/underscore'
import { last } from '../util'

function domResolve (root: string, path: string) {
const base = document.createElement('base')
Expand Down
2 changes: 2 additions & 0 deletions src/fs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './loader'
export * from './fs'
3 changes: 1 addition & 2 deletions src/fs/loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FS } from './fs'
import { escapeRegex } from '../util/underscore'
import { assert } from '../util/assert'
import { assert, escapeRegex } from '../util'

export interface LoaderOptions {
fs: FS;
Expand Down
6 changes: 3 additions & 3 deletions src/fs/node.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as _ from '../util/underscore'
import { promisify } from '../util'
import { sep, resolve as nodeResolve, extname, dirname as nodeDirname } from 'path'
import { stat, statSync, readFile as nodeReadFile, readFileSync as nodeReadFileSync } from 'fs'
import { requireResolve } from './node-require'

const statAsync = _.promisify(stat)
const readFileAsync = _.promisify<string, string, string>(nodeReadFile)
const statAsync = promisify(stat)
const readFileAsync = promisify<string, string, string>(nodeReadFile)

export async function exists (filepath: string) {
try {
Expand Down
16 changes: 16 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* istanbul ignore file */
export const version = '[VI]{version}[/VI]'
export * as TypeGuards from './util/type-guards'
export { toValue, TimezoneDate, createTrie, Trie, toPromise, toValueSync, assert, ParseError, TokenizationError, AssertionError } from './util'
export { Drop } from './drop'
export { Emitter } from './emitters'
// TODO change to _evalToken
export { defaultOperators, Operators, _evalToken, evalQuotedToken, Expression, isFalsy, isTruthy } from './render'
export { Context, Scope } from './context'
export { Value, Hash, Template, FilterImplOptions, Tag, Filter } from './template'
export { Token, TopLevelToken, TagToken, ValueToken } from './tokens'
export { TokenKind, Tokenizer, ParseStream } from './parser'
export { filters } from './filters'
export { tags } from './tags'
export { defaultOptions } from './liquid-options'
export { Liquid } from './liquid'
8 changes: 3 additions & 5 deletions src/liquid-options.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { isArray, isString, isFunction } from './util/underscore'
import { LiquidCache } from './cache/cache'
import { LRU } from './cache/lru'
import { assert, isArray, isString, isFunction } from './util'
import { LRU, LiquidCache } from './cache'
import { FS } from './fs/fs'
import * as fs from './fs/node'
import { defaultOperators, Operators } from './render/operator'
import { defaultOperators, Operators } from './render'
import { filters } from './filters'
import { assert } from './util/assert'

type OutputEscape = (value: any) => string
type OutputEscapeOption = 'escape' | 'json' | OutputEscape
Expand Down
38 changes: 12 additions & 26 deletions src/liquid.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
import { Context } from './context/context'
import { forOwn } from './util/underscore'
import { Template } from './template/template'
import { Context } from './context'
import { toPromise, toValueSync, isFunction, forOwn } from './util'
import { TagClass, createTagClass, TagImplOptions, FilterImplOptions, Template, Value } from './template'
import { LookupType } from './fs/loader'
import { Render } from './render/render'
import Parser from './parser/parser'
import { TagImplOptions } from './template/tag/tag-impl-options'
import { Value } from './template/value'
import { Render } from './render'
import { Parser } from './parser'
import { tags } from './tags'
import { filters } from './filters'
import { TagMap } from './template/tag/tag-map'
import { FilterMap } from './template/filter/filter-map'
import { LiquidOptions, normalizeDirectoryList, NormalizedFullOptions, normalize, RenderOptions } from './liquid-options'
import { FilterImplOptions } from './template/filter/filter-impl-options'
import { toPromise, toValueSync } from './util/async'

export * from './util/error'
export * from './types'
export const version = '[VI]{version}[/VI]'

export class Liquid {
public readonly options: NormalizedFullOptions
public readonly renderer: Render
public readonly renderer = new Render()
public readonly parser: Parser
public readonly filters: FilterMap
public readonly tags: TagMap
public readonly filters: Record<string, FilterImplOptions> = {}
public readonly tags: Record<string, TagClass> = {}

public constructor (opts: LiquidOptions = {}) {
this.options = normalize(opts)
this.parser = new Parser(this)
this.renderer = new Render()
this.filters = new FilterMap(this.options.strictFilters, this)
this.tags = new TagMap()

forOwn(tags, (conf: TagImplOptions, name: string) => this.registerTag(name, conf))
forOwn(tags, (conf: TagClass, name: string) => this.registerTag(name, conf))
forOwn(filters, (handler: FilterImplOptions, name: string) => this.registerFilter(name, handler))
}
public parse (html: string, filepath?: string): Template[] {
Expand Down Expand Up @@ -103,10 +89,10 @@ export class Liquid {
}

public registerFilter (name: string, filter: FilterImplOptions) {
this.filters.set(name, filter)
this.filters[name] = filter
}
public registerTag (name: string, tag: TagImplOptions) {
this.tags.set(name, tag)
public registerTag (name: string, tag: TagClass | TagImplOptions) {
this.tags[name] = isFunction(tag) ? tag : createTagClass(tag)
}
public plugin (plugin: (this: Liquid, L: typeof Liquid) => void) {
return plugin.call(this, Liquid)
Expand Down
Loading

0 comments on commit 9299268

Please sign in to comment.