assign tag, abs filter

This commit is contained in:
harttle
2016-06-15 03:21:35 +08:00
parent 51b2406187
commit 611882ae10
10 changed files with 176 additions and 36 deletions
+85
View File
@@ -1,3 +1,85 @@
# shopify-liquid
[![NPM version](https://img.shields.io/npm/v/shopify-liquid.svg?style=flat)](https://www.npmjs.org/package/shopify-liquid)
[![Build Status](https://travis-ci.org/harttle/shopify-liquid.svg?branch=master)](https://travis-ci.org/harttle/shopify-liquid)
[![Coverage Status](https://coveralls.io/repos/github/harttle/shopify-liquid/badge.svg?branch=master)](https://coveralls.io/github/harttle/shopify-liquid?branch=master)
[![Dependency manager](https://david-dm.org/harttle/shopify-liquid.png)](https://david-dm.org/harttle/shopify-liquid)
[Liquid][shopify-liquid] implementation for Node.js.
All tags and filters listed in [Shopify Documentation][shopify-liquid]
shall be implemented.
> Shopify liquid is used by [Jekyll][jekyll] and [Github Pages][gh].
## Tags
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/)
- [ ] [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/)
- [ ] [for](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [break](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [continue](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [for: limit,offset,range,reversed)](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [cycle](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [cycle: group)](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [tablerow](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [tablerow: cols,limit,offset,range](https://shopify.github.io/liquid/tags/iteration/)
- [ ] [assign](https://shopify.github.io/liquid/tags/variable/)
- [ ] [capture](https://shopify.github.io/liquid/tags/variable/)
- [ ] [increment](https://shopify.github.io/liquid/tags/variable/)
- [ ] [decrement](https://shopify.github.io/liquid/tags/variable/)
## Filters
Documentation: <https://shopify.github.io/liquid/basics/introduction/#filters>
- [x] [abs](https://shopify.github.io/liquid/filters/abs/)
- [ ] [append](https://shopify.github.io/liquid/filters/append)
- [ ] [capitalize](https://shopify.github.io/liquid/filters/capitalize)
- [ ] [ceil](https://shopify.github.io/liquid/filters/ceil)
- [ ] [date](https://shopify.github.io/liquid/filters/date)
- [ ] [default](https://shopify.github.io/liquid/filters/default)
- [ ] [divided_by](https://shopify.github.io/liquid/filters/divided_by)
- [ ] [downcase](https://shopify.github.io/liquid/filters/downcase)
- [ ] [escape](https://shopify.github.io/liquid/filters/escape)
- [ ] [escape_once](https://shopify.github.io/liquid/filters/escape_once)
- [ ] [first](https://shopify.github.io/liquid/filters/first)
- [ ] [floor](https://shopify.github.io/liquid/filters/floor)
- [ ] [join](https://shopify.github.io/liquid/filters/join)
- [ ] [last](https://shopify.github.io/liquid/filters/last)
- [ ] [lstrip](https://shopify.github.io/liquid/filters/lstrip)
- [ ] [map](https://shopify.github.io/liquid/filters/map)
- [ ] [minus](https://shopify.github.io/liquid/filters/minus)
- [ ] [modulo](https://shopify.github.io/liquid/filters/modulo)
- [ ] [newline_to_br](https://shopify.github.io/liquid/filters/newline_to_br)
- [ ] [plus](https://shopify.github.io/liquid/filters/plus)
- [ ] [prepend](https://shopify.github.io/liquid/filters/prepend)
- [ ] [remove](https://shopify.github.io/liquid/filters/remove)
- [ ] [remove_first](https://shopify.github.io/liquid/filters/remove_first)
- [ ] [replace](https://shopify.github.io/liquid/filters/replace)
- [ ] [replace_first](https://shopify.github.io/liquid/filters/replace_first)
- [ ] [reverse](https://shopify.github.io/liquid/filters/reverse)
- [ ] [round](https://shopify.github.io/liquid/filters/round)
- [ ] [rstrip](https://shopify.github.io/liquid/filters/rstrip)
- [ ] [size](https://shopify.github.io/liquid/filters/size)
- [ ] [slice](https://shopify.github.io/liquid/filters/slice)
- [ ] [sort](https://shopify.github.io/liquid/filters/sort)
- [ ] [split](https://shopify.github.io/liquid/filters/split)
- [ ] [strip](https://shopify.github.io/liquid/filters/strip)
- [ ] [strip_html](https://shopify.github.io/liquid/filters/strip_html)
- [ ] [strip_newlines](https://shopify.github.io/liquid/filters/strip_newlines)
- [ ] [times](https://shopify.github.io/liquid/filters/times)
- [ ] [truncate](https://shopify.github.io/liquid/filters/truncate)
- [ ] [truncatewords](https://shopify.github.io/liquid/filters/truncatewords)
- [ ] [uniq](https://shopify.github.io/liquid/filters/uniq)
- [ ] [upcase](https://shopify.github.io/liquid/filters/upcase)
- [ ] [url_encode](https://shopify.github.io/liquid/filters/url_encode)
## Async Support
harttle/shopify-liquid do NOT support async rendering, this is by design.
@@ -12,3 +94,6 @@ For template-driven projects, checkout these Liquid-like engines:
[nunjucks]: http://mozilla.github.io/nunjucks/
[liquid-node]: https://github.com/sirlantis/liquid-node
[shopify-liquid]: https://shopify.github.io/liquid/
[jekyll]: http://jekyllrb.com/
[gh]: https://pages.github.com/
+4
View File
@@ -18,6 +18,10 @@ var context = {
}
return '';
},
set: function(k, v){
this.context[this.context.length-1][k] = v;
return this;
},
push: function(ctx) {
return this.context.push(ctx);
},
+3 -1
View File
@@ -1 +1,3 @@
module.exports = v => Math.abs(v);
module.exports = function(liquid) {
liquid.registerFilter('abs', v => Math.abs(v));
};
+12 -9
View File
@@ -1,6 +1,6 @@
const context = require('./context');
const tokenizer = require('./tokenizer.js');
const render = require('./render.js');
const Render = require('./render.js');
const lexical = require('./lexical.js');
const path = require("path");
const fs = require('fs');
@@ -11,15 +11,11 @@ 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);
return this.tag.register(name, tag);
}
};
@@ -27,20 +23,27 @@ function factory(){
var engine = Object.create(_engine);
engine.tag = Tag();
engine.filter = Filter();
engine.doRender = render(engine.filter, engine.tag);
var render = Render(engine.filter, engine.tag);
engine.render = function(html, ctx) {
return render.render(tokenizer.parse(html), context.factory(ctx));
},
engine.evaluate = render.evaluate;
fs.readdirSync(tagsPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
if(!match) return;
engine.tag.register(match[1], require("./tags/" + f));
require("./tags/" + f)(engine);
});
fs.readdirSync(filtersPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
if(!match) return;
engine.filter.register(match[1], require("./filters/" + f));
require("./filters/" + f)(engine);
});
return engine;
}
factory.lexical = lexical;
module.exports = factory;
+28 -23
View File
@@ -1,44 +1,49 @@
module.exports = (Filter, Tag) =>
module.exports = function(Filter, Tag) {
function render(tokens, ctx) {
var html = '';
for (var i = 0; i < tokens.length; i++) {
while (tokens.length) {
var token = tokens.shift();
switch (token.type) {
case 'html':
html += token.value;
break;
case 'output':
html += renderOutput(token);
html += evaluate(token.value, ctx);
break;
case 'tag':
html += renderTag(token);
html += renderTag(token, tokens, ctx);
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 evaluate(str, ctx) {
var filters = str.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`);
}
function renderTag(token, tokens, ctx) {
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);
}
return tag.render(subTokens, ctx);
}
return {
render, evaluate, renderTag
};
};
+2 -1
View File
@@ -3,7 +3,8 @@ const lexical = require('./lexical.js');
var _tagInstance = {
render: function(tokens, ctx) {
var obj = hash(this.markup, ctx);
return this.tag.render(tokens, ctx, this.markup, obj);
var res = this.tag.render(tokens, ctx, this.markup, obj);
return res === undefined ? '' : res;
}
};
+15
View File
@@ -0,0 +1,15 @@
var Liquid = require('..');
var patterns = Liquid.lexical.patterns;
var re = new RegExp(`(${patterns.identifier.source})\\s*=(.*)`);
module.exports = function(liquid) {
liquid.registerTag('assign', {
render: function(tokens, ctx, markup, hash) {
var match = markup.match(re);
var k = match[1], v = match[2];
ctx.set(k, liquid.evaluate(v, ctx));
}
});
};
+7 -1
View File
@@ -6,7 +6,7 @@ var context = require('../context.js');
describe('context', function() {
var ctx;
before(function(){
beforeEach(function(){
ctx = context.factory({
foo: 'bar',
bar: ['a', {b: [1, 2]}]
@@ -23,6 +23,11 @@ describe('context', function() {
ctx.get('foo').should.equal('bar');
});
it('should set property', function() {
ctx.set('foo', 'FOO');
ctx.get('foo').should.equal('FOO');
});
it('should get desendent property', function() {
ctx.get('bar[0]').should.equal('a');
ctx.get('bar[1].b').should.deep.equal([1, 2]);
@@ -37,6 +42,7 @@ describe('context', function() {
});
it('should pop context', function() {
ctx.push({foo: 'foo', foo1: 'foo1'});
ctx.pop();
expect(ctx.get('foo')).to.equal('bar');
expect(ctx.get('foo1')).to.equal('');
+1 -1
View File
@@ -8,7 +8,7 @@ chai.use(sinonChai);
var tag = require('../tag.js')();
var context = require('../context.js');
var filter = require('../filter')();
var render = require('../render.js')(filter, tag);
var render = require('../render.js')(filter, tag).render;
describe('render', function() {
var ctx, htmlToken, tagToken, filterToken;
+19
View File
@@ -0,0 +1,19 @@
const chai = require("chai");
const expect = chai.expect;
var liquid = require('..')();
var ctx = {
foo: 'bar',
arr: [-2, 'a']
};
describe('tags', function() {
it('should support assign', function() {
test('{% assign foo="bar"%}{{foo}}', 'bar');
});
});
function test(src, dst){
expect(liquid.render(src, ctx)).to.equal(dst);
}