mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
enable cache: parse and render
This commit is contained in:
@@ -11,6 +11,58 @@ shall be implemented.
|
||||
|
||||
> [Shopify liquid][shopify-liquid] is used by [Jekyll][jekyll] and [Github Pages][gh].
|
||||
|
||||
## Usage
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
npm install --save shopify-liquid
|
||||
```
|
||||
|
||||
Parse and Render:
|
||||
|
||||
```javascript
|
||||
var Liquid = require('shopify-liquid');
|
||||
var engine = Liquid();
|
||||
|
||||
engine.parseAndRender('{{name | capitalize}}', {name: 'alice'}); // Alice
|
||||
```
|
||||
|
||||
Caching templates:
|
||||
|
||||
```javascript
|
||||
var tpl = engine.parse('{{name | capitalize}}');
|
||||
engine.render(tpl, {name: 'alice'}); // Alice
|
||||
```
|
||||
|
||||
Register Filters:
|
||||
|
||||
```javascript
|
||||
// Usage: {{ name | uppper }}
|
||||
engine.registerFilter('upper', function(v){
|
||||
return v.toUpperCase();
|
||||
});
|
||||
```
|
||||
|
||||
> See existing filter implementations: <https://github.com/harttle/shopify-liquid/blob/master/filters.js>
|
||||
|
||||
Register Tags:
|
||||
|
||||
```javascript
|
||||
// Usage: {% upper name%}
|
||||
engine.registerTag('upper', {
|
||||
parse: function(tagToken, remainTokens) {
|
||||
this.str = tagToken.args; // name
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
var str = Liquid.evalValue(this.str, scope); // 'alice'
|
||||
return str.toUpperCase(); // 'Alice'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
> See existing tag implementations: <https://github.com/harttle/shopify-liquid/blob/master/tags/>
|
||||
|
||||
## Operators
|
||||
|
||||
Documentation: <https://shopify.github.io/liquid/basics/operators/>
|
||||
|
||||
@@ -6,61 +6,62 @@ const path = require("path");
|
||||
const fs = require('fs');
|
||||
const Tag = require('./tag.js');
|
||||
const Filter = require('./filter.js');
|
||||
const error = require('./error.js');
|
||||
const Template = require('./template');
|
||||
const Template = require('./parser');
|
||||
const Expression = require('./expression.js');
|
||||
|
||||
const tagsPath = path.join(__dirname, "tags");
|
||||
|
||||
var _engine = {
|
||||
registerFilter : function(name, filter){
|
||||
init: function(tag, filter) {
|
||||
this.tag = tag;
|
||||
this.filter = filter;
|
||||
this.parser = Template(tag, filter);
|
||||
this.renderer = Render();
|
||||
return this;
|
||||
},
|
||||
parse: function(html) {
|
||||
var tokens = tokenizer.parse(html);
|
||||
return this.parser.parse(tokens);
|
||||
},
|
||||
render: function(tpl, ctx) {
|
||||
this.renderer.resetRegisters();
|
||||
return this.renderer.renderTemplates(tpl, scope.factory(ctx));
|
||||
},
|
||||
parseAndRender: function(html, ctx) {
|
||||
var tpl = this.parse(html);
|
||||
return this.render(tpl, ctx);
|
||||
},
|
||||
evalOutput: function(str, scope) {
|
||||
var tpl = this.parser.parseOutput(str.trim());
|
||||
return this.renderer.evalOutput(tpl, scope);
|
||||
},
|
||||
registerFilter: function(name, filter) {
|
||||
return this.filter.register(name, filter);
|
||||
},
|
||||
registerTag : function(name, tag){
|
||||
registerTag: function(name, tag) {
|
||||
return this.tag.register(name, tag);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function factory(){
|
||||
function factory() {
|
||||
var engine = Object.create(_engine);
|
||||
|
||||
engine.tag = Tag();
|
||||
engine.filter = Filter();
|
||||
engine.tokenize = tokenizer.parse;
|
||||
|
||||
engine.template = Template(engine.tag, engine.filter);
|
||||
engine.parseStream = engine.template.parseStream;
|
||||
|
||||
var renderer = Render(engine.filter, engine.tag);
|
||||
engine.renderTemplates = renderer.renderTemplates;
|
||||
|
||||
engine.render = function(html, ctx) {
|
||||
var tokens = engine.tokenize(html);
|
||||
var templates = engine.template.parse(tokens);
|
||||
engine.register = {};
|
||||
return engine.renderTemplates(templates, scope.factory(ctx));
|
||||
};
|
||||
engine.evalOutput = function(str, scope) {
|
||||
var template = engine.template.parseOutput(str.trim());
|
||||
return renderer.evalOutput(template, scope);
|
||||
};
|
||||
|
||||
fs.readdirSync(tagsPath).map(function(f){
|
||||
var match = /^(\w+)\.js$/.exec(f);
|
||||
if(!match) return;
|
||||
require("./tags/" + f)(engine);
|
||||
});
|
||||
require("./filters.js")(engine);
|
||||
engine.init(Tag(), Filter());
|
||||
registerTagsAndFilters(engine);
|
||||
return engine;
|
||||
}
|
||||
|
||||
function registerTagsAndFilters(engine) {
|
||||
fs.readdirSync(tagsPath).map(f => {
|
||||
var match = /^(\w+)\.js$/.exec(f);
|
||||
if (!match) return;
|
||||
require("./tags/" + f)(engine);
|
||||
});
|
||||
require("./filters.js")(engine);
|
||||
}
|
||||
|
||||
factory.lexical = lexical;
|
||||
factory.error = error;
|
||||
factory.isTruthy = Expression.isTruthy;
|
||||
factory.isFalsy = Expression.isFalsy;
|
||||
factory.stringify = Render.stringify;
|
||||
factory.evalExp = Expression.evalExp;
|
||||
factory.evalValue = Expression.evalValue;
|
||||
|
||||
|
||||
module.exports = factory;
|
||||
|
||||
@@ -2,36 +2,32 @@ const error = require('./error.js');
|
||||
const Exp = require('./expression.js');
|
||||
const assert = require('assert');
|
||||
|
||||
function stringify(val) {
|
||||
if (typeof val === 'string') return val;
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
var render = {
|
||||
|
||||
function factory(Filter, Tag) {
|
||||
|
||||
function renderTemplates(templates, scope) {
|
||||
renderTemplates: function(templates, scope) {
|
||||
assert(scope, 'unable to evalTemplates: scope undefined');
|
||||
var html = '', partial;
|
||||
var html = '',
|
||||
partial;
|
||||
templates.some(template => {
|
||||
if (scope.get('forloop.skip')) return true;
|
||||
switch (template.type) {
|
||||
case 'tag':
|
||||
partial = renderTag(template, scope, this.register);
|
||||
if(partial === undefined) return true;
|
||||
partial = this.renderTag(template, scope, this.register);
|
||||
if (partial === undefined) return true;
|
||||
html += partial;
|
||||
break;
|
||||
case 'html':
|
||||
html += template.value;
|
||||
break;
|
||||
case 'output':
|
||||
var val = evalOutput(template, scope);
|
||||
var val = this.evalOutput(template, scope);
|
||||
html += stringify(val);
|
||||
}
|
||||
});
|
||||
return html;
|
||||
}
|
||||
},
|
||||
|
||||
function renderTag(template, scope, register) {
|
||||
renderTag: function(template, scope, register) {
|
||||
if (template.name === 'continue') {
|
||||
scope.set('forloop.skip', true);
|
||||
return;
|
||||
@@ -42,20 +38,29 @@ function factory(Filter, Tag) {
|
||||
return;
|
||||
}
|
||||
return template.render(scope, register);
|
||||
}
|
||||
},
|
||||
|
||||
function evalOutput(template, scope) {
|
||||
evalOutput: function(template, scope) {
|
||||
assert(scope, 'unable to evalOutput: scope undefined');
|
||||
var val = Exp.evalExp(template.initial, scope);
|
||||
return template.filters
|
||||
.reduce((v, filter) => filter.render(v, scope), val);
|
||||
}
|
||||
},
|
||||
|
||||
return {
|
||||
renderTemplates, evalOutput, renderTag
|
||||
};
|
||||
resetRegisters: function(){
|
||||
return this.register = {};
|
||||
}
|
||||
};
|
||||
|
||||
function factory() {
|
||||
var instance = Object.create(render);
|
||||
instance.register = {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
factory.stringify = stringify;
|
||||
function stringify(val) {
|
||||
if (typeof val === 'string') return val;
|
||||
return JSON.stringify(val);
|
||||
}
|
||||
|
||||
module.exports = factory;
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ module.exports = function(liquid) {
|
||||
this.variable = match[1];
|
||||
this.templates = [];
|
||||
|
||||
var stream = liquid.parseStream(remainTokens);
|
||||
var stream = liquid.parser.parseStream(remainTokens);
|
||||
stream.onTag('endcapture', token => stream.stop())
|
||||
.onTemplate(tpl => this.templates.push(tpl))
|
||||
.onEnd(x => {
|
||||
@@ -21,7 +21,7 @@ module.exports = function(liquid) {
|
||||
stream.start();
|
||||
},
|
||||
render: function(scope, hash) {
|
||||
var html = liquid.renderTemplates(this.templates, scope);
|
||||
var html = liquid.renderer.renderTemplates(this.templates, scope);
|
||||
scope.set(this.variable, html);
|
||||
}
|
||||
});
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@ module.exports = function(liquid) {
|
||||
this.elseTemplates = [];
|
||||
|
||||
var p = [],
|
||||
stream = liquid.parseStream(remainTokens)
|
||||
stream = liquid.parser.parseStream(remainTokens)
|
||||
.onTag('when', token => {
|
||||
if (!this.cases[token.args]) {
|
||||
this.cases.push({
|
||||
@@ -35,10 +35,10 @@ module.exports = function(liquid) {
|
||||
var val = Liquid.evalExp(branch.val, scope);
|
||||
var cond = Liquid.evalExp(this.cond, scope);
|
||||
if (val === cond) {
|
||||
return liquid.renderTemplates(branch.templates, scope);
|
||||
return liquid.renderer.renderTemplates(branch.templates, scope);
|
||||
}
|
||||
}
|
||||
return liquid.renderTemplates(this.elseTemplates, scope);
|
||||
return liquid.renderer.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
+3
-3
@@ -18,7 +18,7 @@ module.exports = function(liquid) {
|
||||
this.templates = [];
|
||||
this.elseTemplates = [];
|
||||
|
||||
var p, stream = liquid.parseStream(remainTokens)
|
||||
var p, stream = liquid.parser.parseStream(remainTokens)
|
||||
.onStart(x => p = this.templates)
|
||||
.onTag('else', token => p = this.elseTemplates)
|
||||
.onTag('endfor', token => stream.stop())
|
||||
@@ -33,7 +33,7 @@ module.exports = function(liquid) {
|
||||
render: function(scope, hash) {
|
||||
var collection = Liquid.evalExp(this.collection, scope);
|
||||
if (Liquid.isFalsy(collection)) {
|
||||
return liquid.renderTemplates(this.elseTemplates, scope);
|
||||
return liquid.renderer.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
|
||||
var html = '',
|
||||
@@ -58,7 +58,7 @@ module.exports = function(liquid) {
|
||||
skip: false
|
||||
};
|
||||
scope.push(ctx);
|
||||
html += liquid.renderTemplates(this.templates, scope);
|
||||
html += liquid.renderer.renderTemplates(this.templates, scope);
|
||||
var breakloop = scope.get('forloop.stop');
|
||||
scope.pop(ctx);
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ module.exports = function(liquid) {
|
||||
this.branches = [];
|
||||
this.elseTemplates = [];
|
||||
|
||||
var p, stream = liquid.parseStream(remainTokens)
|
||||
var p, stream = liquid.parser.parseStream(remainTokens)
|
||||
.onStart(x => this.branches.push({
|
||||
cond: tagToken.args,
|
||||
templates: p = []
|
||||
@@ -37,10 +37,10 @@ module.exports = function(liquid) {
|
||||
var branch = this.branches[i];
|
||||
var cond = Liquid.evalExp(branch.cond, scope);
|
||||
if (Liquid.isTruthy(cond)) {
|
||||
return liquid.renderTemplates(branch.templates, scope);
|
||||
return liquid.renderer.renderTemplates(branch.templates, scope);
|
||||
}
|
||||
}
|
||||
return liquid.renderTemplates(this.elseTemplates, scope);
|
||||
return liquid.renderer.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ module.exports = function(liquid) {
|
||||
|
||||
this.templates = [];
|
||||
|
||||
var p, stream = liquid.parseStream(remainTokens)
|
||||
var p, stream = liquid.parser.parseStream(remainTokens)
|
||||
.onStart(x => p = this.templates)
|
||||
.onTag('endtablerow', token => stream.stop())
|
||||
.onTemplate(tpl => p.push(tpl))
|
||||
@@ -51,7 +51,7 @@ module.exports = function(liquid) {
|
||||
ctx[this.variable] = item;
|
||||
scope.push(ctx);
|
||||
html += `<td class="col${col}">`;
|
||||
html += liquid.renderTemplates(this.templates, scope);
|
||||
html += liquid.renderer.renderTemplates(this.templates, scope);
|
||||
html += '</td>';
|
||||
scope.pop(ctx);
|
||||
});
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ var lexical = Liquid.lexical;
|
||||
module.exports = function(liquid) {
|
||||
liquid.registerTag('unless', {
|
||||
parse: function(tagToken, remainTokens) {
|
||||
var p, stream = liquid.parseStream(remainTokens)
|
||||
var p, stream = liquid.parser.parseStream(remainTokens)
|
||||
.onStart(x => {
|
||||
p = this.templates = [];
|
||||
this.cond = tagToken.args;
|
||||
@@ -22,8 +22,8 @@ module.exports = function(liquid) {
|
||||
render: function(scope, hash) {
|
||||
var cond = Liquid.evalExp(this.cond, scope);
|
||||
return Liquid.isFalsy(cond) ?
|
||||
liquid.renderTemplates(this.templates, scope) :
|
||||
liquid.renderTemplates(this.elseTemplates, scope);
|
||||
liquid.renderer.renderTemplates(this.templates, scope) :
|
||||
liquid.renderer.renderTemplates(this.elseTemplates, scope);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@ var sinon = require("sinon");
|
||||
var expect = chai.expect;
|
||||
chai.use(sinonChai);
|
||||
|
||||
var liquid = require('..')(), ctx;
|
||||
var engine = require('..')(), ctx;
|
||||
|
||||
function test(func, cb){
|
||||
try{
|
||||
@@ -20,7 +20,7 @@ describe('error', function() {
|
||||
|
||||
it('should throw TokenizationError when tag illegal', function() {
|
||||
test(function(){
|
||||
liquid.render('{% -a %}', {});
|
||||
engine.parseAndRender('{% -a %}', {});
|
||||
}, function(err){
|
||||
expect(err.name).to.equal('TokenizationError');
|
||||
expect(err.message).to.equal('illegal tag: {% -a %}');
|
||||
@@ -31,7 +31,7 @@ describe('error', function() {
|
||||
|
||||
it('should throw correct error info', function() {
|
||||
test(function(){
|
||||
liquid.render('{%if true%}\naaa{%endif%}\n{% -a %}\n3', {});
|
||||
engine.parseAndRender('{%if true%}\naaa{%endif%}\n{% -a %}\n3', {});
|
||||
}, function(err){
|
||||
expect(err.input).to.equal('{% -a %}');
|
||||
expect(err.line).to.equal(3);
|
||||
@@ -40,7 +40,7 @@ describe('error', function() {
|
||||
|
||||
it('should throw ParseError when filter not exist', function() {
|
||||
test(function(){
|
||||
liquid.render('{{ a | xz }}', {});
|
||||
engine.parseAndRender('{{ a | xz }}', {});
|
||||
}, function(err){
|
||||
expect(err.name).to.equal('ParseError');
|
||||
expect(err.message).to.equal('filter "xz" not found');
|
||||
@@ -50,7 +50,7 @@ describe('error', function() {
|
||||
});
|
||||
it('should throw ParseError when tag not exist', function() {
|
||||
test(function(){
|
||||
liquid.render('{% a %}', {});
|
||||
engine.parseAndRender('{% a %}', {});
|
||||
}, function(err){
|
||||
expect(err.name).to.equal('ParseError');
|
||||
expect(err.message).to.equal('tag a not found');
|
||||
@@ -61,7 +61,7 @@ describe('error', function() {
|
||||
|
||||
it('should throw ParseError when tag not closed', function() {
|
||||
test(function(){
|
||||
liquid.render('{% if %}', {});
|
||||
engine.parseAndRender('{% if %}', {});
|
||||
}, function(err){
|
||||
expect(err.name).to.equal('ParseError');
|
||||
expect(err.message).to.equal('tag {% if %} not closed');
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ function test(src, dst) {
|
||||
category: 'bar'
|
||||
}]
|
||||
};
|
||||
expect(liquid.render(src, ctx)).to.equal(dst);
|
||||
expect(liquid.parseAndRender(src, ctx)).to.equal(dst);
|
||||
}
|
||||
|
||||
describe('filters', function() {
|
||||
|
||||
+38
-22
@@ -1,31 +1,47 @@
|
||||
const chai = require("chai");
|
||||
const expect = chai.expect;
|
||||
|
||||
var liquid = require('..')(),
|
||||
ctx;
|
||||
|
||||
function test(src, dst) {
|
||||
ctx = {
|
||||
date: new Date(),
|
||||
foo: 'bar',
|
||||
arr: [-2, 'a'],
|
||||
obj: {
|
||||
foo: 'bar'
|
||||
},
|
||||
posts: [{
|
||||
category: 'foo'
|
||||
}, {
|
||||
category: 'bar'
|
||||
}]
|
||||
};
|
||||
expect(liquid.render(src, ctx)).to.equal(dst);
|
||||
}
|
||||
const should = chai.should;
|
||||
const Liquid = require('..');
|
||||
|
||||
describe('liquid', function() {
|
||||
var engine, ctx;
|
||||
beforeEach(function() {
|
||||
engine = Liquid();
|
||||
ctx = {
|
||||
date: new Date(),
|
||||
foo: 'bar',
|
||||
arr: [-2, 'a'],
|
||||
obj: {
|
||||
foo: 'bar'
|
||||
},
|
||||
posts: [{
|
||||
category: 'foo'
|
||||
}, {
|
||||
category: 'bar'
|
||||
}]
|
||||
};
|
||||
});
|
||||
it('should output object', function() {
|
||||
test('{{obj}}', '{"foo":"bar"}');
|
||||
engine.parseAndRender('{{obj}}', ctx).should.equal('{"foo":"bar"}');
|
||||
});
|
||||
it('should output array', function() {
|
||||
test('{{arr}}', '[-2,"a"]');
|
||||
engine.parseAndRender('{{arr}}', ctx).should.equal('[-2,"a"]');
|
||||
});
|
||||
it('should parse html', function() {
|
||||
(function(){
|
||||
engine.parse('{{obj}}');
|
||||
}).should.not.throw();
|
||||
(function(){
|
||||
engine.parse('<html><head>{{obj}}</head></html>');
|
||||
}).should.not.throw();
|
||||
});
|
||||
it('should render template multiple times', function() {
|
||||
var template = engine.parse('{{obj}}');
|
||||
engine.render(template, ctx).should.equal('{"foo":"bar"}');
|
||||
engine.render(template, ctx).should.equal('{"foo":"bar"}');
|
||||
});
|
||||
it('should render filters', function() {
|
||||
var template = engine.parse('<p>{{arr | join: "_"}}</p>');
|
||||
engine.render(template, ctx).should.equal('<p>-2_a</p>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ chai.use(sinonChai);
|
||||
|
||||
var filter = require('../filter.js')();
|
||||
var tag = require('../tag.js')();
|
||||
var Template = require('../template.js');
|
||||
var Template = require('../parser.js');
|
||||
|
||||
describe('template', function() {
|
||||
var scope, template, add = (l, r) => l + r;
|
||||
+2
-12
@@ -9,13 +9,12 @@ var tag = require('../tag.js')();
|
||||
var Scope = require('../scope.js');
|
||||
var filter = require('../filter')();
|
||||
var Render = require('../render.js');
|
||||
var Template = require('../template.js')(tag, filter);
|
||||
var Template = require('../parser.js')(tag, filter);
|
||||
|
||||
describe('render', function() {
|
||||
var scope, render;
|
||||
|
||||
beforeEach(function() {
|
||||
render = Render(filter, tag);
|
||||
scope = Scope.factory({
|
||||
foo: {
|
||||
bar: ['a', 2]
|
||||
@@ -23,16 +22,7 @@ describe('render', function() {
|
||||
});
|
||||
filter.clear();
|
||||
tag.clear();
|
||||
});
|
||||
|
||||
it('should stringify object', function() {
|
||||
expect(Render.stringify({
|
||||
foo: 'bar'
|
||||
})).to.equal('{"foo":"bar"}');
|
||||
});
|
||||
|
||||
it('should stringify object', function() {
|
||||
expect(Render.stringify([1, 2, 3])).to.equal('[1,2,3]');
|
||||
render = Render();
|
||||
});
|
||||
|
||||
it('should render html', function() {
|
||||
|
||||
+2
-2
@@ -5,12 +5,12 @@ var liquid = require('..')(),
|
||||
ctx, src, dst;
|
||||
|
||||
function test(src, dst) {
|
||||
expect(liquid.render(src, ctx)).to.equal(dst);
|
||||
expect(liquid.parseAndRender(src, ctx)).to.equal(dst);
|
||||
}
|
||||
|
||||
function testThrow(src, pattern) {
|
||||
expect(function() {
|
||||
liquid.render(src, ctx);
|
||||
liquid.parseAndRender(src, ctx);
|
||||
}).to.throw(pattern);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user