123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- /*!
- * Stylus - Compiler
- * Copyright (c) Automattic <developer.wordpress.com>
- * MIT Licensed
- */
- /**
- * Module dependencies.
- */
- var Visitor = require('./')
- , utils = require('../utils')
- , fs = require('fs');
- /**
- * Initialize a new `Compiler` with the given `root` Node
- * and the following `options`.
- *
- * Options:
- *
- * - `compress` Compress the CSS output (default: false)
- *
- * @param {Node} root
- * @api public
- */
- var Compiler = module.exports = function Compiler(root, options) {
- options = options || {};
- this.compress = options.compress;
- this.firebug = options.firebug;
- this.linenos = options.linenos;
- this.spaces = options['indent spaces'] || 2;
- this.indents = 1;
- Visitor.call(this, root);
- this.stack = [];
- };
- /**
- * Inherit from `Visitor.prototype`.
- */
- Compiler.prototype.__proto__ = Visitor.prototype;
- /**
- * Compile to css, and return a string of CSS.
- *
- * @return {String}
- * @api private
- */
- Compiler.prototype.compile = function(){
- return this.visit(this.root);
- };
- /**
- * Output `str`
- *
- * @param {String} str
- * @param {Node} node
- * @return {String}
- * @api private
- */
- Compiler.prototype.out = function(str, node){
- return str;
- };
- /**
- * Return indentation string.
- *
- * @return {String}
- * @api private
- */
- Compiler.prototype.__defineGetter__('indent', function(){
- if (this.compress) return '';
- return new Array(this.indents).join(Array(this.spaces + 1).join(' '));
- });
- /**
- * Check if given `node` needs brackets.
- *
- * @param {Node} node
- * @return {Boolean}
- * @api private
- */
- Compiler.prototype.needBrackets = function(node){
- return 1 == this.indents
- || 'atrule' != node.nodeName
- || node.hasOnlyProperties;
- };
- /**
- * Visit Root.
- */
- Compiler.prototype.visitRoot = function(block){
- this.buf = '';
- for (var i = 0, len = block.nodes.length; i < len; ++i) {
- var node = block.nodes[i];
- if (this.linenos || this.firebug) this.debugInfo(node);
- var ret = this.visit(node);
- if (ret) this.buf += this.out(ret + '\n', node);
- }
- return this.buf;
- };
- /**
- * Visit Block.
- */
- Compiler.prototype.visitBlock = function(block){
- var node
- , separator = this.compress ? '' : '\n'
- , needBrackets
- , lastPropertyIndex;
- if (block.hasProperties && !block.lacksRenderedSelectors) {
- needBrackets = this.needBrackets(block.node);
- if (this.compress) {
- for (var i = block.nodes.length - 1; i >= 0; --i) {
- if (block.nodes[i].nodeName === 'property') {
- lastPropertyIndex = i;
- break;
- }
- }
- }
- if (needBrackets) {
- this.buf += this.out(this.compress ? '{' : ' {\n');
- ++this.indents;
- }
- for (var i = 0, len = block.nodes.length; i < len; ++i) {
- this.last = lastPropertyIndex === i;
- node = block.nodes[i];
- switch (node.nodeName) {
- case 'null':
- case 'expression':
- case 'function':
- case 'group':
- case 'block':
- case 'unit':
- case 'media':
- case 'keyframes':
- case 'atrule':
- case 'supports':
- continue;
- // inline comments
- case !this.compress && node.inline && 'comment':
- this.buf = this.buf.slice(0, -1);
- this.buf += this.out(' ' + this.visit(node) + '\n', node);
- break;
- case 'property':
- var ret = this.visit(node) + separator;
- this.buf += this.compress ? ret : this.out(ret, node);
- break;
- default:
- this.buf += this.out(this.visit(node) + separator, node);
- }
- }
- if (needBrackets) {
- --this.indents;
- this.buf += this.out(this.indent + '}' + separator);
- }
- }
- // Nesting
- for (var i = 0, len = block.nodes.length; i < len; ++i) {
- node = block.nodes[i];
- switch (node.nodeName) {
- case 'group':
- case 'block':
- case 'keyframes':
- if (this.linenos || this.firebug) this.debugInfo(node);
- this.visit(node);
- break;
- case 'media':
- case 'import':
- case 'atrule':
- case 'supports':
- this.visit(node);
- break;
- case 'comment':
- // only show unsuppressed comments
- if (!node.suppress) {
- this.buf += this.out(this.indent + this.visit(node) + '\n', node);
- }
- break;
- case 'charset':
- case 'literal':
- case 'namespace':
- this.buf += this.out(this.visit(node) + '\n', node);
- break;
- }
- }
- };
- /**
- * Visit Keyframes.
- */
- Compiler.prototype.visitKeyframes = function(node){
- if (!node.frames) return;
- var prefix = 'official' == node.prefix
- ? ''
- : '-' + node.prefix + '-';
- this.buf += this.out('@' + prefix + 'keyframes '
- + this.visit(node.val)
- + (this.compress ? '{' : ' {\n'), node);
- this.keyframe = true;
- ++this.indents;
- this.visit(node.block);
- --this.indents;
- this.keyframe = false;
- this.buf += this.out('}' + (this.compress ? '' : '\n'));
- };
- /**
- * Visit Media.
- */
- Compiler.prototype.visitMedia = function(media){
- var val = media.val;
- if (!media.hasOutput || !val.nodes.length) return;
- this.buf += this.out('@media ', media);
- this.visit(val);
- this.buf += this.out(this.compress ? '{' : ' {\n');
- ++this.indents;
- this.visit(media.block);
- --this.indents;
- this.buf += this.out('}' + (this.compress ? '' : '\n'));
- };
- /**
- * Visit QueryList.
- */
- Compiler.prototype.visitQueryList = function(queries){
- for (var i = 0, len = queries.nodes.length; i < len; ++i) {
- this.visit(queries.nodes[i]);
- if (len - 1 != i) this.buf += this.out(',' + (this.compress ? '' : ' '));
- }
- };
- /**
- * Visit Query.
- */
- Compiler.prototype.visitQuery = function(node){
- var len = node.nodes.length;
- if (node.predicate) this.buf += this.out(node.predicate + ' ');
- if (node.type) this.buf += this.out(node.type + (len ? ' and ' : ''));
- for (var i = 0; i < len; ++i) {
- this.buf += this.out(this.visit(node.nodes[i]));
- if (len - 1 != i) this.buf += this.out(' and ');
- }
- };
- /**
- * Visit Feature.
- */
- Compiler.prototype.visitFeature = function(node){
- if (!node.expr) {
- return node.name;
- } else if (node.expr.isEmpty) {
- return '(' + node.name + ')';
- } else {
- return '(' + node.name + ':' + (this.compress ? '' : ' ') + this.visit(node.expr) + ')';
- }
- };
- /**
- * Visit Import.
- */
- Compiler.prototype.visitImport = function(imported){
- this.buf += this.out('@import ' + this.visit(imported.path) + ';\n', imported);
- };
- /**
- * Visit Atrule.
- */
- Compiler.prototype.visitAtrule = function(atrule){
- var newline = this.compress ? '' : '\n';
- this.buf += this.out(this.indent + '@' + atrule.type, atrule);
- if (atrule.val) this.buf += this.out(' ' + atrule.val.trim());
- if (atrule.block) {
- if (atrule.block.isEmpty) {
- this.buf += this.out((this.compress ? '' : ' ') + '{}' + newline);
- } else if (atrule.hasOnlyProperties) {
- this.visit(atrule.block);
- } else {
- this.buf += this.out(this.compress ? '{' : ' {\n');
- ++this.indents;
- this.visit(atrule.block);
- --this.indents;
- this.buf += this.out(this.indent + '}' + newline);
- }
- } else {
- this.buf += this.out(';' + newline);
- }
- };
- /**
- * Visit Supports.
- */
- Compiler.prototype.visitSupports = function(node){
- if (!node.hasOutput) return;
- this.buf += this.out(this.indent + '@supports ', node);
- this.isCondition = true;
- this.buf += this.out(this.visit(node.condition));
- this.isCondition = false;
- this.buf += this.out(this.compress ? '{' : ' {\n');
- ++this.indents;
- this.visit(node.block);
- --this.indents;
- this.buf += this.out(this.indent + '}' + (this.compress ? '' : '\n'));
- },
- /**
- * Visit Comment.
- */
- Compiler.prototype.visitComment = function(comment){
- return this.compress
- ? comment.suppress
- ? ''
- : comment.str
- : comment.str;
- };
- /**
- * Visit Function.
- */
- Compiler.prototype.visitFunction = function(fn){
- return fn.name;
- };
- /**
- * Visit Charset.
- */
- Compiler.prototype.visitCharset = function(charset){
- return '@charset ' + this.visit(charset.val) + ';';
- };
- /**
- * Visit Namespace.
- */
- Compiler.prototype.visitNamespace = function(namespace){
- return '@namespace '
- + (namespace.prefix ? this.visit(namespace.prefix) + ' ' : '')
- + this.visit(namespace.val) + ';';
- };
- /**
- * Visit Literal.
- */
- Compiler.prototype.visitLiteral = function(lit){
- var val = lit.val;
- if (lit.css) val = val.replace(/^ /gm, '');
- return val;
- };
- /**
- * Visit Boolean.
- */
- Compiler.prototype.visitBoolean = function(bool){
- return bool.toString();
- };
- /**
- * Visit RGBA.
- */
- Compiler.prototype.visitRGBA = function(rgba){
- return rgba.toString();
- };
- /**
- * Visit HSLA.
- */
- Compiler.prototype.visitHSLA = function(hsla){
- return hsla.rgba.toString();
- };
- /**
- * Visit Unit.
- */
- Compiler.prototype.visitUnit = function(unit){
- var type = unit.type || ''
- , n = unit.val
- , float = n != (n | 0);
- // Compress
- if (this.compress) {
- // Always return '0' unless the unit is a percentage, time, degree or fraction
- if (!(['%', 's', 'ms', 'deg', 'fr'].includes(type)) && 0 == n) return '0';
- // Omit leading '0' on floats
- if (float && n < 1 && n > -1) {
- return n.toString().replace('0.', '.') + type;
- }
- }
- return (float ? parseFloat(n.toFixed(15)) : n).toString() + type;
- };
- /**
- * Visit Group.
- */
- Compiler.prototype.visitGroup = function(group){
- var stack = this.keyframe ? [] : this.stack
- , comma = this.compress ? ',' : ',\n';
- stack.push(group.nodes);
- // selectors
- if (group.block.hasProperties) {
- var selectors = utils.compileSelectors.call(this, stack)
- , len = selectors.length;
- if (len) {
- if (this.keyframe) comma = this.compress ? ',' : ', ';
- for (var i = 0; i < len; ++i) {
- var selector = selectors[i]
- , last = (i == len - 1);
- // keyframe blocks (10%, 20% { ... })
- if (this.keyframe) selector = i ? selector.trim() : selector;
- this.buf += this.out(selector + (last ? '' : comma), group.nodes[i]);
- }
- } else {
- group.block.lacksRenderedSelectors = true;
- }
- }
- // output block
- this.visit(group.block);
- stack.pop();
- };
- /**
- * Visit Ident.
- */
- Compiler.prototype.visitIdent = function(ident){
- return ident.name;
- };
- /**
- * Visit String.
- */
- Compiler.prototype.visitString = function(string){
- return this.isURL
- ? string.val
- : string.toString();
- };
- /**
- * Visit Null.
- */
- Compiler.prototype.visitNull = function(node){
- return '';
- };
- /**
- * Visit Call.
- */
- Compiler.prototype.visitCall = function(call){
- this.isURL = 'url' == call.name;
- var args = call.args.nodes.map(function(arg){
- return this.visit(arg);
- }, this).join(this.compress ? ',' : ', ');
- if (this.isURL) args = '"' + args + '"';
- this.isURL = false;
- return call.name + '(' + args + ')';
- };
- /**
- * Visit Expression.
- */
- Compiler.prototype.visitExpression = function(expr){
- var buf = []
- , self = this
- , len = expr.nodes.length
- , nodes = expr.nodes.map(function(node){ return self.visit(node); });
- nodes.forEach(function(node, i){
- var last = i == len - 1;
- buf.push(node);
- if ('/' == nodes[i + 1] || '/' == node) return;
- if (last) return;
- var space = self.isURL || (self.isCondition
- && (')' == nodes[i + 1] || '(' == node))
- ? '' : ' ';
- buf.push(expr.isList
- ? (self.compress ? ',' : ', ')
- : space);
- });
- return buf.join('');
- };
- /**
- * Visit Arguments.
- */
- Compiler.prototype.visitArguments = Compiler.prototype.visitExpression;
- /**
- * Visit Property.
- */
- Compiler.prototype.visitProperty = function(prop){
- var val = this.visit(prop.expr).trim()
- , name = (prop.name || prop.segments.join(''))
- , arr = [];
- if (name === '@apply') {
- arr.push(
- this.out(this.indent),
- this.out(name + ' ', prop),
- this.out(val, prop.expr),
- this.out(this.compress ? (this.last ? '' : ';') : ';')
- );
- return arr.join('');
- }
- arr.push(
- this.out(this.indent),
- this.out(name + (this.compress ? ':' : ': '), prop),
- this.out(val, prop.expr),
- this.out(this.compress ? (this.last ? '' : ';') : ';')
- );
- return arr.join('');
- };
- /**
- * Debug info.
- */
- Compiler.prototype.debugInfo = function(node){
- var path = node.filename == 'stdin' ? 'stdin' : fs.realpathSync(node.filename)
- , line = (node.nodes && node.nodes.length ? node.nodes[0].lineno : node.lineno) || 1;
- if (this.linenos){
- this.buf += '\n/* ' + 'line ' + line + ' : ' + path + ' */\n';
- }
- if (this.firebug){
- // debug info for firebug, the crazy formatting is needed
- path = 'file\\\:\\\/\\\/' + path.replace(/([.:/\\])/g, function(m) {
- return '\\' + (m === '\\' ? '\/' : m)
- });
- line = '\\00003' + line;
- this.buf += '\n@media -stylus-debug-info'
- + '{filename{font-family:' + path
- + '}line{font-family:' + line + '}}\n';
- }
- }
|