mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-19 14:30:38 -07:00
refactor: src/scope.js
This commit is contained in:
+54
-86
@@ -1,38 +1,50 @@
|
|||||||
|
'use strict'
|
||||||
const _ = require('./util/underscore.js')
|
const _ = require('./util/underscore.js')
|
||||||
const lexical = require('./lexical.js')
|
const lexical = require('./lexical.js')
|
||||||
const assert = require('./util/assert.js')
|
const assert = require('./util/assert.js')
|
||||||
|
|
||||||
var Scope = {
|
var Scope = {
|
||||||
getAll: function () {
|
getAll: function () {
|
||||||
var ctx = {}
|
return this.scopes.reduce((ctx, val) => Object.assign(ctx, val), Object.create(null))
|
||||||
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
|
||||||
_.assign(ctx, this.scopes[i])
|
|
||||||
}
|
|
||||||
return ctx
|
|
||||||
},
|
},
|
||||||
get: function (str) {
|
get: function (path) {
|
||||||
try {
|
let paths = this.propertyAccessSeq(path)
|
||||||
return this.getPropertyByPath(this.scopes, str)
|
let scope = this.findScopeFor(paths[0])
|
||||||
} catch (e) {
|
return paths.reduce((value, key) => this.readProperty(value, key), scope)
|
||||||
if (!/undefined variable/.test(e.message) || this.opts.strict_variables) {
|
},
|
||||||
throw e
|
set: function (path, v) {
|
||||||
|
let paths = this.propertyAccessSeq(path)
|
||||||
|
let scope = this.findScopeFor(paths[0])
|
||||||
|
paths.some((key, i) => {
|
||||||
|
if (!_.isObject(scope)) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
if (i === paths.length - 1) {
|
||||||
},
|
scope[key] = v
|
||||||
set: function (k, v) {
|
return true
|
||||||
var scope = this.findScopeFor(k)
|
}
|
||||||
setPropertyByPath(scope, k, v)
|
if (undefined === scope[key]) {
|
||||||
return this
|
scope[key] = {}
|
||||||
|
}
|
||||||
|
scope = scope[key]
|
||||||
|
})
|
||||||
},
|
},
|
||||||
push: function (ctx) {
|
push: function (ctx) {
|
||||||
assert(ctx, `trying to push ${ctx} into scopes`)
|
assert(ctx, `trying to push ${ctx} into scopes`)
|
||||||
return this.scopes.push(ctx)
|
this.scopes.push(ctx)
|
||||||
},
|
},
|
||||||
pop: function () {
|
pop: function (ctx) {
|
||||||
return this.scopes.pop()
|
if (!arguments.length) {
|
||||||
|
return this.scopes.pop()
|
||||||
|
}
|
||||||
|
let i = this.scopes.findIndex(scope => scope === ctx)
|
||||||
|
if (i === -1) {
|
||||||
|
throw new TypeError('scope not found, cannot pop')
|
||||||
|
}
|
||||||
|
return this.scopes.splice(i, 1)[0]
|
||||||
},
|
},
|
||||||
findScopeFor: function (key) {
|
findScopeFor: function (key) {
|
||||||
var i = this.scopes.length - 1
|
let i = this.scopes.length - 1
|
||||||
while (i >= 0 && !(key in this.scopes[i])) {
|
while (i >= 0 && !(key in this.scopes[i])) {
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
@@ -41,32 +53,19 @@ var Scope = {
|
|||||||
}
|
}
|
||||||
return this.scopes[i]
|
return this.scopes[i]
|
||||||
},
|
},
|
||||||
unshift: function (ctx) {
|
readProperty: function (obj, key) {
|
||||||
assert(ctx, `trying to push ${ctx} into scopes`)
|
let val
|
||||||
return this.scopes.unshift(ctx)
|
if (key === 'size' && (_.isArray(obj) || _.isString(obj))) {
|
||||||
},
|
val = obj.length
|
||||||
shift: function () {
|
} else if (_.isNil(obj)) {
|
||||||
return this.scopes.shift()
|
val = undefined
|
||||||
},
|
} else {
|
||||||
|
val = obj[key]
|
||||||
getPropertyByPath: function (scopes, path) {
|
|
||||||
var paths = this.propertyAccessSeq(path + '')
|
|
||||||
if (!paths.length) {
|
|
||||||
throw new TypeError('undefined variable: ' + path)
|
|
||||||
}
|
}
|
||||||
var key = paths.shift()
|
if (_.isNil(val) && this.opts.strict_variables) {
|
||||||
var value = getValueFromScopes(key, scopes)
|
throw new TypeError(`undefined variable: ${key}`)
|
||||||
if (_.isNil(value)) {
|
|
||||||
throw new TypeError('undefined variable: ' + key)
|
|
||||||
}
|
}
|
||||||
while (paths.length) {
|
return val
|
||||||
key = paths.shift()
|
|
||||||
value = getValueFromParent(key, value)
|
|
||||||
if (_.isNil(value)) {
|
|
||||||
throw new TypeError('undefined variable: ' + key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -78,16 +77,17 @@ var Scope = {
|
|||||||
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
* accessSeq("foo[bar.coo]") // ['foo', 'bar'], for bar.coo == 'bar'
|
||||||
*/
|
*/
|
||||||
propertyAccessSeq: function (str) {
|
propertyAccessSeq: function (str) {
|
||||||
var seq = []
|
str = String(str)
|
||||||
var name = ''
|
let seq = []
|
||||||
var j
|
let name = ''
|
||||||
var i = 0
|
let j
|
||||||
|
let i = 0
|
||||||
while (i < str.length) {
|
while (i < str.length) {
|
||||||
switch (str[i]) {
|
switch (str[i]) {
|
||||||
case '[':
|
case '[':
|
||||||
push()
|
push()
|
||||||
|
|
||||||
var delemiter = str[i + 1]
|
let delemiter = str[i + 1]
|
||||||
if (/['"]/.test(delemiter)) { // foo["bar"]
|
if (/['"]/.test(delemiter)) { // foo["bar"]
|
||||||
j = str.indexOf(delemiter, i + 2)
|
j = str.indexOf(delemiter, i + 2)
|
||||||
assert(j !== -1, `unbalanced ${delemiter}: ${str}`)
|
assert(j !== -1, `unbalanced ${delemiter}: ${str}`)
|
||||||
@@ -115,6 +115,10 @@ var Scope = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
push()
|
push()
|
||||||
|
|
||||||
|
if (!seq.length) {
|
||||||
|
throw new TypeError(`invalid path:"${str}"`)
|
||||||
|
}
|
||||||
return seq
|
return seq
|
||||||
|
|
||||||
function push () {
|
function push () {
|
||||||
@@ -124,42 +128,6 @@ var Scope = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPropertyByPath (obj, path, val) {
|
|
||||||
var paths = (path + '').replace(/\[/g, '.').replace(/\]/g, '').split('.')
|
|
||||||
for (var i = 0; i < paths.length; i++) {
|
|
||||||
var key = paths[i]
|
|
||||||
if (!_.isObject(obj)) {
|
|
||||||
// cannot set property of non-object
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// for end point
|
|
||||||
if (i === paths.length - 1) {
|
|
||||||
return (obj[key] = val)
|
|
||||||
}
|
|
||||||
// if path not exist
|
|
||||||
if (undefined === obj[key]) {
|
|
||||||
obj[key] = {}
|
|
||||||
}
|
|
||||||
obj = obj[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValueFromParent (key, value) {
|
|
||||||
return (key === 'size' && (_.isArray(value) || _.isString(value)))
|
|
||||||
? value.length
|
|
||||||
: value[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getValueFromScopes (key, scopes) {
|
|
||||||
for (var i = scopes.length - 1; i > -1; i--) {
|
|
||||||
var scope = scopes[i]
|
|
||||||
if (scope.hasOwnProperty(key)) {
|
|
||||||
return scope[key]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new TypeError('undefined variable: ' + key)
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchRightBracket (str, begin) {
|
function matchRightBracket (str, begin) {
|
||||||
var stack = 1 // count of '[' - count of ']'
|
var stack = 1 // count of '[' - count of ']'
|
||||||
for (var i = begin; i < str.length; i++) {
|
for (var i = begin; i < str.length; i++) {
|
||||||
|
|||||||
+1
-10
@@ -57,19 +57,10 @@ function forOwn (object, iteratee) {
|
|||||||
function assign (object) {
|
function assign (object) {
|
||||||
object = isObject(object) ? object : {}
|
object = isObject(object) ? object : {}
|
||||||
var srcs = Array.prototype.slice.call(arguments, 1)
|
var srcs = Array.prototype.slice.call(arguments, 1)
|
||||||
srcs.forEach(function (src) {
|
srcs.forEach((src) => Object.assign(object, src))
|
||||||
_assignBinary(object, src)
|
|
||||||
})
|
|
||||||
return object
|
return object
|
||||||
}
|
}
|
||||||
|
|
||||||
function _assignBinary (dst, src) {
|
|
||||||
forOwn(src, function (v, k) {
|
|
||||||
dst[k] = v
|
|
||||||
})
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
function last (arr) {
|
function last (arr) {
|
||||||
return arr[arr.length - 1]
|
return arr[arr.length - 1]
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -84,7 +84,7 @@ module.exports = function (liquid) {
|
|||||||
}
|
}
|
||||||
throw e
|
throw e
|
||||||
})
|
})
|
||||||
.then(() => scope.pop())
|
.then(() => scope.pop(context))
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
if (e instanceof RenderBreakError && e.message === 'break') {
|
if (e instanceof RenderBreakError && e.message === 'break') {
|
||||||
return
|
return
|
||||||
|
|||||||
+31
-31
@@ -59,10 +59,16 @@ describe('scope', function () {
|
|||||||
}
|
}
|
||||||
expect(fn).to.not.throw()
|
expect(fn).to.not.throw()
|
||||||
expect(scope.get('notdefined')).to.equal(undefined)
|
expect(scope.get('notdefined')).to.equal(undefined)
|
||||||
expect(scope.get('')).to.equal(undefined)
|
|
||||||
expect(scope.get(false)).to.equal(undefined)
|
expect(scope.get(false)).to.equal(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw for invalid path', function () {
|
||||||
|
function fn () {
|
||||||
|
scope.get('')
|
||||||
|
}
|
||||||
|
expect(fn).to.throw('invalid path:""')
|
||||||
|
})
|
||||||
|
|
||||||
it('should throw when [] unbalanced', function () {
|
it('should throw when [] unbalanced', function () {
|
||||||
expect(function () {
|
expect(function () {
|
||||||
scope.get('foo[bar')
|
scope.get('foo[bar')
|
||||||
@@ -116,10 +122,9 @@ describe('scope', function () {
|
|||||||
expect(scope.get('posts[category.diary[0]].name'), 'A Nice Day')
|
expect(scope.get('posts[category.diary[0]].name'), 'A Nice Day')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create in parent scope if needed', function () {
|
it('should create parent if needed', function () {
|
||||||
scope.push({})
|
scope.set('a.b.c.d', 'COO')
|
||||||
scope.set('bar.coo', 'COO')
|
expect(scope.get('a.b.c.d')).to.equal('COO')
|
||||||
expect(scope.get('bar.coo')).to.equal('COO')
|
|
||||||
})
|
})
|
||||||
it('should keep other properties of parent', function () {
|
it('should keep other properties of parent', function () {
|
||||||
scope.push({obj: {foo: 'FOO'}})
|
scope.push({obj: {foo: 'FOO'}})
|
||||||
@@ -151,7 +156,8 @@ describe('scope', function () {
|
|||||||
}
|
}
|
||||||
expect(fn).to.throw(/undefined variable: notdefined/)
|
expect(fn).to.throw(/undefined variable: notdefined/)
|
||||||
})
|
})
|
||||||
it('should throw when parent not defined', function () {
|
it('should throw when deep variable not exist', function () {
|
||||||
|
scope.set('foo', 'FOO')
|
||||||
function fn () {
|
function fn () {
|
||||||
scope.get('foo.bar.not.defined')
|
scope.get('foo.bar.not.defined')
|
||||||
}
|
}
|
||||||
@@ -209,31 +215,25 @@ describe('scope', function () {
|
|||||||
expect(scope.get('foo')).to.equal('zoo')
|
expect(scope.get('foo')).to.equal('zoo')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
it('should pop specified scope', function () {
|
||||||
describe('.unshift()', function () {
|
let scope1 = {
|
||||||
it('should throw when trying to unshift non-object', function () {
|
foo: 'foo'
|
||||||
expect(function () {
|
}
|
||||||
scope.unshift(false)
|
let scope2 = {
|
||||||
}).to.throw()
|
bar: 'bar'
|
||||||
})
|
}
|
||||||
it('should unshift scope', function () {
|
scope.push(scope1)
|
||||||
scope.unshift({
|
scope.push(scope2)
|
||||||
foo: 'blue',
|
expect(scope.get('foo')).to.equal('foo')
|
||||||
foo1: 'foo1'
|
expect(scope.get('bar')).to.equal('bar')
|
||||||
})
|
scope.pop(scope1)
|
||||||
expect(scope.get('foo')).to.equal('zoo')
|
expect(scope.get('foo')).to.equal('zoo')
|
||||||
expect(scope.get('foo1')).to.equal('foo1')
|
expect(scope.get('bar')).to.equal('bar')
|
||||||
})
|
|
||||||
})
|
})
|
||||||
describe('.shift()', function () {
|
it('should throw when specified scope not found', function () {
|
||||||
it('should shift scope', function () {
|
let scope1 = {
|
||||||
scope.unshift({
|
foo: 'foo'
|
||||||
foo: 'blue',
|
}
|
||||||
foo1: 'foo1'
|
expect(() => scope.pop(scope1)).to.throw('scope not found, cannot pop')
|
||||||
})
|
|
||||||
scope.shift()
|
|
||||||
expect(scope.get('foo')).to.equal('zoo')
|
|
||||||
expect(scope.get('foo1')).to.equal(undefined)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const Liquid = require('..')
|
|||||||
const sinon = require('sinon')
|
const sinon = require('sinon')
|
||||||
const chai = require('chai')
|
const chai = require('chai')
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
|
chai.use(require('chai-as-promised'))
|
||||||
|
|
||||||
describe('xhr', () => {
|
describe('xhr', () => {
|
||||||
if (process.version.match(/^v(\d+)/)[1] < 8) {
|
if (process.version.match(/^v(\d+)/)[1] < 8) {
|
||||||
|
|||||||
Reference in New Issue
Block a user