mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-17 21:40:39 -07:00
chore(TypeScript): refactor objects into classes
fix: `Nil`(null, undefined) now renders as empty string change: `parser.parseValue()` renamed to `parser.parseOutput` change: registered tags/filters become static and shared across different liquid instances
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import assert from 'src/util/assert'
|
||||
import * as lexical from 'src/parser/lexical'
|
||||
import { evalValue } from 'src/render/syntax'
|
||||
import Scope from 'src/scope/scope'
|
||||
|
||||
type impl = (value: any, ...args: any[]) => any
|
||||
|
||||
const valueRE = new RegExp(`${lexical.value.source}`, 'g')
|
||||
|
||||
export default class Filter {
|
||||
name: string
|
||||
impl: impl
|
||||
args: string[]
|
||||
private static impls: {[key: string]: impl} = {}
|
||||
|
||||
constructor (str: string, strict_filters: boolean = false) {
|
||||
let match = lexical.filterLine.exec(str)
|
||||
assert(match, 'illegal filter: ' + str)
|
||||
|
||||
const name = match[1]
|
||||
const argList = match[2] || ''
|
||||
const impl = Filter.impls[name]
|
||||
if (!impl && strict_filters) throw new TypeError(`undefined filter: ${name}`)
|
||||
|
||||
this.name = name
|
||||
this.impl = impl || (x => x)
|
||||
this.args = this.parseArgs(argList)
|
||||
}
|
||||
parseArgs (argList: string): string[] {
|
||||
let match, args = []
|
||||
while ((match = valueRE.exec(argList.trim()))) {
|
||||
const v = match[0]
|
||||
const re = new RegExp(`${v}\\s*:`, 'g')
|
||||
const keyMatch = re.exec(match.input)
|
||||
const currentMatchIsKey = keyMatch && keyMatch.index === match.index
|
||||
currentMatchIsKey ? args.push(`'${v}'`) : args.push(v)
|
||||
}
|
||||
return args
|
||||
}
|
||||
render (value: any, scope: Scope): any {
|
||||
const args = this.args.map(arg => evalValue(arg, scope))
|
||||
args.unshift(value)
|
||||
return this.impl.apply(null, args)
|
||||
}
|
||||
static register(name, filter) {
|
||||
Filter.impls[name] = filter
|
||||
}
|
||||
static clear () {
|
||||
Filter.impls = {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import Template from 'src/template/template'
|
||||
import Scope from 'src/scope/scope'
|
||||
import ITemplate from 'src/template/itemplate'
|
||||
import Token from 'src/parser/token'
|
||||
|
||||
export default class extends Template implements ITemplate {
|
||||
str: string
|
||||
constructor(token: Token) {
|
||||
super(token)
|
||||
this.str = token.value
|
||||
}
|
||||
async render(scope: Scope): Promise<string> {
|
||||
return this.str
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Scope from 'src/scope/scope'
|
||||
import Token from 'src/parser/token'
|
||||
|
||||
export default interface ITemplate {
|
||||
token: Token;
|
||||
render(scope: Scope): Promise<string>;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import Value from './value'
|
||||
import { stringify } from 'src/util/underscore'
|
||||
import Template from 'src/template/template'
|
||||
import ITemplate from 'src/template/itemplate'
|
||||
import Scope from 'src/scope/scope'
|
||||
|
||||
export default class Output extends Template implements ITemplate {
|
||||
value: Value
|
||||
constructor(token, strict_filters?) {
|
||||
super(token)
|
||||
this.value = new Value(token.value, strict_filters)
|
||||
}
|
||||
async render(scope: Scope) {
|
||||
const html = await this.value.value(scope)
|
||||
return stringify(html)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { hashCapture } from 'src/parser/lexical'
|
||||
import { evalValue } from 'src/render/syntax'
|
||||
|
||||
/**
|
||||
* Key-Value Pairs Representing Tag Arguments
|
||||
* Example:
|
||||
* For the markup `{% include 'head.html' foo='bar' %}`,
|
||||
* hash['foo'] === 'bar'
|
||||
*/
|
||||
export default class Hash {
|
||||
constructor(markup, scope) {
|
||||
let match
|
||||
hashCapture.lastIndex = 0
|
||||
while ((match = hashCapture.exec(markup))) {
|
||||
const k = match[1]
|
||||
const v = match[2]
|
||||
this[k] = evalValue(v, scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { create } from 'src/util/underscore'
|
||||
import { stringify } from 'src/util/underscore'
|
||||
import assert from 'src/util/assert'
|
||||
import TagImpl from './tagimpl'
|
||||
import Hash from './hash'
|
||||
import Template from 'src/template/template'
|
||||
import ITemplate from 'src/template/itemplate'
|
||||
import TagToken from 'src/parser/tag-token'
|
||||
|
||||
export default class Tag extends Template implements ITemplate {
|
||||
name: string
|
||||
token: TagToken
|
||||
private impl: TagImpl
|
||||
static impls: object = {}
|
||||
|
||||
constructor (token, tokens, liquid) {
|
||||
super(token)
|
||||
this.name = token.name
|
||||
|
||||
const impl = Tag.impls[token.name]
|
||||
assert(impl, `tag ${token.name} not found`)
|
||||
this.impl = create(impl)
|
||||
this.impl.liquid = liquid
|
||||
if (this.impl.parse) {
|
||||
this.impl.parse(token, tokens)
|
||||
}
|
||||
}
|
||||
async render (scope) {
|
||||
const hash = new Hash(this.token.args, scope)
|
||||
const impl = this.impl
|
||||
if (typeof impl.render !== 'function') {
|
||||
return ''
|
||||
}
|
||||
const html = await impl.render(scope, hash)
|
||||
return stringify(html)
|
||||
}
|
||||
static register (name, tag) {
|
||||
Tag.impls[name] = tag
|
||||
}
|
||||
static clear () {
|
||||
Tag.impls = {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import Liquid from 'src/liquid'
|
||||
import Scope from 'src/scope/scope'
|
||||
|
||||
export default interface TagImpl {
|
||||
liquid: Liquid
|
||||
parse: (token: any, remainingTokens: Array<any>) => void
|
||||
render: (scope: Scope, hash: any) => Promise<string>
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import Token from 'src/parser/token'
|
||||
|
||||
export default class Template {
|
||||
token: Token;
|
||||
constructor(token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { evalExp } from 'src/render/syntax'
|
||||
import * as lexical from 'src/parser/lexical'
|
||||
import assert from 'src/util/assert'
|
||||
import Filter from './filter'
|
||||
import Scope from 'src/scope/scope'
|
||||
|
||||
export default class {
|
||||
initial: any
|
||||
filters: Array<any>
|
||||
constructor(str: string, strict_filters?: boolean) {
|
||||
let match = lexical.matchValue(str)
|
||||
assert(match, `illegal value string: ${str}`)
|
||||
|
||||
const initial = match[0]
|
||||
str = str.substr(match.index + match[0].length)
|
||||
|
||||
const filters = []
|
||||
while ((match = lexical.filter.exec(str))) {
|
||||
filters.push([match[0].trim()])
|
||||
}
|
||||
|
||||
this.initial = initial
|
||||
this.filters = filters.map(str => new Filter(str, strict_filters))
|
||||
}
|
||||
value(scope: Scope) {
|
||||
return this.filters.reduce(
|
||||
(prev, filter) => filter.render(prev, scope),
|
||||
evalExp(this.initial, scope))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user