This commit is contained in:
harttle
2016-06-19 00:46:29 +08:00
parent 4595e2872e
commit 1bc6d9355a
7 changed files with 57 additions and 58 deletions
+4 -4
View File
@@ -6,12 +6,12 @@ var doubleQuoted = /"[^"]*"/;
var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`); var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
var number = /(?:-?\d+\.?\d*|\.?\d+)/; var number = /(?:-?\d+\.?\d*|\.?\d+)/;
var bool = /true|false/i; var bool = /true|false/;
var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/; var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/;
var subscript = /\[\d+\]/; var subscript = /\[\d+\]/;
var quoted = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source})`); var quoted = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source})`);
var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`, 'i'); var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`);
var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`); var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`);
// range related // range related
@@ -19,10 +19,10 @@ var rangeLimit = new RegExp(`(?:${variable.source}|${number.source})`);
var range = new RegExp(`\\(${rangeLimit.source}\\.\\.${rangeLimit.source}\\)`); var range = new RegExp(`\\(${rangeLimit.source}\\.\\.${rangeLimit.source}\\)`);
var rangeCapture = new RegExp(`\\((${rangeLimit.source})\\.\\.(${rangeLimit.source})\\)`); var rangeCapture = new RegExp(`\\((${rangeLimit.source})\\.\\.(${rangeLimit.source})\\)`);
var value = new RegExp(`(?:${literal.source}|${variable.source}|${range.source})`, 'i'); var value = new RegExp(`(?:${literal.source}|${variable.source}|${range.source})`);
// hash related // hash related
var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`, 'g'); var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`);
var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g'); var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
var tagLine = new RegExp(`^\\s*(${identifier.source})\\s*(.*)\\s*$`); var tagLine = new RegExp(`^\\s*(${identifier.source})\\s*(.*)\\s*$`);
+23 -23
View File
@@ -2,21 +2,6 @@ const lexical = require('./lexical.js');
const Exp = require('./expression.js'); const Exp = require('./expression.js');
const TokenizationError = require('./error.js').TokenizationError; const TokenizationError = require('./error.js').TokenizationError;
var _tagInstance = {
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, reg) || '';
},
parse: function(tokens){
if(this.tagImpl.parse){
this.tagImpl.parse(this.token, tokens);
}
return this;
}
};
function hash(markup, scope) { function hash(markup, scope) {
var obj = {}; var obj = {};
lexical.hashCapture.lastIndex = 0; lexical.hashCapture.lastIndex = 0;
@@ -31,6 +16,27 @@ function hash(markup, scope) {
module.exports = function() { module.exports = function() {
var tagImpls = {}; var tagImpls = {};
var _tagInstance = {
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, reg) || '';
},
parse: function(token, tokens){
this.type = 'tag';
this.token = token;
this.name = token.name;
var tagImpl = tagImpls[this.name];
if (!tagImpl) throw new Error(`tag ${this.name} not found`);
this.tagImpl = Object.create(tagImpl);
if(this.tagImpl.parse){
this.tagImpl.parse(token, tokens);
}
}
};
function register(name, tag) { function register(name, tag) {
if (typeof tag.render !== 'function') { if (typeof tag.render !== 'function') {
throw new Error(`expect ${name}.render to be a function`); throw new Error(`expect ${name}.render to be a function`);
@@ -38,15 +44,9 @@ module.exports = function() {
tagImpls[name] = tag; tagImpls[name] = tag;
} }
function construct(token) { function construct(token, tokens) {
var tagImpl = tagImpls[token.name];
if (!tagImpl) throw new Error(`tag ${token.name} not found`);
var instance = Object.create(_tagInstance); var instance = Object.create(_tagInstance);
instance.token = token; instance.parse(token, tokens);
instance.type = 'tag';
instance.name = token.name;
instance.tagImpl = Object.create(tagImpl);
return instance; return instance;
} }
+1 -1
View File
@@ -20,7 +20,7 @@ module.exports = function(liquid) {
} }
if (!this.candidates.length){ if (!this.candidates.length){
throw new Error(`illegal tag: ${tagToken.raw}`); throw new Error(`empty candidates: ${tagToken.raw}`);
} }
}, },
+7 -26
View File
@@ -3,23 +3,11 @@ var lexical = Liquid.lexical;
module.exports = function(liquid) { module.exports = function(liquid) {
liquid.registerTag('unless', { liquid.registerTag('unless', {
parse: function(tagToken, remainTokens) { parse: function(tagToken, remainTokens) {
this.branches = [];
this.elseTemplates = [];
var p, stream = liquid.parseStream(remainTokens) var p, stream = liquid.parseStream(remainTokens)
.onStart(x => this.branches.push({ .onStart(x => {
cond: tagToken.args, p = this.templates = [];
templates: p = [] this.cond = tagToken.args;
}))
.onTag('elsif', token => {
if (!this.branches[token.args]) {
this.branches.push({
cond: token.args,
templates: p = []
});
}
}) })
.onTag('else', token => this.elseTemplates = p = []) .onTag('else', token => this.elseTemplates = p = [])
.onTag('endunless', token => stream.stop()) .onTag('endunless', token => stream.stop())
@@ -32,17 +20,10 @@ module.exports = function(liquid) {
}, },
render: function(scope, hash) { render: function(scope, hash) {
for (var i = 0; i < this.branches.length; i++) { var cond = Liquid.evalExp(this.cond, scope);
var branch = this.branches[i]; return Liquid.isFalsy(cond) ?
var cond = Liquid.evalExp(branch.cond, scope); liquid.renderTemplates(this.templates, scope) :
cond = Liquid.isTruthy(cond); liquid.renderTemplates(this.elseTemplates, scope);
if (i === 0) cond = !cond;
if (cond) {
return liquid.renderTemplates(branch.templates, scope);
}
}
return liquid.renderTemplates(this.elseTemplates, scope);
} }
}); });
}; };
+1 -1
View File
@@ -77,7 +77,7 @@ module.exports = function(Tag, Filter) {
function parseTag(token, tokens) { function parseTag(token, tokens) {
if (token.name === 'continue' || token.name === 'break') return token; if (token.name === 'continue' || token.name === 'break') return token;
return Tag.construct(token).parse(tokens); return Tag.construct(token, tokens);
} }
function parseOutput(str) { function parseOutput(str) {
+3 -3
View File
@@ -23,7 +23,7 @@ describe('tag', function() {
type: 'tag', type: 'tag',
value: 'foo', value: 'foo',
name: 'foo' name: 'foo'
}); }, []);
}).to.throw(/tag foo not found/); }).to.throw(/tag foo not found/);
}); });
@@ -52,7 +52,7 @@ describe('tag', function() {
type: 'tag', type: 'tag',
value: 'foo', value: 'foo',
name: 'foo' name: 'foo'
}).render(scope, {}); }, []).render(scope, {});
expect(spy).to.have.been.called; expect(spy).to.have.been.called;
}); });
@@ -68,7 +68,7 @@ describe('tag', function() {
name: 'foo', name: 'foo',
args: 'aa:foo bb: arr[0] cc: 2.3' 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, { expect(spy).to.have.been.calledWithMatch(scope, {
aa: 'bar', aa: 'bar',
bb: 2, bb: 2,
+18
View File
@@ -27,11 +27,13 @@ describe('tags', function() {
emptyArray: [] emptyArray: []
}; };
}); });
it('should support assign', function() { it('should support assign', function() {
test('{% assign foo="bar" %}{{foo}}', 'bar'); test('{% assign foo="bar" %}{{foo}}', 'bar');
test('{% assign foo=(1..3) %}{{foo}}', '[1,2,3]'); test('{% assign foo=(1..3) %}{{foo}}', '[1,2,3]');
test('{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}', 'A'); test('{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}', 'A');
}); });
it('should support case', function() { it('should support case', function() {
testThrow('{% case "foo"%}', /{% case "foo"%} not closed/); testThrow('{% case "foo"%}', /{% case "foo"%} not closed/);
test('{% case "foo"%}' + test('{% case "foo"%}' +
@@ -69,10 +71,22 @@ describe('tags', function() {
testThrow('{% capture = %}{%endcapture%}', /= not valid identifier/); testThrow('{% capture = %}{%endcapture%}', /= not valid identifier/);
}); });
it('should throw when for capture closed', function() {
testThrow('{%capture c%}{{c}}', /tag .* not closed/);
});
it('should support for', function() { it('should support for', function() {
test('{%for c in alpha%}{{c}}{%endfor%}', 'abc'); test('{%for c in alpha%}{{c}}{%endfor%}', 'abc');
}); });
it('should throw when for not closed', function() {
testThrow('{%for c in alpha%}{{c}}', /tag .* not closed/);
});
it('should support for else', function() {
test('{%for c in ""%}a{%else%}b{%endfor%}', 'b');
});
it('should support for with forloop', function() { it('should support for with forloop', function() {
src = '{%for c in alpha%}' + src = '{%for c in alpha%}' +
'{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' + '{{forloop.first}}.{{forloop.index}}.{{forloop.index0}}.' +
@@ -117,6 +131,10 @@ describe('tags', function() {
test(src + src + src + src, '1231'); test(src + src + src + src, '1231');
}); });
it('should throw when cycle candidates empty', function() {
testThrow('{%cycle%}', /empty candidates/);
});
it('should support cycle in for block', function() { it('should support cycle in for block', function() {
src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}'; src = '{% for i in (1..5) %}{% cycle one, "e"%}{% endfor %}';
test(src, '1e1e1'); test(src, '1e1e1');