liquid instance

This commit is contained in:
harttle
2016-06-15 02:04:30 +08:00
parent d29558035c
commit 51b2406187
11 changed files with 178 additions and 124 deletions
+32 -30
View File
@@ -1,46 +1,48 @@
const lexical = require('./lexical.js');
const _ = require('lodash');
var filters = {};
var _filterInstance = {
render: function(output, ctx) {
var args = this.args.map(arg => ctx.get(arg));
args.unshift(output);
return this.filter.apply(null, args);
}
}
};
function parse(str) {
var match = lexical.patterns.filterLine.exec(str.trim());
if (!match) {
throw new Error('illegal filter: ' + str);
module.exports = function() {
var filters = {};
function parse(str) {
var match = lexical.patterns.filterLine.exec(str.trim());
if (!match) {
throw new Error('illegal filter: ' + str);
}
var k = match[1],
v = match[2];
return factory(k, [v]);
}
var k = match[1],
v = match[2];
return factory(k, [v]);
}
function factory(name, args) {
var filter = filters[name];
if (typeof filter !== 'function')
throw new Error(`filter ${name} not found`);
function factory(name, args) {
var filter = filters[name];
if (typeof filter !== 'function')
throw new Error(`filter ${name} not found`);
var instance = Object.create(_filterInstance);
instance.args = args;
instance.filter = filter;
return instance;
}
var instance = Object.create(_filterInstance);
instance.args = args;
instance.filter = filter;
return instance;
}
function register(name, filter) {
filters[name] = filter;
}
function register(name, filter) {
filters[name] = filter;
}
function clear() {
filters = {};
}
function clear() {
filters = {};
}
exports.parse = parse;
exports.register = register;
exports.clear = clear;
return {
parse, register, clear
};
};
+1
View File
@@ -0,0 +1 @@
module.exports = v => Math.abs(v);
+39 -3
View File
@@ -2,9 +2,45 @@ const context = require('./context');
const tokenizer = require('./tokenizer.js');
const render = require('./render.js');
const lexical = require('./lexical.js');
const path = require("path");
const fs = require('fs');
const Tag = require('./tag.js');
const Filter = require('./filter.js');
exports.render = function(html, ctx){
return render(tokenizer(html), context.factory(ctx));
const tagsPath = path.join(__dirname, "tags");
const filtersPath = path.join(__dirname, "filters");
var _engine = {
render: function(html, ctx) {
return this.doRender(tokenizer.parse(html), context.factory(ctx));
},
lexical,
registerFilter : function(name, filter){
return this.filter.register(name, filter);
},
registerTag : function(name, tag){
return this.filter.register(name, tag);
}
};
exports.lexical = lexical;
function factory(){
var engine = Object.create(_engine);
engine.tag = Tag();
engine.filter = Filter();
engine.doRender = render(engine.filter, engine.tag);
fs.readdirSync(tagsPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
if(!match) return;
engine.tag.register(match[1], require("./tags/" + f));
});
fs.readdirSync(filtersPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
if(!match) return;
engine.filter.register(match[1], require("./filters/" + f));
});
return engine;
}
module.exports = factory;
+1 -1
View File
@@ -3,7 +3,7 @@ const _ = require('lodash');
var singleQuoted = /'[^']*'/;
var doubleQuoted = /"[^"]*"/;
var number = /\d+\.?\d*|\.?\d+/;
var number = /-?\d+\.?\d*|\.?\d+/;
var bool = /true|false/i;
var range = /\((\d+)\.\.(\d+)\)/;
var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/;
+41 -43
View File
@@ -1,46 +1,44 @@
const Filter = require('./filter');
const Tag = require('./tag');
module.exports = function render(tokens, ctx) {
var html = '';
for (var i = 0; i < tokens.length; i++) {
var token = tokens.shift();
switch (token.type) {
case 'html':
html += token.value;
break;
case 'output':
html += renderOutput(token);
break;
case 'tag':
html += renderTag(token);
break;
default:
throw new Error(`unexpected type: ${token.type}`);
}
}
return html;
function renderOutput(token) {
var filters = token.value.split('|');
var val = ctx.get(filters.shift());
return filters
.map(str => Filter.parse(str))
.reduce((v, filter) => filter.render(v, ctx), val);
}
function renderTag(token) {
var tag = Tag.parse(token.value), subTokens = [];
if(tag.needClose){
var curToken, endToken = 'end' + tag.name;
while((curToken = tokens.shift()).value !== endToken){
subTokens.push(curToken);
}
if(curToken.value !== endToken){
throw new Error(`${token.value} not closed`);
module.exports = (Filter, Tag) =>
function render(tokens, ctx) {
var html = '';
for (var i = 0; i < tokens.length; i++) {
var token = tokens.shift();
switch (token.type) {
case 'html':
html += token.value;
break;
case 'output':
html += renderOutput(token);
break;
case 'tag':
html += renderTag(token);
break;
default:
throw new Error(`unexpected type: ${token.type}`);
}
}
return tag.render(subTokens, ctx);
}
};
return html;
function renderOutput(token) {
var filters = token.value.split('|');
var val = ctx.get(filters.shift());
return filters
.map(str => Filter.parse(str))
.reduce((v, filter) => filter.render(v, ctx), val);
}
function renderTag(token) {
var tag = Tag.parse(token.value),
subTokens = [];
if (tag.needClose) {
var curToken, endToken = 'end' + tag.name;
while ((curToken = tokens.shift()).value !== endToken) {
subTokens.push(curToken);
}
if (curToken.value !== endToken) {
throw new Error(`${token.value} not closed`);
}
}
return tag.render(subTokens, ctx);
}
};
+41 -40
View File
@@ -1,57 +1,58 @@
const lexical = require('./lexical.js');
const context = require('./context.js');
var tags = {};
var _tagInstance = {
render: function(tokens, ctx){
render: function(tokens, ctx) {
var obj = hash(this.markup, ctx);
return this.tag.render(tokens, ctx, this.markup, obj);
}
};
function register(name, tag){
if(typeof tag.render !== 'function'){
throw new Error(`expect ${name}.render to be a 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 hash(markup, ctx){
function hash(markup, ctx) {
var obj = {};
lexical.patterns.hash.lastIndex = 0;
while(match = lexical.patterns.hash.exec(markup)){
var k = match[1], v = match[2];
if(!k) continue;
while (match = lexical.patterns.hash.exec(markup)) {
var k = match[1],
v = match[2];
if (!k) continue;
obj[k] = ctx.get(v);
}
return obj;
}
function factory(name, markup){
var tag = tags[name];
if(!tag) throw new Error(`tag ${name} not found`);
var instance = Object.create(_tagInstance);
instance.name = name;
instance.markup = markup;
instance.tag = tag;
return instance;
}
module.exports = function() {
var tags = {};
function clear(){
tags = {};
}
function register(name, tag) {
if (typeof tag.render !== 'function') {
throw new Error(`expect ${name}.render to be a function`);
}
tags[name] = tag;
}
exports.parse = parse;
exports.register = register;
exports.hash = hash;
exports.clear = clear;
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`);
var instance = Object.create(_tagInstance);
instance.name = name;
instance.markup = markup;
instance.tag = tag;
return instance;
}
function clear() {
tags = {};
}
return {
parse, register, hash, clear
};
};
+1 -1
View File
@@ -5,7 +5,7 @@ const expect = chai.expect;
chai.use(sinonChai);
var filter = require('../filter.js');
var filter = require('../filter.js')();
var context = require('../context.js');
describe('filter', function() {
+16
View File
@@ -0,0 +1,16 @@
const chai = require("chai");
const expect = chai.expect;
var liquid = require('..')();
describe('filters', function() {
var ctx = {
foo: 'bar',
arr: [-2, 'a']
};
it('should support abs', function() {
expect(liquid.render('{{ -3 | abs }}', ctx)).to.equal('3');
expect(liquid.render('{{ arr[0] | abs }}', ctx)).to.equal('2');
});
});
+2 -2
View File
@@ -14,7 +14,7 @@ describe('identifier', function() {
it('should test number literal', function() {
identifier.isLiteral('2.3').should.equal(true);
identifier.isLiteral('.3').should.equal(true);
identifier.isLiteral('3.').should.equal(true);
identifier.isLiteral('-3.').should.equal(true);
identifier.isLiteral('23').should.equal(true);
});
@@ -58,7 +58,7 @@ describe('identifier', function() {
it('should parse number literal', function() {
identifier.parseLiteral('2.3').should.equal(2.3);
identifier.parseLiteral('.32').should.equal(0.32);
identifier.parseLiteral('23.').should.equal(23);
identifier.parseLiteral('-23.').should.equal(-23);
identifier.parseLiteral('23').should.equal(23);
});
+3 -3
View File
@@ -5,10 +5,10 @@ const expect = chai.expect;
chai.use(sinonChai);
var tag = require('../tag.js');
var tag = require('../tag.js')();
var context = require('../context.js');
var filter = require('../filter');
var render = require('../render.js');
var filter = require('../filter')();
var render = require('../render.js')(filter, tag);
describe('render', function() {
var ctx, htmlToken, tagToken, filterToken;
+1 -1
View File
@@ -4,7 +4,7 @@ var sinon = require("sinon");
var expect = chai.expect;
chai.use(sinonChai);
var tag = require('../tag.js');
var tag = require('../tag.js')();
var context = require('../context.js');
describe('tag', function() {