mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-20 15:00:42 -07:00
strict_filters/strict_variables when creating engine
This commit is contained in:
@@ -48,7 +48,9 @@ engine.render(tpl, {name: 'alice'})
|
|||||||
var engine = Liquid({
|
var engine = Liquid({
|
||||||
root: path.resolve(__dirname, 'views/'), // for layouts and includes
|
root: path.resolve(__dirname, 'views/'), // for layouts and includes
|
||||||
extname: '.liquid',
|
extname: '.liquid',
|
||||||
cache: false
|
cache: false,
|
||||||
|
strict_filters: false, // default: false
|
||||||
|
strict_variables: false // default: false
|
||||||
});
|
});
|
||||||
engine.renderFile("hello.liquid", {name: 'alice'})
|
engine.renderFile("hello.liquid", {name: 'alice'})
|
||||||
.then(function(html){
|
.then(function(html){
|
||||||
@@ -69,26 +71,11 @@ Defaults to `process.cwd()`
|
|||||||
|
|
||||||
* `cache` indicates whether or not to cache resolved templates. Defaults to `false`.
|
* `cache` indicates whether or not to cache resolved templates. Defaults to `false`.
|
||||||
|
|
||||||
## Strict Rendering
|
* `strict_filters` is used to enable strict filter existence. If set to `false`, undefined filters will be rendered as empty string. Otherwise, undefined filters will cause an exception. Defaults to `false`.
|
||||||
|
|
||||||
Undefined filters and variables will be rendered as empty string by default.
|
* `strict_variables` is used to enable strict variable derivation.
|
||||||
Enable strict rendering to throw errors upon undefined variables/filters:
|
If set to `false`, undefined variables will be rendered as empty string.
|
||||||
|
Otherwise, undefined variables will cause an exception. Defaults to `false`.
|
||||||
```javascript
|
|
||||||
var opts = {
|
|
||||||
strict_variables: true,
|
|
||||||
strict_filters: true
|
|
||||||
};
|
|
||||||
engine.parseAndRender("{{ foo }}", {}, opts).catch(function(err){
|
|
||||||
// err.message === undefined variable: foo
|
|
||||||
});
|
|
||||||
engine.parseAndRender("{{ 'foo' | filter1 }}", {}, opts).catch(function(err){
|
|
||||||
// err.message === undefined filter: filter1
|
|
||||||
});
|
|
||||||
// Note: the below opts also work:
|
|
||||||
// engine.render(tpl, ctx, opts)
|
|
||||||
// engine.renderFile(path, ctx, opts)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use with Express.js
|
## Use with Express.js
|
||||||
|
|
||||||
@@ -104,7 +91,8 @@ app.set('view engine', 'liquid'); // set to default
|
|||||||
|
|
||||||
> There's an Express demo [here](demo/express/).
|
> There's an Express demo [here](demo/express/).
|
||||||
|
|
||||||
Note: includes and layouts lookup path should always be specified by `Liquid({root: []})`.
|
When using with Express.js, partials(includes and layouts) will be looked up in
|
||||||
|
both Liquid `root` and Express `views` directories.
|
||||||
|
|
||||||
## Use in Browser
|
## Use in Browser
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ var _engine = {
|
|||||||
return this.parser.parse(tokens);
|
return this.parser.parse(tokens);
|
||||||
},
|
},
|
||||||
render: function(tpl, ctx, opts) {
|
render: function(tpl, ctx, opts) {
|
||||||
|
opts = _.assign({}, this.options, opts);
|
||||||
var scope = Scope.factory(ctx, opts);
|
var scope = Scope.factory(ctx, opts);
|
||||||
return this.renderer.renderTemplates(tpl, scope);
|
return this.renderer.renderTemplates(tpl, scope);
|
||||||
},
|
},
|
||||||
@@ -73,6 +74,7 @@ var _engine = {
|
|||||||
},
|
},
|
||||||
lookup: function(filepath, root) {
|
lookup: function(filepath, root) {
|
||||||
root = this.options.root.concat(root || []);
|
root = this.options.root.concat(root || []);
|
||||||
|
root = _.uniq(root);
|
||||||
var paths = root.map(root => path.resolve(root, filepath));
|
var paths = root.map(root => path.resolve(root, filepath));
|
||||||
return anySeries(paths, path => statFileAsync(path).then(() => path))
|
return anySeries(paths, path => statFileAsync(path).then(() => path))
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ module.exports = function() {
|
|||||||
if (typeof filter !== 'function'){
|
if (typeof filter !== 'function'){
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
error: new Error(`undefined filter: ${name}`)
|
error: new TypeError(`undefined filter: ${name}`)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-10
@@ -4,9 +4,9 @@ const assert = require('./util/assert.js');
|
|||||||
const referenceError = /undefined variable|Cannot read property .* of undefined/;
|
const referenceError = /undefined variable|Cannot read property .* of undefined/;
|
||||||
|
|
||||||
var Scope = {
|
var Scope = {
|
||||||
getAll: function(str) {
|
getAll: function() {
|
||||||
var ctx = {};
|
var ctx = {};
|
||||||
for (i = this.scopes.length - 1; i >= 0; i--) {
|
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
||||||
_.assign(ctx, this.scopes[i]);
|
_.assign(ctx, this.scopes[i]);
|
||||||
}
|
}
|
||||||
return ctx;
|
return ctx;
|
||||||
@@ -26,13 +26,6 @@ var Scope = {
|
|||||||
throw new TypeError('undefined variable: ' + str);
|
throw new TypeError('undefined variable: ' + str);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
safeGet: function(str) {
|
|
||||||
try {
|
|
||||||
var val = this.safeGet(str);
|
|
||||||
} catch (e) {;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
set: function(k, v) {
|
set: function(k, v) {
|
||||||
this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v);
|
this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v);
|
||||||
return this;
|
return this;
|
||||||
@@ -112,7 +105,7 @@ var Scope = {
|
|||||||
}
|
}
|
||||||
// foo["bar"]
|
// foo["bar"]
|
||||||
else {
|
else {
|
||||||
var j = str.indexOf(delemiter, i + 2);
|
j = str.indexOf(delemiter, i + 2);
|
||||||
assert(j !== -1, `unbalanced ${delemiter}: ${str}`);
|
assert(j !== -1, `unbalanced ${delemiter}: ${str}`);
|
||||||
name = str.slice(i + 2, j);
|
name = str.slice(i + 2, j);
|
||||||
seq.push(name);
|
seq.push(name);
|
||||||
|
|||||||
+51
-3
@@ -25,27 +25,75 @@ function forOwn(object, iteratee) {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
function assign(dst, src) {
|
/*
|
||||||
dst = dst || {};
|
* Assigns own enumerable string keyed properties of source objects to the destination object.
|
||||||
|
* Source objects are applied from left to right.
|
||||||
|
* Subsequent sources overwrite property assignments of previous sources.
|
||||||
|
*
|
||||||
|
* Note: This method mutates object and is loosely based on Object.assign.
|
||||||
|
*
|
||||||
|
* @param {Object} object The destination object.
|
||||||
|
* @param {...Object} sources The source objects.
|
||||||
|
* @return {Object} Returns object.
|
||||||
|
*/
|
||||||
|
function assign(object) {
|
||||||
|
object = isObject(object) ? object : {};
|
||||||
|
var srcs = Array.prototype.slice.call(arguments, 1);
|
||||||
|
srcs.forEach(function(src) {
|
||||||
|
_assignBinary(object, src);
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _assignBinary(dst, src) {
|
||||||
|
if (!dst) return dst;
|
||||||
forOwn(src, function(v, k) {
|
forOwn(src, function(v, k) {
|
||||||
dst[k] = v;
|
dst[k] = v;
|
||||||
});
|
});
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function isArray(value) {
|
function isArray(value) {
|
||||||
return value instanceof Array;
|
return value instanceof Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
function echo(prefix){
|
function echo(prefix) {
|
||||||
return v => {
|
return v => {
|
||||||
console.log('[' + prefix + ']', v);
|
console.log('[' + prefix + ']', v);
|
||||||
return v;
|
return v;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function uniq(arr) {
|
||||||
|
var u = {},
|
||||||
|
a = [];
|
||||||
|
for (var i = 0, l = arr.length; i < l; ++i) {
|
||||||
|
if (u.hasOwnProperty(arr[i])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
a.push(arr[i]);
|
||||||
|
u[arr[i]] = 1;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if value is the language type of Object.
|
||||||
|
* (e.g. arrays, functions, objects, regexes, new Number(0), and new String(''))
|
||||||
|
* @param {any} value The value to check.
|
||||||
|
* @return {Boolean} Returns true if value is an object, else false.
|
||||||
|
*/
|
||||||
|
function isObject(value) {
|
||||||
|
return value !== null && typeof value === 'object';
|
||||||
|
}
|
||||||
|
|
||||||
exports.isString = isString;
|
exports.isString = isString;
|
||||||
exports.isArray = isArray;
|
exports.isArray = isArray;
|
||||||
|
exports.isObject = isObject;
|
||||||
|
|
||||||
exports.forOwn = forOwn;
|
exports.forOwn = forOwn;
|
||||||
exports.assign = assign;
|
exports.assign = assign;
|
||||||
|
exports.uniq = uniq;
|
||||||
|
|
||||||
exports.echo = echo;
|
exports.echo = echo;
|
||||||
|
|||||||
+4
-5
@@ -1,5 +1,4 @@
|
|||||||
const chai = require("chai");
|
const chai = require("chai");
|
||||||
const should = chai.should();
|
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const Liquid = require('..');
|
const Liquid = require('..');
|
||||||
const mock = require('mock-fs');
|
const mock = require('mock-fs');
|
||||||
@@ -152,10 +151,10 @@ describe('liquid', function() {
|
|||||||
it('should be disabled by default', function() {
|
it('should be disabled by default', function() {
|
||||||
return engine.renderFile('files/foo')
|
return engine.renderFile('files/foo')
|
||||||
.then(x => expect(x).to.equal('foo'))
|
.then(x => expect(x).to.equal('foo'))
|
||||||
.then(x => mock({
|
.then(() => mock({
|
||||||
'/root/files/foo.html': 'bar'
|
'/root/files/foo.html': 'bar'
|
||||||
}))
|
}))
|
||||||
.then(x => engine.renderFile('files/foo'))
|
.then(() => engine.renderFile('files/foo'))
|
||||||
.then(x => expect(x).to.equal('bar'));
|
.then(x => expect(x).to.equal('bar'));
|
||||||
});
|
});
|
||||||
it('should respect cache=true option', function() {
|
it('should respect cache=true option', function() {
|
||||||
@@ -166,10 +165,10 @@ describe('liquid', function() {
|
|||||||
});
|
});
|
||||||
return engine.renderFile('files/foo')
|
return engine.renderFile('files/foo')
|
||||||
.then(x => expect(x).to.equal('foo'))
|
.then(x => expect(x).to.equal('foo'))
|
||||||
.then(x => mock({
|
.then(() => mock({
|
||||||
'/root/files/foo.html': 'bar'
|
'/root/files/foo.html': 'bar'
|
||||||
}))
|
}))
|
||||||
.then(x => engine.renderFile('files/foo'))
|
.then(() => engine.renderFile('files/foo'))
|
||||||
.then(x => expect(x).to.equal('foo'));
|
.then(x => expect(x).to.equal('foo'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+86
-48
@@ -1,65 +1,103 @@
|
|||||||
const chai = require("chai");
|
const chai = require("chai");
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const mock = require('mock-fs');
|
const mock = require('mock-fs');
|
||||||
|
chai.use(require("chai-as-promised"));
|
||||||
|
|
||||||
var engine = require('../..')(), ctx;
|
var engine = require('../..')();
|
||||||
|
var strictEngine = require('../..')({
|
||||||
function test(promise, cb){
|
strict_variables: true,
|
||||||
return promise
|
strict_filters: true
|
||||||
.then((result) => {
|
});
|
||||||
return cb({});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
return cb(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('error', function() {
|
describe('error', function() {
|
||||||
|
|
||||||
it('should throw TokenizationError when tag illegal', function() {
|
describe('TokenizationError', function() {
|
||||||
return test(engine.parseAndRender('{% . a %}', {}), function(err){
|
it('should throw TokenizationError when tag illegal', function() {
|
||||||
expect(err.name).to.equal('TokenizationError');
|
return engine.parseAndRender('{% . a %}', {}).catch(function(err) {
|
||||||
expect(err.message).to.equal('illegal tag: {% . a %}');
|
expect(err.name).to.equal('TokenizationError');
|
||||||
expect(err.input).to.equal('{% . a %}');
|
expect(err.message).to.equal('illegal tag: {% . a %}');
|
||||||
expect(err.line).to.equal(1);
|
expect(err.input).to.equal('{% . a %}');
|
||||||
|
expect(err.line).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should throw TokenizationError when tag syntax illegal', function() {
|
||||||
|
return engine.parseAndRender('{% . a }', {}).catch(function(err) {
|
||||||
|
expect(err.name).to.equal('TokenizationError');
|
||||||
|
expect(err.message).to.equal('illegal tag: {% . a }');
|
||||||
|
expect(err.input).to.equal('{% . a }');
|
||||||
|
expect(err.line).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should throw TokenizationError when filter syntax illegal', function() {
|
||||||
|
return engine.parseAndRender('{{ a|| }}', {}).catch(function(err) {
|
||||||
|
expect(err.name).to.equal('TokenizationError');
|
||||||
|
expect(err.message).to.equal('{{ a|| }');
|
||||||
|
expect(err.input).to.equal('{{ a|| }');
|
||||||
|
expect(err.line).to.equal(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw correct error info', function() {
|
describe('TypeError', function() {
|
||||||
return test(engine.parseAndRender('{%if true%}\naaa{%endif%}\n{% -a %}\n3', {}), function(err){
|
it('should not throw when variable undefined by default', function() {
|
||||||
expect(err.input).to.equal('{% -a %}');
|
return expect(engine.parseAndRender('X{{a}}Y')).to.eventually.equal('XY');
|
||||||
expect(err.line).to.equal(3);
|
});
|
||||||
|
it('should throw TypeError when variable not defined', function() {
|
||||||
|
return expect(strictEngine.parseAndRender('{{a}}')).to.eventually
|
||||||
|
.be.rejected
|
||||||
|
.then(function(e){
|
||||||
|
expect(e).to.have.property('name', 'TypeError');
|
||||||
|
expect(e).to.have.property('message', 'undefined variable: a');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should throw TypeError when filter not defined', function() {
|
||||||
|
return expect(strictEngine.parseAndRender('{{1 | a}}')).to.eventually
|
||||||
|
.be.rejected
|
||||||
|
.then(function(e){
|
||||||
|
expect(e).to.have.property('name', 'TypeError');
|
||||||
|
expect(e).to.have.property('message', 'undefined filter: a');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw correct error info for files', function() {
|
describe('ParseError', function() {
|
||||||
mock({
|
it('should throw correct error info', function() {
|
||||||
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
|
var src = '{%if true%}\naaa{%endif%}\n{% -a %}\n3';
|
||||||
|
return engine.parseAndRender(src).catch(function(err) {
|
||||||
|
expect(err.name).to.equal('ParseError');
|
||||||
|
expect(err.input).to.equal('{% -a %}');
|
||||||
|
expect(err.line).to.equal(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return test(engine.renderFile('/foo.html', {}), function(err){
|
it('should throw correct error info for files', function() {
|
||||||
mock.restore();
|
mock({
|
||||||
expect(err.input).to.equal('{% raw %}');
|
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
|
||||||
expect(err.line).to.equal(4);
|
});
|
||||||
expect(err.file).to.equal('/foo.html');
|
return engine.renderFile('/foo.html').catch(function(err) {
|
||||||
|
mock.restore();
|
||||||
|
expect(err.name).to.equal('ParseError');
|
||||||
|
expect(err.input).to.equal('{% raw %}');
|
||||||
|
expect(err.line).to.equal(4);
|
||||||
|
expect(err.file).to.equal('/foo.html');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw ParseError when tag not exist', function() {
|
||||||
|
return engine.parseAndRender('{% a %}').catch(function(err) {
|
||||||
|
expect(err.name).to.equal('ParseError');
|
||||||
|
expect(err.message).to.equal('tag a not found');
|
||||||
|
expect(err.input).to.equal('{% a %}');
|
||||||
|
expect(err.line).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw ParseError when tag not closed', function() {
|
||||||
|
return engine.parseAndRender('{% if %}').catch(function(err) {
|
||||||
|
expect(err.name).to.equal('ParseError');
|
||||||
|
expect(err.message).to.equal('tag {% if %} not closed');
|
||||||
|
expect(err.input).to.equal('{% if %}');
|
||||||
|
expect(err.line).to.equal(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw ParseError when tag not exist', function() {
|
|
||||||
return test(engine.parseAndRender('{% a %}', {}), function(err){
|
|
||||||
expect(err.name).to.equal('ParseError');
|
|
||||||
expect(err.message).to.equal('tag a not found');
|
|
||||||
expect(err.input).to.equal('{% a %}');
|
|
||||||
expect(err.line).to.equal(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw ParseError when tag not closed', function() {
|
|
||||||
return test(engine.parseAndRender('{% if %}', {}), function(err){
|
|
||||||
expect(err.name).to.equal('ParseError');
|
|
||||||
expect(err.message).to.equal('tag {% if %} not closed');
|
|
||||||
expect(err.input).to.equal('{% if %}');
|
|
||||||
expect(err.line).to.equal(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
+47
-2
@@ -11,7 +11,7 @@ describe('util/underscore', function() {
|
|||||||
expect(_.isString('foo')).to.be.true;
|
expect(_.isString('foo')).to.be.true;
|
||||||
});
|
});
|
||||||
it('should return true String instance', function() {
|
it('should return true String instance', function() {
|
||||||
expect(_.isString(new String('foo'))).to.be.true;
|
expect(_.isString(String('foo'))).to.be.true;
|
||||||
});
|
});
|
||||||
it('should return false for 123 ', function() {
|
it('should return false for 123 ', function() {
|
||||||
expect(_.isString(123)).to.be.false;
|
expect(_.isString(123)).to.be.false;
|
||||||
@@ -53,7 +53,7 @@ describe('util/underscore', function() {
|
|||||||
expect(_.isArray("foo")).to.be.false;
|
expect(_.isArray("foo")).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('.echo()', function(){
|
describe('.echo()', function() {
|
||||||
it('should be transparent', function() {
|
it('should be transparent', function() {
|
||||||
expect(_.echo('foo')('bar')).to.equal('bar');
|
expect(_.echo('foo')('bar')).to.equal('bar');
|
||||||
});
|
});
|
||||||
@@ -63,4 +63,49 @@ describe('util/underscore', function() {
|
|||||||
expect(log).to.have.been.calledWith('[foo]', 'bar');
|
expect(log).to.have.been.calledWith('[foo]', 'bar');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('.assign()', function() {
|
||||||
|
it('should handle null dst', function() {
|
||||||
|
expect(_.assign(null, {
|
||||||
|
foo: 'bar'
|
||||||
|
})).to.deep.equal({
|
||||||
|
foo: 'bar'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should assign 2 objects', function() {
|
||||||
|
var src = {
|
||||||
|
foo: 'foo',
|
||||||
|
bar: 'bar'
|
||||||
|
};
|
||||||
|
var dst = {
|
||||||
|
foo: 'bar',
|
||||||
|
kaa: 'kaa'
|
||||||
|
};
|
||||||
|
expect(_.assign(dst, src)).to.deep.equal({
|
||||||
|
foo: 'foo',
|
||||||
|
bar: 'bar',
|
||||||
|
kaa: 'kaa'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('should assign 3 objects', function() {
|
||||||
|
expect(_.assign({
|
||||||
|
foo: 'foo'
|
||||||
|
}, {
|
||||||
|
bar: 'bar'
|
||||||
|
}, {
|
||||||
|
car: 'car'
|
||||||
|
})).to.deep.equal({
|
||||||
|
foo: 'foo',
|
||||||
|
bar: 'bar',
|
||||||
|
car: 'car'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('.uniq()', function() {
|
||||||
|
it('should handle empty array', function() {
|
||||||
|
expect(_.uniq([])).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
it('should do uniq', function() {
|
||||||
|
expect(_.uniq([1, 'a', 'a', 1])).to.deep.equal([1, 'a']);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user