refactor: Tag class support in registerTag()

This commit is contained in:
Jun Yang
2022-11-27 14:04:01 +08:00
parent 1f6ce7c822
commit 92992689cd
145 changed files with 694 additions and 768 deletions
+21 -2
View File
@@ -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'
}
});
@@ -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
@@ -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'
}
});
```
## 反注册标签/过滤器
+1 -4
View File
@@ -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"
},
@@ -44,9 +44,6 @@
"LICENSE",
"README.md"
],
"engines": {
"node": ">=4.8.7"
},
"keywords": [
"liquid",
"template engine",
+3 -3
View File
@@ -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',
+2
View File
@@ -0,0 +1,2 @@
export * from './cache'
export * from './lru'
+1 -1
View File
@@ -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>
+1 -3
View File
@@ -1,8 +1,6 @@
enum BlockMode {
export enum BlockMode {
/* store rendered html into blocks */
OUTPUT,
/* output rendered html directly */
STORE
}
export default BlockMode
+1 -3
View File
@@ -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;
+3
View File
@@ -0,0 +1,3 @@
export * from './context'
export * from './scope'
export * from './block-mode'
+2 -3
View File
@@ -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
+2 -2
View File
@@ -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) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { isFunction } from '../util/underscore'
import { isFunction } from '../util'
export interface Comparable {
equals: (rhs: any) => boolean;
+1 -1
View File
@@ -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) {
+7
View File
@@ -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'
+1 -1
View File
@@ -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) {
+4
View File
@@ -0,0 +1,4 @@
export * from './emitter'
export * from './simple-emitter'
export * from './streamed-emitter'
export * from './keeping-type-emitter'
+2 -2
View File
@@ -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 = '';
+1 -1
View File
@@ -1,4 +1,4 @@
import { stringify } from '../util/underscore'
import { stringify } from '../util'
import { Emitter } from './emitter'
export class SimpleEmitter implements Emitter {
+1 -1
View File
@@ -1,4 +1,4 @@
import { stringify } from '../util/underscore'
import { stringify } from '../util'
import { Emitter } from './emitter'
import { PassThrough } from 'stream'
+5 -6
View File
@@ -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) : '')
+2 -5
View File
@@ -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
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -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)
+1 -2
View File
@@ -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')
+1 -1
View File
@@ -1,4 +1,4 @@
import { last } from '../util/underscore'
import { last } from '../util'
function domResolve (root: string, path: string) {
const base = document.createElement('base')
+2
View File
@@ -0,0 +1,2 @@
export * from './loader'
export * from './fs'
+1 -2
View File
@@ -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;
+3 -3
View File
@@ -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 {
+16
View File
@@ -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'
+3 -5
View File
@@ -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
+12 -26
View File
@@ -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[] {
@@ -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)
+1 -1
View File
@@ -5,6 +5,6 @@ type KeyValuePair = [string?, ValueToken?]
export type FilterArg = ValueToken | KeyValuePair
export function isKeyValuePair (arr: FilterArg): arr is KeyValuePair { // TODO check
export function isKeyValuePair (arr: FilterArg): arr is KeyValuePair {
return isArray(arr)
}
+5
View File
@@ -0,0 +1,5 @@
export * from './tokenizer'
export * from './parser'
export * from './parse-stream'
export * from './parse-string-literal'
export * from './token-kind'
+2 -3
View File
@@ -1,8 +1,7 @@
import { IDENTIFIER, TYPES } from '../util/character'
import { Trie } from '../util/operator-trie'
import { Trie, TrieNode, IDENTIFIER, TYPES } from '../util'
export function matchOperator (str: string, begin: number, trie: Trie, end = str.length) {
let node = trie
let node: TrieNode = trie
let i = begin
let info
while (node[str[i]] && i < end) {
+4 -5
View File
@@ -1,13 +1,12 @@
import { Token } from '../tokens/token'
import { Template } from '../template/template'
import { isTagToken } from '../util/type-guards'
import { TopLevelToken } from '../tokens/toplevel-token'
import { Token, TopLevelToken } from '../tokens'
import { Template } from '../template'
import { isTagToken } from '../util'
type ParseToken<T extends Token> = ((token: T, remainTokens: T[]) => Template)
export class ParseStream<T extends Token = TopLevelToken> {
private tokens: T[]
private handlers: {[key: string]: (arg: any) => void} = {}
private handlers: Record<string, (arg: any) => void> = {}
private stopRequested = false
private parseToken: ParseToken<T>
+11 -15
View File
@@ -1,19 +1,13 @@
import { ParseError } from '../util/error'
import { Liquid, Tokenizer } from '../liquid'
import { toPromise, assert, isTagToken, isOutputToken, ParseError } from '../util'
import { Tokenizer } from './tokenizer'
import { ParseStream } from './parse-stream'
import { isTagToken, isOutputToken } from '../util/type-guards'
import { OutputToken } from '../tokens/output-token'
import { Tag } from '../template/tag/tag'
import { Output } from '../template/output'
import { HTML } from '../template/html'
import { Template } from '../template/template'
import { TopLevelToken } from '../tokens/toplevel-token'
import { LiquidCache } from '../cache/cache'
import { Loader, LookupType } from '../fs/loader'
import { toPromise } from '../util/async'
import { FS } from '../fs/fs'
import { TopLevelToken, OutputToken } from '../tokens'
import { Template, Output, HTML } from '../template'
import { LiquidCache } from '../cache'
import { FS, Loader, LookupType } from '../fs'
import type { Liquid } from '../liquid'
export default class Parser {
export class Parser {
public parseFile: (file: string, sync?: boolean, type?: LookupType, currentFile?: string) => Generator<unknown, Template[], Template[] | string>
private liquid: Liquid
@@ -44,7 +38,9 @@ export default class Parser {
public parseToken (token: TopLevelToken, remainTokens: TopLevelToken[]) {
try {
if (isTagToken(token)) {
return new Tag(token, remainTokens, this.liquid)
const TagClass = this.liquid.tags[token.name]
assert(TagClass, `tag "${token.name}" not found`)
return new TagClass(token, remainTokens, this.liquid)
}
if (isOutputToken(token)) {
return new Output(token as OutputToken, this.liquid)
+6 -27
View File
@@ -1,31 +1,10 @@
import { whiteSpaceCtrl } from './whitespace-ctrl'
import { NumberToken } from '../tokens/number-token'
import { IdentifierToken } from '../tokens/identifier-token'
import { literalValues } from '../util/literal'
import { LiteralToken } from '../tokens/literal-token'
import { OperatorToken } from '../tokens/operator-token'
import { PropertyAccessToken } from '../tokens/property-access-token'
import { assert } from '../util/assert'
import { TopLevelToken } from '../tokens/toplevel-token'
import { FilterArg } from './filter-arg'
import { FilterToken } from '../tokens/filter-token'
import { HashToken } from '../tokens/hash-token'
import { QuotedToken } from '../tokens/quoted-token'
import { ellipsis } from '../util/underscore'
import { HTMLToken } from '../tokens/html-token'
import { TagToken } from '../tokens/tag-token'
import { Token } from '../tokens/token'
import { RangeToken } from '../tokens/range-token'
import { ValueToken } from '../tokens/value-token'
import { OutputToken } from '../tokens/output-token'
import { TokenizationError } from '../util/error'
import { TagToken, HTMLToken, HashToken, QuotedToken, LiquidTagToken, OutputToken, ValueToken, Token, RangeToken, FilterToken, TopLevelToken, PropertyAccessToken, OperatorToken, LiteralToken, IdentifierToken, NumberToken } from '../tokens'
import { Trie, createTrie, ellipsis, literalValues, assert, TokenizationError, TYPES, QUOTE, BLANK, IDENTIFIER } from '../util'
import { Operators, Expression } from '../render'
import { NormalizedFullOptions, defaultOptions } from '../liquid-options'
import { TYPES, QUOTE, BLANK, IDENTIFIER } from '../util/character'
import { FilterArg } from './filter-arg'
import { matchOperator } from './match-operator'
import { Trie, createTrie } from '../util/operator-trie'
import { Expression } from '../render/expression'
import { Operators } from '../render/operator'
import { LiquidTagToken } from '../tokens/liquid-tag-token'
import { whiteSpaceCtrl } from './whitespace-ctrl'
export class Tokenizer {
p = 0
@@ -36,7 +15,7 @@ export class Tokenizer {
constructor (
public input: string,
operators: Operators = defaultOptions.operators,
public file: string = ''
public file?: string
) {
this.N = input.length
this.opTrie = createTrie(operators)
+2 -3
View File
@@ -1,7 +1,6 @@
import { Token } from '../tokens/token'
import { isTagToken, isHTMLToken, isDelimitedToken } from '../util/type-guards'
import { Token } from '../tokens'
import { NormalizedFullOptions } from '../liquid-options'
import { TYPES, INLINE_BLANK, BLANK } from '../util/character'
import { isTagToken, isHTMLToken, isDelimitedToken, TYPES, INLINE_BLANK, BLANK } from '../util'
export function whiteSpaceCtrl (tokens: Token[], options: NormalizedFullOptions) {
let inRaw = false
+13 -31
View File
@@ -1,19 +1,8 @@
import { QuotedToken } from '../tokens/quoted-token'
import { PropertyAccessToken } from '../tokens/property-access-token'
import { NumberToken } from '../tokens/number-token'
import { assert } from '../util/assert'
import { literalValues } from '../util/literal'
import { LiteralToken } from '../tokens/literal-token'
import * as TypeGuards from '../util/type-guards'
import { Token } from '../tokens/token'
import { OperatorToken } from '../tokens/operator-token'
import { RangeToken } from '../tokens/range-token'
import { parseStringLiteral } from '../parser/parse-string-literal'
import { Context } from '../context/context'
import { range } from '../util/underscore'
import { Operators } from '../render/operator'
import { UndefinedVariableError } from '../util/error'
import { toValueSync } from '../util/async'
import { RangeToken, OperatorToken, Token, LiteralToken, NumberToken, PropertyAccessToken, QuotedToken } from '../tokens'
import { isQuotedToken, isWordToken, isNumberToken, isLiteralToken, isRangeToken, isPropertyAccessToken, UndefinedVariableError, range, isOperatorToken, literalValues, assert } from '../util'
import { parseStringLiteral } from '../parser'
import { Context } from '../context'
import { Operators } from '../render'
export class Expression {
private postfix: Token[]
@@ -25,7 +14,7 @@ export class Expression {
assert(ctx, 'unable to evaluate: context not defined')
const operands: any[] = []
for (const token of this.postfix) {
if (TypeGuards.isOperatorToken(token)) {
if (isOperatorToken(token)) {
const r = operands.pop()
const l = operands.pop()
const result = yield evalOperatorToken(ctx.opts.operators, token, l, r, ctx)
@@ -38,20 +27,13 @@ export class Expression {
}
}
/**
* @deprecated use `_evalToken` instead
*/
export function * evalToken (token: Token | undefined, ctx: Context, lenient = false) {
return toValueSync(_evalToken(token, ctx, lenient))
}
export function * _evalToken (token: Token | undefined, ctx: Context, lenient = false): IterableIterator<unknown> {
if (TypeGuards.isPropertyAccessToken(token)) return yield evalPropertyAccessToken(token, ctx, lenient)
if (TypeGuards.isRangeToken(token)) return yield evalRangeToken(token, ctx)
if (TypeGuards.isLiteralToken(token)) return evalLiteralToken(token)
if (TypeGuards.isNumberToken(token)) return evalNumberToken(token)
if (TypeGuards.isWordToken(token)) return token.getText()
if (TypeGuards.isQuotedToken(token)) return evalQuotedToken(token)
if (isPropertyAccessToken(token)) return yield evalPropertyAccessToken(token, ctx, lenient)
if (isRangeToken(token)) return yield evalRangeToken(token, ctx)
if (isLiteralToken(token)) return evalLiteralToken(token)
if (isNumberToken(token)) return evalNumberToken(token)
if (isWordToken(token)) return token.getText()
if (isQuotedToken(token)) return evalQuotedToken(token)
}
function * evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean): IterableIterator<unknown> {
@@ -94,7 +76,7 @@ function * evalRangeToken (token: RangeToken, ctx: Context) {
function * toPostfix (tokens: IterableIterator<Token>): IterableIterator<Token> {
const ops: OperatorToken[] = []
for (const token of tokens) {
if (TypeGuards.isOperatorToken(token)) {
if (isOperatorToken(token)) {
while (ops.length && ops[ops.length - 1].getPrecedence() > token.getPrecedence()) {
yield ops.pop()!
}
+4
View File
@@ -0,0 +1,4 @@
export * from './render'
export * from './expression'
export * from './operator'
export * from './boolean'
+4 -5
View File
@@ -1,11 +1,10 @@
import { isComparable } from '../drop/comparable'
import { Context } from '../context/context'
import { isFunction, toValue } from '../util/underscore'
import { Context } from '../context'
import { isFunction, toValue } from '../util'
import { isTruthy } from '../render/boolean'
export interface Operators {
[key: string]: (lhs: any, rhs: any, ctx: Context) => boolean;
}
export type OperatorHandler = (lhs: any, rhs: any, ctx: Context) => boolean;
export type Operators = Record<string, OperatorHandler>
export const defaultOperators: Operators = {
'==': (l: any, r: any) => {
+4 -8
View File
@@ -1,11 +1,7 @@
import { RenderError } from '../util/error'
import { Context } from '../context/context'
import { Template } from '../template/template'
import { Emitter } from '../emitters/emitter'
import { SimpleEmitter } from '../emitters/simple-emitter'
import { StreamedEmitter } from '../emitters/streamed-emitter'
import { toPromise } from '../util/async'
import { KeepingTypeEmitter } from '../emitters/keeping-type-emitter'
import { toPromise, RenderError } from '../util'
import { Context } from '../context'
import { Template } from '../template'
import { Emitter, KeepingTypeEmitter, StreamedEmitter, SimpleEmitter } from '../emitters'
export class Render {
public renderTemplatesToNodeStream (templates: Template[], ctx: Context): NodeJS.ReadableStream {
+10 -7
View File
@@ -1,15 +1,18 @@
import { Value, Tokenizer, assert, TagImplOptions, TagToken, Context } from '../types'
import { Value, assert, Tokenizer, Liquid, TopLevelToken, TagToken, Context, Tag } from '..'
export default class extends Tag {
private key: string
private value: Value
export default {
parse: function (token: TagToken) {
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, liquid.options.operators)
this.key = tokenizer.readIdentifier().content
tokenizer.skipBlank()
assert(tokenizer.peek() === '=', () => `illegal token ${token.getText()}`)
tokenizer.advance()
this.value = new Value(tokenizer.remaining(), this.liquid)
},
render: function * (ctx: Context): Generator<unknown, void, unknown> {
}
* render (ctx: Context): Generator<unknown, void, unknown> {
ctx.bottom()[this.key] = yield this.value.value(ctx, this.liquid.options.lenientIf)
}
} as TagImplOptions
}
+20 -15
View File
@@ -1,29 +1,34 @@
import BlockMode from '../context/block-mode'
import { BlockDrop } from '../drop/block-drop'
import { TagToken, TopLevelToken, Template, Context, TagImpl, Emitter } from '../types'
import { BlockMode } from '../context'
import { isTagToken } from '../util'
import { BlockDrop } from '../drop'
import { Liquid, TagToken, TopLevelToken, Template, Context, Emitter, Tag } from '..'
export default {
parse (this: TagImpl, token: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
private block: string
private tpls: Template[] = []
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const match = /\w+/.exec(token.args)
this.block = match ? match[0] : ''
this.tpls = [] as Template[]
this.liquid.parser.parseStream(remainTokens)
.on('tag:endblock', function () { this.stop() })
.on('template', (tpl: Template) => this.tpls.push(tpl))
.on('end', () => { throw new Error(`tag ${token.getText()} not closed`) })
.start()
},
while (remainTokens.length) {
const token = remainTokens.shift()!
if (isTagToken(token) && token.name === 'endblock') return
const template = liquid.parser.parseToken(token, remainTokens)
this.tpls.push(template)
}
throw new Error(`tag ${token.getText()} not closed`)
}
* render (this: TagImpl, ctx: Context, emitter: Emitter) {
* render (ctx: Context, emitter: Emitter) {
const blockRender = this.getBlockRender(ctx)
if (ctx.getRegister('blockMode') === BlockMode.STORE) {
ctx.getRegister('blocks')[this.block] = blockRender
} else {
yield blockRender(new BlockDrop(), emitter)
}
},
}
getBlockRender (this: TagImpl, ctx: Context) {
private getBlockRender (ctx: Context) {
const { liquid, tpls } = this
const renderChild = ctx.getRegister('blocks')[this.block]
const renderCurrent = function * (superBlock: BlockDrop, emitter: Emitter) {
+3 -3
View File
@@ -1,7 +1,7 @@
import { Emitter, Context } from '../types'
import { Context, Emitter, Tag } from '..'
export default {
render: function (ctx: Context, emitter: Emitter) {
export default class extends Tag {
render (ctx: Context, emitter: Emitter) {
emitter['break'] = true
}
}
+18 -17
View File
@@ -1,28 +1,29 @@
import { Tokenizer, assert, Template, Context, TagImplOptions, TagToken, TopLevelToken } from '../types'
import { evalQuotedToken } from '../render/expression'
import { Liquid, Tag, Tokenizer, assert, Template, Context, TagToken, TopLevelToken } from '..'
import { evalQuotedToken } from '../render'
import { isTagToken } from '../util'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
private variable: string
private templates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators)
this.variable = readVariableName(tokenizer)
this.variable = readVariableName(tokenizer)!
assert(this.variable, () => `${tagToken.args} not valid identifier`)
this.templates = []
const stream = this.liquid.parser.parseStream(remainTokens)
stream.on('tag:endcapture', () => stream.stop())
.on('template', (tpl: Template) => this.templates.push(tpl))
.on('end', () => {
throw new Error(`tag ${tagToken.getText()} not closed`)
})
stream.start()
},
render: function * (ctx: Context): Generator<unknown, void, string> {
while (remainTokens.length) {
const token = remainTokens.shift()!
if (isTagToken(token) && token.name === 'endcapture') return
this.templates.push(liquid.parser.parseToken(token, remainTokens))
}
throw new Error(`tag ${tagToken.getText()} not closed`)
}
* render (ctx: Context): Generator<unknown, void, string> {
const r = this.liquid.renderer
const html = yield r.renderTemplates(this.templates, ctx)
ctx.bottom()[this.variable] = html
}
} as TagImplOptions
}
function readVariableName (tokenizer: Tokenizer) {
const word = tokenizer.readIdentifier().content
+10 -8
View File
@@ -1,10 +1,12 @@
import { toValue, _evalToken, Value, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types'
import { Tokenizer } from '../parser/tokenizer'
import { ValueToken, Liquid, Tokenizer, toValue, _evalToken, Value, Emitter, TagToken, TopLevelToken, Context, Template, Tag, ParseStream } from '..'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
private cond: Value
private cases: { val?: ValueToken, templates: Template[] }[] = []
private elseTemplates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
this.cond = new Value(tagToken.args, this.liquid)
this.cases = []
this.elseTemplates = []
let p: Template[] = []
@@ -31,9 +33,9 @@ export default {
})
stream.start()
},
}
render: function * (ctx: Context, emitter: Emitter) {
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
const r = this.liquid.renderer
const cond = toValue(yield this.cond.value(ctx, ctx.opts.lenientIf))
for (const branch of this.cases) {
@@ -45,4 +47,4 @@ export default {
}
yield r.renderTemplates(this.elseTemplates, ctx, emitter)
}
} as TagImplOptions
}
+12 -15
View File
@@ -1,17 +1,14 @@
import { TagToken } from '../tokens/tag-token'
import { TopLevelToken } from '../tokens/toplevel-token'
import { TagImplOptions } from '../template/tag/tag-impl-options'
import { Liquid, TopLevelToken, TagToken, Tag } from '..'
import { isTagToken } from '../util'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
const stream = this.liquid.parser.parseStream(remainTokens)
stream
.on('token', (token: TagToken) => {
if (token.name === 'endcomment') stream.stop()
})
.on('end', () => {
throw new Error(`tag ${tagToken.getText()} not closed`)
})
stream.start()
export default class extends Tag {
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
while (remainTokens.length) {
const token = remainTokens.shift()!
if (isTagToken(token) && token.name === 'endcomment') return
}
throw new Error(`tag ${tagToken.getText()} not closed`)
}
} as TagImplOptions
render () {}
}
+3 -3
View File
@@ -1,7 +1,7 @@
import { Emitter, Context } from '../types'
import { Tag, Emitter, Context } from '..'
export default {
render: function (ctx: Context, emitter: Emitter) {
export default class extends Tag {
render (ctx: Context, emitter: Emitter) {
emitter['continue'] = true
}
}
+11 -13
View File
@@ -1,15 +1,14 @@
import { assert } from '../util/assert'
import { _evalToken, Emitter, TagToken, Context, TagImplOptions } from '../types'
import { Tokenizer } from '../parser/tokenizer'
import { Tokenizer, assert, TopLevelToken, Liquid, ValueToken, _evalToken, Emitter, TagToken, Context, Tag } from '..'
export default {
parse: function (tagToken: TagToken) {
export default class extends Tag {
private candidates: ValueToken[] = []
private group?: ValueToken
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators)
const group = tokenizer.readValue()
tokenizer.skipBlank()
this.candidates = []
if (group) {
if (tokenizer.peek() === ':') {
this.group = group
@@ -23,10 +22,10 @@ export default {
tokenizer.readTo(',')
}
assert(this.candidates.length, () => `empty candidates: ${tagToken.getText()}`)
},
}
render: function * (ctx: Context, emitter: Emitter) {
const group = yield _evalToken(this.group, ctx)
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
const group = (yield _evalToken(this.group, ctx)) as ValueToken
const fingerprint = `cycle:${group}:` + this.candidates.join(',')
const groups = ctx.getRegister('cycle')
let idx = groups[fingerprint]
@@ -38,7 +37,6 @@ export default {
const candidate = this.candidates[idx]
idx = (idx + 1) % this.candidates.length
groups[fingerprint] = idx
const html = yield _evalToken(candidate, ctx)
emitter.write(html)
return yield _evalToken(candidate, ctx)
}
} as TagImplOptions
}
+9 -7
View File
@@ -1,16 +1,18 @@
import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../types'
import { isNumber, stringify } from '../util/underscore'
import { Tag, Liquid, TopLevelToken, Tokenizer, Emitter, TagToken, Context } from '..'
import { isNumber, stringify } from '../util'
export default {
parse: function (token: TagToken) {
export default class extends Tag {
private variable: string
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
this.variable = tokenizer.readIdentifier().content
},
render: function (context: Context, emitter: Emitter) {
}
render (context: Context, emitter: Emitter) {
const scope = context.environments
if (!isNumber(scope[this.variable])) {
scope[this.variable] = 0
}
emitter.write(stringify(--scope[this.variable]))
}
} as TagImplOptions
}
+8 -8
View File
@@ -1,13 +1,13 @@
import { Value } from '../template/value'
import { Emitter } from '../emitters/emitter'
import { TagImplOptions, TagToken, Context } from '../types'
import { Liquid, TopLevelToken, Emitter, Value, TagToken, Context, Tag } from '..'
export default {
parse: function (token: TagToken) {
export default class extends Tag {
private value: Value
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
this.value = new Value(token.args, this.liquid)
},
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const val = yield this.value.value(ctx, false)
emitter.write(val)
}
} as TagImplOptions
}
+16 -13
View File
@@ -1,24 +1,27 @@
import { assert, Tokenizer, _evalToken, Emitter, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types'
import { Hash, ValueToken, Liquid, Tag, Tokenizer, _evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
import { toEnumerable } from '../util/collection'
import { ForloopDrop } from '../drop/forloop-drop'
import { Hash, HashValue } from '../template/tag/hash'
const MODIFIERS = ['offset', 'limit', 'reversed']
type valueof<T> = T[keyof T]
export default {
type: 'block',
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
export default class extends Tag {
private variable: string
private collection: ValueToken
private hash: Hash
private templates: Template[]
private elseTemplates: Template[]
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
const variable = tokenizer.readIdentifier()
const inStr = tokenizer.readIdentifier()
const collection = tokenizer.readValue()
assert(
variable.size() && inStr.content === 'in' && collection,
() => `illegal tag: ${token.getText()}`
)
if (!variable.size() || inStr.content !== 'in' || !collection) {
throw new Error(`illegal tag: ${token.getText()}`)
}
this.variable = variable.content
this.collection = collection
@@ -37,8 +40,8 @@ export default {
})
stream.start()
},
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void | string, HashValue | Template[]> {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void | string, Template[]> {
const r = this.liquid.renderer
let collection = toEnumerable(yield _evalToken(this.collection, ctx))
@@ -77,7 +80,7 @@ export default {
}
ctx.pop()
}
} as TagImplOptions
}
function reversed<T> (arr: Array<T>) {
return [...arr].reverse()
+10 -9
View File
@@ -1,12 +1,13 @@
import { Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template, TagImplOptions } from '../types'
import { Liquid, Tag, Value, Emitter, isTruthy, TagToken, TopLevelToken, Context, Template } from '..'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.branches = []
this.elseTemplates = []
export default class extends Tag {
private branches: { predicate: Value, templates: Template[] }[] = []
private elseTemplates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
let p
this.liquid.parser.parseStream(remainTokens)
liquid.parser.parseStream(remainTokens)
.on('start', () => this.branches.push({
predicate: new Value(tagToken.args, this.liquid),
templates: (p = [])
@@ -20,9 +21,9 @@ export default {
.on('template', (tpl: Template) => p.push(tpl))
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
.start()
},
}
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, string> {
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, string> {
const r = this.liquid.renderer
for (const { predicate, templates } of this.branches) {
@@ -34,4 +35,4 @@ export default {
}
yield r.renderTemplates(this.elseTemplates, ctx, emitter)
}
} as TagImplOptions
}
+14 -13
View File
@@ -1,14 +1,15 @@
import { assert, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, TagImplOptions } from '../types'
import BlockMode from '../context/block-mode'
import { Template, ValueToken, TopLevelToken, Liquid, Tag, assert, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context } from '..'
import { BlockMode, Scope } from '../context'
import { parseFilePath, renderFilePath } from './render'
export default {
parseFilePath,
renderFilePath,
parse: function (token: TagToken) {
export default class extends Tag {
private withVar?: ValueToken
private hash: Hash
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const args = token.args
const tokenizer = new Tokenizer(args, this.liquid.options.operators)
this['file'] = this.parseFilePath(tokenizer, this.liquid)
this['file'] = parseFilePath(tokenizer, this.liquid)
this['currentFile'] = token.file
const begin = tokenizer.p
@@ -21,22 +22,22 @@ export default {
} else tokenizer.p = begin
this.hash = new Hash(tokenizer.remaining(), this.liquid.options.jekyllInclude)
},
render: function * (ctx: Context, emitter: Emitter) {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash, withVar } = this
const { renderer } = liquid
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string
assert(filepath, () => `illegal filename "${filepath}"`)
const saved = ctx.saveRegister('blocks', 'blockMode')
ctx.setRegister('blocks', {})
ctx.setRegister('blockMode', BlockMode.OUTPUT)
const scope = yield hash.render(ctx)
const scope = (yield hash.render(ctx)) as Scope
if (withVar) scope[filepath] = yield _evalToken(withVar, ctx)
const templates = yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])) as Template[]
ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope)
yield renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
ctx.restoreRegister(saved)
}
} as TagImplOptions
}
+9 -7
View File
@@ -1,12 +1,14 @@
import { isNumber, stringify } from '../util/underscore'
import { Tokenizer, Emitter, TagToken, Context, TagImplOptions } from '../types'
import { isNumber, stringify } from '../util'
import { Tag, Liquid, TopLevelToken, Tokenizer, Emitter, TagToken, Context } from '..'
export default {
parse: function (token: TagToken) {
export default class extends Tag {
private variable: string
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
this.variable = tokenizer.readIdentifier().content
},
render: function (context: Context, emitter: Emitter) {
}
render (context: Context, emitter: Emitter) {
const scope = context.environments
if (!isNumber(scope[this.variable])) {
scope[this.variable] = 0
@@ -15,4 +17,4 @@ export default {
scope[this.variable]++
emitter.write(stringify(val))
}
} as TagImplOptions
}
+2 -2
View File
@@ -19,8 +19,8 @@ import Continue from './continue'
import echo from './echo'
import liquid from './liquid'
import inlineComment from './inline-comment'
import { TagImplOptions } from '../template/tag/tag-impl-options'
import type { TagClass } from '../template/tag'
export const tags: { [key: string]: TagImplOptions } = {
export const tags: Record<string, TagClass> = {
assign, 'for': For, capture, 'case': Case, comment, include, render, decrement, increment, cycle, 'if': If, layout, block, raw, tablerow, unless, 'break': Break, 'continue': Continue, echo, liquid, '#': inlineComment
}
+6 -6
View File
@@ -1,11 +1,11 @@
import { TagToken } from '../tokens/tag-token'
import { TopLevelToken } from '../tokens/toplevel-token'
import { TagImplOptions } from '../template/tag/tag-impl-options'
import { TagToken, Liquid, TopLevelToken, Tag } from '..'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
if (tagToken.args.search(/\n\s*[^#\s]/g) !== -1) {
throw new Error('every line of an inline comment must start with a \'#\' character')
}
}
} as TagImplOptions
render () { }
}
+18 -16
View File
@@ -1,29 +1,31 @@
import { assert, Tokenizer, Emitter, Hash, TagToken, TopLevelToken, Context, TagImplOptions } from '../types'
import BlockMode from '../context/block-mode'
import { parseFilePath, renderFilePath } from './render'
import { BlankDrop } from '../drop/blank-drop'
import { Scope, Template, Liquid, Tag, assert, Tokenizer, Emitter, Hash, TagToken, TopLevelToken, Context } from '..'
import { BlockMode } from '../context'
import { parseFilePath, renderFilePath, ParsedFileName } from './render'
import { BlankDrop } from '../drop'
export default {
parseFilePath,
renderFilePath,
parse: function (token: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
private hash: Hash
private tpls: Template[]
private file?: ParsedFileName
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
this['file'] = this.parseFilePath(tokenizer, this.liquid)
this.file = parseFilePath(tokenizer, this.liquid)
this['currentFile'] = token.file
this.hash = new Hash(tokenizer.remaining())
this.tpls = this.liquid.parser.parseTokens(remainTokens)
},
render: function * (ctx: Context, emitter: Emitter) {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
const { liquid, hash, file } = this
const { renderer } = liquid
if (file === null) {
if (file === undefined) {
ctx.setRegister('blockMode', BlockMode.OUTPUT)
yield renderer.renderTemplates(this.tpls, ctx, emitter)
return
}
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string
assert(filepath, () => `illegal filename "${filepath}"`)
const templates = yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile'])
const templates = (yield liquid._parseLayoutFile(filepath, ctx.sync, this['currentFile'])) as Template[]
// render remaining contents and store rendered results
ctx.setRegister('blockMode', BlockMode.STORE)
@@ -35,8 +37,8 @@ export default {
ctx.setRegister('blockMode', BlockMode.OUTPUT)
// render the layout file use stored blocks
ctx.push(yield hash.render(ctx))
ctx.push((yield hash.render(ctx)) as Scope)
yield renderer.renderTemplates(templates, ctx, emitter)
ctx.pop()
}
} as TagImplOptions
}
+8 -8
View File
@@ -1,14 +1,14 @@
import { Emitter } from '../emitters/emitter'
import { TagImplOptions, TagToken, Context } from '../types'
import { Tokenizer } from '../parser/tokenizer'
import { Template, Tokenizer, Emitter, Liquid, TopLevelToken, TagToken, Context, Tag } from '..'
export default {
parse: function (token: TagToken) {
export default class extends Tag {
private tpls: Template[]
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const tokenizer = new Tokenizer(token.args, this.liquid.options.operators)
const tokens = tokenizer.readLiquidTagTokens(this.liquid.options)
this.tpls = this.liquid.parser.parseTokens(tokens)
},
render: function * (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
yield this.liquid.renderer.renderTemplates(this.tpls, ctx, emitter)
}
} as TagImplOptions
}
+15 -18
View File
@@ -1,21 +1,18 @@
import { TagToken, TopLevelToken, TagImplOptions } from '../types'
import { Liquid, TagToken, TopLevelToken, Tag } from '..'
import { isTagToken } from '../util'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.tokens = []
const stream = this.liquid.parser.parseStream(remainTokens)
stream
.on('token', (token: TagToken) => {
if (token.name === 'endraw') stream.stop()
else this.tokens.push(token)
})
.on('end', () => {
throw new Error(`tag ${tagToken.getText()} not closed`)
})
stream.start()
},
render: function () {
export default class extends Tag {
private tokens: TopLevelToken[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
while (remainTokens.length) {
const token = remainTokens.shift()!
if (isTagToken(token) && token.name === 'endraw') return
this.tokens.push(token)
}
throw new Error(`tag ${tagToken.getText()} not closed`)
}
render () {
return this.tokens.map((token: TopLevelToken) => token.getText()).join('')
}
} as TagImplOptions
}
+24 -26
View File
@@ -1,18 +1,20 @@
import { __assign } from 'tslib'
import { assert } from '../util/assert'
import { ForloopDrop } from '../drop/forloop-drop'
import { toEnumerable } from '../util/collection'
import { Liquid } from '../liquid'
import { Token, Template, evalQuotedToken, TypeGuards, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, TagImplOptions } from '../types'
import { ForloopDrop } from '../drop'
import { toEnumerable } from '../util'
import { TopLevelToken, assert, Liquid, Token, Template, evalQuotedToken, TypeGuards, Tokenizer, _evalToken, Hash, Emitter, TagToken, Context, Tag } from '..'
export default {
parseFilePath,
renderFilePath,
parse: function (token: TagToken) {
export type ParsedFileName = Template[] | Token | string | undefined
export default class extends Tag {
private file: ParsedFileName
private currentFile?: string
private hash: Hash
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
const args = token.args
const tokenizer = new Tokenizer(args, this.liquid.options.operators)
this['file'] = this.parseFilePath(tokenizer, this.liquid)
this['currentFile'] = token.file
this.file = parseFilePath(tokenizer, this.liquid)
this.currentFile = token.file
while (!tokenizer.end()) {
tokenizer.skipBlank()
const begin = tokenizer.p
@@ -33,8 +35,7 @@ export default {
this[keyword.content] = { value, alias: alias && alias.content }
tokenizer.skipBlank()
if (tokenizer.peek() === ',') tokenizer.advance()
// matched!
continue
continue // matched!
}
}
}
@@ -45,10 +46,10 @@ export default {
break
}
this.hash = new Hash(tokenizer.remaining())
},
render: function * (ctx: Context, emitter: Emitter) {
}
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
const { liquid, hash } = this
const filepath = yield this.renderFilePath(this['file'], ctx, liquid)
const filepath = (yield renderFilePath(this['file'], ctx, liquid)) as string
assert(filepath, () => `illegal filename "${filepath}"`)
const childCtx = new Context({}, ctx.opts, { sync: ctx.sync, globals: ctx.globals, strictVariables: ctx.strictVariables })
@@ -61,23 +62,20 @@ export default {
if (this['for']) {
const { value, alias } = this['for']
let collection = yield _evalToken(value, ctx)
collection = toEnumerable(collection)
const collection = toEnumerable(yield _evalToken(value, ctx))
scope['forloop'] = new ForloopDrop(collection.length, value.getText(), alias)
for (const item of collection) {
scope[alias] = item
const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[]
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
scope['forloop'].next()
}
} else {
const templates = yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])
const templates = (yield liquid._parsePartialFile(filepath, childCtx.sync, this['currentFile'])) as Template[]
yield liquid.renderer.renderTemplates(templates, childCtx, emitter)
}
}
} as TagImplOptions
type ParsedFileName = Template[] | Token | string | undefined
}
/**
* @return null for "none",
@@ -85,11 +83,11 @@ type ParsedFileName = Template[] | Token | string | undefined
* @return Token for expression (not quoted)
* @throws TypeError if cannot read next token
*/
export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFileName | null {
export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFileName {
if (liquid.options.dynamicPartials) {
const file = tokenizer.readValue()
if (file === undefined) throw new TypeError(`illegal argument "${tokenizer.input}"`)
if (file.getText() === 'none') return null
if (file.getText() === 'none') return
if (TypeGuards.isQuotedToken(file)) {
// for filenames like "files/{{file}}", eval as liquid template
const templates = liquid.parse(evalQuotedToken(file))
@@ -99,7 +97,7 @@ export function parseFilePath (tokenizer: Tokenizer, liquid: Liquid): ParsedFile
}
const tokens = [...tokenizer.readFileNameTemplate(liquid.options)]
const templates = optimize(liquid.parser.parseTokens(tokens))
return templates === 'none' ? null : templates
return templates === 'none' ? undefined : templates
}
function optimize (templates: Template[]): string | Template[] {
+18 -10
View File
@@ -1,20 +1,28 @@
import { toEnumerable } from '../util/collection'
import { assert, _evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, TagImplOptions, ParseStream } from '../types'
import { ValueToken, Liquid, Tag, _evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
import { TablerowloopDrop } from '../drop/tablerowloop-drop'
import { Tokenizer } from '../parser/tokenizer'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
export default class extends Tag {
private variable: string
private hash: Hash
private templates: Template[]
private collection: ValueToken
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
const tokenizer = new Tokenizer(tagToken.args, this.liquid.options.operators)
const variable = tokenizer.readIdentifier()
tokenizer.skipBlank()
const tmp = tokenizer.readIdentifier()
assert(tmp && tmp.content === 'in', () => `illegal tag: ${tagToken.getText()}`)
const predicate = tokenizer.readIdentifier()
const collectionToken = tokenizer.readValue()
if (predicate.content !== 'in' || !collectionToken) {
throw new Error(`illegal tag: ${tagToken.getText()}`)
}
this.variable = variable.content
this.collection = tokenizer.readValue()
this.collection = collectionToken
this.hash = new Hash(tokenizer.remaining())
this.templates = []
@@ -28,11 +36,11 @@ export default {
})
stream.start()
},
}
render: function * (ctx: Context, emitter: Emitter) {
* render (ctx: Context, emitter: Emitter): Generator<unknown, void, unknown> {
let collection = toEnumerable(yield _evalToken(this.collection, ctx))
const hash = yield this.hash.render(ctx)
const hash = (yield this.hash.render(ctx)) as Record<string, any>
const offset = hash.offset || 0
const limit = (hash.limit === undefined) ? collection.length : hash.limit
@@ -57,4 +65,4 @@ export default {
if (collection.length) emitter.write('</tr>')
ctx.pop()
}
} as TagImplOptions
}
+9 -8
View File
@@ -1,9 +1,10 @@
import { Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, Context, TagImplOptions, TagToken } from '../types'
import { Liquid, Tag, Value, TopLevelToken, Template, Emitter, isTruthy, isFalsy, Context, TagToken } from '..'
export default {
parse: function (tagToken: TagToken, remainTokens: TopLevelToken[]) {
this.branches = []
this.elseTemplates = []
export default class extends Tag {
private branches: { predicate: Value, test: (val: any, ctx: Context) => boolean, templates: Template[] }[] = []
private elseTemplates: Template[] = []
constructor (tagToken: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(tagToken, remainTokens, liquid)
let p
this.liquid.parser.parseStream(remainTokens)
.on('start', () => this.branches.push({
@@ -21,9 +22,9 @@ export default {
.on('template', (tpl: Template) => p.push(tpl))
.on('end', () => { throw new Error(`tag ${tagToken.getText()} not closed`) })
.start()
},
}
render: function * (ctx: Context, emitter: Emitter) {
* render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, unknown> {
const r = this.liquid.renderer
for (const { predicate, test, templates } of this.branches) {
@@ -36,4 +37,4 @@ export default {
yield r.renderTemplates(this.elseTemplates, ctx, emitter)
}
} as TagImplOptions
}
+11
View File
@@ -0,0 +1,11 @@
import type { Context } from '../context'
import type { Liquid } from '../liquid'
export interface FilterImpl {
context: Context;
liquid: Liquid;
}
export interface FilterImplOptions {
(this: FilterImpl, value: any, ...args: any[]): any;
}
@@ -1,9 +1,9 @@
import { _evalToken } from '../../render/expression'
import { Context } from '../../context/context'
import { identify } from '../../util/underscore'
import { _evalToken } from '../render'
import { Context } from '../context'
import { identify } from '../util/underscore'
import { FilterImplOptions } from './filter-impl-options'
import { FilterArg, isKeyValuePair } from '../../parser/filter-arg'
import { Liquid } from '../../liquid'
import { FilterArg, isKeyValuePair } from '../parser/filter-arg'
import { Liquid } from '../liquid'
export class Filter {
public name: string
@@ -11,7 +11,7 @@ export class Filter {
private impl: FilterImplOptions
private liquid: Liquid
public constructor (name: string, impl: FilterImplOptions, args: FilterArg[], liquid: Liquid) {
public constructor (name: string, impl: FilterImplOptions | undefined, args: FilterArg[], liquid: Liquid) {
this.name = name
this.impl = impl || identify
this.args = args
@@ -1,5 +0,0 @@
import { FilterImpl } from './filter-impl'
export interface FilterImplOptions {
(this: FilterImpl, value: any, ...args: any[]): any;
}
-7
View File
@@ -1,7 +0,0 @@
import { Context } from '../../context/context'
import { Liquid } from '../../liquid'
export interface FilterImpl {
context: Context;
liquid: Liquid;
}
-28
View File
@@ -1,28 +0,0 @@
import { FilterImplOptions } from './filter-impl-options'
import { Filter } from './filter'
import { FilterArg } from '../../parser/filter-arg'
import { assert } from '../../util/assert'
import { Liquid } from '../../liquid'
export class FilterMap {
private impls: {[key: string]: FilterImplOptions} = {}
constructor (
private readonly strictFilters: boolean,
private readonly liquid: Liquid
) {}
get (name: string) {
const impl = this.impls[name]
assert(impl || !this.strictFilters, () => `undefined filter: ${name}`)
return impl
}
set (name: string, impl: FilterImplOptions) {
this.impls[name] = impl
}
create (name: string, args: FilterArg[]) {
return new Filter(name, this.get(name), args, this.liquid)
}
}
@@ -1,10 +1,9 @@
import { _evalToken } from '../../render/expression'
import { Context } from '../../context/context'
import { Tokenizer } from '../../parser/tokenizer'
import { _evalToken } from '../render/expression'
import { Context } from '../context/context'
import { Tokenizer } from '../parser/tokenizer'
import { Token } from '../tokens/token'
export interface HashValue {
[key: string]: any;
}
type HashValueTokens = Record<string, Token | undefined>
/**
* Key-Value Pairs Representing Tag Arguments
@@ -15,14 +14,14 @@ export interface HashValue {
* hash['reversed'] === undefined
*/
export class Hash {
hash: HashValue = {}
hash: HashValueTokens = {}
constructor (markup: string, jekyllStyle?: boolean) {
const tokenizer = new Tokenizer(markup, {})
for (const hash of tokenizer.readHashes(jekyllStyle)) {
this.hash[hash.name.content] = hash.value
}
}
* render (ctx: Context): Generator<unknown, HashValue, unknown> {
* render (ctx: Context): Generator<unknown, Record<string, any>, unknown> {
const hash = {}
for (const key of Object.keys(this.hash)) {
hash[key] = this.hash[key] === undefined ? true : yield _evalToken(this.hash[key], ctx)
+4 -5
View File
@@ -1,8 +1,7 @@
import { TemplateImpl } from '../template/template-impl'
import { Template } from '../template/template'
import { HTMLToken } from '../tokens/html-token'
import { Context } from '../context/context'
import { Emitter } from '../emitters/emitter'
import { TemplateImpl, Template } from '../template'
import { HTMLToken } from '../tokens'
import { Context } from '../context'
import { Emitter } from '../emitters'
export class HTML extends TemplateImpl<HTMLToken> implements Template {
private str: string
+10
View File
@@ -0,0 +1,10 @@
export * from './template'
export * from './template-impl'
export * from './tag'
export * from './tag-options-adapter'
export * from './filter'
export * from './filter-impl-options'
export * from './hash'
export * from './value'
export * from './output'
export * from './html'
+2 -3
View File
@@ -1,11 +1,10 @@
import { Value } from './value'
import { TemplateImpl } from '../template/template-impl'
import { Template } from '../template/template'
import { Template, TemplateImpl } from '../template'
import { Context } from '../context/context'
import { Emitter } from '../emitters/emitter'
import { OutputToken } from '../tokens/output-token'
import { Liquid } from '../liquid'
import { Filter } from './filter/filter'
import { Filter } from './filter'
export class Output extends TemplateImpl<OutputToken> implements Template {
private value: Value
+27
View File
@@ -0,0 +1,27 @@
import { isFunction } from '../util'
import { Hash } from './hash'
import { Tag, TagClass, TagRenderReturn } from './tag'
import { TagToken, TopLevelToken } from '../tokens'
import { Emitter } from '../emitters'
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<string, any>) => TagRenderReturn;
}
export function createTagClass (options: TagImplOptions): TagClass {
return class extends Tag {
constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) {
super(token, tokens, liquid)
if (isFunction(options.parse)) {
options.parse.call(this, token, tokens)
}
}
* render (ctx: Context, emitter: Emitter): TagRenderReturn {
const hash = (yield new Hash(this.token.args).render(ctx)) as Record<string, any>
return yield options.render.call(this, ctx, emitter, hash)
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import { TemplateImpl } from './template-impl'
import type { Emitter } from '../emitters/emitter'
import type { Context } from '../context/context'
import type { TopLevelToken, TagToken } from '../tokens'
import type { Template } from './template'
import type { Liquid } from '../liquid'
export type TagRenderReturn = Generator<unknown, unknown, unknown> | Promise<unknown> | unknown
export abstract class Tag extends TemplateImpl<TagToken> implements Template {
public name: string
protected liquid: Liquid
public constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token)
this.name = token.name
this.liquid = liquid
}
public abstract render (ctx: Context, emitter: Emitter): TagRenderReturn;
}
export interface TagClass {
new(token: TagToken, tokens: TopLevelToken[], liquid: Liquid): Tag
}
-11
View File
@@ -1,11 +0,0 @@
import { Context } from '../../context/context'
import { TagToken } from '../../tokens/tag-token'
import { TopLevelToken } from '../../tokens/toplevel-token'
import { TagImpl } from './tag-impl'
import { HashValue } from '../../template/tag/hash'
import { Emitter } from '../../emitters/emitter'
export interface TagImplOptions {
parse?: (this: TagImpl, token: TagToken, remainingTokens: TopLevelToken[]) => void;
render: (this: TagImpl, ctx: Context, emitter: Emitter, hash: HashValue) => void | string | Promise<void | string> | Generator<unknown, void | string, unknown>;
}
-7
View File
@@ -1,7 +0,0 @@
import { Liquid } from '../../liquid'
import { TagImplOptions } from './tag-impl-options'
export interface TagImpl extends TagImplOptions {
liquid: Liquid;
[key: string]: any;
}
-16
View File
@@ -1,16 +0,0 @@
import { TagImplOptions } from './tag-impl-options'
import { assert } from '../../util/assert'
export class TagMap {
private impls: {[key: string]: TagImplOptions} = {}
get (name: string) {
const impl = this.impls[name]
assert(impl, () => `tag "${name}" not found`)
return impl
}
set (name: string, impl: TagImplOptions) {
this.impls[name] = impl
}
}
-29
View File
@@ -1,29 +0,0 @@
import { isFunction } from '../../util/underscore'
import { Liquid } from '../../liquid'
import { TemplateImpl } from '../../template/template-impl'
import { Emitter, Hash, Context, TagToken, Template, TopLevelToken } from '../../types'
import { TagImpl } from './tag-impl'
import { HashValue } from './hash'
export class Tag extends TemplateImpl<TagToken> implements Template {
public name: string
private impl: TagImpl
public constructor (token: TagToken, tokens: TopLevelToken[], liquid: Liquid) {
super(token)
this.name = token.name
const impl = liquid.tags.get(token.name)
this.impl = Object.create(impl)
this.impl.liquid = liquid
if (this.impl.parse) {
this.impl.parse(token, tokens)
}
}
public * render (ctx: Context, emitter: Emitter): Generator<unknown, unknown, HashValue | unknown> {
const hash = (yield new Hash(this.token.args).render(ctx)) as HashValue
const impl = this.impl
if (isFunction(impl.render)) return yield impl.render(ctx, emitter, hash)
}
}
+12 -6
View File
@@ -1,8 +1,9 @@
import { Expression } from '../render/expression'
import { Tokenizer } from '../parser/tokenizer'
import { Filter } from './filter/filter'
import { Context } from '../context/context'
import { Liquid } from '../liquid'
import { Filter } from './filter'
import { Expression } from '../render'
import { Tokenizer } from '../parser'
import { assert } from '../util'
import type { Liquid } from '../liquid'
import type { Context } from '../context'
export class Value {
public readonly filters: Filter[] = []
@@ -14,7 +15,7 @@ export class Value {
public constructor (str: string, liquid: Liquid) {
const tokenizer = new Tokenizer(str, liquid.options.operators)
this.initial = tokenizer.readExpression()
this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, liquid.filters.get(name), args, liquid))
this.filters = tokenizer.readFilters().map(({ name, args }) => new Filter(name, this.getFilter(liquid, name), args, liquid))
}
public * value (ctx: Context, lenient: boolean): Generator<unknown, unknown, unknown> {
lenient = lenient || (ctx.opts.lenientIf && this.filters.length > 0 && this.filters[0].name === 'default')
@@ -25,4 +26,9 @@ export class Value {
}
return val
}
private getFilter (liquid: Liquid, name: string) {
const impl = liquid.filters[name]
assert(impl || !liquid.options.strictFilters, () => `undefined filter: ${name}`)
return impl
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { TokenKind } from '../parser/token-kind'
import { last } from '../util/underscore'
import { TokenKind } from '../parser'
import { last } from '../util'
export abstract class DelimitedToken extends Token {
public trimLeft = false
+1 -1
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { FilterArg } from '../parser/filter-arg'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class FilterToken extends Token {
public constructor (
+1 -1
View File
@@ -1,7 +1,7 @@
import { Token } from './token'
import { ValueToken } from './value-token'
import { IdentifierToken } from './identifier-token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class HashToken extends Token {
constructor (
+1 -1
View File
@@ -1,5 +1,5 @@
import { Token } from './token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class HTMLToken extends Token {
trimLeft = 0
+2 -2
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { NUMBER, TYPES, SIGN } from '../util/character'
import { TokenKind } from '../parser/token-kind'
import { NUMBER, TYPES, SIGN } from '../util'
import { TokenKind } from '../parser'
export class IdentifierToken extends Token {
public content: string
+17
View File
@@ -0,0 +1,17 @@
export * from './top-level-token'
export * from './tag-token'
export * from './output-token'
export * from './html-token'
export * from './number-token'
export * from './identifier-token'
export * from './literal-token'
export * from './operator-token'
export * from './property-access-token'
export * from './filter-token'
export * from './hash-token'
export * from './quoted-token'
export * from './token'
export * from './range-token'
export * from './value-token'
export * from './liquid-tag-token'
export * from './delimited-token'
+2 -3
View File
@@ -1,8 +1,7 @@
import { DelimitedToken } from './delimited-token'
import { TokenizationError } from '../util/error'
import { TokenizationError } from '../util'
import { NormalizedFullOptions } from '../liquid-options'
import { TokenKind } from '../parser/token-kind'
import { Tokenizer } from '../parser/tokenizer'
import { Tokenizer, TokenKind } from '../parser'
export class LiquidTagToken extends DelimitedToken {
public name: string
+1 -1
View File
@@ -1,5 +1,5 @@
import { Token } from './token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class LiteralToken extends Token {
public literal: string
+1 -1
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { IdentifierToken } from './identifier-token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class NumberToken extends Token {
constructor (
+1 -1
View File
@@ -1,5 +1,5 @@
import { Token } from './token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export const precedence = {
'==': 1,
+1 -1
View File
@@ -1,6 +1,6 @@
import { DelimitedToken } from './delimited-token'
import { NormalizedFullOptions } from '../liquid-options'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class OutputToken extends DelimitedToken {
public constructor (
+1 -2
View File
@@ -1,8 +1,7 @@
import { Token } from './token'
import { IdentifierToken } from './identifier-token'
import { QuotedToken } from './quoted-token'
import { TokenKind } from '../parser/token-kind'
import { parseStringLiteral } from '../parser/parse-string-literal'
import { TokenKind, parseStringLiteral } from '../parser'
export class PropertyAccessToken extends Token {
public propertyName: string
+1 -1
View File
@@ -1,5 +1,5 @@
import { Token } from './token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class QuotedToken extends Token {
constructor (
+1 -1
View File
@@ -1,6 +1,6 @@
import { Token } from './token'
import { ValueToken } from './value-token'
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export class RangeToken extends Token {
constructor (
+2 -3
View File
@@ -1,8 +1,7 @@
import { DelimitedToken } from './delimited-token'
import { TokenizationError } from '../util/error'
import { NormalizedFullOptions } from '../liquid-options'
import { TokenKind } from '../parser/token-kind'
import { Tokenizer } from '../parser/tokenizer'
import { Tokenizer, TokenKind } from '../parser'
import type { NormalizedFullOptions } from '../liquid-options'
export class TagToken extends DelimitedToken {
public name: string
+1 -1
View File
@@ -1,4 +1,4 @@
import { TokenKind } from '../parser/token-kind'
import { TokenKind } from '../parser'
export abstract class Token {
public constructor (
+5
View File
@@ -0,0 +1,5 @@
import type { TagToken } from './tag-token'
import type { HTMLToken } from './html-token'
import type { OutputToken } from './output-token'
export type TopLevelToken = TagToken | OutputToken | HTMLToken
-5
View File
@@ -1,5 +0,0 @@
import { TagToken } from './tag-token'
import { HTMLToken } from './html-token'
import { OutputToken } from './output-token'
export type TopLevelToken = TagToken | OutputToken | HTMLToken
-32
View File
@@ -1,32 +0,0 @@
/* istanbul ignore file */
import * as TypeGuards from './util/type-guards'
export { TypeGuards }
export { ParseError, TokenizationError, AssertionError } from './util/error'
export { assert } from './util/assert'
export { Drop } from './drop/drop'
export { Emitter } from './emitters/emitter'
export { Expression } from './render/expression'
export { isFalsy, isTruthy } from './render/boolean'
export { TagToken } from './tokens/tag-token'
export { Context } from './context/context'
export { Template } from './template/template'
export { FilterImplOptions } from './template/filter/filter-impl-options'
export { TagImplOptions } from './template/tag/tag-impl-options'
export { TagImpl } from './template/tag/tag-impl'
export { ParseStream } from './parser/parse-stream'
export { Token } from './tokens/token'
export { TokenKind } from './parser/token-kind'
export { TopLevelToken } from './tokens/toplevel-token'
export { Tokenizer } from './parser/tokenizer'
export { Hash } from './template/tag/hash'
export { Value } from './template/value'
// eslint-disable-next-line deprecation/deprecation
export { _evalToken, evalToken, evalQuotedToken } from './render/expression'
export { toPromise, toValueSync } from './util/async'
export { defaultOperators, Operators } from './render/operator'
export { createTrie, Trie } from './util/operator-trie'
export { toValue } from './util/underscore'
export { TimezoneDate } from './util/timezone-date'
export { filters } from './filters'
export { tags } from './tags'
export { defaultOptions } from './liquid-options'
+3 -3
View File
@@ -1,11 +1,11 @@
import { isNil, isString, isObject, isArray, isIterable, toValue } from './underscore'
export function toEnumerable (val: any) {
export function toEnumerable<T = unknown> (val: any): T[] {
val = toValue(val)
if (isArray(val)) return val
if (isString(val) && val.length > 0) return [val]
if (isString(val) && val.length > 0) return [val] as unknown as T[]
if (isIterable(val)) return Array.from(val)
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]])
if (isObject(val)) return Object.keys(val).map((key) => [key, val[key]]) as unknown as T[]
return []
}

Some files were not shown because too many files have changed in this diff Show More