diff --git a/.travis.yml b/.travis.yml index bbe55e329..63c100fde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: node_js node_js: - - "4" + - "5" before_script: - npm install -g mocha after_script: diff --git a/index.js b/index.js index 601deeea5..8ba926f3e 100644 --- a/index.js +++ b/index.js @@ -34,7 +34,7 @@ var _engine = { return this; }, parse: function(html, filepath) { - var tokens = tokenizer.parse(html, filepath); + var tokens = tokenizer.parse(html, filepath, this.options); return this.parser.parse(tokens); }, render: function(tpl, ctx, opts) { @@ -115,14 +115,16 @@ var _engine = { }; function factory(options) { - options = _.assign({}, options); + options = _.assign({ + root: ['.'], + cache: false, + extname: '.liquid', + trim_right: false, + trim_left: false + }, options); options.root = normalizeStringArray(options.root); - if (!options.root.length) options.root = ['.']; - - options.extname = options.extname || '.liquid'; var engine = Object.create(_engine); - engine.init(Tag(), Filter(options), options); return engine; } diff --git a/package.json b/package.json index cbf15d6e3..1eedf3384 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "babelify": "^7.3.0", "browserify": "^13.1.0", "chai": "^3.5.0", - "chai-as-promised": "^5.3.0", + "chai-as-promised": "^6.0.0", "coveralls": "^2.11.9", "express": "^4.14.0", "istanbul": "^0.4.3", diff --git a/src/tokenizer.js b/src/tokenizer.js index 33a4057c7..ac77490c0 100644 --- a/src/tokenizer.js +++ b/src/tokenizer.js @@ -3,11 +3,13 @@ const TokenizationError = require('./util/error.js').TokenizationError; const _ = require('./util/underscore.js'); const assert = require('../src/util/assert.js'); -function parse(html, filepath) { +function parse(html, filepath, options) { assert(_.isString(html), 'illegal input type'); + html = whiteSpaceCtrl(html, options); + var tokens = []; - var syntax = /({%(.*?)%})|({{(.*?)}})/g; + var syntax = /({%-?(.*?)-?%})|({{-?(.*?)-?}})/g; var result, htmlFragment, token; var lastMatchEnd = 0, lastMatchBegin = -1, parsedLinesCount = 0; @@ -71,4 +73,12 @@ function parse(html, filepath) { } } +function whiteSpaceCtrl(html, options){ + options = options || {}; + var rLeft = options.trim_left ? /\s+({[{%])/g : /\s+({[{%]-)/g; + var rRight = options.trim_right ? /([}%]})\s+/g : /(-[}%]})\s+/g; + return html.replace(rLeft, '$1').replace(rRight, '$1'); +} + exports.parse = parse; +exports.whiteSpaceCtrl = whiteSpaceCtrl; diff --git a/test/tokenizer.js b/test/tokenizer.js index 85f655ae3..024d8f648 100644 --- a/test/tokenizer.js +++ b/test/tokenizer.js @@ -1,57 +1,100 @@ -var chai = require("chai"); -var should = chai.should(); -var expect = chai.expect; +const chai = require("chai"); +const parse = require('../src/tokenizer.js').parse; +const whiteSpaceCtrl = require('../src/tokenizer.js').whiteSpaceCtrl; -var tokenizer = require('../src/tokenizer.js'); +const should = chai.should(); +const expect = chai.expect; describe('tokenizer', function() { - it('should handle plain HTML', function() { - var html = '

Lorem Ipsum

'; - var tokens = tokenizer.parse(html); + describe('parse', function() { + it('should handle plain HTML', function() { + var html = '

Lorem Ipsum

'; + var tokens = parse(html); - tokens.length.should.equal(1); - tokens[0].value.should.equal(html); - tokens[0].type.should.equal('html'); - }); - it('should throw when non-string passed in', function() { - expect(function() { - tokenizer.parse({}); - }).to.throw('illegal input type'); - }); - it('should handle tag syntax', function() { - var html = '

{% for p in a[1]%}

'; - var tokens = tokenizer.parse(html); + tokens.length.should.equal(1); + tokens[0].value.should.equal(html); + tokens[0].type.should.equal('html'); + }); + it('should throw when non-string passed in', function() { + expect(function() { + parse({}); + }).to.throw('illegal input type'); + }); + it('should handle tag syntax', function() { + var html = '

{% for p in a[1]%}

'; + var tokens = parse(html); - tokens.length.should.equal(3); - tokens[1].type.should.equal('tag'); - tokens[1].value.should.equal('for p in a[1]'); - }); - it('should handle output syntax', function() { - var html = '

{{foo | date: "%Y-%m-%d"}}

'; - var tokens = tokenizer.parse(html); + tokens.length.should.equal(3); + tokens[1].type.should.equal('tag'); + tokens[1].value.should.equal('for p in a[1]'); + }); + it('should handle output syntax', function() { + var html = '

{{foo | date: "%Y-%m-%d"}}

'; + var tokens = parse(html); - tokens.length.should.equal(3); - tokens[1].type.should.equal('output'); - tokens[1].value.should.equal('foo | date: "%Y-%m-%d"'); - }); - it('should handle successive outputs and tags', function() { - var html = '{{foo}}{{bar}}{%foo%}{%bar%}'; - var tokens = tokenizer.parse(html); + tokens.length.should.equal(3); + tokens[1].type.should.equal('output'); + tokens[1].value.should.equal('foo | date: "%Y-%m-%d"'); + }); + it('should handle successive outputs and tags', function() { + var html = '{{foo}}{{bar}}{%foo%}{%bar%}'; + var tokens = parse(html); - tokens.length.should.equal(4); - tokens[0].type.should.equal('output'); - tokens[3].type.should.equal('tag'); + tokens.length.should.equal(4); + tokens[0].type.should.equal('output'); + tokens[3].type.should.equal('tag'); - tokens[1].value.should.equal('bar'); - tokens[2].value.should.equal('foo'); + tokens[1].value.should.equal('bar'); + tokens[2].value.should.equal('foo'); + }); + it('should keep white spaces and newlines', function() { + var html = '{{foo}}\n{%bar %} \n {{alice}}'; + var tokens = parse(html); + expect(tokens.length).to.equal(5); + expect(tokens[1].type).to.equal('html'); + expect(tokens[1].raw).to.equal('\n'); + expect(tokens[3].type).to.equal('html'); + expect(tokens[3].raw).to.equal(' \n '); + }); }); - it('should keep white spaces and newlines', function() { - var html = '{{foo}}\n{%bar %} \n {{alice}}'; - var tokens = tokenizer.parse(html); - expect(tokens.length).to.equal(5); - expect(tokens[1].type).to.equal('html'); - expect(tokens[1].raw).to.equal('\n'); - expect(tokens[3].type).to.equal('html'); - expect(tokens[3].raw).to.equal(' \n '); + describe('whitespace control', function() { + it('should strip left whitespaces', function() { + expect(whiteSpaceCtrl(' {{- foo }}')).to.equal('{{- foo }}'); + }); + it('should strip right whitespaces', function() { + expect(whiteSpaceCtrl('{{ foo -}} ')).to.equal('{{ foo -}}'); + }); + it('should not strip left when not specified', function() { + expect(whiteSpaceCtrl(' {%foo-%} ')).to.equal(' {%foo-%}'); + }); + it('should not strip right when not specified', function() { + expect(whiteSpaceCtrl(' {{-foo}} ')).to.equal('{{-foo}} '); + }); + it('should strip all blank characters', function() { + expect(whiteSpaceCtrl('\t\r{{-foo-}}\n \n')).to.equal('{{-foo-}}'); + }); + it('should stop stripping when encountered normal chars', () => + expect(whiteSpaceCtrl('\ta\r{{-foo-}} b ')).to.equal('\ta{{-foo-}}b ')); + it('should strip whitespaces when set trim_left', function() { + expect(whiteSpaceCtrl(' {{foo}} ', { + trim_left: true + })).to.equal('{{foo}} '); + }); + it('should strip whitespaces when set trim_right', function() { + expect(whiteSpaceCtrl(' {{foo}} ', { + trim_right: true + })).to.equal(' {{foo}}'); + }); + it('markup should has priority over options', function() { + expect(whiteSpaceCtrl(' {{-foo}} ', { + trim_left: false + })).to.equal('{{-foo}} '); + }); + it('should support a mix of markup and options', function() { + expect(whiteSpaceCtrl(' {%-foo%} ', { + trim_left: true, + trim_right: true + })).to.equal('{%-foo%}'); + }); }); }); diff --git a/test/util/error.js b/test/util/error.js index 66c7a95bd..f02369006 100644 --- a/test/util/error.js +++ b/test/util/error.js @@ -233,7 +233,7 @@ describe('error', function() { .be.rejected .then(function(err) { expect(err.stack).to.contain('intended render reject'); - expect(err.stack).to.contain('at Object.engine.registerTag.render'); + expect(err.stack).to.match(/at .*:\d+:\d+\)/); }); }); @@ -365,7 +365,7 @@ describe('error', function() { .be.rejected .then(function(err) { expect(err.stack).to.contain('AssertionError: tag -a not found'); - expect(err.stack).to.contain('at Object._tagInstance.parse'); + expect(err.stack).to.match(/at .*:\d+:\d+\)$/); }); });