mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-17 05:10:40 -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({
|
||||
root: path.resolve(__dirname, 'views/'), // for layouts and includes
|
||||
extname: '.liquid',
|
||||
cache: false
|
||||
cache: false,
|
||||
strict_filters: false, // default: false
|
||||
strict_variables: false // default: false
|
||||
});
|
||||
engine.renderFile("hello.liquid", {name: 'alice'})
|
||||
.then(function(html){
|
||||
@@ -69,26 +71,11 @@ Defaults to `process.cwd()`
|
||||
|
||||
* `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.
|
||||
Enable strict rendering to throw errors upon undefined variables/filters:
|
||||
|
||||
```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)
|
||||
```
|
||||
* `strict_variables` is used to enable strict variable derivation.
|
||||
If set to `false`, undefined variables will be rendered as empty string.
|
||||
Otherwise, undefined variables will cause an exception. Defaults to `false`.
|
||||
|
||||
## Use with Express.js
|
||||
|
||||
@@ -104,7 +91,8 @@ app.set('view engine', 'liquid'); // set to default
|
||||
|
||||
> 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
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ var _engine = {
|
||||
return this.parser.parse(tokens);
|
||||
},
|
||||
render: function(tpl, ctx, opts) {
|
||||
opts = _.assign({}, this.options, opts);
|
||||
var scope = Scope.factory(ctx, opts);
|
||||
return this.renderer.renderTemplates(tpl, scope);
|
||||
},
|
||||
@@ -73,6 +74,7 @@ var _engine = {
|
||||
},
|
||||
lookup: function(filepath, root) {
|
||||
root = this.options.root.concat(root || []);
|
||||
root = _.uniq(root);
|
||||
var paths = root.map(root => path.resolve(root, filepath));
|
||||
return anySeries(paths, path => statFileAsync(path).then(() => path))
|
||||
.catch((e) => {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ module.exports = function() {
|
||||
if (typeof filter !== 'function'){
|
||||
return {
|
||||
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/;
|
||||
|
||||
var Scope = {
|
||||
getAll: function(str) {
|
||||
getAll: function() {
|
||||
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]);
|
||||
}
|
||||
return ctx;
|
||||
@@ -26,13 +26,6 @@ var Scope = {
|
||||
throw new TypeError('undefined variable: ' + str);
|
||||
}
|
||||
},
|
||||
safeGet: function(str) {
|
||||
try {
|
||||
var val = this.safeGet(str);
|
||||
} catch (e) {;
|
||||
}
|
||||
return val;
|
||||
},
|
||||
set: function(k, v) {
|
||||
this.setPropertyByPath(this.scopes[this.scopes.length - 1], k, v);
|
||||
return this;
|
||||
@@ -112,7 +105,7 @@ var Scope = {
|
||||
}
|
||||
// foo["bar"]
|
||||
else {
|
||||
var j = str.indexOf(delemiter, i + 2);
|
||||
j = str.indexOf(delemiter, i + 2);
|
||||
assert(j !== -1, `unbalanced ${delemiter}: ${str}`);
|
||||
name = str.slice(i + 2, j);
|
||||
seq.push(name);
|
||||
|
||||
+51
-3
@@ -25,27 +25,75 @@ function forOwn(object, iteratee) {
|
||||
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) {
|
||||
dst[k] = v;
|
||||
});
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
function isArray(value) {
|
||||
return value instanceof Array;
|
||||
}
|
||||
|
||||
function echo(prefix){
|
||||
function echo(prefix) {
|
||||
return v => {
|
||||
console.log('[' + prefix + ']', 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.isArray = isArray;
|
||||
exports.isObject = isObject;
|
||||
|
||||
exports.forOwn = forOwn;
|
||||
exports.assign = assign;
|
||||
exports.uniq = uniq;
|
||||
|
||||
exports.echo = echo;
|
||||
|
||||
+4
-5
@@ -1,5 +1,4 @@
|
||||
const chai = require("chai");
|
||||
const should = chai.should();
|
||||
const expect = chai.expect;
|
||||
const Liquid = require('..');
|
||||
const mock = require('mock-fs');
|
||||
@@ -152,10 +151,10 @@ describe('liquid', function() {
|
||||
it('should be disabled by default', function() {
|
||||
return engine.renderFile('files/foo')
|
||||
.then(x => expect(x).to.equal('foo'))
|
||||
.then(x => mock({
|
||||
.then(() => mock({
|
||||
'/root/files/foo.html': 'bar'
|
||||
}))
|
||||
.then(x => engine.renderFile('files/foo'))
|
||||
.then(() => engine.renderFile('files/foo'))
|
||||
.then(x => expect(x).to.equal('bar'));
|
||||
});
|
||||
it('should respect cache=true option', function() {
|
||||
@@ -166,10 +165,10 @@ describe('liquid', function() {
|
||||
});
|
||||
return engine.renderFile('files/foo')
|
||||
.then(x => expect(x).to.equal('foo'))
|
||||
.then(x => mock({
|
||||
.then(() => mock({
|
||||
'/root/files/foo.html': 'bar'
|
||||
}))
|
||||
.then(x => engine.renderFile('files/foo'))
|
||||
.then(() => engine.renderFile('files/foo'))
|
||||
.then(x => expect(x).to.equal('foo'));
|
||||
});
|
||||
});
|
||||
|
||||
+86
-48
@@ -1,65 +1,103 @@
|
||||
const chai = require("chai");
|
||||
const expect = chai.expect;
|
||||
const mock = require('mock-fs');
|
||||
chai.use(require("chai-as-promised"));
|
||||
|
||||
var engine = require('../..')(), ctx;
|
||||
|
||||
function test(promise, cb){
|
||||
return promise
|
||||
.then((result) => {
|
||||
return cb({});
|
||||
})
|
||||
.catch((error) => {
|
||||
return cb(error);
|
||||
});
|
||||
}
|
||||
var engine = require('../..')();
|
||||
var strictEngine = require('../..')({
|
||||
strict_variables: true,
|
||||
strict_filters: true
|
||||
});
|
||||
|
||||
describe('error', function() {
|
||||
|
||||
it('should throw TokenizationError when tag illegal', function() {
|
||||
return test(engine.parseAndRender('{% . a %}', {}), 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);
|
||||
describe('TokenizationError', function() {
|
||||
it('should throw TokenizationError when tag 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 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() {
|
||||
return test(engine.parseAndRender('{%if true%}\naaa{%endif%}\n{% -a %}\n3', {}), function(err){
|
||||
expect(err.input).to.equal('{% -a %}');
|
||||
expect(err.line).to.equal(3);
|
||||
describe('TypeError', function() {
|
||||
it('should not throw when variable undefined by default', function() {
|
||||
return expect(engine.parseAndRender('X{{a}}Y')).to.eventually.equal('XY');
|
||||
});
|
||||
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() {
|
||||
mock({
|
||||
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
|
||||
describe('ParseError', function() {
|
||||
it('should throw correct error info', function() {
|
||||
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){
|
||||
mock.restore();
|
||||
expect(err.input).to.equal('{% raw %}');
|
||||
expect(err.line).to.equal(4);
|
||||
expect(err.file).to.equal('/foo.html');
|
||||
it('should throw correct error info for files', function() {
|
||||
mock({
|
||||
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
|
||||
});
|
||||
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;
|
||||
});
|
||||
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() {
|
||||
expect(_.isString(123)).to.be.false;
|
||||
@@ -53,7 +53,7 @@ describe('util/underscore', function() {
|
||||
expect(_.isArray("foo")).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('.echo()', function(){
|
||||
describe('.echo()', function() {
|
||||
it('should be transparent', function() {
|
||||
expect(_.echo('foo')('bar')).to.equal('bar');
|
||||
});
|
||||
@@ -63,4 +63,49 @@ describe('util/underscore', function() {
|
||||
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