grunt
Version:
A task-based command line build tool for JavaScript projects.
260 lines (229 loc) • 7.76 kB
JavaScript
/*
* grunt
* https://github.com/cowboy/grunt
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
var nodeutil = require('util');
// Temporarily suppress output.
var suppressOutput;
// Allow external muting of output.
exports.muted = false;
// True once anything has actually been logged.
var hasLogged;
// Apparently writing to stdout in node.js is non-blocking. But stderr blocks.
// So we'll use that instead. WTF. https://github.com/joyent/node/issues/1669
process.stdout = process.stderr;
// Write output.
exports.write = function(msg) {
// Actually write output.
if (!exports.muted && !suppressOutput) {
hasLogged = true;
process.stdout.write(msg || '');
}
// Chainable!
return this;
};
// Write a line of output.
exports.writeln = function(msg) {
// Actually write output.
this.write((msg || '') + '\n');
// Chainable!
return this;
};
// Stuff.
exports.error = function(msg) {
if (msg) {
fail.errorcount++;
return this.writeln('>> '.red + util.chomp(msg).replace(/\n/g, '\n>> '.red));
} else {
return this.writeln('ERROR'.red);
}
};
exports.ok = function(msg) {
if (msg) {
return this.writeln('>> '.green + util.chomp(msg).replace(/\n/g, '\n>> '.green));
} else {
return this.writeln('OK'.green);
}
};
exports.success = function(msg) { return this.writeln(msg.green); };
exports.fail = function(msg) { return this.writeln(msg.red); };
exports.header = function(msg) {
// Skip line before header, but not if header is the very first line output.
if (hasLogged) { this.writeln(); }
return this.writeln(msg.underline);
};
exports.subhead = function(msg) {
// Skip line before subhead, but not if subhead is the very first line output.
if (hasLogged) { this.writeln(); }
return this.writeln(msg.bold);
};
// For debugging.
exports.debug = function() {
if (option('debug')) {
this.writeln(('[D] ' + nodeutil.format.apply(this, arguments)).magenta);
}
return this;
};
// Write a line of a table.
exports.writetableln = function(widths, texts) {
return this.writeln(this.table(widths, texts));
};
// Wrap a long line of text to 80 columns.
exports.writelns = function(msg) {
return this.writeln(exports.wraptext(80, msg));
};
// Display flags in verbose mode.
exports.writeflags = function(obj, prefix) {
var wordlist;
if (obj instanceof Array) {
wordlist = exports.wordlist(obj);
} else if (typeof obj === 'object' && obj) {
wordlist = exports.wordlist(Object.keys(obj).map(function(key) {
var val = obj[key];
return key + (val === true ? '' : '=' + JSON.stringify(val));
}));
}
return this.writeln((prefix || 'Flags') + ': ' + (wordlist || '(none)'.cyan));
};
// Create explicit "verbose" and "notverbose" functions, one for each already-
// defined log function, that do the same thing but ONLY if -v or --verbose is
// specified (or not specified).
exports.verbose = {};
exports.notverbose = {};
// Iterate over all exported functions.
Object.keys(exports).filter(function(key) {
return typeof exports[key] === 'function';
}).forEach(function(key) {
// Like any other log function, but suppresses output if the "verbose" option
// IS NOT set.
exports.verbose[key] = function() {
suppressOutput = !option('verbose');
exports[key].apply(this, arguments);
suppressOutput = false;
return this;
};
// Like any other log function, but suppresses output if the "verbose" option
// IS set.
exports.notverbose[key] = function() {
suppressOutput = option('verbose');
exports[key].apply(this, arguments);
suppressOutput = false;
return this;
};
});
// A way to switch between verbose and notverbose modes. For example, this will
// write 'foo' if verbose logging is enabled, otherwise write 'bar':
// verbose.write('foo').or.write('bar');
exports.verbose.or = exports.notverbose;
exports.notverbose.or = exports.verbose;
// Static methods.
// Pretty-format a word list.
exports.wordlist = function(arr, separator) {
return arr.map(function(item) {
return item.cyan;
}).join(separator || ', ');
};
// Return a string, uncolored (suitable for testing .length, etc).
exports.uncolor = function(str) {
return str.replace(/\x1B\[\d+m/g, '');
};
// Word-wrap text to a given width, permitting ANSI color codes.
exports.wraptext = function(width, text) {
// notes to self:
// grab 1st character or ansi code from string
// if ansi code, add to array and save for later, strip from front of string
// if character, add to array and increment counter, strip from front of string
// if width + 1 is reached and current character isn't space:
// slice off everything after last space in array and prepend it to string
// etc
// This result array will be joined on \n.
var result = [];
var matches, color, tmp;
var captured = [];
var charlen = 0;
while (matches = text.match(/(?:(\x1B\[\d+m)|\n|(.))([\s\S]*)/)) {
// Updated text to be everything not matched.
text = matches[3];
// Matched a color code?
if (matches[1]) {
// Save last captured color code for later use.
color = matches[1];
// Capture color code.
captured.push(matches[1]);
continue;
// Matched a non-newline character?
} else if (matches[2]) {
// If this is the first character and a previous color code was set, push
// that onto the captured array first.
if (charlen === 0 && color) { captured.push(color); }
// Push the matched character.
captured.push(matches[2]);
// Increment the current charlen.
charlen++;
// If not yet at the width limit or a space was matched, continue.
if (charlen <= width || matches[2] === ' ') { continue; }
// The current charlen exceeds the width and a space wasn't matched.
// "Roll everything back" until the last space character.
tmp = captured.lastIndexOf(' ');
text = captured.slice(tmp === -1 ? tmp : tmp + 1).join('') + text;
captured = captured.slice(0, tmp);
}
// The limit has been reached. Push captured string onto result array.
result.push(captured.join(''));
// Reset captured array and charlen.
captured = [];
charlen = 0;
}
result.push(captured.join(''));
return result.join('\n');
};
// todo: write unit tests
//
// function logs(text) {
// [4, 6, 10, 15, 20, 25, 30, 40].forEach(function(n) {
// log(n, text);
// });
// }
//
// function log(n, text) {
// console.log(Array(n + 1).join('-'));
// console.log(wrap(n, text));
// }
//
// var text = 'this is '.red + 'a simple'.yellow.inverse + ' test of'.green + ' ' + 'some wrapped'.blue + ' text over '.inverse.magenta + 'many lines'.red;
// logs(text);
//
// var text = 'foolish '.red.inverse + 'monkeys'.yellow + ' eating'.green + ' ' + 'delicious'.inverse.blue + ' bananas '.magenta + 'forever'.red;
// logs(text);
//
// var text = 'foolish monkeys eating delicious bananas forever'.rainbow;
// logs(text);
// Format output into columns, wrapping words as-necessary.
exports.table = function(widths, texts) {
var rows = [];
widths.forEach(function(width, i) {
var lines = exports.wraptext(width, texts[i]).split('\n');
lines.forEach(function(line, j) {
var row = rows[j];
if (!row) { row = rows[j] = []; }
row[i] = line;
});
});
var lines = [];
rows.forEach(function(row) {
var txt = '';
var column;
for (var i = 0; i < row.length; i++) {
column = row[i] || '';
txt += column;
var diff = widths[i] - exports.uncolor(column).length;
if (diff > 0) { txt += util.repeat(diff, ' '); }
}
lines.push(txt);
});
return lines.join('\n');
};