case/when

This commit is contained in:
harttle
2016-06-15 14:55:00 +08:00
parent 611882ae10
commit d5ecce9c8b
12 changed files with 98 additions and 41 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ shall be implemented.
Documentation: <https://shopify.github.io/liquid/basics/introduction/#tags>
- [x] [assign](https://shopify.github.io/liquid/tags/control-flow/)
- [ ] [case/when](https://shopify.github.io/liquid/tags/control-flow/)
- [x] [case/when](https://shopify.github.io/liquid/tags/control-flow/)
- [ ] [if](https://shopify.github.io/liquid/tags/control-flow/)
- [ ] [unless](https://shopify.github.io/liquid/tags/control-flow/)
- [ ] [elsif/else](https://shopify.github.io/liquid/tags/control-flow/)
+2 -2
View File
@@ -12,7 +12,7 @@ var _filterInstance = {
module.exports = function() {
var filters = {};
function parse(str) {
function construct(str) {
var match = lexical.patterns.filterLine.exec(str.trim());
if (!match) {
throw new Error('illegal filter: ' + str);
@@ -43,6 +43,6 @@ module.exports = function() {
}
return {
parse, register, clear
construct, register, clear
};
};
+4 -3
View File
@@ -24,11 +24,12 @@ function factory(){
engine.tag = Tag();
engine.filter = Filter();
var render = Render(engine.filter, engine.tag);
var renderer = Render(engine.filter, engine.tag);
engine.evaluate = renderer.evaluate;
engine.renderTokens = renderer.render;
engine.render = function(html, ctx) {
return render.render(tokenizer.parse(html), context.factory(ctx));
return engine.renderTokens(tokenizer.parse(html), context.factory(ctx));
},
engine.evaluate = render.evaluate;
fs.readdirSync(tagsPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
+1 -1
View File
@@ -25,7 +25,7 @@ var rangeLine = new RegExp(`^(?:${range.source})$`);
var filterLine = new RegExp(`^(?:${filter.source})$`);
exports.patterns = {
quoted, number, bool, range, literal, hash, filter, identifier,
quoted, number, bool, range, literal, hash, filter, identifier, value,
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine
};
+5 -4
View File
@@ -21,22 +21,23 @@ module.exports = function(Filter, Tag) {
}
function evaluate(str, ctx) {
if(!ctx) throw new Error('ctx is needed to evaluate');
var filters = str.split('|');
var val = ctx.get(filters.shift());
return filters
.map(str => Filter.parse(str))
.map(str => Filter.construct(str))
.reduce((v, filter) => filter.render(v, ctx), val);
}
function renderTag(token, tokens, ctx) {
var tag = Tag.parse(token.value),
var tag = Tag.construct(token),
subTokens = [];
if (tag.needClose) {
var curToken, endToken = 'end' + tag.name;
while ((curToken = tokens.shift()).value !== endToken) {
while ((curToken = tokens.shift()) && curToken.value !== endToken) {
subTokens.push(curToken);
}
if (curToken.value !== endToken) {
if (!curToken) {
throw new Error(`${token.value} not closed`);
}
}
+7 -14
View File
@@ -30,22 +30,15 @@ module.exports = function() {
tags[name] = tag;
}
function parse(str) {
var match = lexical.patterns.identifier.exec(str.trim());
if (!match) throw new Error('illegal tag: ' + str);
var tagInstance = factory(match[0], str);
return tagInstance;
}
function factory(name, markup) {
var tag = tags[name];
if (!tag) throw new Error(`tag ${name} not found`);
function construct(token) {
var tag = tags[token.name];
if (!tag) throw new Error(`tag ${token.name} not found`);
var instance = Object.create(_tagInstance);
instance.name = name;
instance.markup = markup;
instance.name = token.name;
instance.markup = token.value;
instance.tag = tag;
instance.needClose = tag.needClose;
return instance;
}
@@ -54,6 +47,6 @@ module.exports = function() {
}
return {
parse, register, hash, clear
construct, register, hash, clear
};
};
+34
View File
@@ -0,0 +1,34 @@
var Liquid = require('..');
var patterns = Liquid.lexical.patterns;
var caseRE = new RegExp(`^\\s*case\\s+(${patterns.value.source})`);
var whenRE = new RegExp(`^\\s*when\\s+(${patterns.value.source})`);
module.exports = function(liquid) {
liquid.registerTag('case', {
needClose: true,
render: function(tokens, ctx, markup, hash) {
var match = markup.match(caseRE);
var cond = liquid.evaluate(match[1], ctx);
var partialTokens = [],
matching = false;
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
if (token.type === 'tag' && token.name === 'when') {
if (matching) break;
match = token.value.match(whenRE);
if(!match) continue;
var val = liquid.evaluate(match[1], ctx);
if (val === cond) matching = true;
} else if (token.type === 'tag' && token.name === 'else') {
if (matching) break;
else matching = true;
} else if (matching) partialTokens.push(token);
}
return liquid.renderTokens(partialTokens, ctx);
}
});
};
+3 -3
View File
@@ -16,19 +16,19 @@ describe('filter', function() {
});
it('should throw when not registered', function() {
expect(function() {
filter.parse('foo');
filter.construct('foo');
}).to.throw(/filter foo not found/);
});
it('should register a simple filter', function(){
filter.register('foo', x => x.toUpperCase());
expect(filter.parse('foo').render('foo', ctx)).to.equal('FOO');
expect(filter.construct('foo').render('foo', ctx)).to.equal('FOO');
});
it('should call filter with corrct arguments', function(){
var spy = sinon.spy();
filter.register('foo', spy);
filter.parse('foo: 33').render('foo', ctx);
filter.construct('foo: 33').render('foo', ctx);
expect(spy).to.have.been.calledWith('foo', 33);
});
});
+2 -1
View File
@@ -22,7 +22,8 @@ describe('render', function() {
});
tagToken = {
type: 'tag',
value: 'foo bar:x foo:"FOO" num:2.3'
value: 'foo bar:x foo:"FOO" num:2.3',
name: 'foo'
};
htmlToken = {
type: 'html',
+16 -4
View File
@@ -9,7 +9,7 @@ var context = require('../context.js');
describe('tag', function() {
var ctx;
before(function(){
before(function() {
ctx = context.factory({
foo: 'bar',
arr: [2, 1]
@@ -19,7 +19,11 @@ describe('tag', function() {
it('should throw when not registered', function() {
expect(function() {
tag.parse('foo');
tag.construct({
type: 'tag',
value: 'foo',
name: 'foo'
});
}).to.throw(/tag foo not found/);
});
@@ -44,7 +48,11 @@ describe('tag', function() {
tag.register('foo', {
render: spy
});
tag.parse('foo').render(tokens, ctx);
tag.construct({
type: 'tag',
value: 'foo',
name: 'foo'
}).render(tokens, ctx);
expect(spy).to.have.been.called;
});
@@ -54,7 +62,11 @@ describe('tag', function() {
tag.register('foo', {
render: spy
});
var t = tag.parse('foo aa:foo bb: arr[0] cc: 2.3');
var t = tag.construct({
type: 'tag',
value: 'foo aa:foo bb: arr[0] cc: 2.3',
name: 'foo'
});
t.render(tokens, ctx);
expect(spy).to.have.been.calledWithMatch(tokens, ctx, 'foo', {
aa: 'bar',
+19 -4
View File
@@ -4,16 +4,31 @@ const expect = chai.expect;
var liquid = require('..')();
var ctx = {
empty: '',
foo: 'bar',
arr: [-2, 'a']
};
describe('tags', function() {
it('should support assign', function() {
test('{% assign foo="bar"%}{{foo}}', 'bar');
expect(liquid.render('{% assign foo="bar"%}{{foo}}', ctx)).to.equal('bar');
});
it('should support case', function() {
expect(function() {
liquid.render('{% case "foo"%}');
}).to.throw(/case "foo" not closed/);
expect(liquid.render('{% case "foo"%}' +
'{% when "foo" %}foo{% when "bar"%}bar' +
'{%endcase%}', ctx)).to.equal('foo');
expect(liquid.render('{% case empty %}' +
'{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}')).to.equal('bar');
expect(liquid.render('{% case false %}' +
'{% when "foo" %}foo{% when ""%}bar' +
'{%endcase%}')).to.equal('');
expect(liquid.render('{% case "a" %}' +
'{% when "b" %}b{% when "c"%}c{%else %}d' +
'{%endcase%}')).to.equal('d');
});
});
function test(src, dst){
expect(liquid.render(src, ctx)).to.equal(dst);
}
+4 -4
View File
@@ -1,3 +1,4 @@
const lexical = require('./lexical.js');
function parse(html){
var tokens = [];
@@ -12,21 +13,21 @@ function parse(html){
htmlFragment = html.slice(idx, result.index);
tokens.push({
type: 'html',
raw: htmlFragment,
value: htmlFragment
});
}
if(result[1]){
var match = result[1].trim().match(lexical.patterns.identifier);
if (!match) throw new Error('illegal tag: ' + result[1]);
tokens.push({
type: 'tag',
raw: result[1],
name: match[0],
value: result[2].trim()
});
}
else{
tokens.push({
type: 'output',
raw: result[3],
value: result[4].trim()
});
}
@@ -37,7 +38,6 @@ function parse(html){
htmlFragment = html.slice(idx, html.length);
tokens.push({
type: 'html',
raw: htmlFragment,
value: htmlFragment
});
}