From 51b240618713ea7ebbf95cf73fe4cd8db7ca51a6 Mon Sep 17 00:00:00 2001 From: harttle Date: Wed, 15 Jun 2016 02:04:30 +0800 Subject: [PATCH] liquid instance --- filter.js | 62 ++++++++++++++++++------------------ filters/abs.js | 1 + index.js | 42 +++++++++++++++++++++++-- lexical.js | 2 +- render.js | 84 ++++++++++++++++++++++++------------------------- tag.js | 81 ++++++++++++++++++++++++----------------------- test/filter.js | 2 +- test/filters.js | 16 ++++++++++ test/lexical.js | 4 +-- test/render.js | 6 ++-- test/tag.js | 2 +- 11 files changed, 178 insertions(+), 124 deletions(-) create mode 100644 filters/abs.js create mode 100644 test/filters.js diff --git a/filter.js b/filter.js index 595208bd0..59359dddd 100644 --- a/filter.js +++ b/filter.js @@ -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 + }; +}; diff --git a/filters/abs.js b/filters/abs.js new file mode 100644 index 000000000..2303ea5a3 --- /dev/null +++ b/filters/abs.js @@ -0,0 +1 @@ +module.exports = v => Math.abs(v); diff --git a/index.js b/index.js index 91a9ee3fd..7d08816aa 100644 --- a/index.js +++ b/index.js @@ -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; diff --git a/lexical.js b/lexical.js index f309d1e4b..613be6bf5 100644 --- a/lexical.js +++ b/lexical.js @@ -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]*/; diff --git a/render.js b/render.js index cc0c77c30..5a2b73406 100644 --- a/render.js +++ b/render.js @@ -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); + } + }; diff --git a/tag.js b/tag.js index 8787d455f..81ebc7222 100644 --- a/tag.js +++ b/tag.js @@ -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 + }; +}; diff --git a/test/filter.js b/test/filter.js index 5f4fe9e18..e79433dee 100644 --- a/test/filter.js +++ b/test/filter.js @@ -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() { diff --git a/test/filters.js b/test/filters.js new file mode 100644 index 000000000..0c60f09be --- /dev/null +++ b/test/filters.js @@ -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'); + }); +}); diff --git a/test/lexical.js b/test/lexical.js index 01cc44c9f..bb681f0e2 100644 --- a/test/lexical.js +++ b/test/lexical.js @@ -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); }); diff --git a/test/render.js b/test/render.js index 5e63ac044..ad4e06726 100644 --- a/test/render.js +++ b/test/render.js @@ -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; diff --git a/test/tag.js b/test/tag.js index 0c2003996..a56057b43 100644 --- a/test/tag.js +++ b/test/tag.js @@ -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() {