mirror of
https://github.com/harttle/liquidjs.git
synced 2026-10-03 09:05:14 -07:00
feature: strict_variables, working on #9
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
const scope = require('./src/scope');
|
const Scope = require('./src/scope');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const tokenizer = require('./src/tokenizer.js');
|
const tokenizer = require('./src/tokenizer.js');
|
||||||
@@ -34,16 +34,22 @@ var _engine = {
|
|||||||
var tokens = tokenizer.parse(html);
|
var tokens = tokenizer.parse(html);
|
||||||
return this.parser.parse(tokens);
|
return this.parser.parse(tokens);
|
||||||
},
|
},
|
||||||
render: function(tpl, ctx) {
|
render: function(tpl, ctx, opts) {
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
strict_variables: false,
|
||||||
|
strict_filters: false
|
||||||
|
});
|
||||||
this.renderer.resetRegisters();
|
this.renderer.resetRegisters();
|
||||||
return this.renderer.renderTemplates(tpl, scope.factory(ctx));
|
var scope = Scope.factory(ctx, {
|
||||||
|
strict: opts.strict_variables,
|
||||||
|
});
|
||||||
|
return this.renderer.renderTemplates(tpl, scope);
|
||||||
},
|
},
|
||||||
parseAndRender: function(html, ctx) {
|
parseAndRender: function(html, ctx, opts) {
|
||||||
try {
|
try {
|
||||||
var tpl = this.parse(html);
|
var tpl = this.parse(html);
|
||||||
return this.render(tpl, ctx);
|
return this.render(tpl, ctx, opts);
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
// A throw inside of a then or catch of a Promise automatically rejects, but since we mix a sync call
|
// A throw inside of a then or catch of a Promise automatically rejects, but since we mix a sync call
|
||||||
// with an async call, we need to do this in case the sync call throws.
|
// with an async call, we need to do this in case the sync call throws.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
@@ -83,8 +89,11 @@ var _engine = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
getTemplate: function(filepath) {
|
getTemplate: function(filepath) {
|
||||||
var html = fs.readFileSync(filepath, 'utf8');
|
return new Promise(function(resolve, reject) {
|
||||||
return Promise.resolve(html);
|
fs.readFile(filepath, 'utf8', function(err, html) {
|
||||||
|
err ? reject(err) : resolve(html);
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
express: function() {
|
express: function() {
|
||||||
return (filePath, options, callback) => {
|
return (filePath, options, callback) => {
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ util.inherits(TokenizationError, Error);
|
|||||||
|
|
||||||
function ParseError(message, input, line) {
|
function ParseError(message, input, line) {
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
this.name = "ParseError";
|
this.name = this.constructor.name;
|
||||||
|
|
||||||
this.message = message || "";
|
this.message = message || "";
|
||||||
this.input = input;
|
this.input = input;
|
||||||
|
|||||||
+8
-3
@@ -2,11 +2,16 @@ const error = require('./error.js');
|
|||||||
const Exp = require('./expression.js');
|
const Exp = require('./expression.js');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const Promise = require('any-promise');
|
const Promise = require('any-promise');
|
||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
var render = {
|
var render = {
|
||||||
|
|
||||||
renderTemplates: function(templates, scope) {
|
renderTemplates: function(templates, scope, opts) {
|
||||||
assert(scope, 'unable to evalTemplates: scope undefined');
|
assert(scope, 'unable to evalTemplates: scope undefined');
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
strict_variables: false,
|
||||||
|
strict_filters: false
|
||||||
|
});
|
||||||
|
|
||||||
var html = '';
|
var html = '';
|
||||||
|
|
||||||
@@ -15,10 +20,10 @@ var render = {
|
|||||||
// emptyPromise.then(renderTag(template0).then(renderTag(template1).then(renderTag(template2)...
|
// emptyPromise.then(renderTag(template0).then(renderTag(template1).then(renderTag(template2)...
|
||||||
var lastPromise = templates.reduce((promise, template) => {
|
var lastPromise = templates.reduce((promise, template) => {
|
||||||
return promise.then((partial) => {
|
return promise.then((partial) => {
|
||||||
if (scope.get('forloop.skip')) {
|
if (scope.safeGet('forloop.skip')) {
|
||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
}
|
}
|
||||||
if (scope.get('forloop.stop')) {
|
if (scope.safeGet('forloop.stop')) {
|
||||||
throw new Error('forloop.stop'); // this will stop/break the sequential promise chain and go to the catch
|
throw new Error('forloop.stop'); // this will stop/break the sequential promise chain and go to the catch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-11
@@ -1,20 +1,30 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const lexical = require('./lexical.js');
|
const lexical = require('./lexical.js');
|
||||||
|
|
||||||
var scope = {
|
var Scope = {
|
||||||
get: function(str) {
|
safeGet: function(str) {
|
||||||
var ctx = {};
|
var i;
|
||||||
for (var i = this.scopes.length - 1; i >= 0; i--) {
|
// get all
|
||||||
if (str === undefined) {
|
if (str === undefined) {
|
||||||
|
var ctx = {};
|
||||||
|
for (i = this.scopes.length - 1; i >= 0; i--) {
|
||||||
_.merge(ctx, this.scopes[i]);
|
_.merge(ctx, this.scopes[i]);
|
||||||
}
|
}
|
||||||
var v = _.get(this.scopes[i], str);
|
|
||||||
if (v !== undefined) return v;
|
|
||||||
if(str === undefined){
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
// get one path
|
||||||
|
for (i = this.scopes.length - 1; i >= 0; i--) {
|
||||||
|
var v = _.get(this.scopes[i], str);
|
||||||
|
if (v !== undefined) return v;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
get: function(str){
|
||||||
|
var val = this.safeGet(str);
|
||||||
|
if (val === undefined && this.opts.strict) {
|
||||||
|
throw new Error(`[strict_variables] undefined variable: ${str}`);
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
},
|
||||||
set: function(k, v) {
|
set: function(k, v) {
|
||||||
_.set(this.scopes[this.scopes.length - 1], k, v);
|
_.set(this.scopes[this.scopes.length - 1], k, v);
|
||||||
return this;
|
return this;
|
||||||
@@ -28,8 +38,13 @@ var scope = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.factory = function(_ctx) {
|
exports.factory = function(_ctx, opts) {
|
||||||
var ctx = Object.create(scope);
|
opts = _.defaults(opts, {
|
||||||
ctx.scopes = [_ctx || {}];
|
strict: false
|
||||||
return ctx;
|
});
|
||||||
|
|
||||||
|
var scope = Object.create(Scope);
|
||||||
|
scope.opts = opts;
|
||||||
|
scope.scopes = [_ctx || {}];
|
||||||
|
return scope;
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-1
@@ -13,7 +13,8 @@ describe('expression', function() {
|
|||||||
scope = Scope.factory({
|
scope = Scope.factory({
|
||||||
one: 1,
|
one: 1,
|
||||||
two: 2,
|
two: 2,
|
||||||
x: 'XXX'
|
x: 'XXX',
|
||||||
|
z: 'z'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+35
-14
@@ -74,20 +74,41 @@ describe('liquid', function() {
|
|||||||
return engine.renderFile('files/name', ctx).should.eventually.equal('My name is harttle.');
|
return engine.renderFile('files/name', ctx).should.eventually.equal('My name is harttle.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// todo: make these async
|
describe('#express()', function() {
|
||||||
// describe('#express()', function() {
|
it('should render templates', function() {
|
||||||
// it('should render templates', function() {
|
engine.express()('/root/files/name.html', ctx, function(err, html) {
|
||||||
// engine.express()('/root/files/name.html', ctx, function(err, html) {
|
expect(err).to.equal(null);
|
||||||
// expect(err).to.equal(null);
|
expect(html).to.equal('My name is harttle.');
|
||||||
// expect(html).to.equal('My name is harttle.');
|
});
|
||||||
// });
|
});
|
||||||
// });
|
it('should pass error when file not found', function() {
|
||||||
// it('should pass error when file not found', function() {
|
engine.express()('/root/files/name1.html', ctx, function(err, html) {
|
||||||
// engine.express()('/root/files/name1.html', ctx, function(err, html) {
|
expect(err.code).to.equal('ENOENT');
|
||||||
// expect(err.code).to.equal('ENOENT');
|
});
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// });
|
describe('strict', function() {
|
||||||
|
it('should not throw when strict_variables false (default)', function() {
|
||||||
|
return expect(engine.parseAndRender('before{{notdefined}}after', ctx)).to
|
||||||
|
.eventually.equal('beforeafter');
|
||||||
|
});
|
||||||
|
it('should throw when strict_variables true', function() {
|
||||||
|
var tpl = engine.parse('before{{notdefined}}after');
|
||||||
|
var opts = {
|
||||||
|
strict_variables: true
|
||||||
|
};
|
||||||
|
return expect(engine.render(tpl, ctx, opts)).to
|
||||||
|
.be.rejectedWith(/undefined variable: notdefined/);
|
||||||
|
});
|
||||||
|
it('should pass strict_variables to render by parseAndRender', function() {
|
||||||
|
var html = 'before{{notdefined}}after';
|
||||||
|
var opts = {
|
||||||
|
strict_variables: true
|
||||||
|
};
|
||||||
|
return expect(engine.parseAndRender(html, ctx, opts)).to
|
||||||
|
.be.rejectedWith(/undefined variable: notdefined/);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('cache', function() {
|
describe('cache', function() {
|
||||||
it('should be disabled by default', function() {
|
it('should be disabled by default', function() {
|
||||||
mock({
|
mock({
|
||||||
|
|||||||
@@ -18,6 +18,24 @@ describe('scope', function() {
|
|||||||
scope.get('foo').should.equal('bar');
|
scope.get('foo').should.equal('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should get undefined property', function() {
|
||||||
|
function fn(){
|
||||||
|
scope.get('notdefined');
|
||||||
|
}
|
||||||
|
expect(fn).to.not.throw();
|
||||||
|
expect(scope.get('notdefined')).to.equal(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw undefined in strict mode', function() {
|
||||||
|
scope = Scope.factory(ctx, {
|
||||||
|
strict: true
|
||||||
|
});
|
||||||
|
function fn(){
|
||||||
|
scope.get('notdefined');
|
||||||
|
}
|
||||||
|
expect(fn).to.throw(/undefined variable: notdefined/);
|
||||||
|
});
|
||||||
|
|
||||||
it('should get all property', function() {
|
it('should get all property', function() {
|
||||||
scope.get().should.deep.equal(ctx);
|
scope.get().should.deep.equal(ctx);
|
||||||
expect(scope.get('')).to.equal(undefined);
|
expect(scope.get('')).to.equal(undefined);
|
||||||
|
|||||||
Reference in New Issue
Block a user