mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-17 13:20:41 -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,179 @@
|
||||
import * as _ from '../util/underscore'
|
||||
import * as lexical from '../parser/lexical'
|
||||
import assert from '../util/assert'
|
||||
import { LiquidOptions, defaultOptions } from '../liquid-options'
|
||||
import BlockMode from './block-mode'
|
||||
|
||||
export default class Scope {
|
||||
opts: LiquidOptions
|
||||
contexts: Array<object>
|
||||
blocks: object = {}
|
||||
blockMode: BlockMode = BlockMode.OUTPUT
|
||||
constructor (ctx: object = {}, opts: LiquidOptions = defaultOptions) {
|
||||
this.opts = _.assign({
|
||||
dynamicPartials: true,
|
||||
strict_variables: false,
|
||||
strict_filters: false,
|
||||
root: []
|
||||
} , opts)
|
||||
this.contexts = [ctx || {}]
|
||||
}
|
||||
getAll () {
|
||||
return this.contexts.reduce((ctx, val) => _.assign(ctx, val), _.create(null))
|
||||
}
|
||||
get (path: string): any {
|
||||
const paths = this.propertyAccessSeq(path)
|
||||
const scope = this.findContextFor(paths[0]) || _.last(this.contexts)
|
||||
return paths.reduce((value, key) => this.readProperty(value, key), scope)
|
||||
}
|
||||
set (path: string, v: any): void {
|
||||
const paths = this.propertyAccessSeq(path)
|
||||
let scope = this.findContextFor(paths[0]) || _.last(this.contexts)
|
||||
paths.some((key, i) => {
|
||||
if (!_.isObject(scope)) {
|
||||
return true
|
||||
}
|
||||
if (i === paths.length - 1) {
|
||||
scope[key] = v
|
||||
return true
|
||||
}
|
||||
if (undefined === scope[key]) {
|
||||
scope[key] = {}
|
||||
}
|
||||
scope = scope[key]
|
||||
})
|
||||
}
|
||||
unshift (ctx: object): any {
|
||||
return this.contexts.unshift(ctx)
|
||||
}
|
||||
push (ctx: object): any {
|
||||
return this.contexts.push(ctx)
|
||||
}
|
||||
pop (ctx?: object): object {
|
||||
if (!arguments.length) {
|
||||
return this.contexts.pop()
|
||||
}
|
||||
const i = this.contexts.findIndex(scope => scope === ctx)
|
||||
if (i === -1) {
|
||||
throw new TypeError('scope not found, cannot pop')
|
||||
}
|
||||
return this.contexts.splice(i, 1)[0]
|
||||
}
|
||||
findContextFor (key: string, filter = (arg => true)) {
|
||||
for (let i = this.contexts.length - 1; i >= 0; i--) {
|
||||
const candidate = this.contexts[i]
|
||||
if (!filter(candidate)) continue
|
||||
if (key in candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
readProperty (obj, key) {
|
||||
let val
|
||||
if (_.isNil(obj)) {
|
||||
val = undefined
|
||||
} else {
|
||||
obj = toLiquid(obj)
|
||||
val = key === 'size' ? readSize(obj) : obj[key]
|
||||
if (_.isFunction(obj.liquid_method_missing)) {
|
||||
val = obj.liquid_method_missing(key)
|
||||
}
|
||||
}
|
||||
if (_.isNil(val) && this.opts.strict_variables) {
|
||||
throw new TypeError(`undefined variable: ${key}`)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse property access sequence from access string
|
||||
* @example
|
||||
* accessSeq("foo.bar") // ['foo', 'bar']
|
||||
* accessSeq("foo['bar']") // ['foo', 'bar']
|
||||
* accessSeq("foo['b]r']") // ['foo', 'b]r']
|
||||
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
||||
*/
|
||||
propertyAccessSeq (str) {
|
||||
str = String(str)
|
||||
const seq = []
|
||||
let name = ''
|
||||
let j
|
||||
let i = 0
|
||||
while (i < str.length) {
|
||||
switch (str[i]) {
|
||||
case '[':
|
||||
push()
|
||||
|
||||
const delemiter = str[i + 1]
|
||||
if (/['"]/.test(delemiter)) { // foo["bar"]
|
||||
j = str.indexOf(delemiter, i + 2)
|
||||
assert(j !== -1, `unbalanced ${delemiter}: ${str}`)
|
||||
name = str.slice(i + 2, j)
|
||||
push()
|
||||
i = j + 2
|
||||
} else { // foo[bar.coo]
|
||||
j = matchRightBracket(str, i + 1)
|
||||
assert(j !== -1, `unbalanced []: ${str}`)
|
||||
name = str.slice(i + 1, j)
|
||||
if (!lexical.isInteger(name)) { // foo[bar] vs. foo[1]
|
||||
name = String(this.get(name))
|
||||
}
|
||||
push()
|
||||
i = j + 1
|
||||
}
|
||||
break
|
||||
case '.':// foo.bar, foo[0].bar
|
||||
push()
|
||||
i++
|
||||
break
|
||||
default:// foo.bar
|
||||
name += str[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
push()
|
||||
|
||||
if (!seq.length) {
|
||||
throw new TypeError(`invalid path:"${str}"`)
|
||||
}
|
||||
return seq
|
||||
|
||||
function push () {
|
||||
if (name.length) seq.push(name)
|
||||
name = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toLiquid (obj) {
|
||||
if (_.isFunction(obj.to_liquid)) {
|
||||
return obj.to_liquid()
|
||||
}
|
||||
if (_.isFunction(obj.toLiquid)) {
|
||||
return obj.toLiquid()
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function readSize (obj) {
|
||||
if (!_.isNil(obj.size)) return obj.size
|
||||
if (_.isArray(obj) || _.isString(obj)) return obj.length
|
||||
return obj.size
|
||||
}
|
||||
|
||||
function matchRightBracket (str, begin) {
|
||||
let stack = 1 // count of '[' - count of ']'
|
||||
for (let i = begin; i < str.length; i++) {
|
||||
if (str[i] === '[') {
|
||||
stack++
|
||||
}
|
||||
if (str[i] === ']') {
|
||||
stack--
|
||||
if (stack === 0) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
Reference in New Issue
Block a user