Skip to content
This repository has been archived by the owner on Apr 11, 2018. It is now read-only.

Commit

Permalink
merging conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
paularmstrong committed Oct 2, 2011
2 parents 5ca1ab6 + 1ffa245 commit 4cd894e
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 105 deletions.
19 changes: 15 additions & 4 deletions docs/tags.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,20 @@ If the loop object is empty or `length === 0`, the content following the `empty`

### if

Supports the following expressions.
All normal JavaScript-valid if statements are supported, including some extra helpful syntaxes:

{% if x %}{% endif %}
{% if !x %}{% endif %}
{% if x operator y %}
Operators: ==, !=, <, <=, >, >=, ===, !==, in
The 'in' operator checks for presence in arrays too.
{% if not x %}{% endif %}

{% if x and y %}{% endif %}
{% if x && y %}{% endif %}
{% if x or y %}{% endif %}
{% if x || y %}{% endif %}
{% if x || (y && z) %}{% endif %}

{% if x [operator] y %}
Operators: ==, !=, <, <=, >, >=, ===, !==
{% endif %}
{% if x == 'five' %}
The operands can be also be string or number literals
Expand All @@ -77,6 +84,10 @@ Supports the following expressions.
You can use filters on any operand in the statement.
{% endif %}

{% if x in y %}
If x is a value that is present in y, this will return true.
{% endif %}

#### else and else if

Also supports using the `{% else %}` and `{% else if ... %}` tags within an if-block. Using `{% else [if ...] %}` anywhere outside an immediate parent if-block will throw an exception.
Expand Down
206 changes: 109 additions & 97 deletions lib/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,130 +41,142 @@ exports.include = function (indent) {
].join('\n' + indent);
};

