fix: post-increment, pre-decrement working on #76

This commit is contained in:
harttle
2018-07-21 22:59:52 +08:00
parent b5cfd2d2c5
commit c3de3fbb19
12 changed files with 146 additions and 92 deletions
+12 -11
View File
@@ -1,15 +1,16 @@
'use strict'
const strftime = require('./src/util/strftime.js')
const _ = require('./src/util/underscore.js')
const isTruthy = require('./src/syntax.js').isTruthy
var escapeMap = {
let escapeMap = {
'&': '&',
'<': '&lt;',
'>': '&gt;',
'"': '&#34;',
"'": '&#39;'
}
var unescapeMap = {
let unescapeMap = {
'&amp;': '&',
'&lt;': '<',
'&gt;': '>',
@@ -17,14 +18,14 @@ var unescapeMap = {
'&#39;': "'"
}
var filters = {
let filters = {
'abs': v => Math.abs(v),
'append': (v, arg) => v + arg,
'capitalize': str => stringify(str).charAt(0).toUpperCase() + str.slice(1),
'ceil': v => Math.ceil(v),
'concat': (v, arg) => Array.prototype.concat.call(v, arg),
'date': (v, arg) => {
var date = v
let date = v
if (v === 'now') {
date = new Date()
} else if (_.isString(v)) {
@@ -41,7 +42,7 @@ var filters = {
'first': v => v[0],
'floor': v => Math.floor(v),
'join': (v, arg) => v.join(arg),
'last': v => v[v.length - 1],
'last': v => _.last(v),
'lstrip': v => stringify(v).replace(/^\s+/, ''),
'map': (arr, arg) => arr.map(v => v[arg]),
'minus': bindFixed((v, arg) => v - arg),
@@ -56,7 +57,7 @@ var filters = {
'replace_first': (v, arg1, arg2) => stringify(v).replace(arg1, arg2),
'reverse': v => v.reverse(),
'round': (v, arg) => {
var amp = Math.pow(10, arg || 0)
let amp = Math.pow(10, arg || 0)
return Math.round(v * amp, arg) / amp
},
'rstrip': str => stringify(str).replace(/\s+$/, ''),
@@ -78,13 +79,13 @@ var filters = {
},
'truncatewords': (v, l, o) => {
if (o === undefined) o = '...'
var arr = v.split(' ')
var ret = arr.slice(0, l).join(' ')
let arr = v.split(' ')
let ret = arr.slice(0, l).join(' ')
if (arr.length > l) ret += o
return ret
},
'uniq': function (arr) {
var u = {}
let u = {}
return (arr || []).filter(val => {
if (u.hasOwnProperty(val)) {
return false
@@ -106,7 +107,7 @@ function unescape (str) {
}
function getFixed (v) {
var p = (v + '').split('.')
let p = (v + '').split('.')
return (p.length > 1) ? p[1].length : 0
}
@@ -120,7 +121,7 @@ function stringify (obj) {
function bindFixed (cb) {
return (l, r) => {
var f = getMaxFixed(l, r)
let f = getMaxFixed(l, r)
return cb(l, r).toFixed(f)
}
}
+27 -16
View File
@@ -5,16 +5,16 @@ const assert = require('./util/assert.js')
var Scope = {
getAll: function () {
return this.scopes.reduce((ctx, val) => Object.assign(ctx, val), Object.create(null))
return this.contexts.reduce((ctx, val) => Object.assign(ctx, val), Object.create(null))
},
get: function (path) {
let paths = this.propertyAccessSeq(path)
let scope = this.findScopeFor(paths[0])
let scope = this.findContextFor(paths[0]) || _.last(this.contexts)
return paths.reduce((value, key) => this.readProperty(value, key), scope)
},
set: function (path, v) {
let paths = this.propertyAccessSeq(path)
let scope = this.findScopeFor(paths[0])
let scope = this.findContextFor(paths[0]) || _.last(this.contexts)
paths.some((key, i) => {
if (!_.isObject(scope)) {
return true
@@ -29,28 +29,32 @@ var Scope = {
scope = scope[key]
})
},
unshift: function (ctx) {
return this.contexts.unshift(ctx)
},
push: function (ctx) {
this.scopes.push(ctx)
return this.contexts.push(ctx)
},
pop: function (ctx) {
if (!arguments.length) {
return this.scopes.pop()
return this.contexts.pop()
}
let i = this.scopes.findIndex(scope => scope === ctx)
let i = this.contexts.findIndex(scope => scope === ctx)
if (i === -1) {
throw new TypeError('scope not found, cannot pop')
}
return this.scopes.splice(i, 1)[0]
return this.contexts.splice(i, 1)[0]
},
findScopeFor: function (key) {
let i = this.scopes.length - 1
while (i >= 0 && !(key in this.scopes[i])) {
i--
findContextFor: function (key, filter) {
filter = filter || (() => true)
for (let i = this.contexts.length - 1; i >= 0; i--) {
let candidate = this.contexts[i]
if (!filter(candidate)) continue
if (key in candidate) {
return candidate
}
}
if (i < 0) {
i = this.scopes.length - 1
}
return this.scopes[i]
return null
},
readProperty: function (obj, key) {
let val
@@ -153,6 +157,13 @@ exports.factory = function (ctx, opts) {
}
var scope = Object.create(Scope)
scope.opts = _.assign(defaultOptions, opts)
scope.scopes = [ctx || {}]
scope.contexts = [ctx || {}]
return scope
}
exports.types = {
AssignScope: Object.create(null),
CaptureScope: Object.create(null),
IncrementScope: Object.create(null),
DecrementScope: Object.create(null)
}
+11 -10
View File
@@ -1,26 +1,27 @@
'use strict'
const lexical = require('./lexical.js')
const Syntax = require('./syntax.js')
const assert = require('./util/assert.js')
function hash (markup, scope) {
var obj = {}
var match
let obj = {}
let match
lexical.hashCapture.lastIndex = 0
while ((match = lexical.hashCapture.exec(markup))) {
var k = match[1]
var v = match[2]
let k = match[1]
let v = match[2]
obj[k] = Syntax.evalValue(v, scope)
}
return obj
}
module.exports = function () {
var tagImpls = {}
let tagImpls = {}
var _tagInstance = {
let _tagInstance = {
render: function (scope) {
var obj = hash(this.token.args, scope)
var impl = this.tagImpl
let obj = hash(this.token.args, scope)
let impl = this.tagImpl
if (typeof impl.render !== 'function') {
return Promise.resolve('')
}
@@ -31,7 +32,7 @@ module.exports = function () {
this.token = token
this.name = token.name
var tagImpl = tagImpls[this.name]
let tagImpl = tagImpls[this.name]
assert(tagImpl, `tag ${this.name} not found`)
this.tagImpl = Object.create(tagImpl)
if (this.tagImpl.parse) {
@@ -45,7 +46,7 @@ module.exports = function () {
}
function construct (token, tokens) {
var instance = Object.create(_tagInstance)
let instance = Object.create(_tagInstance)
instance.parse(token, tokens)
return instance
}
+2 -1
View File
@@ -1,6 +1,7 @@
const resolve = require('resolve-url')
const splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^/]+?|)(\.[^./]*|))(?:[/]*)$/
const urlRe = /^(?:\w+:)?\/\/([^\s.]+\.\S{2}|localhost[:?\d]*)\S*$/
const _ = require('./underscore')
// https://github.com/jinder/path/blob/master/path.js#L567
exports.extname = function (path) {
@@ -16,7 +17,7 @@ exports.resolve = function (root, path) {
if (Object.prototype.toString.call(root) === '[object Array]') {
root = root[0]
}
if (root && root.charAt(root.length - 1) !== '/') {
if (root && _.last(root) !== '/') {
root += '/'
}
return resolve(root, path)
+2 -1
View File
@@ -3,6 +3,7 @@ const Liquid = require('..')
const lexical = Liquid.lexical
const re = new RegExp(`(${lexical.identifier.source})\\s*=(.*)`)
const assert = require('../src/util/assert.js')
const types = require('../src/scope').types
module.exports = function (liquid) {
liquid.registerTag('assign', {
@@ -13,7 +14,7 @@ module.exports = function (liquid) {
this.value = match[2]
},
render: function (scope) {
let ctx = Object.create(null)
let ctx = Object.create(types.AssignScope)
ctx[this.key] = liquid.evalValue(this.value, scope)
scope.push(ctx)
return Promise.resolve('')
+17 -3
View File
@@ -1,6 +1,8 @@
'use strict'
const Liquid = require('..')
const lexical = Liquid.lexical
const assert = require('../src/util/assert.js')
const types = require('../src/scope').types
module.exports = function (liquid) {
liquid.registerTag('decrement', {
@@ -10,9 +12,21 @@ module.exports = function (liquid) {
this.variable = match[0]
},
render: function (scope, hash) {
var v = scope.get(this.variable)
if (typeof v !== 'number') v = 0
scope.set(this.variable, v - 1)
let context = scope.findContextFor(
this.variable,
ctx => {
return Object.getPrototypeOf(ctx) !== types.CaptureScope &&
Object.getPrototypeOf(ctx) !== types.AssignScope
}
)
if (!context) {
context = Object.create(types.DecrementScope)
scope.unshift(context)
}
if (typeof context[this.variable] !== 'number') {
context[this.variable] = 0
}
return --context[this.variable]
}
})
}
+20 -4
View File
@@ -1,18 +1,34 @@
'use strict'
const Liquid = require('..')
const assert = require('../src/util/assert.js')
const lexical = Liquid.lexical
const types = require('../src/scope').types
module.exports = function (liquid) {
liquid.registerTag('increment', {
parse: function (token) {
var match = token.args.match(lexical.identifier)
let match = token.args.match(lexical.identifier)
assert(match, `illegal identifier ${token.args}`)
this.variable = match[0]
},
render: function (scope, hash) {
var v = scope.get(this.variable)
if (typeof v !== 'number') v = 0
scope.set(this.variable, v + 1)
let context = scope.findContextFor(
this.variable,
ctx => {
return Object.getPrototypeOf(ctx) !== types.CaptureScope &&
Object.getPrototypeOf(ctx) !== types.AssignScope
}
)
if (!context) {
context = Object.create(types.IncrementScope)
scope.unshift(context)
}
if (typeof context[this.variable] !== 'number') {
context[this.variable] = 0
}
let val = context[this.variable]
context[this.variable]++
return val
}
})
}
+2 -3
View File
@@ -9,14 +9,13 @@ module.exports = function (liquid) {
if (token.name === 'endraw') stream.stop()
else this.tokens.push(token)
})
.on('end', x => {
.on('end', () => {
throw new Error(`tag ${tagToken.raw} not closed`)
})
stream.start()
},
render: function (scope, hash) {
var tokens = this.tokens.map(token => token.raw).join('')
return Promise.resolve(tokens)
return this.tokens.map(token => token.raw).join('')
}
})
}
+2 -2
View File
@@ -141,7 +141,7 @@ describe('filters', function () {
it('should support split/first', function () {
var src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}'
'{{ my_array | first }}'
return test(src, 'apples')
})
@@ -160,7 +160,7 @@ describe('filters', function () {
it('should support split/last', function () {
var src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' +
'{{ my_array|last }}'
'{{ my_array|last }}'
return test(src, 'tiger')
})
+25 -21
View File
@@ -1,3 +1,4 @@
'use strict'
const Liquid = require('../..')
const chai = require('chai')
const expect = chai.expect
@@ -5,34 +6,37 @@ chai.use(require('chai-as-promised'))
describe('tags/decrement', function () {
var liquid = Liquid()
it('should throw when variable expression illegal', function () {
var src = '{% decrement / %}{{one}}'
var src = '{% decrement / %}{{var}}'
var ctx = {}
return expect(liquid.parseAndRender(src, ctx)).to.be.rejectedWith(/illegal/)
})
it('should support decrement', function () {
var src = '{% decrement one %}{{one}}'
var ctx = {
one: 1
}
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('0')
})
it('should decrement undefined', function () {
var src = '{% decrement empty %}{{empty}}'
it('should decrement undefined variable', function () {
let src = '{% decrement var %}{% decrement var %}{% decrement var %}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('-1')
.to.eventually.equal('-1-2-3')
})
it('should support decrement multiple times', function () {
var src = '{% decrement foo %}{%decrement foo%}{{foo}}'
var ctx = {
foo: 1
}
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('-1')
it('should decrement defined variable', function () {
let src = '{% decrement var %}{% decrement var %}{% decrement var %}'
let ctx = {'var': 10}
return liquid.parseAndRender(src, ctx)
.then(x => {
expect(x).to.equal('987')
expect(ctx.var).to.equal(7)
})
})
it('should be independent from assign', function () {
let src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('-1-2-3')
})
it('should not shading assign', function () {
let src = '{% assign var=10 %}{% decrement var %}{% decrement var %}{% decrement var %} {{var}}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('-1-2-3 10')
})
})
+25 -20
View File
@@ -1,32 +1,37 @@
'use strict'
const Liquid = require('../..')
const chai = require('chai')
const expect = chai.expect
chai.use(require('chai-as-promised'))
describe('tags/increment', function () {
var liquid = Liquid()
let liquid = Liquid()
it('should support increment', function () {
var src = '{% increment one %}{{one}}'
var ctx = {
one: 1
}
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('2')
})
it('should increment undefined', function () {
var src = '{% increment empty %}{{empty}}'
it('should increment undefined variable', function () {
let src = '{% increment one %}{% increment one %}{% increment one %}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('1')
.to.eventually.equal('012')
})
it('should support increment multiple times', function () {
var src = '{% increment foo %}{%increment foo%}{{foo}}'
var ctx = {
foo: 1
}
return expect(liquid.parseAndRender(src, ctx))
.to.eventually.equal('3')
it('should increment defined variable', function () {
let src = '{% increment one %}{% increment one %}{% increment one %}'
let ctx = {one: 7}
return liquid.parseAndRender(src, ctx)
.then(x => {
expect(x).to.equal('789')
expect(ctx.one).to.equal(10)
})
})
it('should be independent from assign', function () {
let src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('012')
})
it('should not shading assign', function () {
let src = '{% assign var=10 %}{% increment var %}{% increment var %}{% increment var %} {{var}}'
return expect(liquid.parseAndRender(src))
.to.eventually.equal('012 10')
})
})
+1
View File
@@ -260,6 +260,7 @@ describe('error', function () {
.be.rejected
.then(function (err) {
mock.restore()
console.log(err, err.name)
expect(err.name).to.equal('RenderError')
expect(err.file).to.equal(path.resolve('/foo.html'))
})