1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | var Visitor = require('./')
|
12 | , utils = require('../utils')
|
13 | , fs = require('fs');
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | var Compiler = module.exports = function Compiler(root, options) {
|
28 | options = options || {};
|
29 | this.compress = options.compress;
|
30 | this.firebug = options.firebug;
|
31 | this.linenos = options.linenos;
|
32 | this.spaces = options['indent spaces'] || 2;
|
33 | this.indents = 1;
|
34 | Visitor.call(this, root);
|
35 | this.stack = [];
|
36 | };
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 | Compiler.prototype.__proto__ = Visitor.prototype;
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | Compiler.prototype.compile = function(){
|
52 | return this.visit(this.root);
|
53 | };
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | Compiler.prototype.out = function(str, node){
|
65 | return str;
|
66 | };
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | Compiler.prototype.__defineGetter__('indent', function(){
|
76 | if (this.compress) return '';
|
77 | return new Array(this.indents).join(Array(this.spaces + 1).join(' '));
|
78 | });
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | Compiler.prototype.needBrackets = function(node){
|
89 | return 1 == this.indents
|
90 | || 'atrule' != node.nodeName
|
91 | || node.hasOnlyProperties;
|
92 | };
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | Compiler.prototype.visitRoot = function(block){
|
99 | this.buf = '';
|
100 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
101 | var node = block.nodes[i];
|
102 | if (this.linenos || this.firebug) this.debugInfo(node);
|
103 | var ret = this.visit(node);
|
104 | if (ret) this.buf += this.out(ret + '\n', node);
|
105 | }
|
106 | return this.buf;
|
107 | };
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | Compiler.prototype.visitBlock = function(block){
|
114 | var node
|
115 | , separator = this.compress ? '' : '\n'
|
116 | , needBrackets;
|
117 |
|
118 | if (block.hasProperties && !block.lacksRenderedSelectors) {
|
119 | needBrackets = this.needBrackets(block.node);
|
120 |
|
121 | if (needBrackets) {
|
122 | this.buf += this.out(this.compress ? '{' : ' {\n');
|
123 | ++this.indents;
|
124 | }
|
125 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
126 | this.last = len - 1 == i;
|
127 | node = block.nodes[i];
|
128 | switch (node.nodeName) {
|
129 | case 'null':
|
130 | case 'expression':
|
131 | case 'function':
|
132 | case 'group':
|
133 | case 'block':
|
134 | case 'unit':
|
135 | case 'media':
|
136 | case 'keyframes':
|
137 | case 'atrule':
|
138 | case 'supports':
|
139 | continue;
|
140 |
|
141 | case !this.compress && node.inline && 'comment':
|
142 | this.buf = this.buf.slice(0, -1);
|
143 | this.buf += this.out(' ' + this.visit(node) + '\n', node);
|
144 | break;
|
145 | case 'property':
|
146 | var ret = this.visit(node) + separator;
|
147 | this.buf += this.compress ? ret : this.out(ret, node);
|
148 | break;
|
149 | default:
|
150 | this.buf += this.out(this.visit(node) + separator, node);
|
151 | }
|
152 | }
|
153 | if (needBrackets) {
|
154 | --this.indents;
|
155 | this.buf += this.out(this.indent + '}' + separator);
|
156 | }
|
157 | }
|
158 |
|
159 |
|
160 | for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
161 | node = block.nodes[i];
|
162 | switch (node.nodeName) {
|
163 | case 'group':
|
164 | case 'block':
|
165 | case 'keyframes':
|
166 | if (this.linenos || this.firebug) this.debugInfo(node);
|
167 | this.visit(node);
|
168 | break;
|
169 | case 'media':
|
170 | case 'import':
|
171 | case 'atrule':
|
172 | case 'supports':
|
173 | this.visit(node);
|
174 | break;
|
175 | case 'comment':
|
176 |
|
177 | if (!node.suppress) {
|
178 | this.buf += this.out(this.indent + this.visit(node) + '\n', node);
|
179 | }
|
180 | break;
|
181 | case 'charset':
|
182 | case 'literal':
|
183 | case 'namespace':
|
184 | this.buf += this.out(this.visit(node) + '\n', node);
|
185 | break;
|
186 | }
|
187 | }
|
188 | };
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | Compiler.prototype.visitKeyframes = function(node){
|
195 | if (!node.frames) return;
|
196 |
|
197 | var prefix = 'official' == node.prefix
|
198 | ? ''
|
199 | : '-' + node.prefix + '-';
|
200 |
|
201 | this.buf += this.out('@' + prefix + 'keyframes '
|
202 | + this.visit(node.val)
|
203 | + (this.compress ? '{' : ' {\n'), node);
|
204 |
|
205 | this.keyframe = true;
|
206 | ++this.indents;
|
207 | this.visit(node.block);
|
208 | --this.indents;
|
209 | this.keyframe = false;
|
210 |
|
211 | this.buf += this.out('}' + (this.compress ? '' : '\n'));
|
212 | };
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 | Compiler.prototype.visitMedia = function(media){
|
219 | var val = media.val;
|
220 | if (!media.hasOutput || !val.nodes.length) return;
|
221 |
|
222 | this.buf += this.out('@media ', media);
|
223 | this.visit(val);
|
224 | this.buf += this.out(this.compress ? '{' : ' {\n');
|
225 | ++this.indents;
|
226 | this.visit(media.block);
|
227 | --this.indents;
|
228 | this.buf += this.out('}' + (this.compress ? '' : '\n'));
|
229 | };
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 | Compiler.prototype.visitQueryList = function(queries){
|
236 | for (var i = 0, len = queries.nodes.length; i < len; ++i) {
|
237 | this.visit(queries.nodes[i]);
|
238 | if (len - 1 != i) this.buf += this.out(',' + (this.compress ? '' : ' '));
|
239 | }
|
240 | };
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 | Compiler.prototype.visitQuery = function(node){
|
247 | var len = node.nodes.length;
|
248 | if (node.predicate) this.buf += this.out(node.predicate + ' ');
|
249 | if (node.type) this.buf += this.out(node.type + (len ? ' and ' : ''));
|
250 | for (var i = 0; i < len; ++i) {
|
251 | this.buf += this.out(this.visit(node.nodes[i]));
|
252 | if (len - 1 != i) this.buf += this.out(' and ');
|
253 | }
|
254 | };
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 | Compiler.prototype.visitFeature = function(node){
|
261 | if (!node.expr) {
|
262 | return node.name;
|
263 | } else if (node.expr.isEmpty) {
|
264 | return '(' + node.name + ')';
|
265 | } else {
|
266 | return '(' + node.name + ':' + (this.compress ? '' : ' ') + this.visit(node.expr) + ')';
|
267 | }
|
268 | };
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 | Compiler.prototype.visitImport = function(imported){
|
275 | this.buf += this.out('@import ' + this.visit(imported.path) + ';\n', imported);
|
276 | };
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 | Compiler.prototype.visitAtrule = function(atrule){
|
283 | var newline = this.compress ? '' : '\n';
|
284 |
|
285 | this.buf += this.out(this.indent + '@' + atrule.type, atrule);
|
286 |
|
287 | if (atrule.val) this.buf += this.out(' ' + atrule.val.trim());
|
288 |
|
289 | if (atrule.block) {
|
290 | if (atrule.hasOnlyProperties) {
|
291 | this.visit(atrule.block);
|
292 | } else {
|
293 | this.buf += this.out(this.compress ? '{' : ' {\n');
|
294 | ++this.indents;
|
295 | this.visit(atrule.block);
|
296 | --this.indents;
|
297 | this.buf += this.out(this.indent + '}' + newline);
|
298 | }
|
299 | } else {
|
300 | this.buf += this.out(';' + newline);
|
301 | }
|
302 | };
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 | Compiler.prototype.visitSupports = function(node){
|
309 | if (!node.hasOutput) return;
|
310 |
|
311 | this.buf += this.out(this.indent + '@supports ', node);
|
312 | this.isCondition = true;
|
313 | this.buf += this.out(this.visit(node.condition));
|
314 | this.isCondition = false;
|
315 | this.buf += this.out(this.compress ? '{' : ' {\n');
|
316 | ++this.indents;
|
317 | this.visit(node.block);
|
318 | --this.indents;
|
319 | this.buf += this.out(this.indent + '}' + (this.compress ? '' : '\n'));
|
320 | },
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 | Compiler.prototype.visitComment = function(comment){
|
327 | return this.compress
|
328 | ? comment.suppress
|
329 | ? ''
|
330 | : comment.str
|
331 | : comment.str;
|
332 | };
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 | Compiler.prototype.visitFunction = function(fn){
|
339 | return fn.name;
|
340 | };
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 | Compiler.prototype.visitCharset = function(charset){
|
347 | return '@charset ' + this.visit(charset.val) + ';';
|
348 | };
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 | Compiler.prototype.visitNamespace = function(namespace){
|
355 | return '@namespace '
|
356 | + (namespace.prefix ? this.visit(namespace.prefix) + ' ' : '')
|
357 | + this.visit(namespace.val) + ';';
|
358 | };
|
359 |
|
360 |
|
361 |
|
362 |
|
363 |
|
364 | Compiler.prototype.visitLiteral = function(lit){
|
365 | var val = lit.val;
|
366 | if (lit.css) val = val.replace(/^ /gm, '');
|
367 | return val;
|
368 | };
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 | Compiler.prototype.visitBoolean = function(bool){
|
375 | return bool.toString();
|
376 | };
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | Compiler.prototype.visitRGBA = function(rgba){
|
383 | return rgba.toString();
|
384 | };
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | Compiler.prototype.visitHSLA = function(hsla){
|
391 | return hsla.rgba.toString();
|
392 | };
|
393 |
|
394 |
|
395 |
|
396 |
|
397 |
|
398 | Compiler.prototype.visitUnit = function(unit){
|
399 | var type = unit.type || ''
|
400 | , n = unit.val
|
401 | , float = n != (n | 0);
|
402 |
|
403 |
|
404 | if (this.compress) {
|
405 |
|
406 | if ('%' != type && 's' != type && 'ms' != type && 0 == n) return '0';
|
407 |
|
408 | if (float && n < 1 && n > -1) {
|
409 | return n.toString().replace('0.', '.') + type;
|
410 | }
|
411 | }
|
412 |
|
413 | return (float ? parseFloat(n.toFixed(15)) : n).toString() + type;
|
414 | };
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 | Compiler.prototype.visitGroup = function(group){
|
421 | var stack = this.keyframe ? [] : this.stack
|
422 | , comma = this.compress ? ',' : ',\n';
|
423 |
|
424 | stack.push(group.nodes);
|
425 |
|
426 |
|
427 | if (group.block.hasProperties) {
|
428 | var selectors = utils.compileSelectors.call(this, stack)
|
429 | , len = selectors.length;
|
430 |
|
431 | if (len) {
|
432 | if (this.keyframe) comma = this.compress ? ',' : ', ';
|
433 |
|
434 | for (var i = 0; i < len; ++i) {
|
435 | var selector = selectors[i]
|
436 | , last = (i == len - 1);
|
437 |
|
438 |
|
439 | if (this.keyframe) selector = i ? selector.trim() : selector;
|
440 |
|
441 | this.buf += this.out(selector + (last ? '' : comma), group.nodes[i]);
|
442 | }
|
443 | } else {
|
444 | group.block.lacksRenderedSelectors = true;
|
445 | }
|
446 | }
|
447 |
|
448 |
|
449 | this.visit(group.block);
|
450 | stack.pop();
|
451 | };
|
452 |
|
453 |
|
454 |
|
455 |
|
456 |
|
457 | Compiler.prototype.visitIdent = function(ident){
|
458 | return ident.name;
|
459 | };
|
460 |
|
461 |
|
462 |
|
463 |
|
464 |
|
465 | Compiler.prototype.visitString = function(string){
|
466 | return this.isURL
|
467 | ? string.val
|
468 | : string.toString();
|
469 | };
|
470 |
|
471 |
|
472 |
|
473 |
|
474 |
|
475 | Compiler.prototype.visitNull = function(node){
|
476 | return '';
|
477 | };
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 | Compiler.prototype.visitCall = function(call){
|
484 | this.isURL = 'url' == call.name;
|
485 | var args = call.args.nodes.map(function(arg){
|
486 | return this.visit(arg);
|
487 | }, this).join(this.compress ? ',' : ', ');
|
488 | if (this.isURL) args = '"' + args + '"';
|
489 | this.isURL = false;
|
490 | return call.name + '(' + args + ')';
|
491 | };
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 | Compiler.prototype.visitExpression = function(expr){
|
498 | var buf = []
|
499 | , self = this
|
500 | , len = expr.nodes.length
|
501 | , nodes = expr.nodes.map(function(node){ return self.visit(node); });
|
502 |
|
503 | nodes.forEach(function(node, i){
|
504 | var last = i == len - 1;
|
505 | buf.push(node);
|
506 | if ('/' == nodes[i + 1] || '/' == node) return;
|
507 | if (last) return;
|
508 |
|
509 | var space = self.isURL || (self.isCondition
|
510 | && (')' == nodes[i + 1] || '(' == node))
|
511 | ? '' : ' ';
|
512 |
|
513 | buf.push(expr.isList
|
514 | ? (self.compress ? ',' : ', ')
|
515 | : space);
|
516 | });
|
517 |
|
518 | return buf.join('');
|
519 | };
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 | Compiler.prototype.visitArguments = Compiler.prototype.visitExpression;
|
526 |
|
527 |
|
528 |
|
529 |
|
530 |
|
531 | Compiler.prototype.visitProperty = function(prop){
|
532 | var val = this.visit(prop.expr).trim()
|
533 | , name = (prop.name || prop.segments.join(''))
|
534 | , arr = [];
|
535 | arr.push(
|
536 | this.out(this.indent),
|
537 | this.out(name + (this.compress ? ':' : ': '), prop),
|
538 | this.out(val, prop.expr),
|
539 | this.out(this.compress ? (this.last ? '' : ';') : ';')
|
540 | );
|
541 | return arr.join('');
|
542 | };
|
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 | Compiler.prototype.debugInfo = function(node){
|
549 |
|
550 | var path = node.filename == 'stdin' ? 'stdin' : fs.realpathSync(node.filename)
|
551 | , line = (node.nodes && node.nodes.length ? node.nodes[0].lineno : node.lineno) || 1;
|
552 |
|
553 | if (this.linenos){
|
554 | this.buf += '\n/* ' + 'line ' + line + ' : ' + path + ' */\n';
|
555 | }
|
556 |
|
557 | if (this.firebug){
|
558 |
|
559 | path = 'file\\\:\\\/\\\/' + path.replace(/([.:/\\])/g, function(m) {
|
560 | return '\\' + (m === '\\' ? '\/' : m)
|
561 | });
|
562 | line = '\\00003' + line;
|
563 | this.buf += '\n@media -stylus-debug-info'
|
564 | + '{filename{font-family:' + path
|
565 | + '}line{font-family:' + line + '}}\n';
|
566 | }
|
567 | }
|