Skip to content

Commit c155474

Browse files
base .for-each implementation
1 parent 90bb9de commit c155474

File tree

8 files changed

+278
-3
lines changed

8 files changed

+278
-3
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2015 Max Mikhailov
3+
Copyright (c) 2017 Max Mikhailov
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ See [Using a plugin in code](http://lesscss.org/usage/#plugins-using-a-plugin-in
4242

4343
## Additional features
4444

45-
* [Vector Arithmetic](docs/va.md) - perform arithmetic operations on lists
45+
* **[.for](docs/for.md) and [.for-each](docs/for-each.md)** - (almost) canonical syntax for iterative statements
46+
* **[Vector Arithmetic](docs/va.md)** - perform arithmetic operations on lists

docs/for-each.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
## `.for-each`
3+
4+
Less code:
5+
```less
6+
@fruit: banana, apple, pear, potato, carrot, peach;
7+
8+
#fruit {
9+
.for-each(@value in @fruit) {
10+
some: @value;
11+
}
12+
}
13+
```
14+
15+
CSS output:
16+
```css
17+
#fruit {
18+
some: banana;
19+
some: apple;
20+
some: pear;
21+
some: potato;
22+
some: carrot;
23+
some: peach;
24+
}
25+
```

lib/for.js

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
2+
'use strict';
3+
4+
module.exports = function(less, manager) {
5+
6+
// ............................................................
7+
8+
var tree = less.tree,
9+
Node = tree.Node,
10+
Rule = tree.Rule,
11+
Ruleset = tree.Ruleset,
12+
Variable = tree.Variable,
13+
Dimension = tree.Dimension,
14+
isArray = Array.isArray,
15+
scopeSelector = [new tree.Selector([new tree.Element('', '&')])];
16+
17+
// ............................................................
18+
19+
function toArray(list) {
20+
return isArray(list.value) ? list.value : [list];
21+
}
22+
23+
function findFileInfo(node) {
24+
// Curently mixin definitions do not have index/fileInfo
25+
// so... let's try and force something from its rules (if any):
26+
// * for nested mixins it's usually `node.rules[0]`
27+
// * for global mixins it's usually `node.rules[0].rules[0]`
28+
while (node) {
29+
if ((node = (node.rules && node.rules[0])
30+
|| node.node) && node.currentFileInfo) {
31+
var fileInfo = node.currentFileInfo;
32+
fileInfo.index = node.index;
33+
return fileInfo;
34+
}
35+
}
36+
37+
return {index: undefined};
38+
}
39+
40+
// ............................................................
41+
42+
function Evaluator(node) {
43+
this.node = node;
44+
}
45+
46+
Evaluator.prototype = new Node();
47+
Evaluator.prototype.type = "ForEvaluator";
48+
Evaluator.prototype.evalFirst = true;
49+
Evaluator.prototype.accept = function(visitor) {
50+
this.node.rules = visitor.visitArray(this.node.rules);
51+
};
52+
53+
Evaluator.prototype.eval = function(context) {
54+
var node = this.node,
55+
args = node.params,
56+
fileInfo = findFileInfo(node);
57+
58+
// parse args:
59+
var n = args.length,
60+
value = args[0],
61+
index = (n === 4) ? args[n - 3].name : "@__index",
62+
magic = args[n - 2],
63+
list = args[n - 1],
64+
valid;
65+
66+
valid = (n > 2) || (n < 5);
67+
valid = valid && magic && magic.value
68+
&& (magic.value.value === "in");
69+
70+
if (!valid) throw {
71+
type: "Syntax",
72+
message: "[plugin-lists] unexpected .for-each parameters",
73+
index: fileInfo.index,
74+
filename: fileInfo.filename
75+
};
76+
77+
// apply:
78+
var result = [],
79+
rules = node.rules;
80+
value = value.name;
81+
list = toArray((new Variable(list.name,
82+
fileInfo.index, fileInfo)).eval(context));
83+
84+
for (var i = 0; i < list.length; i++) {
85+
var push = true,
86+
iter = [new Rule(value, list[i]),
87+
new Rule(index, new Dimension(i + 1))];
88+
iter = new Ruleset(scopeSelector, iter);
89+
90+
if (node.condition) {
91+
var frames = context.frames;
92+
frames.unshift(iter[0], iter[1]);
93+
push = node.condition.eval(context);
94+
frames.shift(); frames.shift();
95+
}
96+
97+
if (push) {
98+
Array.prototype.push.apply(iter.rules, rules); // iter.rules = iter.rules.concat(rules);
99+
result.push(iter);
100+
}
101+
}
102+
103+
return (new Ruleset(scopeSelector, result)).eval(context);
104+
};
105+
106+
// ............................................................
107+
108+
function Visitor() {
109+
this.native_ = new less.visitors.Visitor(this);
110+
}
111+
112+
Visitor.prototype = {
113+
isPreEvalVisitor: true,
114+
isReplacing: true,
115+
116+
run: function(root) {
117+
return this.native_.visit(root);
118+
},
119+
120+
visitMixinDefinition: function(node) {
121+
if (node.name === ".for-each")
122+
return new Evaluator(node);
123+
124+
return node;
125+
},
126+
};
127+
128+
manager.addVisitor(new Visitor());
129+
130+
// ............................................................
131+
132+
}; // ~ end of module.exports

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ function ThisPlugin() {}
88
ThisPlugin.prototype.install
99
= function(less, manager) {
1010
require("./main")(less, manager);
11+
require("./for")(less, manager);
1112
};

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "less-plugin-lists",
3-
"version": "1.0.0",
3+
"version": "1.0.8",
44
"description": "list/array manipulation functions for Less",
55
"homepage": "https://github.com/seven-phases-max/less-plugin-lists",
66
"author": "Max Mikhailov",

test/css/for-each.css

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#for-each-fruit {
2+
value: banana;
3+
value: apple;
4+
value: pear;
5+
value: potato;
6+
value: carrot;
7+
value: peach;
8+
}
9+
#for-each-index-fruit {
10+
1: banana;
11+
2: apple;
12+
3: pear;
13+
4: potato;
14+
5: carrot;
15+
6: peach;
16+
}
17+
#for-each-nested {
18+
r: a of [a b c];
19+
r: b of [a b c];
20+
r: c of [a b c];
21+
r: e of [e f g];
22+
r: f of [e f g];
23+
r: g of [e f g];
24+
}
25+
#for-each-nested-index {
26+
11: a;
27+
12: b;
28+
13: c;
29+
21: e;
30+
22: f;
31+
23: g;
32+
}
33+
#for-each-nested-scope {
34+
outer-1: outer a b c;
35+
inner-1: inner a;
36+
inner-2: inner b;
37+
inner-3: inner c;
38+
outer-2: outer e f g;
39+
inner-1: inner e;
40+
inner-2: inner f;
41+
inner-3: inner g;
42+
}
43+
#for-each-nested-lazy-eval {
44+
outer-1: a b c;
45+
inner-1: a = a;
46+
inner-2: b = b;
47+
inner-3: c = c;
48+
outer-2: e f g;
49+
inner-1: e = e;
50+
inner-2: f = f;
51+
inner-3: g = g;
52+
}
53+
.for-each {
54+
me: is not a loop;
55+
}
56+
.for-each .for-each {
57+
me: is not a loop too;
58+
}

