1 | (function (tree) { |
---|
2 | |
---|
3 | tree.Ruleset = function (selectors, rules) { |
---|
4 | this.selectors = selectors; |
---|
5 | this.rules = rules; |
---|
6 | this._lookups = {}; |
---|
7 | }; |
---|
8 | tree.Ruleset.prototype = { |
---|
9 | eval: function (env) { |
---|
10 | var ruleset = new(tree.Ruleset)(this.selectors, this.rules.slice(0)); |
---|
11 | |
---|
12 | ruleset.root = this.root; |
---|
13 | |
---|
14 | // push the current ruleset to the frames stack |
---|
15 | env.frames.unshift(ruleset); |
---|
16 | |
---|
17 | // Evaluate imports |
---|
18 | if (ruleset.root) { |
---|
19 | for (var i = 0; i < ruleset.rules.length; i++) { |
---|
20 | if (ruleset.rules[i] instanceof tree.Import) { |
---|
21 | Array.prototype.splice |
---|
22 | .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); |
---|
23 | } |
---|
24 | } |
---|
25 | } |
---|
26 | |
---|
27 | // Store the frames around mixin definitions, |
---|
28 | // so they can be evaluated like closures when the time comes. |
---|
29 | for (var i = 0; i < ruleset.rules.length; i++) { |
---|
30 | if (ruleset.rules[i] instanceof tree.mixin.Definition) { |
---|
31 | ruleset.rules[i].frames = env.frames.slice(0); |
---|
32 | } |
---|
33 | } |
---|
34 | |
---|
35 | // Evaluate mixin calls. |
---|
36 | for (var i = 0; i < ruleset.rules.length; i++) { |
---|
37 | if (ruleset.rules[i] instanceof tree.mixin.Call) { |
---|
38 | Array.prototype.splice |
---|
39 | .apply(ruleset.rules, [i, 1].concat(ruleset.rules[i].eval(env))); |
---|
40 | } |
---|
41 | } |
---|
42 | |
---|
43 | // Evaluate everything else |
---|
44 | for (var i = 0, rule; i < ruleset.rules.length; i++) { |
---|
45 | rule = ruleset.rules[i]; |
---|
46 | |
---|
47 | if (! (rule instanceof tree.mixin.Definition)) { |
---|
48 | ruleset.rules[i] = rule.eval ? rule.eval(env) : rule; |
---|
49 | } |
---|
50 | } |
---|
51 | |
---|
52 | // Pop the stack |
---|
53 | env.frames.shift(); |
---|
54 | |
---|
55 | return ruleset; |
---|
56 | }, |
---|
57 | match: function (args) { |
---|
58 | return !args || args.length === 0; |
---|
59 | }, |
---|
60 | variables: function () { |
---|
61 | if (this._variables) { return this._variables } |
---|
62 | else { |
---|
63 | return this._variables = this.rules.reduce(function (hash, r) { |
---|
64 | if (r instanceof tree.Rule && r.variable === true) { |
---|
65 | hash[r.name] = r; |
---|
66 | } |
---|
67 | return hash; |
---|
68 | }, {}); |
---|
69 | } |
---|
70 | }, |
---|
71 | variable: function (name) { |
---|
72 | return this.variables()[name]; |
---|
73 | }, |
---|
74 | rulesets: function () { |
---|
75 | if (this._rulesets) { return this._rulesets } |
---|
76 | else { |
---|
77 | return this._rulesets = this.rules.filter(function (r) { |
---|
78 | return (r instanceof tree.Ruleset) || (r instanceof tree.mixin.Definition); |
---|
79 | }); |
---|
80 | } |
---|
81 | }, |
---|
82 | find: function (selector, self) { |
---|
83 | self = self || this; |
---|
84 | var rules = [], rule, match, |
---|
85 | key = selector.toCSS(); |
---|
86 | |
---|
87 | if (key in this._lookups) { return this._lookups[key] } |
---|
88 | |
---|
89 | this.rulesets().forEach(function (rule) { |
---|
90 | if (rule !== self) { |
---|
91 | for (var j = 0; j < rule.selectors.length; j++) { |
---|
92 | if (match = selector.match(rule.selectors[j])) { |
---|
93 | if (selector.elements.length > 1) { |
---|
94 | Array.prototype.push.apply(rules, rule.find( |
---|
95 | new(tree.Selector)(selector.elements.slice(1)), self)); |
---|
96 | } else { |
---|
97 | rules.push(rule); |
---|
98 | } |
---|
99 | break; |
---|
100 | } |
---|
101 | } |
---|
102 | } |
---|
103 | }); |
---|
104 | return this._lookups[key] = rules; |
---|
105 | }, |
---|
106 | // |
---|
107 | // Entry point for code generation |
---|
108 | // |
---|
109 | // `context` holds an array of arrays. |
---|
110 | // |
---|
111 | toCSS: function (context, env) { |
---|
112 | var css = [], // The CSS output |
---|
113 | rules = [], // node.Rule instances |
---|
114 | rulesets = [], // node.Ruleset instances |
---|
115 | paths = [], // Current selectors |
---|
116 | selector, // The fully rendered selector |
---|
117 | rule; |
---|
118 | |
---|
119 | if (! this.root) { |
---|
120 | if (context.length === 0) { |
---|
121 | paths = this.selectors.map(function (s) { return [s] }); |
---|
122 | } else { |
---|
123 | this.joinSelectors( paths, context, this.selectors ); |
---|
124 | } |
---|
125 | } |
---|
126 | |
---|
127 | // Compile rules and rulesets |
---|
128 | for (var i = 0; i < this.rules.length; i++) { |
---|
129 | rule = this.rules[i]; |
---|
130 | |
---|
131 | if (rule.rules || (rule instanceof tree.Directive)) { |
---|
132 | rulesets.push(rule.toCSS(paths, env)); |
---|
133 | } else if (rule instanceof tree.Comment) { |
---|
134 | if (!rule.silent) { |
---|
135 | if (this.root) { |
---|
136 | rulesets.push(rule.toCSS(env)); |
---|
137 | } else { |
---|
138 | rules.push(rule.toCSS(env)); |
---|
139 | } |
---|
140 | } |
---|
141 | } else { |
---|
142 | if (rule.toCSS && !rule.variable) { |
---|
143 | rules.push(rule.toCSS(env)); |
---|
144 | } else if (rule.value && !rule.variable) { |
---|
145 | rules.push(rule.value.toString()); |
---|
146 | } |
---|
147 | } |
---|
148 | } |
---|
149 | |
---|
150 | rulesets = rulesets.join(''); |
---|
151 | |
---|
152 | // If this is the root node, we don't render |
---|
153 | // a selector, or {}. |
---|
154 | // Otherwise, only output if this ruleset has rules. |
---|
155 | if (this.root) { |
---|
156 | css.push(rules.join(env.compress ? '' : '\n')); |
---|
157 | } else { |
---|
158 | if (rules.length > 0) { |
---|
159 | selector = paths.map(function (p) { |
---|
160 | return p.map(function (s) { |
---|
161 | return s.toCSS(env); |
---|
162 | }).join('').trim(); |
---|
163 | }).join(env.compress ? ',' : (paths.length > 3 ? ',\n' : ', ')); |
---|
164 | css.push(selector, |
---|
165 | (env.compress ? '{' : ' {\n ') + |
---|
166 | rules.join(env.compress ? '' : '\n ') + |
---|
167 | (env.compress ? '}' : '\n}\n')); |
---|
168 | } |
---|
169 | } |
---|
170 | css.push(rulesets); |
---|
171 | |
---|
172 | return css.join('') + (env.compress ? '\n' : ''); |
---|
173 | }, |
---|
174 | |
---|
175 | joinSelectors: function (paths, context, selectors) { |
---|
176 | for (var s = 0; s < selectors.length; s++) { |
---|
177 | this.joinSelector(paths, context, selectors[s]); |
---|
178 | } |
---|
179 | }, |
---|
180 | |
---|
181 | joinSelector: function (paths, context, selector) { |
---|
182 | var before = [], after = [], beforeElements = [], |
---|
183 | afterElements = [], hasParentSelector = false, el; |
---|
184 | |
---|
185 | for (var i = 0; i < selector.elements.length; i++) { |
---|
186 | el = selector.elements[i]; |
---|
187 | if (el.combinator.value[0] === '&') { |
---|
188 | hasParentSelector = true; |
---|
189 | } |
---|
190 | if (hasParentSelector) afterElements.push(el); |
---|
191 | else beforeElements.push(el); |
---|
192 | } |
---|
193 | |
---|
194 | if (! hasParentSelector) { |
---|
195 | afterElements = beforeElements; |
---|
196 | beforeElements = []; |
---|
197 | } |
---|
198 | |
---|
199 | if (beforeElements.length > 0) { |
---|
200 | before.push(new(tree.Selector)(beforeElements)); |
---|
201 | } |
---|
202 | |
---|
203 | if (afterElements.length > 0) { |
---|
204 | after.push(new(tree.Selector)(afterElements)); |
---|
205 | } |
---|
206 | |
---|
207 | for (var c = 0; c < context.length; c++) { |
---|
208 | paths.push(before.concat(context[c]).concat(after)); |
---|
209 | } |
---|
210 | } |
---|
211 | }; |
---|
212 | })(require('less/tree')); |
---|