/*1* cliff.js: CLI output formatting tools: "Your CLI Formatting Friend".2*3* (C) 2010, Nodejitsu Inc.4*5*/67var colors = require('colors'),8eyes = require('eyes'),9winston = require('winston');1011var cliff = exports,12logger;1314cliff.__defineGetter__('logger', function () {15delete cliff.logger;16return cliff.logger = logger;17});1819cliff.__defineSetter__('logger', function (val) {20logger = val;2122//23// Setup winston to use the `cli` formats24//25if (logger.cli) {26logger.cli();27}28});2930//31// Set the default logger for cliff.32//33cliff.logger = new winston.Logger({34transports: [new winston.transports.Console()]35});3637//38// Expose a default `eyes` inspector.39//40cliff.inspector = eyes.inspector;41cliff.inspect = eyes.inspector({ stream: null,42styles: { // Styles applied to stdout43all: null, // Overall style applied to everything44label: 'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]`45other: 'inverted', // Objects which don't have a literal representation, such as functions46key: 'grey', // The keys in object literals, like 'a' in `{a: 1}`47special: 'grey', // null, undefined...48number: 'blue', // 0, 1, 2...49bool: 'magenta', // true false50regexp: 'green' // /\d+/51}52});5354//55// ### function extractFrom (obj, properties)56// #### @obj {Object} Object to extract properties from.57// #### @properties {Array} List of properties to output.58// Creates an array representing the values for `properties` in `obj`.59//60cliff.extractFrom = function (obj, properties) {61return properties.map(function (p) {62return obj[p];63});64};6566//67// ### function columnMajor (rows)68// #### @rows {ArrayxArray} Row-major Matrix to transpose69// Transposes the row-major Matrix, represented as an array of rows,70// into column major form (i.e. an array of columns).71//72cliff.columnMajor = function (rows) {73var columns = [];7475rows.forEach(function (row) {76for (var i = 0; i < row.length; i += 1) {77if (!columns[i]) {78columns[i] = [];79}8081columns[i].push(row[i]);82}83});8485return columns;86};8788//89// ### arrayLengths (arrs)90// #### @arrs {ArrayxArray} Arrays to calculate lengths for91// Creates an array with values each representing the length92// of an array in the set provided.93//94cliff.arrayLengths = function (arrs) {95var i, lengths = [];96for (i = 0; i < arrs.length; i += 1) {97lengths.push(longestElement(arrs[i].map(cliff.stringifyLiteral)));98}99return lengths;100};101102//103// ### function stringifyRows (rows, colors)104// #### @rows {ArrayxArray} Matrix of properties to output in row major form105// #### @colors {Array} Set of colors to use for the headers106// Outputs the specified `rows` as fixed-width columns, adding107// colorized headers if `colors` are supplied.108//109cliff.stringifyRows = function (rows, colors) {110var lengths, columns, output = [], headers;111112columns = cliff.columnMajor(rows);113lengths = cliff.arrayLengths(columns);114115function stringifyRow(row, colorize) {116var rowtext = '', padding, item, i, length;117for (i = 0; i < row.length; i += 1) {118item = cliff.stringifyLiteral(row[i]);119item = colorize ? item[colors[i]] : item;120length = realLength(item);121padding = length < lengths[i] ? lengths[i] - length + 2 : 2;122rowtext += item + new Array(padding).join(' ');123}124125output.push(rowtext);126}127128// If we were passed colors, then assume the first row129// is the headers for the rows130if (colors) {131headers = rows.splice(0, 1)[0];132stringifyRow(headers, true);133}134135rows.forEach(function (row) {136stringifyRow(row, false);137});138139return output.join('\n');140};141142//143// ### function rowifyObjects (objs, properties, colors)144// #### @objs {Array} List of objects to create output for145// #### @properties {Array} List of properties to output146// #### @colors {Array} Set of colors to use for the headers147// Extracts the lists of `properties` from the specified `objs`148// and formats them according to `cliff.stringifyRows`.149//150cliff.stringifyObjectRows = cliff.rowifyObjects = function (objs, properties, colors) {151var rows = [properties].concat(objs.map(function (obj) {152return cliff.extractFrom(obj, properties);153}));154155return cliff.stringifyRows(rows, colors);156};157158//159// ### function putRows (level, rows, colors)160// #### @level {String} Log-level to use161// #### @rows {Array} Array of rows to log at the specified level162// #### @colors {Array} Set of colors to use for the specified row(s) headers.163// Logs the stringified table result from `rows` at the appropriate `level` using164// `cliff.logger`. If `colors` are supplied then use those when stringifying `rows`.165//166cliff.putRows = function (level, rows, colors) {167cliff.stringifyRows(rows, colors).split('\n').forEach(function (str) {168logger.log(level, str);169});170};171172//173// ### function putObjectRows (level, rows, colors)174// #### @level {String} Log-level to use175// #### @objs {Array} List of objects to create output for176// #### @properties {Array} List of properties to output177// #### @colors {Array} Set of colors to use for the headers178// Logs the stringified table result from `objs` at the appropriate `level` using179// `cliff.logger`. If `colors` are supplied then use those when stringifying `objs`.180//181cliff.putObjectRows = function (level, objs, properties, colors) {182cliff.rowifyObjects(objs, properties, colors).split('\n').forEach(function (str) {183logger.log(level, str);184});185};186187//188// ### function putObject (obj, [rewriters, padding])189// #### @obj {Object} Object to log to the command line190// #### @rewriters {Object} **Optional** Set of methods to rewrite certain object keys191// #### @padding {Number} **Optional** Length of padding to put around the output.192// Inspects the object `obj` on the command line rewriting any properties which match193// keys in `rewriters` if any. Adds additional `padding` if supplied.194//195cliff.putObject = function (/*obj, [rewriters, padding] */) {196var args = Array.prototype.slice.call(arguments),197obj = args.shift(),198padding = typeof args[args.length - 1] === 'number' && args.pop(),199rewriters = typeof args[args.length -1] === 'object' && args.pop(),200keys = Object.keys(obj).sort(),201sorted = {},202matchers = {},203inspected;204205padding = padding || 0;206rewriters = rewriters || {};207208function pad () {209for (var i = 0; i < padding / 2; i++) {210logger.data('');211}212}213214keys.forEach(function (key) {215sorted[key] = obj[key];216});217218inspected = cliff.inspect(sorted);219220Object.keys(rewriters).forEach(function (key) {221matchers[key] = new RegExp(key);222});223224pad();225inspected.split('\n').forEach(function (line) {226Object.keys(rewriters).forEach(function (key) {227if (matchers[key].test(line)) {228line = rewriters[key](line);229}230});231logger.data(line);232});233pad();234};235236cliff.stringifyLiteral = function stringifyLiteral (literal) {237switch (cliff.typeOf(literal)) {238case 'number' : return literal + '';239case 'null' : return 'null';240case 'undefined': return 'undefined';241case 'boolean' : return literal + '';242default : return literal;243}244};245246cliff.typeOf = function typeOf(value) {247var s = typeof(value),248types = [Object, Array, String, RegExp, Number, Function, Boolean, Date];249250if (s === 'object' || s === 'function') {251if (value) {252types.forEach(function (t) {253if (value instanceof t) {254s = t.name.toLowerCase();255}256});257} else {258s = 'null';259}260}261262return s;263};264265function realLength(str) {266return ("" + str).replace(/\u001b\[\d+m/g,'').length;267}268269function longestElement(a) {270var l = 0;271for (var i = 0; i < a.length; i++) {272var new_l = realLength(a[i]);273if (l < new_l) {274l = new_l;275}276}277278return l;279}280281282