Skip to content

Commit

Permalink
Add support for ES iterators
Browse files Browse the repository at this point in the history
This requires the Symbol.iterator well known symbol and Array.from to
coerce them into vanilla arrays.
  • Loading branch information
maxnordlund committed Jan 26, 2018
1 parent e6cc590 commit 7086e2d
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ master (unreleased)

* Support objects created with Object.create(null). fixes [#468](https://github.com/mozilla/nunjucks/issues/468)

* Support ESNext iterators, using Array.from. Merge of
[#760](https://github.com/mozilla/nunjucks/pull/760)

3.0.1 (May 24 2017)
-------------------
Expand Down
17 changes: 17 additions & 0 deletions docs/templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,23 @@ var food = {
The [`dictsort`](http://jinja.pocoo.org/docs/templates/#dictsort) filter is
available for sorting objects when iterating over them.

ES iterators are supported, like the new builtin Map and Set. But also
anything implementing the iterator protocal.

```js
var fruits = new Map([
["banana", "yellow"],
["apple", "red"],
["peach", "pink"]
])
```

```jinja
{% for fruit, color in fruits %}
Did you know that {{ fruit }} is {{ color }}?
{% endfor %}
```

Additionally, Nunjucks will unpack arrays into variables:

```js
Expand Down
5 changes: 3 additions & 2 deletions nunjucks/src/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ class Compiler extends Obj {
this.emitLine(';');

this.emit(`if(${arr}) {`);
this.emitLine(arr + ' = runtime.fromIterator(' + arr + ');');

// If multiple names are passed, we need to bind them
// appropriately
Expand Down Expand Up @@ -754,9 +755,9 @@ class Compiler extends Obj {

this.emitLine('frame = frame.push();');

this.emit('var ' + arr + ' = ');
this.emit('var ' + arr + ' = runtime.fromIterator(');
this._compileExpression(node.arr, frame);
this.emitLine(';');
this.emitLine(');');

if (node.name instanceof nodes.Array) {
const arrayLen = node.name.children.length;
Expand Down
18 changes: 17 additions & 1 deletion nunjucks/src/runtime.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
'use strict';

var lib = require('./lib');
var arrayFrom = Array.from;
var supportsIterators = (
typeof Symbol === 'function' && Symbol.iterator && typeof arrayFrom === 'function'
);


// Frames keep track of scoping both at compile-time and run-time so
// we know how to access variables. Block tags can introduce special
Expand Down Expand Up @@ -342,6 +347,16 @@ function asyncAll(arr, dimen, func, cb) {
}
}

function fromIterator(arr) {
if (arr == null || lib.isArray(arr)) {
return arr;
} else if (supportsIterators && Symbol.iterator in arr) {
return arrayFrom(arr);
} else {
return arr;
}
}

module.exports = {
Frame: Frame,
makeMacro: makeMacro,
Expand All @@ -360,5 +375,6 @@ module.exports = {
markSafe: markSafe,
asyncEach: asyncEach,
asyncAll: asyncAll,
inOperator: lib.inOperator
inOperator: lib.inOperator,
fromIterator: fromIterator
};
36 changes: 36 additions & 0 deletions tests/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,42 @@
},
'showing test\nshowing 1\nshowing 2\nshowing 3\n');
});
/* global Set */
it('should work with Set builtin', function() {
if (typeof Set === 'undefined') {
this.skip();
} else {
equal('{% ' + block + ' i in set %}{{ i }}{% ' + end + ' %}',
{ set: new Set([1, 2, 3, 4, 5]) },
'12345');

equal('{% ' + block + ' i in set %}{{ i }}{% else %}empty{% ' + end + ' %}',
{ set: new Set([1, 2, 3, 4, 5]) },
'12345');

equal('{% ' + block + ' i in set %}{{ i }}{% else %}empty{% ' + end + ' %}',
{ set: new Set() },
'empty');
}
});
/* global Map */
it('should work with Map builtin', function() {
if (typeof Map === 'undefined') {
this.skip();
} else {
equal('{% ' + block + ' k, v in map %}[{{ k }},{{ v }}]{% ' + end + ' %}',
{ map: new Map([[1, 2], [3, 4], [5, 6]]) },
'[1,2][3,4][5,6]');

equal('{% ' + block + ' k, v in map %}[{{ k }},{{ v }}]{% else %}empty{% ' + end + ' %}',
{ map: new Map([[1, 2], [3, 4], [5, 6]]) },
'[1,2][3,4][5,6]');

equal('{% ' + block + ' k, v in map %}[{{ k }},{{ v }}]{% else %}empty{% ' + end + ' %}',
{ map: new Map() },
'empty');
}
});
});
}

Expand Down

0 comments on commit 7086e2d

Please sign in to comment.