mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-17 21:40:39 -07:00
refactor: Tag class support in registerTag()
This commit is contained in:
@@ -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
@@ -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
@@ -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',
|
||||
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
export * from './cache'
|
||||
export * from './lru'
|
||||
Vendored
+1
-1
@@ -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,8 +1,6 @@
|
||||
enum BlockMode {
|
||||
export enum BlockMode {
|
||||
/* store rendered html into blocks */
|
||||
OUTPUT,
|
||||
/* output rendered html directly */
|
||||
STORE
|
||||
}
|
||||
|
||||
export default BlockMode
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './context'
|
||||
export * from './scope'
|
||||
export * from './block-mode'
|
||||
@@ -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
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import { isFunction } from '../util/underscore'
|
||||
import { isFunction } from '../util'
|
||||
|
||||
export interface Comparable {
|
||||
equals: (rhs: any) => boolean;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './emitter'
|
||||
export * from './simple-emitter'
|
||||
export * from './streamed-emitter'
|
||||
export * from './keeping-type-emitter'
|
||||
@@ -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,4 +1,4 @@
|
||||
import { stringify } from '../util/underscore'
|
||||
import { stringify } from '../util'
|
||||
import { Emitter } from './emitter'
|
||||
|
||||
export class SimpleEmitter implements Emitter {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { stringify } from '../util/underscore'
|
||||
import { stringify } from '../util'
|
||||
import { Emitter } from './emitter'
|
||||
import { PassThrough } from 'stream'
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
import { last } from '../util/underscore'
|
||||
import { last } from '../util'
|
||||
|
||||
function domResolve (root: string, path: string) {
|
||||
const base = document.createElement('base')
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './loader'
|
||||
export * from './fs'
|
||||
+1
-2
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './tokenizer'
|
||||
export * from './parser'
|
||||
export * from './parse-stream'
|
||||
export * from './parse-string-literal'
|
||||
export * from './token-kind'
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './render'
|
||||
export * from './expression'
|
||||
export * from './operator'
|
||||
export * from './boolean'
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 () {}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Context } from '../../context/context'
|
||||
import { Liquid } from '../../liquid'
|
||||
|
||||
export interface FilterImpl {
|
||||
context: Context;
|
||||
liquid: Liquid;
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Liquid } from '../../liquid'
|
||||
import { TagImplOptions } from './tag-impl-options'
|
||||
|
||||
export interface TagImpl extends TagImplOptions {
|
||||
liquid: Liquid;
|
||||
[key: string]: any;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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,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,5 +1,5 @@
|
||||
import { Token } from './token'
|
||||
import { TokenKind } from '../parser/token-kind'
|
||||
import { TokenKind } from '../parser'
|
||||
|
||||
export class HTMLToken extends Token {
|
||||
trimLeft = 0
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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,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,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,5 +1,5 @@
|
||||
import { Token } from './token'
|
||||
import { TokenKind } from '../parser/token-kind'
|
||||
import { TokenKind } from '../parser'
|
||||
|
||||
export const precedence = {
|
||||
'==': 1,
|
||||
|
||||
@@ -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,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,5 +1,5 @@
|
||||
import { Token } from './token'
|
||||
import { TokenKind } from '../parser/token-kind'
|
||||
import { TokenKind } from '../parser'
|
||||
|
||||
export class QuotedToken extends Token {
|
||||
constructor (
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
@@ -1,4 +1,4 @@
|
||||
import { TokenKind } from '../parser/token-kind'
|
||||
import { TokenKind } from '../parser'
|
||||
|
||||
export abstract class Token {
|
||||
public constructor (
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
Reference in New Issue
Block a user