test/less/for-each.less

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
@fruit: banana, apple, pear, potato, carrot, peach;
3+
@2d-list: a b c, e f g;
4+
5+
#for-each-fruit {
6+
.for-each(@value in @fruit) {
7+
value: @value;
8+
}
9+
}
10+
11+
#for-each-index-fruit {
12+
.for-each(@value, @index in @fruit) {
13+
@{index}: @value;
14+
}
15+
}
16+
17+
#for-each-nested {
18+
.for-each(@x in @2d-list) {
19+
.for-each(@y in @x) {
20+
r: @y of _inspect(@x);
21+
}
22+
}
23+
}
24+
25+
#for-each-nested-index {
26+
.for-each(@x, @i in @2d-list) {
27+
.for-each(@y, @j in @x) {
28+
@p: @i * 10 + @j;
29+
@{p}: @y;
30+
}
31+
}
32+
}
33+
34+
#for-each-nested-scope {
35+
.for-each(@x, @i in @2d-list) {
36+
outer-@{i}: outer @x;
37+
.for-each(@x, @i in @x) {
38+
inner-@{i}: inner @x;
39+
}
40+
}
41+
}
42+
43+
#for-each-nested-lazy-eval {
44+
.for-each(@x, @i in @2d-list) {
45+
outer-@{i}: @lazy;
46+
.for-each(@x, @j in @x) {
47+
inner-@{j}: @x ~'=' @lazy;
48+
}
49+
@lazy: @x;
50+
}
51+
}
52+
53+
.for-each {
54+
me: is not a loop;
55+
.for-each {
56+
me: is not a loop too;
57+
}
58+
}

0 commit comments

Comments
 (0)