filters: all

This commit is contained in:
harttle
2016-06-18 23:18:46 +08:00
parent b478c798fd
commit 4595e2872e
21 changed files with 627 additions and 211 deletions
+35 -31
View File
@@ -53,37 +53,41 @@ Documentation: <https://shopify.github.io/liquid/basics/introduction/#filters>
- [x] downcase [Document](https://shopify.github.io/liquid/filters/downcase) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] escape [Document](https://shopify.github.io/liquid/filters/escape) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] escape_once [Document](https://shopify.github.io/liquid/filters/escape_once) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] first [Document](https://shopify.github.io/liquid/filters/first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] floor [Document](https://shopify.github.io/liquid/filters/floor) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] join [Document](https://shopify.github.io/liquid/filters/join) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] last [Document](https://shopify.github.io/liquid/filters/last) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] lstrip [Document](https://shopify.github.io/liquid/filters/lstrip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] map [Document](https://shopify.github.io/liquid/filters/map) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] minus [Document](https://shopify.github.io/liquid/filters/minus) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] modulo [Document](https://shopify.github.io/liquid/filters/modulo) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] newline_to_br [Document](https://shopify.github.io/liquid/filters/newline_to_br) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] plus [Document](https://shopify.github.io/liquid/filters/plus) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] prepend [Document](https://shopify.github.io/liquid/filters/prepend) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] remove [Document](https://shopify.github.io/liquid/filters/remove) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] remove_first [Document](https://shopify.github.io/liquid/filters/remove_first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] replace [Document](https://shopify.github.io/liquid/filters/replace) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] replace_first [Document](https://shopify.github.io/liquid/filters/replace_first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] reverse [Document](https://shopify.github.io/liquid/filters/reverse) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] round [Document](https://shopify.github.io/liquid/filters/round) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] rstrip [Document](https://shopify.github.io/liquid/filters/rstrip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] size [Document](https://shopify.github.io/liquid/filters/size) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] slice [Document](https://shopify.github.io/liquid/filters/slice) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] sort [Document](https://shopify.github.io/liquid/filters/sort) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] split [Document](https://shopify.github.io/liquid/filters/split) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] strip [Document](https://shopify.github.io/liquid/filters/strip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] strip_html [Document](https://shopify.github.io/liquid/filters/strip_html) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] strip_newlines [Document](https://shopify.github.io/liquid/filters/strip_newlines) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] times [Document](https://shopify.github.io/liquid/filters/times) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] truncate [Document](https://shopify.github.io/liquid/filters/truncate) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] truncatewords [Document](https://shopify.github.io/liquid/filters/truncatewords) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] uniq [Document](https://shopify.github.io/liquid/filters/uniq) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] upcase [Document](https://shopify.github.io/liquid/filters/upcase) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [ ] url_encode [Document](https://shopify.github.io/liquid/filters/url_encode) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] first [Document](https://shopify.github.io/liquid/filters/first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] floor [Document](https://shopify.github.io/liquid/filters/floor) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] join [Document](https://shopify.github.io/liquid/filters/join) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] last [Document](https://shopify.github.io/liquid/filters/last) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] lstrip [Document](https://shopify.github.io/liquid/filters/lstrip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] map [Document](https://shopify.github.io/liquid/filters/map) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] minus [Document](https://shopify.github.io/liquid/filters/minus) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] modulo [Document](https://shopify.github.io/liquid/filters/modulo) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] newline_to_br [Document](https://shopify.github.io/liquid/filters/newline_to_br) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] plus [Document](https://shopify.github.io/liquid/filters/plus) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] prepend [Document](https://shopify.github.io/liquid/filters/prepend) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] remove [Document](https://shopify.github.io/liquid/filters/remove) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] remove_first [Document](https://shopify.github.io/liquid/filters/remove_first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] replace [Document](https://shopify.github.io/liquid/filters/replace) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] replace_first [Document](https://shopify.github.io/liquid/filters/replace_first) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] reverse [Document](https://shopify.github.io/liquid/filters/reverse) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] round [Document](https://shopify.github.io/liquid/filters/round) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] rstrip [Document](https://shopify.github.io/liquid/filters/rstrip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] size [Document](https://shopify.github.io/liquid/filters/size) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] slice [Document](https://shopify.github.io/liquid/filters/slice) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] sort [Document](https://shopify.github.io/liquid/filters/sort) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] split [Document](https://shopify.github.io/liquid/filters/split) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] strip [Document](https://shopify.github.io/liquid/filters/strip) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] strip_html [Document](https://shopify.github.io/liquid/filters/strip_html) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] strip_newlines [Document](https://shopify.github.io/liquid/filters/strip_newlines) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] times [Document](https://shopify.github.io/liquid/filters/times) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] truncate [Document](https://shopify.github.io/liquid/filters/truncate) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] truncatewords [Document](https://shopify.github.io/liquid/filters/truncatewords) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] uniq [Document](https://shopify.github.io/liquid/filters/uniq) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] upcase [Document](https://shopify.github.io/liquid/filters/upcase) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
- [x] url_encode [Document](https://shopify.github.io/liquid/filters/url_encode) [Source](https://github.com/harttle/shopify-liquid/blob/master/filters.js) [Test][ft]
## Differences from Shopify Liquid
* `url_encode` is based on `encodeURIComponent`, ` ` will be converted to `%20`.
## Async Support
+3 -3
View File
@@ -5,12 +5,12 @@ const _ = require('lodash');
function evalExp(exp, scope) {
if (!scope) throw new Error('unable to evalExp: scope undefined');
var operatorREs = lexical.operators;
var operatorREs = lexical.operators,
match;
for (var i = 0; i < operatorREs.length; i++) {
var operatorRE = operatorREs[i];
var expRE = new RegExp(`^(${lexical.quoteBalanced.source})(${operatorRE.source})(${lexical.quoteBalanced.source})$`);
var match = exp.match(expRE);
if (match) {
if (match = exp.match(expRE)) {
var l = evalExp(match[1], scope);
var op = syntax.operators[match[2].trim()];
var r = evalExp(match[3], scope);
+30 -21
View File
@@ -2,33 +2,42 @@ const lexical = require('./lexical.js');
const _ = require('lodash');
const Exp = require('./expression.js');
var _filterInstance = {
render: function(output, scope) {
var args = this.args.map(arg => Exp.evalValue(arg, scope));
args.unshift(output);
return this.filter.apply(null, args);
}
};
var valueRE = new RegExp(`${lexical.value.source}`, 'g');
module.exports = function() {
var filters = {};
var _filterInstance = {
render: function(output, scope) {
var args = this.args.map(arg => Exp.evalValue(arg, scope));
args.unshift(output);
return this.filter.apply(null, args);
},
parse: function(str) {
var match = lexical.filterLine.exec(str);
if (!match) throw new Error('illegal filter: ' + str);
var name = match[1], argList = match[2] || '', filter = filters[name];
if (typeof filter !== 'function'){
throw new Error(`filter "${name}" not found`);
}
var args = [];
while(match = valueRE.exec(argList.trim())){
args.push(match[0]);
}
this.name = name;
this.filter = filter;
this.args = args;
return this;
}
};
function construct(str) {
var match = lexical.filterLine.exec(str.trim());
if (!match) error('illegal filter: ' + str);
var k = match[1], v = match[2];
return factory(k, [v]);
}
function factory(name, args) {
var filter = filters[name];
if (typeof filter !== 'function')
throw new Error(`filter ${name} not found`);
var instance = Object.create(_filterInstance);
instance.args = args;
instance.filter = filter;
instance.parse(str);
return instance;
}
+59 -1
View File
@@ -8,8 +8,66 @@ module.exports = function(liquid) {
liquid.registerFilter('ceil', v => Math.ceil(v));
liquid.registerFilter('date', (v, arg) => strftime(arg, v));
liquid.registerFilter('default', (v, arg) => arg || v);
liquid.registerFilter('divided_by', (v, arg) => Math.floor(v/arg));
liquid.registerFilter('divided_by', (v, arg) => Math.floor(v / arg));
liquid.registerFilter('downcase', v => v.toLowerCase());
liquid.registerFilter('escape', v => _.escape(v));
liquid.registerFilter('escape_once', v => _.escape(_.unescape(v)));
liquid.registerFilter('first', v => v[0]);
liquid.registerFilter('floor', v => Math.floor(v));
liquid.registerFilter('join', _.join);
liquid.registerFilter('last', v => v[v.length - 1]);
liquid.registerFilter('lstrip', v => _.trimStart(v));
liquid.registerFilter('map', (v, arg) => _.map(v, arg));
liquid.registerFilter('minus', bindFixed((v, arg) => v - arg));
liquid.registerFilter('modulo', bindFixed((v, arg) => v % arg));
liquid.registerFilter('newline_to_br', v => v.replace(/\n/g, '<br />'));
liquid.registerFilter('plus', bindFixed((v, arg) => v + arg));
liquid.registerFilter('prepend', (v, arg) => arg + v);
liquid.registerFilter('remove', (v, arg) => v.split(arg).join(''));
liquid.registerFilter('remove_first', (v, l) => v.replace(l, ''));
liquid.registerFilter('replace', (v, pattern, replacement) =>
_.replace(v, new RegExp(_.escapeRegExp(pattern), 'g'), replacement));
liquid.registerFilter('replace_first', _.replace);
liquid.registerFilter('reverse', _.reverse);
liquid.registerFilter('round', _.round);
liquid.registerFilter('rstrip', _.trimEnd);
liquid.registerFilter('size', _.size);
liquid.registerFilter('slice', (v, begin, length) =>
v.substr(begin, length === undefined ? 1 : length));
liquid.registerFilter('sort', _.sortBy);
liquid.registerFilter('split', _.split);
liquid.registerFilter('strip', _.trim);
liquid.registerFilter('strip_html', v => _.replace(v, /<\/?\s*\w+\s*\/?>/g, ''));
liquid.registerFilter('strip_newlines', v => _.replace(v, /\n/g, ''));
liquid.registerFilter('times', (v, arg) => v * arg);
liquid.registerFilter('truncate', (v, l, o) => _.truncate(v, {
length: l,
omission: o === undefined ? '...' : o
}));
liquid.registerFilter('truncatewords', (v, l, o) => {
if (o === undefined) o = '...';
var arr = v.split(' ');
var ret = arr.slice(0, l).join(' ');
if (arr.length > l) ret += o;
return ret;
});
liquid.registerFilter('uniq', _.uniq);
liquid.registerFilter('upcase', _.toUpper);
liquid.registerFilter('url_encode', encodeURIComponent);
};
function getFixed(v) {
var p = (v + "").split(".");
return (p.length > 1) ? p[1].length : 0;
}
function getMaxFixed(l, r) {
return Math.max(getFixed(l), getFixed(r));
}
function bindFixed(cb) {
return (l, r) => {
var f = getMaxFixed(l, r);
return cb(l, r).toFixed(f);
};
}
+8 -7
View File
@@ -28,21 +28,22 @@ function factory(){
engine.filter = Filter();
engine.tokenize = tokenizer.parse;
var template = Template(engine.tag);
engine.parse = template.parse;
engine.parseTag = template.parseTag;
engine.parseStream = template.parseStream;
engine.template = Template(engine.tag, engine.filter);
engine.parseStream = engine.template.parseStream;
var renderer = Render(engine.filter, engine.tag);
engine.evalFilter = renderer.evalFilter;
engine.renderTemplates = renderer.renderTemplates;
engine.render = function(html, ctx) {
var tokens = engine.tokenize(html);
var templates = engine.parse(tokens);
var templates = engine.template.parse(tokens);
engine.register = {};
return engine.renderTemplates(templates, scope.factory(ctx));
},
};
engine.evalOutput = function(str, scope) {
var template = engine.template.parseOutput(str.trim());
return renderer.evalOutput(template, scope);
};
fs.readdirSync(tagsPath).map(function(f){
var match = /^(\w+)\.js$/.exec(f);
+27 -18
View File
@@ -1,33 +1,43 @@
const _ = require('lodash');
// quote related
var singleQuoted = /'[^']*'/;
var doubleQuoted = /"[^"]*"/;
var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
var number = /-?\d+\.?\d*|\.?\d+/;
var number = /(?:-?\d+\.?\d*|\.?\d+)/;
var bool = /true|false/i;
var identifier = /[a-zA-Z_$][a-zA-Z_$0-9]*/;
var subscript = /\[\d+\]/;
var quoted = new RegExp(`${singleQuoted.source}|${doubleQuoted.source}`);
var literal = new RegExp(`${quoted.source}|${bool.source}|${number.source}`, 'i');
var quoted = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source})`);
var literal = new RegExp(`(?:${quoted.source}|${bool.source}|${number.source})`, 'i');
var variable = new RegExp(`${identifier.source}(?:\\.${identifier.source}|${subscript.source})*`);
var value = new RegExp(`${literal.source}|${variable.source}`, 'i');
var range = new RegExp(`\\((?:${value.source})\\.\\.(?:${value.source})\\)`);
var rangeCapture = new RegExp(`\\((${value.source})\\.\\.(${value.source})\\)`);
var variableOrRange = new RegExp(`${variable.source}|${range.source}`);
// range related
var rangeLimit = new RegExp(`(?:${variable.source}|${number.source})`);
var range = new RegExp(`\\(${rangeLimit.source}\\.\\.${rangeLimit.source}\\)`);
var rangeCapture = new RegExp(`\\((${rangeLimit.source})\\.\\.(${rangeLimit.source})\\)`);
var value = new RegExp(`(?:${literal.source}|${variable.source}|${range.source})`, 'i');
// hash related
var hash = new RegExp(`(?:${identifier.source})\\s*:\\s*(?:${value.source})`, 'g');
var hashCapture = new RegExp(`(${identifier.source})\\s*:\\s*(${value.source})`, 'g');
var filter = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${value.source}))?`);
var quoteBalanced = new RegExp(`(?:${singleQuoted.source}|${doubleQuoted.source}|[^'"])*`);
var tagLine = new RegExp(`^\\s*(${identifier.source})\\s*(.*)\\s*$`);
var literalLine = new RegExp(`^(?:${literal.source})$`, 'i');
var variableLine = new RegExp(`^(?:${variable.source})$`);
var numberLine = new RegExp(`^(?:${number.source})$`);
var boolLine = new RegExp(`^(?:${bool.source})$`, 'i');
var quotedLine = new RegExp(`^(?:${quoted.source})$`);
var rangeLine = new RegExp(`^(?:${rangeCapture.source})$`);
var filterLine = new RegExp(`^(?:${filter.source})$`);
var literalLine = new RegExp(`^${literal.source}$`, 'i');
var variableLine = new RegExp(`^${variable.source}$`);
var numberLine = new RegExp(`^${number.source}$`);
var boolLine = new RegExp(`^${bool.source}$`, 'i');
var quotedLine = new RegExp(`^${quoted.source}$`);
var rangeLine = new RegExp(`^${rangeCapture.source}$`);
// filter related
var valueList = new RegExp(`${value.source}(\\s*,\\s*${value.source})*`);
var filter = new RegExp(`${identifier.source}(?:\\s*:\\s*${valueList.source})?`, 'g');
var filterCapture = new RegExp(`(${identifier.source})(?:\\s*:\\s*(${valueList.source}))?`);
var filterLine = new RegExp(`^${filterCapture.source}$`);
var operators = [
/\s+or\s+/,
@@ -35,7 +45,6 @@ var operators = [
/==|!=|<=|>=|<|>|\s+contains\s+/
];
function isLiteral(str) {
return literalLine.test(str);
}
@@ -64,7 +73,7 @@ function parseLiteral(str) {
module.exports = {
quoted, number, bool, literal, filter,
hash, hashCapture,
range, rangeCapture, variableOrRange,
range, rangeCapture,
identifier, value, quoteBalanced, operators,
quotedLine, numberLine, boolLine, rangeLine, literalLine, filterLine, tagLine,
isLiteral, isVariable, parseLiteral, isRange
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "shopify-liquid",
"version": "1.0.3",
"version": "1.1.0",
"description": "A Shopify Liquid Implementation in Node.js",
"main": "index.js",
"scripts": {
View File
+34 -26
View File
@@ -1,6 +1,6 @@
const lexical = require('./lexical.js');
const error = require('./error.js');
const Exp = require('./expression.js');
const assert = require('assert');
function stringify(val) {
if (typeof val === 'string') return val;
@@ -8,43 +8,51 @@ function stringify(val) {
}
function factory(Filter, Tag) {
function renderTemplates(templates, scope) {
if (!scope) throw new Error('unable to evalTemplates: scope undefined');
var html = '';
assert(scope, 'unable to evalTemplates: scope undefined');
var html = '', partial;
templates.some(template => {
if (scope.get('forloop.skip')) return true;
if (template.type === 'tag') {
if (template.name === 'continue') {
scope.set('forloop.skip', true);
return true;
}
if (template.name === 'break') {
scope.set('forloop.stop', true);
scope.set('forloop.skip', true);
return true;
}
html += template.render(scope, this.register);
} else if (template.type === 'html') {
html += template.value;
} else if (template.type === 'output') {
var val = evalFilter(template.value, scope);
html += stringify(val);
switch (template.type) {
case 'tag':
partial = renderTag(template, scope, this.register);
if(partial === undefined) return true;
html += partial;
break;
case 'html':
html += template.value;
break;
case 'output':
var val = evalOutput(template, scope);
html += stringify(val);
}
});
return html;
}
function evalFilter(str, scope) {
if (!scope) throw new Error('unable to evalFilter: scope undefined');
var filters = str.split('|');
var val = Exp.evalValue(filters.shift(), scope);
return filters
.map(str => Filter.construct(str))
function renderTag(template, scope, register) {
if (template.name === 'continue') {
scope.set('forloop.skip', true);
return;
}
if (template.name === 'break') {
scope.set('forloop.stop', true);
scope.set('forloop.skip', true);
return;
}
return template.render(scope, register);
}
function evalOutput(template, scope) {
assert(scope, 'unable to evalOutput: scope undefined');
var val = Exp.evalExp(template.initial, scope);
return template.filters
.reduce((v, filter) => filter.render(v, scope), val);
}
return {
renderTemplates, evalFilter
renderTemplates, evalOutput, renderTag
};
}
+1 -1
View File
@@ -12,7 +12,7 @@ module.exports = function(liquid) {
this.value = match[2];
},
render: function(scope, hash) {
scope.set(this.key, Liquid.evalValue(this.value, scope));
scope.set(this.key, liquid.evalOutput(this.value, scope));
}
});
+1 -1
View File
@@ -1,7 +1,7 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
`(${lexical.variableOrRange.source})` +
`(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*` +
`(?:\\s+(reversed))?$`);
+7 -11
View File
@@ -1,7 +1,7 @@
var Liquid = require('..');
var lexical = Liquid.lexical;
var re = new RegExp(`^(${lexical.identifier.source})\\s+in\\s+` +
`(${lexical.variableOrRange.source})` +
`(${lexical.value.source})` +
`(?:\\s+${lexical.hash.source})*$`);
module.exports = function(liquid) {
@@ -14,11 +14,9 @@ module.exports = function(liquid) {
this.collection = match[2];
this.templates = [];
this.elseTemplates = [];
var p, stream = liquid.parseStream(remainTokens)
.onStart(x => p = this.templates)
.onTag('else', token => p = this.elseTemplates)
.onTag('endtablerow', token => stream.stop())
.onTemplate(tpl => p.push(tpl))
.onEnd(x => {
@@ -29,10 +27,7 @@ module.exports = function(liquid) {
},
render: function(scope, hash) {
var collection = Liquid.evalExp(this.collection, scope);
if (Liquid.isFalsy(collection)) {
return liquid.renderTemplates(this.elseTemplates, scope);
}
var collection = Liquid.evalExp(this.collection, scope) || [];
var html = '<table>',
ctx = {},
@@ -40,12 +35,12 @@ module.exports = function(liquid) {
var offset = hash.offset || 0;
var limit = (hash.limit === undefined) ? collection.length : hash.limit;
var cols = hash.cols;
var cols = hash.cols, row, col;
if (!cols) throw new Error(`illegal cols: ${cols}`);
collection.slice(offset, offset + limit).some((item, i) => {
var row = Math.floor(i / cols) + 1,
col = (i % cols) + 1;
row = Math.floor(i / cols) + 1;
col = (i % cols) + 1;
if(col === 1){
if(row !== 1){
html += '</tr>';
@@ -60,7 +55,8 @@ module.exports = function(liquid) {
html += '</td>';
scope.pop(ctx);
});
html += '</tr></table>';
if(row > 0) html += '</tr>';
html += '</table>';
return html;
}
});
+42 -22
View File
@@ -2,7 +2,7 @@ const lexical = require('./lexical.js');
const error = require('./error.js');
const ParseError = require('./error.js').ParseError;
module.exports = function(Tag) {
module.exports = function(Tag, Filter) {
var stream = {
init: function(tokens) {
@@ -24,17 +24,11 @@ module.exports = function(Tag) {
start: function() {
this.trigger('start');
while (!this.stopRequested && (token = this.tokens.shift())) {
var template;
if (token.type == 'tag') {
if (this.trigger(`tag:${token.name}`, token)) continue;
if (token.name === 'continue' || token.name === 'break') {
template = token;
} else {
template = parseTag(token, this.tokens);
}
} else {
template = token;
if (token.type == 'tag' &&
this.trigger(`tag:${token.name}`, token)) {
continue;
}
var template = parseToken(token, this.tokens);
this.trigger('template', template);
}
if (!this.stopRequested) this.trigger('end');
@@ -59,32 +53,58 @@ module.exports = function(Tag) {
};
function parse(tokens) {
var templates = [];
var token;
var token, templates = [];
while (token = tokens.shift()) {
if (token.type === 'tag') {
var tagInstance = parseTag(token, tokens);
templates.push(tagInstance);
} else templates.push(token);
templates.push(parseToken(token, tokens));
}
return templates;
}
function parseTag(token, tokens) {
function parseToken(token, tokens) {
try {
return Tag.construct(token).parse(tokens);
switch (token.type) {
case 'tag':
return parseTag(token, tokens);
case 'output':
return parseOutput(token.value);
case 'html':
return token;
}
} catch (e) {
throw new ParseError(e.message,
token.input, token.line, e.stack);
throw new ParseError(e.message, token.input, token.line, e.stack);
}
}
function parseTag(token, tokens) {
if (token.name === 'continue' || token.name === 'break') return token;
return Tag.construct(token).parse(tokens);
}
function parseOutput(str) {
var match = lexical.value.exec(str);
if(!match) throw new Error(`illegal output string: ${str}`);
var initial = match[0];
str = str.substr(match.index + match[0].length);
var filters = [];
while(match = lexical.filter.exec(str)){
filters.push([match[0].trim()]);
}
return {
type: 'output',
initial: initial,
filters: filters.map(str => Filter.construct(str))
};
}
function parseStream(tokens) {
var s = Object.create(stream);
return s.init(tokens);
}
return {
parse, parseTag, parseStream
parse, parseTag, parseStream, parseOutput
};
};
+10
View File
@@ -38,6 +38,16 @@ describe('error', function() {
});
});
it('should throw ParseError when filter not exist', function() {
test(function(){
liquid.render('{{ a | xz }}', {});
}, function(err){
expect(err.name).to.equal('ParseError');
expect(err.message).to.equal('filter "xz" not found');
expect(err.input).to.equal('{{ a | xz }}');
expect(err.line).to.equal(1);
});
});
it('should throw ParseError when tag not exist', function() {
test(function(){
liquid.render('{% a %}', {});
+21 -3
View File
@@ -17,12 +17,30 @@ describe('filter', function() {
it('should throw when not registered', function() {
expect(function() {
filter.construct('foo');
}).to.throw(/filter foo not found/);
}).to.throw(/filter "foo" not found/);
});
it('should parse argument syntax', function(){
filter.register('foo', x => x);
var f = filter.construct('foo: a, "b"');
expect(f.name).to.equal('foo');
expect(f.args).to.deep.equal(['a', '"b"']);
});
it('should register a simple filter', function(){
filter.register('foo', x => x.toUpperCase());
expect(filter.construct('foo').render('foo', scope)).to.equal('FOO');
filter.register('upcase', x => x.toUpperCase());
expect(filter.construct('upcase').render('foo', scope)).to.equal('FOO');
});
it('should register a argumented filter', function(){
filter.register('add', (a, b) => a + b);
expect(filter.construct('add: 2').render(3, scope)).to.equal(5);
});
it('should register a multi-argumented filter', function(){
filter.register('add', (a, b, c) => a + b + c);
expect(filter.construct('add: 2, "c"').render(3, scope)).to.equal("5c");
});
it('should call filter with corrct arguments', function(){
+208 -5
View File
@@ -1,7 +1,8 @@
const chai = require("chai");
const expect = chai.expect;
var liquid = require('..')(), ctx;
var liquid = require('..')(),
ctx;
function test(src, dst) {
ctx = {
@@ -10,16 +11,21 @@ function test(src, dst) {
arr: [-2, 'a'],
obj: {
foo: 'bar'
}
},
posts: [{
category: 'foo'
}, {
category: 'bar'
}]
};
expect(liquid.render(src, ctx)).to.equal(dst);
}
describe('filters', function() {
it('should output object', function(){
it('should output object', function() {
test('{{obj}}', '{"foo":"bar"}');
});
it('should output array', function(){
it('should output array', function() {
test('{{arr}}', '[-2,"a"]');
});
it('should support abs', function() {
@@ -67,9 +73,206 @@ describe('filters', function() {
'Have you read &#39;James &amp; the Giant Peach&#39;?');
test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara');
});
it('should support escape_once', function() {
test('{{ "1 < 2 & 3" | escape_once }}', '1 &lt; 2 &amp; 3');
test('{{ "1 &lt; 2 &amp; 3" | escape_once }}', '1 &lt; 2 &amp; 3');
});
});
it('should support split/first', function() {
src = '{% assign my_array = "apples, oranges, peaches, plums" | split: ", " %}' +
'{{ my_array | first }}';
test(src, 'apples');
});
it('should support floor', function() {
test('{{ 1.2 | floor }}', '1');
test('{{ 2.0 | floor }}', '2');
test('{{ 183.357 | floor }}', '183');
test('{{ "3.5" | floor }}', '3');
});
it('should support join', function() {
src = '{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
'{{ beatles | join: " and " }}';
test(src, 'John and Paul and George and Ringo');
});
it('should support split/last', function() {
src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' +
'{{ my_array|last }}';
test(src, 'tiger');
});
it('should support lstrip', function() {
src = '{{ " So much room for activities! " | lstrip }}';
test(src, 'So much room for activities! ');
});
it('should support map', function() {
test('{{posts | map: "category"}}', '["foo","bar"]');
});
it('should support minus', function() {
test('{{ 4 | minus: 2 }}', '2');
test('{{ 16 | minus: 4 }}', '12');
test('{{ 183.357 | minus: 12 }}', '171.357');
});
it('should support modulo', function() {
test('{{ 3 | modulo: 2 }}', '1');
test('{{ 24 | modulo: 7 }}', '3');
test('{{ 183.357 | modulo: 12 }}', '3.357');
});
it('should support string_with_newlines', function() {
src = '{% capture string_with_newlines %}\n' +
'Hello\n' +
'there\n' +
'{% endcapture %}' +
'{{ string_with_newlines | newline_to_br }}';
dst = '<br />' +
'Hello<br />' +
'there<br />';
test(src, dst);
});
it('should support plus', function() {
test('{{ 4 | plus: 2 }}', '6');
test('{{ 16 | plus: 4 }}', '20');
test('{{ 183.357 | plus: 12 }}', '195.357');
});
it('should support prepend', function() {
test('{% assign url = "liquidmarkup.com" %}' +
'{{ "/index.html" | prepend: url }}',
'liquidmarkup.com/index.html');
});
it('should support remove', function() {
test('{{ "I strained to see the train through the rain" | remove: "rain" }}',
'I sted to see the t through the ');
});
it('should support remove_first', function() {
test('{{ "I strained to see the train through the rain" | remove_first: "rain" }}',
'I sted to see the train through the rain');
});
it('should support replace', function() {
test('{{ "Take my protein pills and put my helmet on" | replace: "my", "your" }}',
'Take your protein pills and put your helmet on');
});
it('should support replace_first', function() {
test('{% assign my_string = "Take my protein pills and put my helmet on" %}\n' +
'{{ my_string | replace_first: "my", "your" }}',
'\nTake your protein pills and put my helmet on');
});
it('should support reverse', function() {
test('{{ "Ground control to Major Tom." | split: "" | reverse | join: "" }}',
'.moT rojaM ot lortnoc dnuorG');
});
it('should support round', function() {
test('{{1.2|round}}', '1');
test('{{2.7|round}}', '3');
test('{{183.357|round: 2}}', '183.36');
});
it('should support rstrip', function() {
test('{{ " So much room for activities! " | rstrip }}',
' So much room for activities!');
});
it('should support size', function() {
test('{{ "Ground control to Major Tom." | size }}', '28');
test('{% assign my_array = "apples, oranges, peaches, plums"' +
' | split: ", " %}{{ my_array | size }}',
'4');
});
it('should support slice', function() {
test('{{ "Liquid" | slice: 0 }}', 'L');
test('{{ "Liquid" | slice: 2 }}', 'q');
test('{{ "Liquid" | slice: 2, 5 }}', 'quid');
test('{{ "Liquid" | slice: -3, 2 }}', 'ui');
});
it('should support sort', function() {
test('{% assign my_array = "zebra, octopus, giraffe, Sally Snake"' +
' | split: ", " %}' +
'{{ my_array | sort | join: ", " }}',
'Sally Snake, giraffe, octopus, zebra');
});
it('should support split', function() {
test('{% assign beatles = "John, Paul, George, Ringo" | split: ", " %}' +
'{% for member in beatles %}' +
'{{ member }} ' +
'{% endfor %}',
'John Paul George Ringo ');
});
it('should support strip', function() {
test('{{ " So much room for activities! " | strip }}',
'So much room for activities!');
});
it('should support strip_tml', function() {
test('{{ "Have <em>you</em> read <strong>Ulysses</strong>?" | strip_html }}',
'Have you read Ulysses?');
test('{{"<br/><br />< p ></p></ p >" | strip_html }}', '');
});
it('should support strip_newlines', function() {
test('{% capture string_with_newlines %}\n' +
'Hello\nthere\n{% endcapture %}' +
'{{ string_with_newlines | strip_newlines }}',
'Hellothere');
});
it('should support times', function() {
test('{{ 3 | times: 2 }}', '6');
test('{{ 24 | times: 7 }}', '168');
test('{{ 183.357 | times: 12 }}', '2200.284');
});
it('should support truncate', function() {
test('{{ "Ground control to Major Tom." | truncate: 20 }}',
'Ground control to...');
test('{{ "Ground control to Major Tom." | truncate: 80 }}',
'Ground control to Major Tom.');
test('{{ "Ground control to Major Tom." | truncate: 25,", and so on" }}',
'Ground control, and so on');
test('{{ "Ground control to Major Tom." | truncate: 20, "" }}',
'Ground control to Ma');
});
it('should support truncatewords', function() {
test('{{ "Ground control to Major Tom." | truncatewords: 3 }}',
'Ground control to...');
test('{{ "Ground control to Major Tom." | truncatewords: 8 }}',
'Ground control to Major Tom.');
test('{{ "Ground control to Major Tom." | truncatewords: 3, "--" }}',
'Ground control to--');
test('{{ "Ground control to Major Tom." | truncatewords: 3, "" }}',
'Ground control to');
});
it('should support uniq', function() {
test('{% assign my_array = "ants, bugs, bees, bugs, ants" | split: ", " %}' +
'{{ my_array | uniq | join: ", " }}',
'ants, bugs, bees');
});
it('should support upcase', function() {
test('{{ "Parker Moore" | upcase }}', 'PARKER MOORE');
});
it('should support url_encode', function() {
test('{{ "[email protected]" | url_encode }}', 'john%40liquid.com');
test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro%20Takara');
});
});
+44 -35
View File
@@ -1,70 +1,79 @@
var chai = require("chai");
var should = chai.should();
var expect = chai.expect;
var identifier = require('../lexical.js');
var lexical = require('../lexical.js');
describe('identifier', function() {
describe('lexical', function() {
it('should test filter syntax', function(){
lexical.filterLine.test('abs').should.equal(true);
lexical.filterLine.test('plus:1').should.equal(true);
lexical.filterLine.test('replace: "a", b').should.equal(true);
lexical.filterLine.test('foo: a, "b"').should.equal(true);
lexical.filterLine.test('abs | another').should.equal(false);
lexical.filterLine.test('join: "," | another').should.equal(false);
});
it('should test boolean literal', function() {
identifier.isLiteral('true').should.equal(true);
identifier.isLiteral('TrUE').should.equal(true);
identifier.isLiteral('false').should.equal(true);
lexical.isLiteral('true').should.equal(true);
lexical.isLiteral('TrUE').should.equal(true);
lexical.isLiteral('false').should.equal(true);
});
it('should test number literal', function() {
identifier.isLiteral('2.3').should.equal(true);
identifier.isLiteral('.3').should.equal(true);
identifier.isLiteral('-3.').should.equal(true);
identifier.isLiteral('23').should.equal(true);
lexical.isLiteral('2.3').should.equal(true);
lexical.isLiteral('.3').should.equal(true);
lexical.isLiteral('-3.').should.equal(true);
lexical.isLiteral('23').should.equal(true);
});
it("should test range literal", function() {
identifier.isRange("(12..32)").should.equal(true);
identifier.isRange("(12..foo)").should.equal(true);
identifier.isRange("(foo.bar..foo)").should.equal(true);
lexical.isRange("(12..32)").should.equal(true);
lexical.isRange("(12..foo)").should.equal(true);
lexical.isRange("(foo.bar..foo)").should.equal(true);
});
it('should test string literal', function() {
identifier.isLiteral('""').should.equal(true);
identifier.isLiteral('"a\'b"').should.equal(true);
identifier.isLiteral("''").should.equal(true);
identifier.isLiteral("'a bcd'").should.equal(true);
lexical.isLiteral('""').should.equal(true);
lexical.isLiteral('"a\'b"').should.equal(true);
lexical.isLiteral("''").should.equal(true);
lexical.isLiteral("'a bcd'").should.equal(true);
});
it("should test variable", function() {
identifier.isVariable("foo").should.equal(true);
identifier.isVariable("foo.bar.foo").should.equal(true);
identifier.isVariable("foo[0].b").should.equal(true);
lexical.isVariable("foo").should.equal(true);
lexical.isVariable("foo.bar.foo").should.equal(true);
lexical.isVariable("foo[0].b").should.equal(true);
});
it('should test none literal', function() {
identifier.isLiteral('2a').should.equal(false);
identifier.isLiteral('"x').should.equal(false);
identifier.isLiteral('a2').should.equal(false);
lexical.isLiteral('2a').should.equal(false);
lexical.isLiteral('"x').should.equal(false);
lexical.isLiteral('a2').should.equal(false);
});
it('should test none variable', function() {
identifier.isVariable("a.").should.equal(false);
identifier.isVariable(".b").should.equal(false);
identifier.isVariable(".").should.equal(false);
identifier.isVariable("0a").should.equal(false);
identifier.isVariable("[0][12].bar[0]").should.equal(false);
lexical.isVariable("a.").should.equal(false);
lexical.isVariable(".b").should.equal(false);
lexical.isVariable(".").should.equal(false);
lexical.isVariable("0a").should.equal(false);
lexical.isVariable("[0][12].bar[0]").should.equal(false);
});
it('should parse boolean literal', function() {
identifier.parseLiteral('true').should.equal(true);
identifier.parseLiteral('TrUE').should.equal(true);
identifier.parseLiteral('false').should.equal(false);
lexical.parseLiteral('true').should.equal(true);
lexical.parseLiteral('TrUE').should.equal(true);
lexical.parseLiteral('false').should.equal(false);
});
it('should parse number literal', function() {
identifier.parseLiteral('2.3').should.equal(2.3);
identifier.parseLiteral('.32').should.equal(0.32);
identifier.parseLiteral('-23.').should.equal(-23);
identifier.parseLiteral('23').should.equal(23);
lexical.parseLiteral('2.3').should.equal(2.3);
lexical.parseLiteral('.32').should.equal(0.32);
lexical.parseLiteral('-23.').should.equal(-23);
lexical.parseLiteral('23').should.equal(23);
});
it('should parse string literal', function() {
identifier.parseLiteral('"ab\'c"').should.equal("ab\'c");
lexical.parseLiteral('"ab\'c"').should.equal("ab\'c");
});
});
+20 -8
View File
@@ -8,14 +8,14 @@ chai.use(sinonChai);
var tag = require('../tag.js')();
var Scope = require('../scope.js');
var filter = require('../filter')();
var Render = require('../render.js')(filter, tag);
var render = Render.renderTemplates;
var evalFilter = Render.evalFilter;
var Render = require('../render.js');
var Template = require('../template.js')(tag, filter);
describe('render', function() {
var scope;
var scope, render;
beforeEach(function() {
render = Render(filter, tag);
scope = Scope.factory({
foo: {
bar: ['a', 2]
@@ -25,8 +25,18 @@ describe('render', function() {
tag.clear();
});
it('should stringify object', function() {
expect(Render.stringify({
foo: 'bar'
})).to.equal('{"foo":"bar"}');
});
it('should stringify object', function() {
expect(Render.stringify([1, 2, 3])).to.equal('[1,2,3]');
});
it('should render html', function() {
expect(render([{
expect(render.renderTemplates([{
type: 'html',
value: '<p>'
}], scope)).to.equal('<p>');
@@ -37,14 +47,16 @@ describe('render', function() {
var time = sinon.spy();
filter.register('date', date);
filter.register('time', time);
evalFilter('foo.bar[0] | date: "b" | time:2', scope);
var tpl = Template.parseOutput('foo.bar[0] | date: "b" | time:2');
render.evalOutput(tpl, scope);
expect(date).to.have.been.calledWith('a', 'b');
expect(time).to.have.been.calledWith('y', 2);
});
it('should eval filter', function() {
it('should eval output', function() {
filter.register('date', (l, r) => l + r);
filter.register('time', (l, r) => l + 3 * r);
expect(evalFilter('foo.bar[0] | date: "b" | time:2', scope)).to.equal('ab6');
var tpl = Template.parseOutput('foo.bar[0] | date: "b" | time:2');
expect(render.evalOutput(tpl, scope)).to.equal('ab6');
});
});
+9 -1
View File
@@ -28,7 +28,9 @@ describe('tags', function() {
};
});
it('should support assign', function() {
test('{% assign foo="bar"%}{{foo}}', 'bar');
test('{% assign foo="bar" %}{{foo}}', 'bar');
test('{% assign foo=(1..3) %}{{foo}}', '[1,2,3]');
test('{% assign foo="a b" | capitalize | split: " " | first %}{{foo}}', 'A');
});
it('should support case', function() {
testThrow('{% case "foo"%}', /{% case "foo"%} not closed/);
@@ -146,6 +148,12 @@ describe('tags', function() {
test(src, dst);
});
it('should support empty tablerow', function() {
src = '{% tablerow i in (1..0) cols:2 %}{{ i }}{% endtablerow %}';
dst = '<table></table>';
test(src, dst);
});
it('should support tablerow with range', function() {
src = '{% tablerow i in (1..5) cols:2 %}{{ i }}{% endtablerow %}';
dst = '<table>' +
+43
View File
@@ -0,0 +1,43 @@
const chai = require("chai");
const sinon = require("sinon");
const sinonChai = require("sinon-chai");
const should = chai.should();
const expect = chai.expect;
chai.use(sinonChai);
var filter = require('../filter.js')();
var tag = require('../tag.js')();
var Template = require('../template.js');
describe('template', function() {
var scope, template, add = (l, r) => l + r;
beforeEach(function(){
filter.clear();
filter.register('add', add);
tag.clear();
template = Template(tag, filter);
});
it('should parse output string', function() {
var tpl = template.parseOutput('foo');
expect(tpl.type).to.equal('output');
expect(tpl.initial).to.equal('foo');
expect(tpl.filters).to.deep.equal([]);
});
it('should parse output string with a simple filter', function() {
var tpl = template.parseOutput('foo | add: 3, "foo"');
expect(tpl.initial).to.equal('foo');
expect(tpl.filters.length).to.equal(1);
expect(tpl.filters[0].filter).to.equal(add);
});
it('should parse output string with filters', function() {
var tpl = template.parseOutput('foo | add: "|" | add');
expect(tpl.initial).to.equal('foo');
expect(tpl.filters.length).to.equal(2);
});
});
+24 -16
View File
@@ -12,6 +12,7 @@ function parse(html) {
var _count = 0;
while ((result = syntax.exec(html)) !== null) {
// passed html fragments
if (result.index > idx) {
htmlFragment = html.slice(idx, result.index);
tokens.push({
@@ -19,25 +20,29 @@ function parse(html) {
value: htmlFragment
});
}
// tag appeared
if (result[1]) {
token = factory('tag', 1, result);
var match = token.value.match(lexical.tagLine);
if (!match) {
throw new TokenizationError(`illegal tag: ${token.raw}`,
token.input, token.line);
}
token.name = match[1];
token.args = match[2];
tokens.push(token);
} else {
}
// output
else {
token = factory('output', 3, result);
tokens.push(token);
}
idx = syntax.lastIndex;
}
// remaining html
if (html.length > idx) {
htmlFragment = html.slice(idx, html.length);
tokens.push({
@@ -48,25 +53,28 @@ function parse(html) {
return tokens;
function factory(type, offset, match) {
var lines = match.input.slice(_idx + 1, match.index).split('\n');
var idx1 = match.input.lastIndexOf('\n', match.index);
var idx2 = match.input.indexOf('\n', match.index);
if(idx2 === -1) idx2 = match.input.length;
var input = match.input.slice(idx1 + 1, idx2);
_count += lines.length - 1;
_idx = match.index;
var token = {
return {
type: type,
raw: match[offset],
value: match[offset + 1].trim(),
line: _count + 1,
input: input
line: getLineNum(match),
input: getLineContent(match)
};
return token;
}
function getLineContent(match) {
var idx1 = match.input.lastIndexOf('\n', match.index);
var idx2 = match.input.indexOf('\n', match.index);
if (idx2 === -1) idx2 = match.input.length;
return match.input.slice(idx1 + 1, idx2);
}
function getLineNum(match) {
var lines = match.input.slice(_idx + 1, match.index).split('\n');
_count += lines.length - 1;
_idx = match.index;
return _count + 1;
}
}
exports.parse = parse;