function checkIfArgs(leftOperand, operator, rightOperand) {
var negation = false;
function parseIfArgs(args) {
var operators = ['==', '<', '>', '!=', '<=', '>=', '===', '!==', '&&', '||', 'in', 'and', 'or'],
errorString = 'Bad if-syntax in `{% if ' + args.join(' ') + ' %}...',
tokens = [],
prevType,
last,
closing = 0;

_.each(args, function (value, index) {
var endsep = false,
operand;

if ((/^\(/).test(value)) {
closing += 1;
value = value.substr(1);
tokens.push({ type: 'separator', value: '(' });
}

// Check if there is negation
if (leftOperand.name[0] === '!') {
negation = true;
leftOperand.name = leftOperand.name.substr(1);
}
if ((/^\![^=]/).test(value) || (value === 'not')) {
if (value === 'not') {
value = '';
} else {
value = value.substr(1);
}
tokens.push({ type: 'operator', value: '!' });
}

// '!something == else' - this syntax is forbidden. Use 'something != else' instead
if (negation && operator) {
throw new Error('Invalid syntax for "if" tag');
}
// Check for valid argument
if (!helpers.isLiteral(leftOperand.name) && !helpers.isValidName(leftOperand.name)) {
throw new Error('Invalid arguments (' + leftOperand.name + ') passed to "if" tag');
}
// Check for valid operator
if (operator && ['==', '<', '>', '!=', '<=', '>=', '===', '!==', 'in'].indexOf(operator) === -1) {
throw new Error('Invalid operator (' + operator + ') passed to "if" tag');
}
// Check for presence of operand 2 if operator is present
if (operator && !rightOperand) {
throw new Error('Missing argument in "if" tag');
}
if ((/\)$/).test(value)) {
if (!closing) {
throw new Error(errorString);
}
value = value.replace(/\)$/, '');
endsep = true;
closing -= 1;
}

if (value === 'in') {
last = tokens.pop();
prevType = 'inindex';
} else if (_.indexOf(operators, value) !== -1) {
if (prevType === 'operator') {
throw new Error(errorString);
}
value = value.replace('and', '&&').replace('or', '||');
tokens.push({
value: value
});
prevType = 'operator';
} else if (value !== '') {
if (prevType === 'value') {
throw new Error(errorString);
}
operand = parser.parseVariable(value);

if (prevType === 'inindex') {
tokens.push({
preout: last.preout + helpers.setVar('__op' + index, operand),
value: '(((_.isArray(__op' + index + ') || typeof __op' + index + ' === "string") && __op' + index + '.indexOf(' + last.value + ') !== -1) || (typeof __op' + index + ' === "object" && ' + last.value + ' in __op' + index + '))'
});
last = null;
} else {
tokens.push({
preout: helpers.setVar('__op' + index, operand),
value: '__op' + index
});
}
prevType = 'value';
}

// Check for valid argument
if (operator && !helpers.isLiteral(rightOperand.name) && !helpers.isValidName(rightOperand.name)) {
throw new Error('Invalid arguments (' + rightOperand.name + ') passed to "if" tag');
if (endsep) {
tokens.push({ type: 'separator', value: ')' });
}
});

if (closing > 0) {
throw new Error(errorString);
}

return negation;
return tokens;
}

exports['if'] = function (indent) {
var leftOperand = parser.parseVariable(this.args[0]),
operator = this.args[1],
rightOperand = parser.parseVariable(this.args[2]),
negation = checkIfArgs(leftOperand, operator, rightOperand),
out = '';

indent = indent || '';
exports.if = function (indent) {
var args = (parseIfArgs(this.args)),
out = '(function () {\n';

out = '(function () {';
out += helpers.setVar('__op1', leftOperand);
if (rightOperand.name === '') {
out += ' if (' + (negation ? '!' : '!!') + '__op1) {';
out += parser.compile.call(this, indent + ' ');
out += ' }';
} else {
out += helpers.setVar('__op2', rightOperand);

if (typeof operator !== 'undefined') {
if (operator === 'in') {
out += 'if (';
out += ' (_.isArray(__op2) && __op2.indexOf(__op1) > -1) ||';
out += ' (typeof __op2 === "string" && __op2.indexOf(__op1) > -1) ||';
out += ' (!_.isArray(__op2) && typeof __op2 === "object" && __op1 in __op2)';
out += ') {';
out += parser.compile.call(this, indent + ' ');
out += '}';
} else {
out += 'if (__op1 ' + helpers.escape(operator) + ' __op2) {';
out += parser.compile.call(this, indent + ' ');
out += '}';
}
_.each(args, function (token) {
if (token.hasOwnProperty('preout') && token.preout) {
out += token.preout;
}
}
});

out += '\n\nif (';
_.each(args, function (token) {
out += token.value + ' ';
});
out += ') {\n';
out += parser.compile.call(this, indent + ' ');
out += '\n}\n';
out += '})();';

return out;
};
exports['if'].ends = true;
exports.if.ends = true;

exports.else = function (indent) {
if (_.last(this.parent).name !== 'if') {
throw new Error('Cannot call else tag outside of "if" context.');
}

var ifarg = this.args.shift(),
leftOperand,
operator,
rightOperand,
negation,
args = (parseIfArgs(this.args)),
out = '';

if (ifarg) {
leftOperand = parser.parseVariable(this.args[0]);
operator = this.args[1];
rightOperand = parser.parseVariable(this.args[2]);
negation = checkIfArgs(leftOperand, operator, rightOperand);
out = [];

if (rightOperand.name === '') {
out += '} else if (' + (negation ? '!' : '!!') + '(function() {' + helpers.setVar('__op1', leftOperand) + ' return __op1; })()) {';
} else {
out += helpers.setVar('__op2', rightOperand);

if (operator) {
if (operator === 'in') {
out += '} else if (';
out += ' (function() {';
out += ' ' + helpers.setVar('__op1', leftOperand);
out += ' ' + helpers.setVar('__op2', rightOperand);
out += ' return (_.isArray(__op2) && __op2.indexOf(__op1) > -1) ||';
out += ' (typeof __op2 === "string" && __op2.indexOf(__op1) > -1) ||';
out += ' (!_.isArray(__op2) && typeof __op2 === "object" && __op1 in __op2);';
out += ' })()';
out += ') {';
} else {
out += '} else if (';
out += ' (function() {';
out += ' ' + helpers.setVar('__op1', leftOperand);
out += ' ' + helpers.setVar('__op2', rightOperand);
out += ' return __op1 ' + helpers.escape(operator) + ' __op2;';
out += ' })()';
out += ') {';
}
out += '} else if (';
out += ' (function () {';

_.each(args, function (token) {
if (token.hasOwnProperty('preout') && token.preout) {
out += token.preout;
}
}
});

out += 'return (';
_.each(args, function (token) {
out += token.value + ' ';
});
out += ');\n';

out += ' })()\n';
out += ') {\n';

return out;
}

return indent + '} else {';
return indent + '\n} else {\n';
};

/**
Expand Down
43 changes: 39 additions & 4 deletions tests/tags.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,44 @@ exports.if = testCase({
},

basic: function (test) {
var tmpl8 = swig.fromString('{% if foo %}hi!{% endif %}{% if bar %}nope{% endif %}');
test.strictEqual(tmpl8.render({ foo: 1, bar: false }), 'hi!');
var tpl = swig.fromString('{% if foo %}hi!{% endif %}{% if bar %}nope{% endif %}');
test.strictEqual(tpl.render({ foo: 1, bar: false }), 'hi!');

tmpl8 = swig.fromString('{% if !foo %}hi!{% endif %}{% if !bar %}nope{% endif %}');
test.strictEqual(tmpl8.render({ foo: 1, bar: false }), 'nope');
tpl = swig.fromString('{% if !foo %}hi!{% endif %}{% if !bar %}nope{% endif %}');
test.strictEqual(tpl.render({ foo: 1, bar: false }), 'nope', '! operator');

tpl = swig.fromString('{% if not foo %}hi!{% endif %}{% if not bar %}nope{% endif %}');
test.strictEqual(tpl.render({ foo: true, bar: false }), 'nope', 'not operator');

tpl = swig.fromString('{% if foo && (bar || baz) %}hi!{% endif %}');
test.strictEqual(tpl.render({ foo: true, bar: true }), 'hi!');
test.strictEqual(tpl.render({ foo: true, baz: true }), 'hi!');
test.strictEqual(tpl.render({ foo: false }), '');

tpl = swig.fromString('{% if foo and bar %}hi!{% endif %}');
test.strictEqual(tpl.render({ foo: true, bar: true }), 'hi!', 'and syntax');
tpl = swig.fromString('{% if foo or bar %}hi!{% endif %}');
test.strictEqual(tpl.render({ foo: false, bar: true }), 'hi!', 'or syntax');

tpl = swig.fromString('{% if foo in bar %}hi!{% endif %}');
test.strictEqual(tpl.render({ foo: 'b', bar: ['a', 'b', 'c'] }), 'hi!', 'in syntax');

test.done();
},

errors: function (test) {
test.throws(function () {
swig.fromString('{% if foo bar %}{% endif %}');
});
test.throws(function () {
swig.fromString('{% if foo !== > bar %}{% endif %}');
});
test.throws(function () {
swig.fromString('{% if (foo %}{% endif %}');
});
test.throws(function () {
swig.fromString('{% if foo > bar) %}{% endif %}');
});
test.done();
},

Expand Down Expand Up @@ -171,6 +203,9 @@ exports.if = testCase({
test.strictEqual(tmpl8.render({ foo: [1, 2] }), '');
test.strictEqual(tmpl8.render({ foo: [1] }), 'bar');

tmpl8 = swig.fromString('{% if foo %}foo{% else if bar && baz %}bar{% endif %}');
test.strictEqual(tmpl8.render({ bar: true, baz: true }), 'bar');

test.done();
},

Expand Down

0 comments on commit 4cd894e

Please sign in to comment.