refactor: use assert, fix: respect to express settings.views, close #16

This commit is contained in:
harttle
2016-11-03 00:43:23 +08:00
parent ce99bb56eb
commit f70b2ee203
25 changed files with 135 additions and 95 deletions
+24 -18
View File
@@ -1,5 +1,6 @@
const Scope = require('./src/scope');
const _ = require('./src/util/underscore.js');
const assert = require('./src/util/assert.js');
const tokenizer = require('./src/tokenizer.js');
const statFileAsync = require('./src/util/fs.js').statFileAsync;
const readFileAsync = require('./src/util/fs.js').readFileAsync;
@@ -40,12 +41,11 @@ var _engine = {
opts = opts || {};
opts.strict_variables = opts.strict_variables || false;
opts.strict_filters = opts.strict_filters || false;
this.renderer.resetRegisters();
this.renderer.initRegister(opts);
var scope = Scope.factory(ctx, {
strict: opts.strict_variables,
});
return this.renderer.renderTemplates(tpl, scope, opts);
return this.renderer.renderTemplates(tpl, scope);
},
parseAndRender: function(html, ctx, opts) {
return Promise.resolve()
@@ -59,15 +59,11 @@ var _engine = {
});
},
renderFile: function(filepath, ctx, opts) {
return this.getTemplate(filepath)
.then((templates) => {
return this.render(templates, ctx, opts);
})
.catch((e) => {
opts = opts || {};
return this.getTemplate(filepath, opts.root)
.then(templates => this.render(templates, ctx, opts))
.catch(e => {
e.file = filepath;
if (e.code === 'ENOENT') {
e.message = `Failed to lookup ${filepath} in: ${this.options.root}`;
}
throw e;
});
},
@@ -81,16 +77,23 @@ var _engine = {
registerTag: function(name, tag) {
return this.tag.register(name, tag);
},
lookup: function(filepath) {
var paths = this.options.root.map(root => pathResolve(root, filepath));
return anySeries(paths, path => statFileAsync(path).then(() => path));
lookup: function(filepath, root) {
root = this.options.root.concat(root || []);
var paths = root.map(root => pathResolve(root, filepath));
return anySeries(paths, path => statFileAsync(path).then(() => path))
.catch((e) => {
if (e.code === 'ENOENT') {
e.message = `Failed to lookup ${filepath} in: ${root}`;
}
throw e;
});
},
getTemplate: function(filepath) {
getTemplate: function(filepath, root) {
if (!filepath.match(/\.\w+$/)) {
filepath += this.options.extname;
}
return this
.lookup(filepath)
.lookup(filepath, root)
.then(filepath => {
if (this.options.cache) {
var tpl = this.cache[filepath];
@@ -107,8 +110,11 @@ var _engine = {
},
express: function(renderOption) {
renderOption = renderOption || {};
return (filePath, options, callback) => {
this.renderFile(filePath, options, renderOption)
var self = this;
return function(filePath, options, callback) {
assert(_.isArray(this.root), 'illegal views root, are you using express.js?');
renderOption.root = this.root;
self.renderFile(filePath, options, renderOption)
.then(html => callback(null, html))
.catch(e => callback(e));
};
+2 -1
View File
@@ -1,5 +1,6 @@
const lexical = require('./lexical.js');
const Syntax = require('./syntax.js');
const assert = require('./util/assert.js');
var valueRE = new RegExp(`${lexical.value.source}`, 'g');
@@ -14,7 +15,7 @@ module.exports = function() {
},
parse: function(str) {
var match = lexical.filterLine.exec(str);
if (!match) throw new Error('illegal filter: ' + str);
assert(match, 'illegal filter: ' + str);
var name = match[1], argList = match[2] || '', filter = filters[name];
if (typeof filter !== 'function'){
+2 -1
View File
@@ -1,5 +1,6 @@
const lexical = require('./lexical.js');
const ParseError = require('./util/error.js').ParseError;
const assert = require('./util/assert.js');
module.exports = function(Tag, Filter) {
@@ -71,7 +72,7 @@ module.exports = function(Tag, Filter) {
function parseOutput(str) {
var match = lexical.matchValue(str);
if(!match) throw new Error(`illegal output string: ${str}`);
assert(match, `illegal output string: ${str}`);
var initial = match[0];
str = str.substr(match.index + match[0].length);
+14 -15
View File
@@ -2,13 +2,12 @@ const Syntax = require('./syntax.js');
const Promise = require('any-promise');
const mapSeries = require('./util/promise.js').mapSeries;
const RenderBreak = require('./util/error.js').RenderBreak;
const assert = require('./util/assert.js');
var render = {
renderTemplates: function(templates, scope, opts) {
if (!scope) throw new Error('unable to evalTemplates: scope undefined');
opts = opts || {};
opts.strict_filters = opts.strict_filters || false;
renderTemplates: function(templates, scope) {
assert(scope, 'unable to evalTemplates: scope undefined');
var html = '';
return mapSeries(templates, (tpl) => {
@@ -24,10 +23,10 @@ var render = {
function renderTemplate(template){
if (template.type === 'tag') {
return this.renderTag(template, scope, this.register)
return this.renderTag(template, scope)
.then(partial => partial === undefined ? '' : partial);
} else if (template.type === 'output') {
return Promise.resolve(this.evalOutput(template, scope, opts))
return Promise.resolve(this.evalOutput(template, scope))
.then(partial => partial === undefined ? '' : stringify(partial));
} else { // template.type === 'html'
return Promise.resolve(template.value);
@@ -35,25 +34,25 @@ var render = {
}
},
renderTag: function(template, scope, register) {
renderTag: function(template, scope) {
if (template.name === 'continue') {
return Promise.reject(new RenderBreak('continue'));
}
if (template.name === 'break') {
return Promise.reject(new RenderBreak('break'));
}
return template.render(scope, register);
return template.render(scope, this.register);
},
evalOutput: function(template, scope, opts) {
if (!scope) throw new Error('unable to evalOutput: scope undefined');
evalOutput: function(template, scope) {
assert(scope, 'unable to evalOutput: scope undefined');
var val = Syntax.evalExp(template.initial, scope);
template.filters.some(filter => {
if (filter.error) {
if (opts.strict_filters) {
if (this.register.strict_filters) {
throw filter.error;
} else { // render as null
val = '';
} else {
val = ''
return true;
}
}
@@ -62,8 +61,8 @@ var render = {
return val;
},
resetRegisters: function() {
return this.register = {};
initRegister: function(opts) {
return this.register = opts;
}
};
+5 -8
View File
@@ -1,5 +1,6 @@
const _ = require('./util/underscore.js');
const lexical = require('./lexical.js');
const assert = require('./util/assert.js');
var Scope = {
safeGet: function(str) {
@@ -35,14 +36,14 @@ var Scope = {
return this;
},
push: function(ctx) {
if (!ctx) throw new Error(`trying to push ${ctx} into scopes`);
assert(ctx, `trying to push ${ctx} into scopes`);
return this.scopes.push(ctx);
},
pop: function() {
return this.scopes.pop();
},
unshift: function(ctx) {
if (!ctx) throw new Error(`trying to push ${ctx} into scopes`);
assert(ctx, `trying to push ${ctx} into scopes`);
return this.scopes.unshift(ctx);
},
shift: function() {
@@ -92,9 +93,7 @@ var Scope = {
// foo[bar.coo]
if (delemiter !== "'" && delemiter !== '"') {
var j = matchRightBracket(str, i + 1);
if (j === -1) {
throw new Error(`unbalanced []: ${str}`);
}
assert(j !== -1, `unbalanced []: ${str}`);
name = str.slice(i + 1, j);
// foo[1]
if(lexical.isInteger(name)){
@@ -110,9 +109,7 @@ var Scope = {
// foo["bar"]
else {
var j = str.indexOf(delemiter, i + 2);
if (j === -1) {
throw new Error(`unbalanced ${delemiter}: ${str}`);
}
assert(j !== -1, `unbalanced ${delemiter}: ${str}`);
name = str.slice(i + 2, j);
seq.push(name);
name = '';
+2 -1
View File
@@ -1,8 +1,9 @@
const operators = require('./operators.js');
const lexical = require('./lexical.js');
const assert = require('../src/util/assert.js');
function evalExp(exp, scope) {
if (!scope) throw new Error('unable to evalExp: scope undefined');
assert(scope, 'unable to evalExp: scope undefined');
var operatorREs = lexical.operators,
match;
for (var i = 0; i < operatorREs.length; i++) {
+3 -4
View File
@@ -1,6 +1,7 @@
const lexical = require('./lexical.js');
const Promise = require('any-promise');
const Syntax = require('./syntax.js');
const assert = require('./util/assert.js');
function hash(markup, scope) {
var obj = {}, match;
@@ -18,10 +19,8 @@ module.exports = function() {
var _tagInstance = {
render: function(scope, register) {
var reg = register[this.name];
if(!reg) reg = register[this.name] = {};
var obj = hash(this.token.args, scope);
return this.tagImpl.render && this.tagImpl.render(scope, obj, reg) || Promise.resolve('');
return this.tagImpl.render && this.tagImpl.render(scope, obj, register) || Promise.resolve('');
},
parse: function(token, tokens){
this.type = 'tag';
@@ -29,7 +28,7 @@ module.exports = function() {
this.name = token.name;
var tagImpl = tagImpls[this.name];
if (!tagImpl) throw new Error(`tag ${this.name} not found`);
assert(tagImpl, `tag ${this.name} not found`);
this.tagImpl = Object.create(tagImpl);
if(this.tagImpl.parse){
this.tagImpl.parse(token, tokens);
+2 -3
View File
@@ -1,12 +1,11 @@
const lexical = require('./lexical.js');
const TokenizationError = require('./util/error.js').TokenizationError;
const _ = require('./util/underscore.js');
const assert = require('../src/util/assert.js');
function parse(html) {
var tokens = [];
if (!_.isString(html)) {
throw new TokenizationError('illegal input type');
}
assert(_.isString(html), new TokenizationError('illegal input type'));
var syntax = /({%(.*?)%})|({{(.*?)}})/g;
var result, htmlFragment, token;
+13
View File
@@ -0,0 +1,13 @@
const AssertionError = require('./error.js').AssertionError;
function assert(predicate, message) {
if (!predicate) {
if (message instanceof Error) {
throw message;
}
var message = message || `expect ${predicate} to be true`;
throw new AssertionError(message);
}
}
module.exports = assert;
+11 -1
View File
@@ -35,6 +35,16 @@ function RenderBreak(message){
RenderBreak.prototype = Object.create(Error.prototype);
RenderBreak.prototype.constructor = RenderBreak;
function AssertionError(message){
if(Error.captureStackTrace){
Error.captureStackTrace(this, this.constructor);
}
this.name = this.constructor.name;
this.message = message;
}
AssertionError.prototype = Object.create(Error.prototype);
AssertionError.prototype.constructor = AssertionError;
module.exports = {
TokenizationError, ParseError, RenderBreak
TokenizationError, ParseError, RenderBreak, AssertionError
};
+6 -5
View File
@@ -1,14 +1,15 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var Promise = require('any-promise');
var re = new RegExp(`(${lexical.identifier.source})\\s*=(.*)`);
const Liquid = require('..');
const lexical = Liquid.lexical;
const Promise = require('any-promise');
const re = new RegExp(`(${lexical.identifier.source})\\s*=(.*)`);
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('assign', {
parse: function(token){
var match = token.args.match(re);
if(!match) throw new Error(`illegal token ${token.raw}`);
assert(match, `illegal token ${token.raw}`);
this.key = match[1];
this.value = match[2];
},
+5 -4
View File
@@ -1,13 +1,14 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`(${lexical.identifier.source})`);
const Liquid = require('..');
const lexical = Liquid.lexical;
const re = new RegExp(`(${lexical.identifier.source})`);
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('capture', {
parse: function(tagToken, remainTokens) {
var match = tagToken.args.match(re);
if (!match) throw new Error(`${tagToken.args} not valid identifier`);
assert(match, `${tagToken.args} not valid identifier`);
this.variable = match[1];
this.templates = [];
+2 -1
View File
@@ -1,4 +1,5 @@
var Liquid = require('..');
const Liquid = require('..');
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('case', {
+10 -11
View File
@@ -1,15 +1,16 @@
var Liquid = require('..');
var Promise = require('any-promise');
var lexical = Liquid.lexical;
var groupRE = new RegExp(`^(?:(${lexical.value.source})\\s*:\\s*)?(.*)$`);
var candidatesRE = new RegExp(lexical.value.source, 'g');
const Liquid = require('..');
const Promise = require('any-promise');
const lexical = Liquid.lexical;
const groupRE = new RegExp(`^(?:(${lexical.value.source})\\s*:\\s*)?(.*)$`);
const candidatesRE = new RegExp(lexical.value.source, 'g');
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('cycle', {
parse: function(tagToken, remainTokens) {
var match = groupRE.exec(tagToken.args);
if(!match) throw new Error(`illegal tag: ${tagToken.raw}`);
assert(match, `illegal tag: ${tagToken.raw}`);
this.group = match[1] || '';
var candidates = match[2];
@@ -20,14 +21,12 @@ module.exports = function(liquid) {
this.candidates.push(match[0]);
}
if (!this.candidates.length){
throw new Error(`empty candidates: ${tagToken.raw}`);
}
assert(this.candidates.length, `empty candidates: ${tagToken.raw}`);
},
render: function(scope, hash, register) {
var fingerprint = Liquid.evalValue(this.group, scope) + ':' +
this.candidates.join(',');
var group = Liquid.evalValue(this.group, scope);
var fingerprint = `cycle:${group}:` + this.candidates.join(',');
var idx = register[fingerprint];
if(idx === undefined){
+2 -1
View File
@@ -1,12 +1,13 @@
const Liquid = require('..');
const lexical = Liquid.lexical;
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('decrement', {
parse: function(token) {
var match = token.args.match(lexical.identifier);
if (!match) throw new Error(`illegal identifier ${token.args}`);
assert(match, `illegal identifier ${token.args}`);
this.variable = match[0];
},
render: function(scope, hash) {
+2 -1
View File
@@ -3,6 +3,7 @@ const Promise = require('any-promise');
const lexical = Liquid.lexical;
const mapSeries = require('../src/util/promise.js').mapSeries;
const RenderBreak = Liquid.Types.RenderBreak;
const assert = require('../src/util/assert.js');
const re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
`(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*` +
@@ -13,7 +14,7 @@ module.exports = function(liquid) {
parse: function(tagToken, remainTokens) {
var match = re.exec(tagToken.args);
if (!match) throw new Error(`illegal tag: ${tagToken.raw}`);
assert(match, `illegal tag: ${tagToken.raw}`);
this.variable = match[1];
this.collection = match[2];
this.reversed = !!match[3];
+1 -1
View File
@@ -1,4 +1,4 @@
var Liquid = require('..');
const Liquid = require('..');
module.exports = function(liquid) {
liquid.registerTag('if', {
+8 -6
View File
@@ -1,13 +1,14 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var withRE = new RegExp(`with\\s+(${lexical.value.source})`);
const Liquid = require('..');
const lexical = Liquid.lexical;
const withRE = new RegExp(`with\\s+(${lexical.value.source})`);
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('include', {
parse: function(token){
var match = lexical.value.exec(token.args);
if(!match) throw(new Error(`illegal token ${token.raw}`));
assert(match, `illegal token ${token.raw}`);
this.value = match[0];
match = withRE.exec(token.args);
@@ -15,12 +16,13 @@ module.exports = function(liquid) {
this.with = match[1];
}
},
render: function(scope, hash) {
render: function(scope, hash, register) {
console.log('include', register.root);
var filepath = Liquid.evalValue(this.value, scope);
if(this.with){
hash[filepath] = Liquid.evalValue(this.with, scope);
}
return liquid.getTemplate(filepath)
return liquid.getTemplate(filepath, register.root)
.then((templates) => {
scope.push(hash);
return liquid.renderer.renderTemplates(templates, scope);
+2 -1
View File
@@ -1,4 +1,5 @@
const Liquid = require('..');
const assert = require('../src/util/assert.js');
const lexical = Liquid.lexical;
module.exports = function(liquid) {
@@ -6,7 +7,7 @@ module.exports = function(liquid) {
liquid.registerTag('increment', {
parse: function(token) {
var match = token.args.match(lexical.identifier);
if (!match) throw (new Error(`illegal identifier ${token.args}`));
assert(match, `illegal identifier ${token.args}`);
this.variable = match[0];
},
render: function(scope, hash) {
+5 -4
View File
@@ -1,13 +1,14 @@
var Liquid = require('..');
var Promise = require('any-promise');
var lexical = Liquid.lexical;
const Liquid = require('..');
const Promise = require('any-promise');
const lexical = Liquid.lexical;
const assert = require('../src/util/assert.js');
module.exports = function(liquid) {
liquid.registerTag('layout', {
parse: function(token, remainTokens){
var match = lexical.value.exec(token.args);
if(!match) throw new Error(`illegal token ${token.raw}`);
assert(match, `illegal token ${token.raw}`);
this.layout = match[0];
this.tpls = liquid.parser.parse(remainTokens);
+1 -1
View File
@@ -1,4 +1,4 @@
var Promise = require('any-promise');
const Promise = require('any-promise');
module.exports = function(liquid) {
+6 -5
View File
@@ -1,7 +1,8 @@
var Liquid = require('..');
var Promise = require('any-promise');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
const Liquid = require('..');
const Promise = require('any-promise');
const lexical = Liquid.lexical;
const assert = require('../src/util/assert.js');
const re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
`(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*$`);
@@ -10,7 +11,7 @@ module.exports = function(liquid) {
parse: function(tagToken, remainTokens) {
var match = re.exec(tagToken.args);
if (!match) throw new Error(`illegal tag: ${tagToken.raw}`);
assert(match, `illegal tag: ${tagToken.raw}`);
this.variable = match[1];
this.collection = match[2];
+1 -1
View File
@@ -1,4 +1,4 @@
var Liquid = require('..');
const Liquid = require('..');
module.exports = function(liquid) {
liquid.registerTag('unless', {
+5
View File
@@ -66,4 +66,9 @@ describe('engine#express()', function() {
.expect('foo')
.expect(200, done);
});
it('should respect express settings.views when lookup', function(done) {
request(app).get('/include/bar')
.expect('bar')
.expect(200, done);
});
});
+1 -1
View File
@@ -37,10 +37,10 @@ describe('error', function() {
"/foo.html": '<html>\n<head>\n\n{% raw %}\n\n'
});
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');
mock.restore();
});
});