mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
filters: all
This commit is contained in:
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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 'James & the Giant Peach'?');
|
||||
test('{{ "Tetsuro Takara" | escape }}', 'Tetsuro Takara');
|
||||
});
|
||||
|
||||
it('should support escape_once', function() {
|
||||
test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 3');
|
||||
test('{{ "1 < 2 & 3" | escape_once }}', '1 < 2 & 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
@@ -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
@@ -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
@@ -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>' +
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user