mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-14 20:00:39 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0ba9bfd9 | ||
|
|
89636736a4 | ||
|
|
deca9677dd | ||
|
|
543368ed89 | ||
|
|
dcd7b01fa4 | ||
|
|
209d3910ab | ||
|
|
40bdd7e94a | ||
|
|
03b7ee95ed | ||
|
|
64e9cb7044 | ||
|
|
20af0c489e | ||
|
|
2bc53a3850 | ||
|
|
3a450bba1b | ||
|
|
397b2e48b6 |
@@ -3,7 +3,7 @@
|
||||
[](https://www.npmjs.org/package/shopify-liquid)
|
||||
[](https://travis-ci.org/harttle/shopify-liquid)
|
||||
[](https://coveralls.io/github/harttle/shopify-liquid?branch=master)
|
||||
[](https://david-dm.org/harttle/shopify-liquid)
|
||||
[](https://david-dm.org/harttle/shopify-liquid)
|
||||
|
||||
A feature-rich Liquid template engine for Node.js and browsers,
|
||||
with compliance with [the Ruby version][shopify-liquid].
|
||||
|
||||
Vendored
+32
-13
@@ -34,10 +34,10 @@ var filters = {
|
||||
},
|
||||
'date': function date(v, arg) {
|
||||
if (v === 'now') v = new Date();
|
||||
return strftime(v, arg);
|
||||
return v instanceof Date ? strftime(v, arg) : '';
|
||||
},
|
||||
'default': function _default(v, arg) {
|
||||
return arg || v;
|
||||
return v || arg;
|
||||
},
|
||||
'divided_by': function divided_by(v, arg) {
|
||||
return Math.floor(v / arg);
|
||||
@@ -241,8 +241,8 @@ var _engine = {
|
||||
|
||||
return this;
|
||||
},
|
||||
parse: function parse(html) {
|
||||
var tokens = tokenizer.parse(html);
|
||||
parse: function parse(html, filepath) {
|
||||
var tokens = tokenizer.parse(html, filepath);
|
||||
return this.parser.parse(tokens);
|
||||
},
|
||||
render: function render(tpl, ctx, opts) {
|
||||
@@ -270,9 +270,6 @@ var _engine = {
|
||||
opts = _.assign({}, opts);
|
||||
return this.getTemplate(filepath, opts.root).then(function (templates) {
|
||||
return _this2.render(templates, ctx, opts);
|
||||
}).catch(function (e) {
|
||||
e.file = filepath;
|
||||
throw e;
|
||||
});
|
||||
},
|
||||
evalOutput: function evalOutput(str, scope) {
|
||||
@@ -321,7 +318,7 @@ var _engine = {
|
||||
});
|
||||
} else {
|
||||
return readFileAsync(filepath).then(function (str) {
|
||||
return _this3.parse(str);
|
||||
return _this3.parse(str, filepath);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1198,7 +1195,7 @@ var TokenizationError = require('./util/error.js').TokenizationError;
|
||||
var _ = require('./util/underscore.js');
|
||||
var assert = require('../src/util/assert.js');
|
||||
|
||||
function parse(html) {
|
||||
function parse(html, filepath) {
|
||||
assert(_.isString(html), 'illegal input type');
|
||||
|
||||
var tokens = [];
|
||||
@@ -1256,7 +1253,8 @@ function parse(html) {
|
||||
raw: match[offset],
|
||||
value: match[offset + 1].trim(),
|
||||
line: getLineNum(match),
|
||||
input: html
|
||||
input: html,
|
||||
file: filepath
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1300,9 +1298,11 @@ function TokenizationError(message, token) {
|
||||
|
||||
this.input = token.input;
|
||||
this.line = token.line;
|
||||
this.file = token.file;
|
||||
|
||||
var context = mkContext(token.input, token.line);
|
||||
this.message = message + '\n' + context;
|
||||
this.message = mkMessage(message, token);
|
||||
this.stack = context + '\n' + (this.stack || '');
|
||||
}
|
||||
TokenizationError.prototype = Object.create(Error.prototype);
|
||||
TokenizationError.prototype.constructor = TokenizationError;
|
||||
@@ -1313,22 +1313,30 @@ function ParseError(e, token) {
|
||||
|
||||
this.input = token.input;
|
||||
this.line = token.line;
|
||||
this.file = token.file;
|
||||
|
||||
var context = mkContext(token.input, token.line);
|
||||
this.message = e.message + '\n' + context;
|
||||
this.message = mkMessage(e.message, token);
|
||||
this.stack = context + '\n' + (this.stack || '');
|
||||
}
|
||||
ParseError.prototype = Object.create(Error.prototype);
|
||||
ParseError.prototype.constructor = ParseError;
|
||||
|
||||
function RenderError(e, tpl) {
|
||||
// return the original render error
|
||||
if (e instanceof RenderError) {
|
||||
return e;
|
||||
}
|
||||
this.name = this.constructor.name;
|
||||
this.stack = e.stack;
|
||||
|
||||
this.input = tpl.token.input;
|
||||
this.line = tpl.token.line;
|
||||
this.file = tpl.token.file;
|
||||
|
||||
var context = mkContext(tpl.token.input, tpl.token.line);
|
||||
this.message = e.message + '\n' + context;
|
||||
this.message = mkMessage(e.message, tpl.token);
|
||||
this.stack = context + '\n' + (e.stack || '');
|
||||
}
|
||||
RenderError.prototype = Object.create(Error.prototype);
|
||||
RenderError.prototype.constructor = RenderError;
|
||||
@@ -1372,6 +1380,17 @@ function align(n, max) {
|
||||
return blank + str;
|
||||
}
|
||||
|
||||
function mkMessage(msg, token) {
|
||||
msg = msg || '';
|
||||
if (token.file) {
|
||||
msg += ', file:' + token.file;
|
||||
}
|
||||
if (token.line) {
|
||||
msg += ', line:' + token.line;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TokenizationError: TokenizationError,
|
||||
ParseError: ParseError,
|
||||
|
||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+2
-2
@@ -23,9 +23,9 @@ var filters = {
|
||||
'ceil': v => Math.ceil(v),
|
||||
'date': (v, arg) => {
|
||||
if (v === 'now') v = new Date();
|
||||
return strftime(v, arg);
|
||||
return v instanceof Date ? strftime(v, arg) : '';
|
||||
},
|
||||
'default': (v, arg) => arg || v,
|
||||
'default': (v, arg) => v || arg,
|
||||
'divided_by': (v, arg) => Math.floor(v / arg),
|
||||
'downcase': v => v.toLowerCase(),
|
||||
'escape': escape,
|
||||
|
||||
@@ -33,8 +33,8 @@ var _engine = {
|
||||
|
||||
return this;
|
||||
},
|
||||
parse: function(html) {
|
||||
var tokens = tokenizer.parse(html);
|
||||
parse: function(html, filepath) {
|
||||
var tokens = tokenizer.parse(html, filepath);
|
||||
return this.parser.parse(tokens);
|
||||
},
|
||||
render: function(tpl, ctx, opts) {
|
||||
@@ -56,11 +56,7 @@ var _engine = {
|
||||
renderFile: function(filepath, ctx, opts) {
|
||||
opts = _.assign({}, opts);
|
||||
return this.getTemplate(filepath, opts.root)
|
||||
.then(templates => this.render(templates, ctx, opts))
|
||||
.catch(e => {
|
||||
e.file = filepath;
|
||||
throw e;
|
||||
});
|
||||
.then(templates => this.render(templates, ctx, opts));
|
||||
},
|
||||
evalOutput: function(str, scope) {
|
||||
var tpl = this.parser.parseOutput(str.trim());
|
||||
@@ -100,7 +96,7 @@ var _engine = {
|
||||
.then(str => this.parse(str))
|
||||
.then(tpl => this.cache[filepath] = tpl);
|
||||
} else {
|
||||
return readFileAsync(filepath).then(str => this.parse(str));
|
||||
return readFileAsync(filepath).then(str => this.parse(str, filepath));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "shopify-liquid",
|
||||
"version": "1.4.2",
|
||||
"version": "1.5.3",
|
||||
"description": "A feature-rich Liquid template engine for Node.js and browsers, with compliance with the Ruby version.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
+3
-2
@@ -3,7 +3,7 @@ const TokenizationError = require('./util/error.js').TokenizationError;
|
||||
const _ = require('./util/underscore.js');
|
||||
const assert = require('../src/util/assert.js');
|
||||
|
||||
function parse(html) {
|
||||
function parse(html, filepath) {
|
||||
assert(_.isString(html), 'illegal input type');
|
||||
|
||||
var tokens = [];
|
||||
@@ -58,7 +58,8 @@ function parse(html) {
|
||||
raw: match[offset],
|
||||
value: match[offset + 1].trim(),
|
||||
line: getLineNum(match),
|
||||
input: html
|
||||
input: html,
|
||||
file: filepath
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+24
-3
@@ -8,9 +8,11 @@ function TokenizationError(message, token) {
|
||||
|
||||
this.input = token.input;
|
||||
this.line = token.line;
|
||||
this.file = token.file;
|
||||
|
||||
var context = mkContext(token.input, token.line);
|
||||
this.message = message + '\n' + context;
|
||||
this.message = mkMessage(message, token);
|
||||
this.stack = context + '\n' + (this.stack || '');
|
||||
}
|
||||
TokenizationError.prototype = Object.create(Error.prototype);
|
||||
TokenizationError.prototype.constructor = TokenizationError;
|
||||
@@ -21,22 +23,30 @@ function ParseError(e, token) {
|
||||
|
||||
this.input = token.input;
|
||||
this.line = token.line;
|
||||
this.file = token.file;
|
||||
|
||||
var context = mkContext(token.input, token.line);
|
||||
this.message = e.message + '\n' + context;
|
||||
this.message = mkMessage(e.message, token);
|
||||
this.stack = context + '\n' + (this.stack || '');
|
||||
}
|
||||
ParseError.prototype = Object.create(Error.prototype);
|
||||
ParseError.prototype.constructor = ParseError;
|
||||
|
||||
function RenderError(e, tpl) {
|
||||
// return the original render error
|
||||
if(e instanceof RenderError){
|
||||
return e;
|
||||
}
|
||||
this.name = this.constructor.name;
|
||||
this.stack = e.stack;
|
||||
|
||||
this.input = tpl.token.input;
|
||||
this.line = tpl.token.line;
|
||||
this.file = tpl.token.file;
|
||||
|
||||
var context = mkContext(tpl.token.input, tpl.token.line);
|
||||
this.message = e.message + '\n' + context;
|
||||
this.message = mkMessage(e.message, tpl.token);
|
||||
this.stack = context + '\n' + (e.stack || '');
|
||||
}
|
||||
RenderError.prototype = Object.create(Error.prototype);
|
||||
RenderError.prototype.constructor = RenderError;
|
||||
@@ -86,6 +96,17 @@ function align(n, max) {
|
||||
return blank + str;
|
||||
}
|
||||
|
||||
function mkMessage(msg, token){
|
||||
msg = msg || '';
|
||||
if(token.file){
|
||||
msg += ', file:' + token.file;
|
||||
}
|
||||
if(token.line){
|
||||
msg += ', line:' + token.line;
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TokenizationError,
|
||||
ParseError,
|
||||
|
||||
+7
-1
@@ -51,9 +51,15 @@ describe('filters', function() {
|
||||
it('should create a new Date when given "now"', function() {
|
||||
return test('{{ "now" | date: "%Y"}}', (new Date).getFullYear().toString());
|
||||
});
|
||||
it('should render as empty string when invalid', function() {
|
||||
return test('{{ "" | date: "%Y"}}', "");
|
||||
});
|
||||
});
|
||||
|
||||
it('should support default', () => test('{{false |default: "a"}}', 'a'));
|
||||
describe('default', function() {
|
||||
it('should use default when falsy', () => test('{{false |default: "a"}}', 'a'));
|
||||
it('should not use default when truthy', () => test('{{true |default: "a"}}', 'true'));
|
||||
});
|
||||
|
||||
describe('divided_by', function() {
|
||||
it('should return 2 for 4,2', () => test('{{4 | divided_by: 2}}', '2'));
|
||||
|
||||
+46
-20
@@ -11,7 +11,10 @@ describe('tag', function() {
|
||||
before(function() {
|
||||
scope = Scope.factory({
|
||||
foo: 'bar',
|
||||
arr: [2, 1]
|
||||
arr: [2, 1],
|
||||
bar: {
|
||||
coo: 'uoo'
|
||||
}
|
||||
});
|
||||
tag.clear();
|
||||
});
|
||||
@@ -36,8 +39,7 @@ describe('tag', function() {
|
||||
});
|
||||
|
||||
it('should call tag.render', function() {
|
||||
var spy = sinon.spy(),
|
||||
tokens = [];
|
||||
var spy = sinon.spy();
|
||||
tag.register('foo', {
|
||||
render: spy
|
||||
});
|
||||
@@ -51,23 +53,47 @@ describe('tag', function() {
|
||||
.then(() => expect(spy).to.have.been.called);
|
||||
});
|
||||
|
||||
it('should call tag.render with resolved hash', function() {
|
||||
var spy = sinon.spy(),
|
||||
tokens = [];
|
||||
tag.register('foo', {
|
||||
render: spy
|
||||
describe('hash', function(){
|
||||
var spy, token;
|
||||
beforeEach(function(){
|
||||
spy = sinon.spy();
|
||||
tag.register('foo', {
|
||||
render: spy
|
||||
});
|
||||
token = {
|
||||
type: 'tag',
|
||||
value: 'foo aa:foo bb: arr[0] cc: 2.3 dd:bar.coo',
|
||||
name: 'foo',
|
||||
args: 'aa:foo bb: arr[0] cc: 2.3 dd:bar.coo'
|
||||
};
|
||||
});
|
||||
it('should call tag.render with scope', function() {
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch(scope));
|
||||
});
|
||||
it('should resolve identifier hash', function() {
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch({}, {
|
||||
aa: 'bar'
|
||||
}));
|
||||
});
|
||||
it('should accept space between key/value', function() {
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch({}, {
|
||||
bb: 2,
|
||||
}));
|
||||
});
|
||||
it('should resolve number value hash', function() {
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch(scope, {
|
||||
cc: 2.3
|
||||
}));
|
||||
});
|
||||
it('should resolve property access hash', function() {
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch(scope, {
|
||||
dd: 'uoo'
|
||||
}));
|
||||
});
|
||||
var token = {
|
||||
type: 'tag',
|
||||
value: 'foo aa:foo bb: arr[0] cc: 2.3',
|
||||
name: 'foo',
|
||||
args: 'aa:foo bb: arr[0] cc: 2.3'
|
||||
};
|
||||
return tag.construct(token, []).render(scope, {})
|
||||
.then(() => expect(spy).to.have.been.calledWithMatch(scope, {
|
||||
aa: 'bar',
|
||||
bb: 2,
|
||||
cc: 2.3
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
+79
-15
@@ -10,6 +10,9 @@ var strictEngine = require('../..')({
|
||||
});
|
||||
|
||||
describe('error', function() {
|
||||
afterEach(function() {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('TokenizationError', function() {
|
||||
it('should throw TokenizationError when tag illegal', function() {
|
||||
@@ -23,16 +26,17 @@ describe('error', function() {
|
||||
it('should contain template content in err.message', function() {
|
||||
var html = ['1st', '2nd', 'X{% . a %} Y', '4th'];
|
||||
var message = [
|
||||
'illegal tag syntax',
|
||||
' 1| 1st',
|
||||
' 2| 2nd',
|
||||
'>> 3| X{% . a %} Y',
|
||||
' 4| 4th'
|
||||
' 4| 4th',
|
||||
'TokenizationError: illegal tag syntax',
|
||||
];
|
||||
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
expect(err.message).to.equal(message.join('\n'));
|
||||
expect(err.message).to.equal('illegal tag syntax, line:3');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
expect(err.name).to.equal('TokenizationError');
|
||||
});
|
||||
});
|
||||
@@ -77,7 +81,9 @@ describe('error', function() {
|
||||
|
||||
describe('RenderError', function() {
|
||||
beforeEach(function() {
|
||||
engine = require('../..')();
|
||||
engine = require('../..')({
|
||||
root: '/'
|
||||
});
|
||||
engine.registerTag('throwingTag', {
|
||||
render: function() {
|
||||
throw new Error('intended render error');
|
||||
@@ -130,21 +136,77 @@ describe('error', function() {
|
||||
expect(e.message).to.contain('undefined variable: a');
|
||||
});
|
||||
});
|
||||
it('should contain template content in err.message', function() {
|
||||
it('should contain template context in err.stack', function() {
|
||||
var html = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th'];
|
||||
var message = [
|
||||
'intended render error',
|
||||
' 2| 2nd',
|
||||
' 3| 3rd',
|
||||
'>> 4| X{%throwingTag%} Y',
|
||||
' 5| 5th',
|
||||
' 6| 6th',
|
||||
' 7| 7th'
|
||||
' 7| 7th',
|
||||
'Error: intended render error',
|
||||
];
|
||||
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
expect(err.message).to.equal(message.join('\n'));
|
||||
expect(err.message).to.equal('intended render error, line:4');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
expect(err.name).to.equal('RenderError');
|
||||
});
|
||||
});
|
||||
it('should contain original error info for {% layout %}', function() {
|
||||
mock({
|
||||
'/throwing-tag.html': [
|
||||
'1st',
|
||||
'2nd',
|
||||
'3rd',
|
||||
'X{%throwingTag%} Y',
|
||||
'5th',
|
||||
'{%block%}{%endblock%}',
|
||||
'7th'
|
||||
].join('\n')
|
||||
});
|
||||
var html = '{%layout "throwing-tag.html"%}';
|
||||
var message = [
|
||||
' 2| 2nd',
|
||||
' 3| 3rd',
|
||||
'>> 4| X{%throwingTag%} Y',
|
||||
' 5| 5th',
|
||||
' 6| {%block%}{%endblock%}',
|
||||
' 7| 7th',
|
||||
'Error: intended render error',
|
||||
];
|
||||
return expect(engine.parseAndRender(html)).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
console.log(err.message);
|
||||
console.log(err.stack);
|
||||
expect(err.message).to.equal('intended render error, file:/throwing-tag.html, line:4');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
expect(err.name).to.equal('RenderError');
|
||||
});
|
||||
});
|
||||
it('should contain original error info for {% include %}', function() {
|
||||
var origin = ['1st', '2nd', '3rd', 'X{%throwingTag%} Y', '5th', '6th', '7th'];
|
||||
mock({
|
||||
'/throwing-tag.html': origin.join('\n')
|
||||
});
|
||||
var html = '{%include "throwing-tag.html"%}';
|
||||
var message = [
|
||||
' 2| 2nd',
|
||||
' 3| 3rd',
|
||||
'>> 4| X{%throwingTag%} Y',
|
||||
' 5| 5th',
|
||||
' 6| 6th',
|
||||
' 7| 7th',
|
||||
'Error: intended render error',
|
||||
];
|
||||
return expect(engine.parseAndRender(html)).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
expect(err.message).to.equal('intended render error, file:/throwing-tag.html, line:4');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
expect(err.name).to.equal('RenderError');
|
||||
});
|
||||
});
|
||||
@@ -243,21 +305,22 @@ describe('error', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('should contain template content in err.message', function() {
|
||||
it('should contain template context in err.stack', function() {
|
||||
var html = ['1st', '2nd', '3rd', 'X{% a %} {% enda %} Y', '5th', '6th', '7th'];
|
||||
var message = [
|
||||
'tag a not found',
|
||||
' 2| 2nd',
|
||||
' 3| 3rd',
|
||||
'>> 4| X{% a %} {% enda %} Y',
|
||||
' 5| 5th',
|
||||
' 6| 6th',
|
||||
' 7| 7th'
|
||||
' 7| 7th',
|
||||
'AssertionError: tag a not found',
|
||||
];
|
||||
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
expect(err.message).to.equal(message.join('\n'));
|
||||
expect(err.message).to.equal('tag a not found, line:4');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
expect(err.name).to.equal('ParseError');
|
||||
});
|
||||
});
|
||||
@@ -265,16 +328,17 @@ describe('error', function() {
|
||||
it('should handle err.message when context not enough', function() {
|
||||
var html = ['1st', 'X{% a %} {% enda %} Y', '3rd', '4th'];
|
||||
var message = [
|
||||
'tag a not found',
|
||||
' 1| 1st',
|
||||
'>> 2| X{% a %} {% enda %} Y',
|
||||
' 3| 3rd',
|
||||
' 4| 4th'
|
||||
' 4| 4th',
|
||||
'AssertionError: tag a not found',
|
||||
];
|
||||
return expect(engine.parseAndRender(html.join('\n'))).to.eventually
|
||||
.be.rejected
|
||||
.then(function(err) {
|
||||
expect(err.message).to.equal(message.join('\n'));
|
||||
expect(err.message).to.equal('tag a not found, line:2');
|
||||
expect(err.stack).to.contain(message.join('\n'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user