diff --git a/README.md b/README.md index 48d1bc873..2780528af 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,8 @@ Documentation: - [x] capture [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/capture.js) [Test][tt] - [x] increment [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/increment.js) [Test][tt] - [x] decrement [Document](https://shopify.github.io/liquid/tags/variable/) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/decrement.js) [Test][tt] +- [x] raw [Document](https://help.shopify.com/themes/liquid/tags/theme-tags#raw) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/raw.js) [Test][tt] +- [x] comment [Document](https://help.shopify.com/themes/liquid/tags/theme-tags#comment) [Source](https://github.com/harttle/shopify-liquid/blob/master/tags/comment.js) [Test][tt] ## Filters diff --git a/package.json b/package.json index 8a3ec331a..01bdc84f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shopify-liquid", - "version": "1.1.1", + "version": "1.1.2", "description": "A Shopify Liquid Implementation in Node.js", "main": "index.js", "scripts": { diff --git a/parser.js b/parser.js index 957a29b09..86bcad5d9 100644 --- a/parser.js +++ b/parser.js @@ -24,6 +24,7 @@ module.exports = function(Tag, Filter) { start: function() { this.trigger('start'); while (!this.stopRequested && (token = this.tokens.shift())) { + if (this.trigger('token', token)) continue; if (token.type == 'tag' && this.trigger(`tag:${token.name}`, token)) { continue; @@ -37,18 +38,6 @@ module.exports = function(Tag, Filter) { stop: function() { this.stopRequested = true; return this; - }, - onStart: function(cb) { - return this.on('start', cb); - }, - onEnd: function(cb) { - return this.on('end', cb); - }, - onTag: function(name, cb) { - return this.on(`tag:${name}`, cb); - }, - onTemplate: function(cb) { - return this.on('template', cb); } }; diff --git a/tag.js b/tag.js index d927ceb4c..4aaacdd27 100644 --- a/tag.js +++ b/tag.js @@ -21,7 +21,7 @@ module.exports = function() { var reg = register[this.name]; if(!reg) reg = register[this.name] = {}; var obj = hash(this.token.args, scope); - return this.tagImpl.render(scope, obj, reg) || ''; + return this.tagImpl.render && this.tagImpl.render(scope, obj, reg) || ''; }, parse: function(token, tokens){ this.type = 'tag'; @@ -38,9 +38,6 @@ module.exports = function() { }; function register(name, tag) { - if (typeof tag.render !== 'function') { - throw new Error(`expect ${name}.render to be a function`); - } tagImpls[name] = tag; } diff --git a/tags/capture.js b/tags/capture.js index 42b11b4e0..ff6af14ba 100644 --- a/tags/capture.js +++ b/tags/capture.js @@ -13,9 +13,9 @@ module.exports = function(liquid) { this.templates = []; var stream = liquid.parser.parseStream(remainTokens); - stream.onTag('endcapture', token => stream.stop()) - .onTemplate(tpl => this.templates.push(tpl)) - .onEnd(x => { + stream.on('tag:endcapture', token => stream.stop()) + .on('template', tpl => this.templates.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); stream.start(); diff --git a/tags/case.js b/tags/case.js index a92fdfbd0..89ebc36e0 100644 --- a/tags/case.js +++ b/tags/case.js @@ -11,7 +11,7 @@ module.exports = function(liquid) { var p = [], stream = liquid.parser.parseStream(remainTokens) - .onTag('when', token => { + .on('tag:when', token => { if (!this.cases[token.args]) { this.cases.push({ val: token.args, @@ -19,10 +19,10 @@ module.exports = function(liquid) { }); } }) - .onTag('else', token => p = this.elseTemplates) - .onTag('endcase', token => stream.stop()) - .onTemplate(tpl => p.push(tpl)) - .onEnd(x => { + .on('tag:else', token => p = this.elseTemplates) + .on('tag:endcase', token => stream.stop()) + .on('template', tpl => p.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); diff --git a/tags/comment.js b/tags/comment.js new file mode 100644 index 000000000..6a1877e4a --- /dev/null +++ b/tags/comment.js @@ -0,0 +1,21 @@ +var Liquid = require('..'); +var lexical = Liquid.lexical; +var re = new RegExp(`(${lexical.identifier.source})`); + +module.exports = function(liquid) { + + liquid.registerTag('comment', { + parse: function(tagToken, remainTokens) { + var stream = liquid.parser.parseStream(remainTokens); + stream + .on('token', token => { + if(token.name === 'endcomment') stream.stop(); + }) + .on('end', x => { + throw new Error(`tag ${tagToken.raw} not closed`); + }); + stream.start(); + } + }); + +}; diff --git a/tags/for.js b/tags/for.js index 6a490456f..ddbecc7c6 100644 --- a/tags/for.js +++ b/tags/for.js @@ -19,11 +19,11 @@ module.exports = function(liquid) { this.elseTemplates = []; var p, stream = liquid.parser.parseStream(remainTokens) - .onStart(x => p = this.templates) - .onTag('else', token => p = this.elseTemplates) - .onTag('endfor', token => stream.stop()) - .onTemplate(tpl => p.push(tpl)) - .onEnd(x => { + .on('start', x => p = this.templates) + .on('tag:else', token => p = this.elseTemplates) + .on('tag:endfor', token => stream.stop()) + .on('template', tpl => p.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); diff --git a/tags/if.js b/tags/if.js index 9c0663023..92f12f06d 100644 --- a/tags/if.js +++ b/tags/if.js @@ -10,11 +10,11 @@ module.exports = function(liquid) { this.elseTemplates = []; var p, stream = liquid.parser.parseStream(remainTokens) - .onStart(x => this.branches.push({ + .on('start', x => this.branches.push({ cond: tagToken.args, templates: p = [] })) - .onTag('elsif', token => { + .on('tag:elsif', token => { if (!this.branches[token.args]) { this.branches.push({ cond: token.args, @@ -22,10 +22,10 @@ module.exports = function(liquid) { }); } }) - .onTag('else', token => p = this.elseTemplates) - .onTag('endif', token => stream.stop()) - .onTemplate(tpl => p.push(tpl)) - .onEnd(x => { + .on('tag:else', token => p = this.elseTemplates) + .on('tag:endif', token => stream.stop()) + .on('template', tpl => p.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); diff --git a/tags/raw.js b/tags/raw.js new file mode 100644 index 000000000..ac5120601 --- /dev/null +++ b/tags/raw.js @@ -0,0 +1,27 @@ +var Liquid = require('..'); +var lexical = Liquid.lexical; +var re = new RegExp(`(${lexical.identifier.source})`); + +module.exports = function(liquid) { + + liquid.registerTag('raw', { + parse: function(tagToken, remainTokens) { + this.tokens = []; + + var stream = liquid.parser.parseStream(remainTokens); + stream + .on('token', token => { + if(token.name === 'endraw') stream.stop(); + else this.tokens.push(token); + }) + .on('end', x => { + throw new Error(`tag ${tagToken.raw} not closed`); + }); + stream.start(); + }, + render: function(scope, hash) { + return this.tokens.map(token => token.raw).join(''); + } + }); + +}; diff --git a/tags/tablerow.js b/tags/tablerow.js index d6932c03f..8cc24cd91 100644 --- a/tags/tablerow.js +++ b/tags/tablerow.js @@ -16,10 +16,10 @@ module.exports = function(liquid) { this.templates = []; var p, stream = liquid.parser.parseStream(remainTokens) - .onStart(x => p = this.templates) - .onTag('endtablerow', token => stream.stop()) - .onTemplate(tpl => p.push(tpl)) - .onEnd(x => { + .on('start', x => p = this.templates) + .on('tag:endtablerow', token => stream.stop()) + .on('template', tpl => p.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); diff --git a/tags/unless.js b/tags/unless.js index 5f0bca6ba..3122d6bdc 100644 --- a/tags/unless.js +++ b/tags/unless.js @@ -5,14 +5,14 @@ module.exports = function(liquid) { liquid.registerTag('unless', { parse: function(tagToken, remainTokens) { var p, stream = liquid.parser.parseStream(remainTokens) - .onStart(x => { + .on('start', x => { p = this.templates = []; this.cond = tagToken.args; }) - .onTag('else', token => this.elseTemplates = p = []) - .onTag('endunless', token => stream.stop()) - .onTemplate(tpl => p.push(tpl)) - .onEnd(x => { + .on('tag:else', token => this.elseTemplates = p = []) + .on('tag:endunless', token => stream.stop()) + .on('template', tpl => p.push(tpl)) + .on('end', x => { throw new Error(`tag ${tagToken.raw} not closed`); }); diff --git a/test/tag.js b/test/tag.js index 834ec574a..0746f4153 100644 --- a/test/tag.js +++ b/test/tag.js @@ -27,12 +27,6 @@ describe('tag', function() { }).to.throw(/tag foo not found/); }); - it('should throw when render method not defined', function() { - expect(function() { - tag.register('foo', {}); - }).to.throw(/expect foo.render to be a function/); - }); - it('should register simple tag', function() { expect( function() { diff --git a/test/tags.js b/test/tags.js index ec0389d88..b9e0e3253 100644 --- a/test/tags.js +++ b/test/tags.js @@ -34,6 +34,18 @@ describe('tags', function() { test('{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}', 'A'); }); + it('should support raw', function() { + testThrow('{% raw%}', /{% raw%} not closed/); + test('{% raw %}{{ 5 | plus: 6 }}{% endraw %} is equal to 11.', '{{ 5 | plus: 6 }} is equal to 11.'); + test('{% raw %}\n{{ foo}} \n{% endraw %}', '\n{{ foo}} \n'); + }); + + it('should support comment', function() { + testThrow('{% comment %}{% raw%}', /{% comment %} not closed/); + test('My name is {% comment %}super{% endcomment %} Shopify.', 'My name is Shopify.'); + test('{% comment %}\n{{ foo}} \n{% endcomment %}', ''); + }); + it('should support case', function() { testThrow('{% case "foo"%}', /{% case "foo"%} not closed/); test('{% case "foo"%}' + diff --git a/test/tokenizer.js b/test/tokenizer.js index a4d7db627..13525249e 100644 --- a/test/tokenizer.js +++ b/test/tokenizer.js @@ -1,5 +1,6 @@ var chai = require("chai"); var should = chai.should(); +var expect = chai.expect; var tokenizer = require('../tokenizer.js'); @@ -39,4 +40,13 @@ describe('tokenizer', function() { 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 = 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 '); + }); }); diff --git a/tokenizer.js b/tokenizer.js index 2723c38f5..1302a2485 100644 --- a/tokenizer.js +++ b/tokenizer.js @@ -17,6 +17,7 @@ function parse(html) { htmlFragment = html.slice(idx, result.index); tokens.push({ type: 'html', + raw: htmlFragment, value: htmlFragment }); } @@ -47,6 +48,7 @@ function parse(html) { htmlFragment = html.slice(idx, html.length); tokens.push({ type: 'html', + raw: htmlFragment, value: htmlFragment }); }