mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 20:30:39 -07:00
fix existing tags
new tags: for,cycle
This commit is contained in:
@@ -25,18 +25,18 @@ Documentation: <https://shopify.github.io/liquid/basics/introduction/#tags>
|
||||
- [x] if [Document](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/if.js) [Test][tt]
|
||||
- [x] unless [Document](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/unless.js) [Test][tt]
|
||||
- [x] elsif [Document/else](https://shopify.github.io/liquid/tags/control-flow/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/if.js) [Test][tt]
|
||||
- [ ] for [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
|
||||
- [ ] break [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [ ] continue [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [ ] for [Document: limit,offset,range,reversed)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [ ] cycle [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [ ] cycle [Document: group)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [x] for [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
|
||||
- [x] break [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
|
||||
- [x] continue [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
|
||||
- [x] for [Document: limit,offset,range,reversed)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/for.js) [Test][tt]
|
||||
- [x] cycle [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/cycle.js) [Test][tt]
|
||||
- [x] cycle [Document: group)](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/cycle.js) [Test][tt]
|
||||
- [ ] tablerow [Document](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [ ] tablerow [Document: cols,limit,offset,range](https://shopify.github.io/liquid/tags/iteration/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [x] assign [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/assign.js) [Test][tt]
|
||||
- [x] capture [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [x] increment [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [x] decrement [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/.js) [Test][tt]
|
||||
- [x] capture [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/capture.js) [Test][tt]
|
||||
- [x] increment [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/increment.js) [Test][tt]
|
||||
- [x] decrement [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/decrement.js) [Test][tt]
|
||||
|
||||
|
||||
## Filters
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
const syntax = require('./syntax.js');
|
||||
const Exp = require('./expression.js');
|
||||
const lexical = require('./lexical.js');
|
||||
const _ = require('lodash');
|
||||
|
||||
function evalExp(exp, scope) {
|
||||
if (!scope) throw new Error('unable to evalExp: scope undefined');
|
||||
var operatorREs = lexical.operators;
|
||||
for (var i = 0; i < operatorREs.length; i++) {
|
||||
var operatorRE = operatorREs[i];
|
||||
var expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`);
|
||||
var match = exp.match(expRE);
|
||||
if (match) {
|
||||
var l = evalExp(match[1], scope);
|
||||
var op = syntax.operators[match[2].trim()];
|
||||
var r = evalExp(match[3], scope);
|
||||
return op(l, r);
|
||||
}
|
||||
}
|
||||
|
||||
if (match = exp.match(lexical.rangeLine)) {
|
||||
var low = evalValue(match[1], scope),
|
||||
high = evalValue(match[2], scope) + 1;
|
||||
return _.range(low, high);
|
||||
}
|
||||
|
||||
return evalValue(exp, scope);
|
||||
}
|
||||
|
||||
function evalValue(str, scope) {
|
||||
str = str && str.trim();
|
||||
if (!str) return undefined;
|
||||
|
||||
if (lexical.isLiteral(str)) {
|
||||
var a = lexical.parseLiteral(str);
|
||||
return lexical.parseLiteral(str);
|
||||
}
|
||||
if (lexical.isVariable(str)) {
|
||||
return scope.get(str);
|
||||
}
|
||||
}
|
||||
|
||||
function isTruthy(val) {
|
||||
if (val instanceof Array) return !!val.length;
|
||||
return !!val;
|
||||
}
|
||||
|
||||
function isFalsy(val) {
|
||||
return !isTruthy(val);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
evalExp, evalValue, isTruthy, isFalsy
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
const lexical = require('./lexical.js');
|
||||
const _ = require('lodash');
|
||||
const Exp = require('./expression.js');
|
||||
|
||||
var _filterInstance = {
|
||||
render: function(output, scope) {
|
||||
var args = this.args.map(arg => scope.get(arg));
|
||||
var args = this.args.map(arg => Exp.evalValue(arg, scope));
|
||||
args.unshift(output);
|
||||
return this.filter.apply(null, args);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const Tag = require('./tag.js');
|
||||
const Filter = require('./filter.js');
|
||||
const error = require('./error.js');
|
||||
const Template = require('./template');
|
||||
const Expression = require('./expression.js');
|
||||
|
||||
const tagsPath = path.join(__dirname, "tags");
|
||||
|
||||
@@ -33,14 +34,13 @@ function factory(){
|
||||
engine.parseStream = template.parseStream;
|
||||
|
||||
var renderer = Render(engine.filter, engine.tag);
|
||||
engine.evaluate = renderer.evalExp;
|
||||
engine.evalExp = renderer.evalExp;
|
||||
engine.evalFilter = renderer.evalFilter;
|
||||
engine.renderTemplates = renderer.renderTemplates;
|
||||
|
||||
engine.render = function(html, ctx) {
|
||||
var tokens = engine.tokenize(html);
|
||||
var templates = engine.parse(tokens);
|
||||
engine.register = {};
|
||||
return engine.renderTemplates(templates, scope.factory(ctx));
|
||||
},
|
||||
|
||||
@@ -55,7 +55,11 @@ function factory(){
|
||||
|
||||
factory.lexical = lexical;
|
||||
factory.error = error;
|
||||
factory.isTruthy = Render.isTruthy;
|
||||
factory.isTruthy = Expression.isTruthy;
|
||||
factory.isFalsy = Expression.isFalsy;
|
||||
factory.stringify = Render.stringify;
|
||||
factory.evalExp = Expression.evalExp;
|
||||
factory.evalValue = Expression.evalValue;
|
||||
|
||||
|
||||
module.exports = factory;
|
||||
|
||||
+18
-13
@@ -5,15 +5,18 @@ var doubleQuoted = /"[^"]*"/;
|
||||
|
||||
var number = /-?\d+\.?\d*|\.?\d+/;
|
||||
var bool = /true|false/i;
|
||||
var range = /\((\d+)\.\.(\d+)\)/;
|
||||
var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/;
|
||||
var subscript = /\[\d+\]/;
|
||||
|
||||
var quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
|
||||
var literal = new RegExp(`${quoted.source}|${range.source}|${bool.source}|${number.source}`, 'i');
|
||||
var literal = new RegExp(`${quoted.source}|${bool.source}|${number.source}`, 'i');
|
||||
var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`);
|
||||
var value = new RegExp(`${literal.source}|${variable.source}`, 'i');
|
||||
var hash = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
|
||||
var range = new RegExp(`\\((?:${value.source})\\.\\.(?:${value.source})\\)`);
|
||||
var rangeCapture = new RegExp(`\\((${value.source})\\.\\.(${value.source})\\)`);
|
||||
var variableOrRange = new RegExp(`${variable.source}|${range.source}`);
|
||||
var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`, 'g');
|
||||
var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
|
||||
var filter = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${value.source}))?`);
|
||||
var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
|
||||
|
||||
@@ -23,7 +26,7 @@ var variableLine = new RegExp(`^(?:${variable.source})$`);
|
||||
var numberLine = new RegExp(`^(?:${number.source})$`);
|
||||
var boolLine = new RegExp(`^(?:${bool.source})$`, 'i');
|
||||
var quotedLine = new RegExp(`^(?:${quoted.source})$`);
|
||||
var rangeLine = new RegExp(`^(?:${range.source})$`);
|
||||
var rangeLine = new RegExp(`^(?:${rangeCapture.source})$`);
|
||||
var filterLine = new RegExp(`^(?:${filter.source})$`);
|
||||
|
||||
var operators = [
|
||||
@@ -33,15 +36,19 @@ var operators = [
|
||||
];
|
||||
|
||||
|
||||
function isLiteral (str) {
|
||||
function isLiteral(str) {
|
||||
return literalLine.test(str);
|
||||
}
|
||||
|
||||
function isVariable (str) {
|
||||
function isRange(str) {
|
||||
return rangeLine.test(str);
|
||||
}
|
||||
|
||||
function isVariable(str) {
|
||||
return variableLine.test(str);
|
||||
}
|
||||
|
||||
function parseLiteral (str) {
|
||||
function parseLiteral(str) {
|
||||
var res;
|
||||
if (res = str.match(numberLine)) {
|
||||
return Number(str);
|
||||
@@ -52,15 +59,13 @@ function parseLiteral (str) {
|
||||
if (res = str.match(quotedLine)) {
|
||||
return str.slice(1, -1);
|
||||
}
|
||||
if (res = str.match(rangeLine)) {
|
||||
return _.range(res[1], res[2]);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
quoted, number, bool, range, literal, hash, filter,
|
||||
quoted, number, bool, literal, filter,
|
||||
hash, hashCapture,
|
||||
range, rangeCapture, variableOrRange,
|
||||
identifier, value, quoteBalanced, operators,
|
||||
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine,
|
||||
isLiteral, isVariable, parseLiteral
|
||||
isLiteral, isVariable, parseLiteral, isRange
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shopify-liquid",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"description": "A Shopify Liquid Implementation in Node.js",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,66 +1,52 @@
|
||||
const lexical = require('./lexical.js');
|
||||
const syntax = require('./syntax.js');
|
||||
const error = require('./error.js');
|
||||
const Exp = require('./expression.js');
|
||||
|
||||
function stringify(val) {
|
||||
if (typeof val === 'string') return val;
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
function isTruthy(val) {
|
||||
if (val instanceof Array) return !!val.length;
|
||||
return !!val;
|
||||
}
|
||||
|
||||
function factory(Filter, Tag) {
|
||||
function renderTemplates(templates, scope) {
|
||||
var html = '';
|
||||
var template;
|
||||
while (template = templates.shift()) {
|
||||
templates.some(template => {
|
||||
if (scope.get('forloop.skip')) return true;
|
||||
if (template.type === 'tag') {
|
||||
html += template.render(scope);
|
||||
if (template.name === 'continue') {
|
||||
scope.set('forloop.skip', true);
|
||||
return true;
|
||||
}
|
||||
if (template.name === 'break') {
|
||||
scope.set('forloop.stop', true);
|
||||
scope.set('forloop.skip', true);
|
||||
return true;
|
||||
}
|
||||
html += template.render(scope, this.register);
|
||||
} else if (template.type === 'html') {
|
||||
html += template.value;
|
||||
} else if (template.type === 'output') {
|
||||
var val = evalFilter(template.value, scope);
|
||||
html += stringify(val);
|
||||
}
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
|
||||
function evalExp(exp, scope) {
|
||||
if (!scope) error('unable to evalExp: scope undefined');
|
||||
var operatorREs = lexical.operators;
|
||||
for (var i = 0; i < operatorREs.length; i++) {
|
||||
var operatorRE = operatorREs[i];
|
||||
var expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`);
|
||||
var match = exp.match(expRE);
|
||||
if (match) {
|
||||
var l = evalExp(match[1], scope);
|
||||
var op = syntax.operators[match[2].trim()];
|
||||
var r = evalExp(match[3], scope);
|
||||
return op(l, r);
|
||||
}
|
||||
}
|
||||
return evalFilter(exp, scope);
|
||||
}
|
||||
|
||||
function evalFilter(str, scope) {
|
||||
if (!scope) error('unable to evalFilter: scope undefined');
|
||||
if (!scope) throw new Error('unable to evalFilter: scope undefined');
|
||||
var filters = str.split('|');
|
||||
var val = scope.get(filters.shift());
|
||||
var val = Exp.evalValue(filters.shift(), scope);
|
||||
return filters
|
||||
.map(str => Filter.construct(str))
|
||||
.reduce((v, filter) => filter.render(v, scope), val);
|
||||
}
|
||||
|
||||
return {
|
||||
renderTemplates, evalFilter, evalExp
|
||||
renderTemplates, evalFilter
|
||||
};
|
||||
};
|
||||
|
||||
factory.isTruthy = isTruthy;
|
||||
factory.stringify = stringify;
|
||||
|
||||
module.exports = factory;
|
||||
|
||||
@@ -4,27 +4,18 @@ const error = require('./error.js');
|
||||
|
||||
var scope = {
|
||||
get: function(str) {
|
||||
str = str && str.trim();
|
||||
if(!str) return '';
|
||||
|
||||
if (lexical.isLiteral(str)) {
|
||||
var a = lexical.parseLiteral(str);
|
||||
return lexical.parseLiteral(str);
|
||||
}
|
||||
if (lexical.isVariable(str)) {
|
||||
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
||||
var v = _.get(this.scopes[i], str);
|
||||
if (v !== undefined) return v;
|
||||
}
|
||||
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
||||
var v = _.get(this.scopes[i], str);
|
||||
if (v !== undefined) return v;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
set: function(k, v){
|
||||
this.scopes[this.scopes.length-1][k] = v;
|
||||
set: function(k, v) {
|
||||
_.set(this.scopes[this.scopes.length - 1], k, v);
|
||||
return this;
|
||||
},
|
||||
push: function(ctx) {
|
||||
if(!ctx) error(`trying to push ${ctx} into scopes`);
|
||||
if (!ctx) error(`trying to push ${ctx} into scopes`);
|
||||
return this.scopes.push(ctx);
|
||||
},
|
||||
pop: function() {
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
const lexical = require('./lexical.js');
|
||||
const Exp = require('./expression.js');
|
||||
|
||||
var _tagInstance = {
|
||||
render: function(scope) {
|
||||
render: function(scope, register) {
|
||||
var reg = register[this.name];
|
||||
if(!reg) reg = register[this.name] = {}
|
||||
var obj = hash(this.token.args, scope);
|
||||
return this.tagImpl.render(scope, obj) || '';
|
||||
return this.tagImpl.render(scope, obj, reg) || '';
|
||||
},
|
||||
parse: function(tokens){
|
||||
this.tagImpl.parse(this.token, tokens);
|
||||
if(this.tagImpl.parse){
|
||||
this.tagImpl.parse(this.token, tokens);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
function hash(markup, scope) {
|
||||
var obj = {};
|
||||
lexical.hash.lastIndex = 0;
|
||||
while (match = lexical.hash.exec(markup)) {
|
||||
lexical.hashCapture.lastIndex = 0;
|
||||
while (match = lexical.hashCapture.exec(markup)) {
|
||||
var k = match[1],
|
||||
v = match[2];
|
||||
if (!k) continue;
|
||||
obj[k] = scope.get(v);
|
||||
obj[k] = Exp.evalValue(v, scope);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
+7
-3
@@ -5,10 +5,14 @@ var re = new RegExp(`(${lexical.identifier.source})\\s*=(.*)`);
|
||||
module.exports = function(liquid) {
|
||||
|
||||
liquid.registerTag('assign', {
|
||||
render: function(tokens, scope, token, hash) {
|
||||
parse: function(token){
|
||||
var match = token.args.match(re);
|
||||
var k = match[1], v = match[2];
|
||||
scope.set(k, liquid.evaluate(v, scope));
|
||||
if(!match) error(`illegal token ${token.raw}`, token);
|
||||
this.key = match[1];
|
||||
this.value = match[2];
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
scope.set(this.key, Liquid.evalValue(this.value, scope));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+34
-23
@@ -1,34 +1,45 @@
|
||||
var Liquid = require('..');
|
||||
var lexical = Liquid.lexical;
|
||||
var caseRE = new RegExp(`^\\s*case\\s+(${lexical.value.source})`);
|
||||
var whenRE = new RegExp(`^\\s*when\\s+(${lexical.value.source})`);
|
||||
|
||||
module.exports = function(liquid) {
|
||||
|
||||
liquid.registerTag('case', {
|
||||
needClose: true,
|
||||
render: function(tokens, scope, token, hash) {
|
||||
var match = token.value.match(caseRE);
|
||||
var cond = liquid.evaluate(match[1], scope);
|
||||
|
||||
var partialTokens = [],
|
||||
matching = false;
|
||||
for (var i = 0; i < tokens.length; i++) {
|
||||
var tk = tokens[i];
|
||||
if (tk.type === 'tag' && tk.name === 'when') {
|
||||
if (matching) break;
|
||||
match = tk.value.match(whenRE);
|
||||
if(!match) continue;
|
||||
parse: function(tagToken, remainTokens) {
|
||||
this.cond = tagToken.args;
|
||||
this.cases = [];
|
||||
this.elseTemplates = [];
|
||||
|
||||
var val = liquid.evaluate(match[1], scope);
|
||||
if (val === cond) matching = true;
|
||||
} else if (tk.type === 'tag' && tk.name === 'else') {
|
||||
if (matching) break;
|
||||
else matching = true;
|
||||
} else if (matching) partialTokens.push(tk);
|
||||
var p = [],
|
||||
stream = liquid.parseStream(remainTokens)
|
||||
.onTag('when', token => {
|
||||
if (!this.cases[token.args]) {
|
||||
this.cases.push({
|
||||
val: token.args,
|
||||
templates: p = []
|
||||
});
|
||||
}
|
||||
})
|
||||
.onTag('else', token => p = this.elseTemplates)
|
||||
.onTag('endcase', token => stream.stop())
|
||||
.onTemplate(tpl => p.push(tpl))
|
||||
.onEnd(x => {
|
||||
throw new Error(`tag ${tagToken.raw} not closed`);
|
||||
});
|
||||
|
||||
stream.start();
|
||||
},
|
||||
|
||||
render: function(scope, hash) {
|
||||
for (var i = 0; i < this.cases.length; i++) {
|
||||
var branch = this.cases[i];
|
||||
var val = Liquid.evalExp(branch.val, scope);
|
||||
var cond = Liquid.evalExp(this.cond, scope);
|
||||
if (val === cond) {
|
||||
return liquid.renderTemplates(branch.templates, scope);
|
||||
}
|
||||
}
|
||||
|
||||
return liquid.renderTokens(partialTokens, scope);
|
||||
return liquid.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
var Liquid = require('..');
|
||||
var lexical = Liquid.lexical;
|
||||
var groupRE = new RegExp(`^(?:(${lexical.value.source})\\s*:\\s*)?(.*)$`);
|
||||
var candidatesRE = new RegExp(lexical.value.source, 'g');
|
||||
|
||||
module.exports = function(liquid) {
|
||||
liquid.registerTag('cycle', {
|
||||
|
||||
parse: function(tagToken, remainTokens) {
|
||||
var match = groupRE.exec(tagToken.args);
|
||||
if(!match) throw new Error(`illegal tag: ${tagToken.raw}`);
|
||||
|
||||
this.group = match[1] || '';
|
||||
var candidates = match[2];
|
||||
|
||||
this.candidates = [];
|
||||
|
||||
while(match = candidatesRE.exec(candidates)){
|
||||
this.candidates.push(match[0]);
|
||||
}
|
||||
|
||||
if (!this.candidates.length){
|
||||
throw new Error(`illegal tag: ${tagToken.raw}`);
|
||||
}
|
||||
},
|
||||
|
||||
render: function(scope, hash, register) {
|
||||
var fingerprint = Liquid.evalValue(this.group, scope) + ':' +
|
||||
this.candidates.join(',');
|
||||
var idx = register[fingerprint];
|
||||
|
||||
if(idx === undefined){
|
||||
idx = register[fingerprint] = 0;
|
||||
}
|
||||
|
||||
var candidate = this.candidates[idx];
|
||||
idx = (idx + 1) % this.candidates.length;
|
||||
register[fingerprint] = idx;
|
||||
|
||||
return Liquid.evalValue(candidate, scope);
|
||||
}
|
||||
});
|
||||
};
|
||||
+8
-5
@@ -5,12 +5,15 @@ const error = Liquid.error;
|
||||
module.exports = function(liquid) {
|
||||
|
||||
liquid.registerTag('decrement', {
|
||||
render: function(tokens, scope, token, hash) {
|
||||
parse: function(token) {
|
||||
var match = token.args.match(lexical.identifier);
|
||||
if(!match) error(`illegal identifier ${token.args}`, token);
|
||||
var k = match[0], v = scope.get(k);
|
||||
if(typeof v !== 'number') v = 0;
|
||||
scope.set(k, v-1);
|
||||
if (!match) error(`illegal identifier ${token.args}`, token);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+37
-5
@@ -1,15 +1,19 @@
|
||||
var Liquid = require('..');
|
||||
var lexical = Liquid.lexical;
|
||||
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+(.+)$`);
|
||||
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
|
||||
`(${lexical.variableOrRange.source})` +
|
||||
`(?:\\s+${lexical.hash.source})*` +
|
||||
`(?:\\s+(reversed))?$`);
|
||||
|
||||
module.exports = function(liquid) {
|
||||
liquid.registerTag('for', {
|
||||
|
||||
parse: function(tagToken, remainTokens) {
|
||||
var match = re.exec(tagToken.args);
|
||||
if(!match) throw new Error(`illegal tag: ${tagToken.raw}`);
|
||||
if (!match) throw new Error(`illegal tag: ${tagToken.raw}`);
|
||||
this.variable = match[1];
|
||||
this.collection = match[2];
|
||||
this.reversed = !!match[3];
|
||||
|
||||
this.templates = [];
|
||||
this.elseTemplates = [];
|
||||
@@ -27,12 +31,40 @@ module.exports = function(liquid) {
|
||||
},
|
||||
|
||||
render: function(scope, hash) {
|
||||
var collection = liquid.evaluate(this.collection, scope);
|
||||
if(!(collection instanceof Array) || !collection.length){
|
||||
var collection = Liquid.evalExp(this.collection, scope);
|
||||
if (Liquid.isFalsy(collection)) {
|
||||
return liquid.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
|
||||
liquid.renderTemplates(this.templates, scope);
|
||||
var html = '',
|
||||
ctx = {},
|
||||
length = collection.length;
|
||||
var offset = hash.offset || 0;
|
||||
var limit = (hash.limit === undefined) ? collection.length : hash.limit;
|
||||
|
||||
var collection = collection.slice(offset, offset + limit);
|
||||
if(this.reversed) collection.reverse();
|
||||
collection.some((item, i) => {
|
||||
ctx[this.variable] = item;
|
||||
ctx.forloop = {
|
||||
first: i === 0,
|
||||
index: i + 1,
|
||||
index0: i,
|
||||
last: i === length - 1,
|
||||
length: length,
|
||||
rindex: length - i,
|
||||
rindex0: length - i - 1,
|
||||
stop: false,
|
||||
skip: false
|
||||
};
|
||||
scope.push(ctx);
|
||||
html += liquid.renderTemplates(this.templates, scope);
|
||||
var breakloop = scope.get('forloop.stop');
|
||||
scope.pop(ctx);
|
||||
|
||||
if (breakloop) return true;
|
||||
});
|
||||
return html;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+2
-1
@@ -5,6 +5,7 @@ module.exports = function(liquid) {
|
||||
liquid.registerTag('if', {
|
||||
|
||||
parse: function(tagToken, remainTokens) {
|
||||
|
||||
this.branches = [];
|
||||
this.elseTemplates = [];
|
||||
|
||||
@@ -34,7 +35,7 @@ module.exports = function(liquid) {
|
||||
render: function(scope, hash) {
|
||||
for (var i = 0; i < this.branches.length; i++) {
|
||||
var branch = this.branches[i];
|
||||
var cond = liquid.evaluate(branch.cond, scope);
|
||||
var cond = Liquid.evalExp(branch.cond, scope);
|
||||
if (Liquid.isTruthy(cond)) {
|
||||
return liquid.renderTemplates(branch.templates, scope);
|
||||
}
|
||||
|
||||
+8
-5
@@ -5,12 +5,15 @@ const error = Liquid.error;
|
||||
module.exports = function(liquid) {
|
||||
|
||||
liquid.registerTag('increment', {
|
||||
render: function(tokens, scope, token, hash) {
|
||||
parse: function(token) {
|
||||
var match = token.args.match(lexical.identifier);
|
||||
if(!match) error(`illegal identifier ${token.args}`, token);
|
||||
var k = match[0], v = scope.get(k);
|
||||
if(typeof v !== 'number') v = 0;
|
||||
scope.set(k, v+1);
|
||||
if (!match) error(`illegal identifier ${token.args}`, token);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ module.exports = function(liquid) {
|
||||
render: function(scope, hash) {
|
||||
for (var i = 0; i < this.branches.length; i++) {
|
||||
var branch = this.branches[i];
|
||||
var cond = liquid.evaluate(branch.cond, scope);
|
||||
var cond = Liquid.evalExp(branch.cond, scope);
|
||||
cond = Liquid.isTruthy(cond);
|
||||
if (i === 0) cond = !cond;
|
||||
if (cond) {
|
||||
|
||||
+6
-1
@@ -26,7 +26,12 @@ module.exports = function(Tag) {
|
||||
var template;
|
||||
if (token.type == 'tag') {
|
||||
if (this.trigger(`tag:${token.name}`, token)) continue;
|
||||
template = parseTag(token, this.tokens);
|
||||
if (token.name === 'continue' || token.name === 'break'){
|
||||
template = token;
|
||||
}
|
||||
else{
|
||||
template = parseTag(token, this.tokens);
|
||||
}
|
||||
} else {
|
||||
template = token;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
const chai = require("chai");
|
||||
const sinonChai = require("sinon-chai");
|
||||
const sinon = require("sinon");
|
||||
const expect = chai.expect;
|
||||
|
||||
chai.use(sinonChai);
|
||||
|
||||
var expression = require('../expression.js');
|
||||
var Scope = require('../scope.js');
|
||||
var evalExp = expression.evalExp;
|
||||
var evalValue = expression.evalValue;
|
||||
|
||||
describe('expression', function() {
|
||||
var scope;
|
||||
|
||||
beforeEach(function() {
|
||||
scope = Scope.factory({
|
||||
one: 1,
|
||||
two: 2,
|
||||
x: 'XXX'
|
||||
});
|
||||
});
|
||||
|
||||
it('should eval literals', function() {
|
||||
expect(evalValue('2.3')).to.equal(2.3);
|
||||
expect(evalValue('"foo"')).to.equal("foo");
|
||||
});
|
||||
|
||||
it('should throw on illegal expression', function() {
|
||||
expect(function() {
|
||||
evalExp('1 contains "x"', scope);
|
||||
}).to.throw();
|
||||
});
|
||||
|
||||
it('should eval variables', function() {
|
||||
expect(evalValue('23', scope)).to.equal(23);
|
||||
expect(evalValue('one', scope)).to.equal(1);
|
||||
expect(evalValue('x', scope)).to.equal('XXX');
|
||||
});
|
||||
|
||||
it('should eval simple expression', function() {
|
||||
expect(evalExp('1<2', scope)).to.equal(true);
|
||||
expect(evalExp('2<=2', scope)).to.equal(true);
|
||||
expect(evalExp('one<=two', scope)).to.equal(true);
|
||||
expect(evalExp('x contains "x"', scope)).to.equal(false);
|
||||
expect(evalExp('x contains "X"', scope)).to.equal(true);
|
||||
expect(evalExp('x contains z', scope)).to.equal(true);
|
||||
expect(evalExp('"<=" == "<="', scope)).to.equal(true);
|
||||
});
|
||||
|
||||
it('should eval complex expression', function() {
|
||||
expect(evalExp('1<2 and x contains "x"', scope)).to.equal(false);
|
||||
expect(evalExp('1<2 or x contains "x"', scope)).to.equal(true);
|
||||
});
|
||||
|
||||
it("should eval range expression", function() {
|
||||
expect(evalExp('(2..4)', scope)).to.deep.equal([2,3,4]);
|
||||
expect(evalExp('(two..4)', scope)).to.deep.equal([2,3,4]);
|
||||
});
|
||||
});
|
||||
+6
-11
@@ -17,6 +17,12 @@ describe('identifier', function() {
|
||||
identifier.isLiteral('23').should.equal(true);
|
||||
});
|
||||
|
||||
it("should test range literal", function() {
|
||||
identifier.isRange("(12..32)").should.equal(true);
|
||||
identifier.isRange("(12..foo)").should.equal(true);
|
||||
identifier.isRange("(foo.bar..foo)").should.equal(true);
|
||||
});
|
||||
|
||||
it('should test string literal', function() {
|
||||
identifier.isLiteral('""').should.equal(true);
|
||||
identifier.isLiteral('"a\'b"').should.equal(true);
|
||||
@@ -24,10 +30,6 @@ describe('identifier', function() {
|
||||
identifier.isLiteral("'a bcd'").should.equal(true);
|
||||
});
|
||||
|
||||
it("should test range literal", function() {
|
||||
identifier.isLiteral("(12..32)").should.equal(true);
|
||||
});
|
||||
|
||||
it("should test variable", function() {
|
||||
identifier.isVariable("foo").should.equal(true);
|
||||
identifier.isVariable("foo.bar.foo").should.equal(true);
|
||||
@@ -65,11 +67,4 @@ describe('identifier', function() {
|
||||
identifier.parseLiteral('"ab\'c"').should.equal("ab\'c");
|
||||
});
|
||||
|
||||
it("should parse range literal", function() {
|
||||
var arr = identifier.parseLiteral("(12..32)");
|
||||
arr.length.should.equal(20);
|
||||
arr[0].should.equal(12);
|
||||
arr[1].should.equal(13);
|
||||
arr[19].should.equal(31);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ var Scope = require('../scope.js');
|
||||
var filter = require('../filter')();
|
||||
var Render = require('../render.js')(filter, tag);
|
||||
var render = Render.renderTemplates;
|
||||
var evalExp = Render.evalExp;
|
||||
var evalFilter = Render.evalFilter;
|
||||
|
||||
describe('render', function() {
|
||||
@@ -18,9 +17,6 @@ describe('render', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
scope = Scope.factory({
|
||||
one: 1,
|
||||
two: 2,
|
||||
x: 'XXX',
|
||||
foo: {
|
||||
bar: ['a', 2]
|
||||
}
|
||||
@@ -51,19 +47,4 @@ describe('render', function() {
|
||||
filter.register('time', (l, r) => l + 3 * r);
|
||||
expect(evalFilter('foo.bar[0] | date: "b" | time:2', scope)).to.equal('ab6');
|
||||
});
|
||||
|
||||
it('should eval expression', function() {
|
||||
expect(evalExp('1<2', scope)).to.equal(true);
|
||||
expect(evalExp('2<=2', scope)).to.equal(true);
|
||||
expect(evalExp('one<=two', scope)).to.equal(true);
|
||||
expect(function() {
|
||||
evalExp('1 contains "x"', scope);
|
||||
}).to.throw();
|
||||
expect(evalExp('x contains "x"', scope)).to.equal(false);
|
||||
expect(evalExp('x contains "X"', scope)).to.equal(true);
|
||||
expect(evalExp('x contains z', scope)).to.equal(true);
|
||||
expect(evalExp('1<2 and x contains "x"', scope)).to.equal(false);
|
||||
expect(evalExp('1<2 or x contains "x"', scope)).to.equal(true);
|
||||
expect(evalExp('"<=" == "<="', scope)).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
+5
-6
@@ -13,12 +13,6 @@ describe('scope', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse literal', function() {
|
||||
scope.get('2.3').should.equal(2.3);
|
||||
scope.get('(2..4)').should.deep.equal([2,3]);
|
||||
scope.get('"foo"').should.equal("foo");
|
||||
});
|
||||
|
||||
it('should get property', function() {
|
||||
scope.get('foo').should.equal('bar');
|
||||
});
|
||||
@@ -28,6 +22,11 @@ describe('scope', function() {
|
||||
scope.get('foo').should.equal('FOO');
|
||||
});
|
||||
|
||||
it('should set child property', function() {
|
||||
scope.set('oo.bar', 'FOO');
|
||||
scope.get('oo.bar').should.equal('FOO');
|
||||
});
|
||||
|
||||
it('should get desendent property', function() {
|
||||
scope.get('bar[0]').should.equal('a');
|
||||
scope.get('bar[1].b').should.deep.equal([1, 2]);
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ describe('tag', function() {
|
||||
type: 'tag',
|
||||
value: 'foo',
|
||||
name: 'foo'
|
||||
}).render(scope);
|
||||
}).render(scope, {});
|
||||
expect(spy).to.have.been.called;
|
||||
});
|
||||
|
||||
@@ -68,7 +68,7 @@ describe('tag', function() {
|
||||
name: 'foo',
|
||||
args: 'aa:foo bb: arr[0] cc: 2.3'
|
||||
};
|
||||
tag.construct(token).render(scope);
|
||||
tag.construct(token).render(scope, {});
|
||||
expect(spy).to.have.been.calledWithMatch(scope, {
|
||||
aa: 'bar',
|
||||
bb: 2,
|
||||
|
||||
+88
-33
@@ -2,7 +2,7 @@ const chai = require("chai");
|
||||
const expect = chai.expect;
|
||||
|
||||
var liquid = require('..')(),
|
||||
ctx;
|
||||
ctx, src, dst;
|
||||
|
||||
function test(src, dst) {
|
||||
expect(liquid.render(src, ctx)).to.equal(dst);
|
||||
@@ -22,30 +22,29 @@ describe('tags', function() {
|
||||
leq: '<=',
|
||||
empty: '',
|
||||
foo: 'bar',
|
||||
arr: [-2, 'a', {
|
||||
foo: 'bar'
|
||||
}],
|
||||
arr: [-2, 'a'],
|
||||
alpha: ['a', 'b', 'c'],
|
||||
emptyArray: []
|
||||
};
|
||||
});
|
||||
//it('should support assign', function() {
|
||||
//test('{% assign foo="bar"%}{{foo}}', 'bar');
|
||||
//});
|
||||
//it('should support case', function() {
|
||||
//testThrow('{% case "foo"%}', /case "foo" not closed/);
|
||||
//test('{% case "foo"%}' +
|
||||
//'{% when "foo" %}foo{% when "bar"%}bar' +
|
||||
//'{%endcase%}', 'foo');
|
||||
//test('{% case empty %}' +
|
||||
//'{% when "foo" %}foo{% when ""%}bar' +
|
||||
//'{%endcase%}', 'bar');
|
||||
//test('{% case false %}' +
|
||||
//'{% when "foo" %}foo{% when ""%}bar' +
|
||||
//'{%endcase%}', '');
|
||||
//test('{% case "a" %}' +
|
||||
//'{% when "b" %}b{% when "c"%}c{%else %}d' +
|
||||
//'{%endcase%}', 'd');
|
||||
//});
|
||||
it('should support assign', function() {
|
||||
test('{% assign foo="bar"%}{{foo}}', 'bar');
|
||||
});
|
||||
it('should support case', function() {
|
||||
testThrow('{% case "foo"%}', /{% case "foo"%} not closed/);
|
||||
test('{% case "foo"%}' +
|
||||
'{% when "foo" %}foo{% when "bar"%}bar' +
|
||||
'{%endcase%}', 'foo');
|
||||
test('{% case empty %}' +
|
||||
'{% when "foo" %}foo{% when ""%}bar' +
|
||||
'{%endcase%}', 'bar');
|
||||
test('{% case false %}' +
|
||||
'{% when "foo" %}foo{% when ""%}bar' +
|
||||
'{%endcase%}', '');
|
||||
test('{% case "a" %}' +
|
||||
'{% when "b" %}b{% when "c"%}c{%else %}d' +
|
||||
'{%endcase%}', 'd');
|
||||
});
|
||||
|
||||
it('should support if', function() {
|
||||
testThrow('{% if false%}yes', /tag {% if false%} not closed/);
|
||||
@@ -68,17 +67,73 @@ describe('tags', function() {
|
||||
testThrow('{% capture = %}{%endcapture%}', /= not valid identifier/);
|
||||
});
|
||||
|
||||
//it('should support for', function() {
|
||||
//test('{%for i in arr%}{{"a" | capitalize}}{%endcapture%}{{f}}', 'A');
|
||||
//});
|
||||
it('should support for', function() {
|
||||
test('{%for c in alpha%}{{c}}{%endfor%}', 'abc');
|
||||
});
|
||||
|
||||
//it('should support increment', function() {
|
||||
//test('{% increment foo %}{%increment foo%}{{foo}}', '2');
|
||||
//test('{% increment one %}{{one}}', '2');
|
||||
//});
|
||||
it('should support for with forloop', function() {
|
||||
src = '{%for c in alpha%}' +
|
||||
'{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' +
|
||||
'{{forloop.last}}.{{forloop.length}}.' +
|
||||
'{{forloop.rindex}}.{{forloop.rindex0}}' +
|
||||
'{{c}}\n' +
|
||||
'{%endfor%}';
|
||||
dst = 'true.1.0.false.3.3.2a\n' +
|
||||
'false.2.1.false.3.2.1b\n' +
|
||||
'false.3.2.true.3.1.0c\n';
|
||||
test(src, dst);
|
||||
});
|
||||
|
||||
//it('should support decrement', function() {
|
||||
//test('{% decrement foo %}{%decrement foo%}{{foo}}', '-2');
|
||||
//test('{% decrement one %}{{one}}', '0');
|
||||
//});
|
||||
it('should support for with continue and break', function() {
|
||||
src = '{% for i in (1..5) %}' +
|
||||
'{% if i == 4 %}{% continue %}' +
|
||||
'{% else %}{{ i }}' +
|
||||
'{% endif %}' +
|
||||
'{% endfor %}';
|
||||
test(src, '1235');
|
||||
src = '{% for i in (one..5) %}' +
|
||||
'{% if i == 4 %}{% break %}{% endif %}' +
|
||||
'{{ i }}' +
|
||||
'{% endfor %}';
|
||||
test(src, '123');
|
||||
});
|
||||
|
||||
it('should support for with limit and offset', function() {
|
||||
src = '{% for i in (1..5) limit:2 %}{{ i }}{% endfor %}';
|
||||
test(src, '12');
|
||||
src = '{% for i in (1..10) limit:2 offset:5%}{{ i }}{% endfor %}';
|
||||
test(src, '67');
|
||||
});
|
||||
|
||||
it('should support for reversed', function() {
|
||||
src = '{% for i in (1..5) limit:2 reversed %}{{ i }}{% endfor %}';
|
||||
test(src, '21');
|
||||
});
|
||||
|
||||
it('should support cycle', function() {
|
||||
src = "{% cycle '1', '2', '3' %}";
|
||||
test(src + src + src + src, '1231');
|
||||
});
|
||||
|
||||
it('should support cycle in for block', function() {
|
||||
src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}';
|
||||
test(src, '1e1e1');
|
||||
});
|
||||
|
||||
it('should support cycle group', function() {
|
||||
src = "{% cycle one: '1', '2', '3'%}" +
|
||||
"{% cycle 1: '1', '2', '3'%}" +
|
||||
"{% cycle 2: '1', '2', '3'%}";
|
||||
test(src, '121');
|
||||
});
|
||||
|
||||
it('should support increment', function() {
|
||||
test('{% increment foo %}{%increment foo%}{{foo}}', '2');
|
||||
test('{% increment one %}{{one}}', '2');
|
||||
});
|
||||
|
||||
it('should support decrement', function() {
|
||||
test('{% decrement foo %}{%decrement foo%}{{foo}}', '-2');
|
||||
test('{% decrement one %}{{one}}', '0');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user