mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
* Size can also be used with dot notation (for example, {{ my_string.size }}).
* Fixed test failed.
* Optimization
183 lines
5.4 KiB
JavaScript
183 lines
5.4 KiB
JavaScript
const _ = require('./util/underscore.js');
|
|
const lexical = require('./lexical.js');
|
|
const assert = require('./util/assert.js');
|
|
const toStr = Object.prototype.toString;
|
|
|
|
var Scope = {
|
|
getAll: function() {
|
|
var ctx = {};
|
|
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
|
_.assign(ctx, this.scopes[i]);
|
|
}
|
|
return ctx;
|
|
},
|
|
get: function(str) {
|
|
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
|
try {
|
|
return this.getPropertyByPath(this.scopes[i], str);
|
|
} catch (e) {
|
|
if (/undefined variable/.test(e.message)) {
|
|
continue;
|
|
}
|
|
if (/Cannot read property/.test(e.message)) {
|
|
if (this.opts.strict_variables) {
|
|
e.message += ': ' + str;
|
|
throw e;
|
|
} else {
|
|
continue;
|
|
}
|
|
} else {
|
|
e.message += ': ' + str;
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
if (this.opts.strict_variables) {
|
|
throw new TypeError('undefined variable: ' + str);
|
|
}
|
|
},
|
|
set: function(k, v) {
|
|
this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v);
|
|
return this;
|
|
},
|
|
push: function(ctx) {
|
|
assert(ctx, `trying to push ${ctx} into scopes`);
|
|
return this.scopes.push(ctx);
|
|
},
|
|
pop: function() {
|
|
return this.scopes.pop();
|
|
},
|
|
unshift: function(ctx) {
|
|
assert(ctx, `trying to push ${ctx} into scopes`);
|
|
return this.scopes.unshift(ctx);
|
|
},
|
|
shift: function() {
|
|
return this.scopes.shift();
|
|
},
|
|
setPropertyByPath: function(obj, path, val) {
|
|
if (_.isString(path)) {
|
|
var paths = path.replace(/\[/g, '.').replace(/\]/g, '').split('.');
|
|
for (var i = 0; i < paths.length; i++) {
|
|
var key = paths[i];
|
|
if (i === paths.length - 1) {
|
|
return obj[key] = val;
|
|
}
|
|
if (undefined === obj[key]) obj[key] = {};
|
|
// case for readonly objects
|
|
obj = obj[key] || {};
|
|
}
|
|
}
|
|
},
|
|
|
|
getPropertyByPath: function(obj, path) {
|
|
var paths = this.propertyAccessSeq(path + '');
|
|
var varName = paths.shift();
|
|
if (!obj.hasOwnProperty(varName)) {
|
|
throw new TypeError('undefined variable');
|
|
}
|
|
var variable = obj[varName];
|
|
var lastName = paths.pop();
|
|
paths.forEach(p => variable = variable[p]);
|
|
if (undefined !== lastName) {
|
|
if (lastName === 'size' &&
|
|
(toStr.call(variable) === '[object Array]'
|
|
|| toStr.call(variable) === '[object String]')) {
|
|
return variable.length;
|
|
}
|
|
variable = variable[lastName]
|
|
}
|
|
return variable;
|
|
},
|
|
|
|
/*
|
|
* 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: function(str) {
|
|
var seq = [],
|
|
name = '';
|
|
for (var i = 0; i < str.length; i++) {
|
|
if (str[i] === '[') {
|
|
seq.push(name);
|
|
name = '';
|
|
|
|
var delemiter = str[i + 1];
|
|
// foo[bar.coo]
|
|
if (delemiter !== "'" && delemiter !== '"') {
|
|
var j = matchRightBracket(str, i + 1);
|
|
assert(j !== -1, `unbalanced []: ${str}`);
|
|
name = str.slice(i + 1, j);
|
|
// foo[1]
|
|
if (lexical.isInteger(name)) {
|
|
seq.push(name);
|
|
}
|
|
// foo["bar"]
|
|
else {
|
|
seq.push(this.get(name));
|
|
}
|
|
name = '';
|
|
i = j;
|
|
}
|
|
// foo["bar"]
|
|
else {
|
|
j = str.indexOf(delemiter, i + 2);
|
|
assert(j !== -1, `unbalanced ${delemiter}: ${str}`);
|
|
name = str.slice(i + 2, j);
|
|
seq.push(name);
|
|
name = '';
|
|
i = j + 1;
|
|
}
|
|
}
|
|
// foo.bar
|
|
else if (str[i] === ".") {
|
|
seq.push(name);
|
|
name = '';
|
|
}
|
|
//foo.bar
|
|
else {
|
|
name += str[i];
|
|
}
|
|
}
|
|
if (name.length) seq.push(name);
|
|
return seq;
|
|
}
|
|
};
|
|
|
|
function matchRightBracket(str, begin) {
|
|
var stack = 1; // count of '[' - count of ']'
|
|
for (var i = begin; i < str.length; i++) {
|
|
if (str[i] === '[') {
|
|
stack++;
|
|
}
|
|
if (str[i] === ']') {
|
|
stack--;
|
|
if (stack === 0) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
exports.factory = function(ctx, opts) {
|
|
opts = _.assign({
|
|
strict_variables: false,
|
|
strict_filters: false,
|
|
blocks: {},
|
|
root: []
|
|
}, opts);
|
|
|
|
ctx = _.assign(ctx, {
|
|
liquid: opts
|
|
});
|
|
|
|
var scope = Object.create(Scope);
|
|
scope.opts = opts;
|
|
scope.scopes = [ctx];
|
|
return scope;
|
|
};
|