4 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
12 var Visitor = require('./')
13 , nodes = require('../nodes');
16 * Initialize a new `Compiler` with the given `root` Node
17 * and the following `options`.
21 * - `compress` Compress the css output, defaults to false
27 var Compiler = module.exports = function Compiler(root, options) {
28 options = options || {};
29 this.compress = options.compress;
31 Visitor.call(this, root);
36 * Inherit from `Visitor.prototype`.
39 Compiler.prototype.__proto__ = Visitor.prototype;
42 * Compile to css, and callback `fn(err, css)`.
44 * @param {Function} fn
48 Compiler.prototype.compile = function(fn){
50 this.css = this.visit(this.root);
55 * Return indentation string.
61 Compiler.prototype.__defineGetter__('indent', function(){
64 : new Array(this.indents).join(' ');
71 Compiler.prototype.visitRoot = function(block){
73 for (var i = 0, len = block.nodes.length; i < len; ++i) {
74 var node = block.nodes[i];
75 if (node instanceof nodes.Null
76 || node instanceof nodes.Expression
77 || node instanceof nodes.Function
78 || node instanceof nodes.Unit) continue;
79 var ret = this.visit(node);
80 if (ret) this.buf += ret + '\n';
89 Compiler.prototype.visitBlock = function(block){
90 if (block.hasProperties) {
91 var arr = [this.compress ? '{' : ' {'];
93 for (var i = 0, len = block.nodes.length; i < len; ++i) {
94 this.last = len - 1 == i;
95 var node = block.nodes[i];
96 if (node instanceof nodes.Null
97 || node instanceof nodes.Expression
98 || node instanceof nodes.Function
99 || node instanceof nodes.Group
100 || node instanceof nodes.Unit) continue;
101 arr.push(this.visit(node));
104 arr.push(this.indent + '}');
105 this.buf += arr.join(this.compress ? '' : '\n');
110 for (var i = 0, len = block.nodes.length; i < len; ++i) {
111 this.visit(block.nodes[i]);
119 Compiler.prototype.visitKeyframes = function(node){
120 this.buf += '@-webkit-keyframes '
121 + this.visit(node.name)
122 + (this.compress ? '{' : ' {');
124 node.frames.forEach(function(frame){
125 if (!this.compress) this.buf += '\n ';
126 this.buf += this.visit(frame.pos);
127 this.visit(frame.block);
130 this.buf += '}' + (this.compress ? '' : '\n');
137 Compiler.prototype.visitMedia = function(media){
138 this.buf += '@media ' + media.val;
139 this.buf += this.compress ? '{' : ' {\n';
141 this.visit(media.block);
143 this.buf += '}' + (this.compress ? '' : '\n');
150 Compiler.prototype.visitPage = function(page){
151 this.buf += this.indent + '@page';
152 this.buf += page.selector ? ' ' + page.selector : '';
153 this.visit(page.block);
160 Compiler.prototype.visitFunction = function(fn){
168 Compiler.prototype.visitVariable = function(variable){
176 Compiler.prototype.visitCharset = function(charset){
177 return '@charset ' + this.visit(charset.val);
184 Compiler.prototype.visitLiteral = function(lit){
185 return lit.val.trim().replace(/^ /gm, '');
192 Compiler.prototype.visitBoolean = function(bool){
193 return bool.toString();
200 Compiler.prototype.visitRGBA = function(rgba){
201 return rgba.toString();
208 Compiler.prototype.visitHSLA = function(hsla){
209 return hsla.rgba.toString();
216 Compiler.prototype.visitUnit = function(unit){
217 var type = unit.type || ''
219 , float = n != (n | 0);
222 if ('px' == type) n = n.toFixed(0);
225 // Zero is always '0'
226 if (0 == n) return '0';
227 // Omit leading '0' on floats
228 if (float && n < 1 && n > -1) {
229 return n.toString().replace('0.', '.') + type;
233 return n.toString() + type;
240 Compiler.prototype.visitGroup = function(group){
243 , prev = tree[tree.length - 1]
246 // Construct an array of arrays
247 // representing the selector hierarchy
248 group.nodes.forEach(function(node){
249 curr.push(node.parent
256 // Reverse recurse the
257 // hierarchy array to build
258 // up the selector permutations.
259 // When we reach root, we have our
260 // selector string built
263 function join(arr, i) {
265 arr[i].forEach(function(str){
271 arr[0].forEach(function(selector){
274 for (var i = 0, len = buf.length; i < len; ++i) {
275 if (~buf[i].indexOf('&')) {
276 str = buf[i].replace('&', str);
282 selectors.push(self.indent + str);
288 if (group.block.hasProperties) {
289 join(tree, tree.length - 1);
290 this.buf += selectors.join(this.compress ? ',' : ',\n');
294 this.visit(group.block);
302 Compiler.prototype.visitIdent = function(ident){
310 Compiler.prototype.visitString = function(string){
320 Compiler.prototype.visitNull = function(node){
328 Compiler.prototype.visitCall = function(call){
329 this.isURL = 'url' == call.name;
330 var args = call.args.nodes.map(function(arg){
331 return this.visit(arg);
332 }, this).join(this.compress ? ',' : ', ');
333 if (this.isURL) args = '"' + args + '"';
335 return call.name + '(' + args + ')';
342 Compiler.prototype.visitImport = function(import){
343 return '@import ' + this.visit(import.path) + ';';
350 Compiler.prototype.visitExpression = function(expr){
351 return expr.nodes.map(function(node){
352 return this.visit(node);
353 }, this).join(expr.isList
354 ? (this.compress ? ',' : ', ')
355 : (this.isURL ? '' : ' '));
362 Compiler.prototype.visitProperty = function(prop){
364 , val = this.visit(prop.expr);
365 return this.indent + (prop.name || prop.segments.join(''))
366 + (this.compress ? ':' + val : ': ' + val)
368 ? (this.last ? '' : ';')