/** * Module dependencies. */ var colors = require('colors/safe') , utils = require('./utils') , repeat = utils.repeat , truncate = utils.truncate , pad = utils.pad; /** * Table constructor * * @param {Object} options * @api public */ function Table (options){ this.options = utils.options({ chars: { 'top': '─' , 'top-mid': '┬' , 'top-left': '┌' , 'top-right': '┐' , 'bottom': '─' , 'bottom-mid': '┴' , 'bottom-left': '└' , 'bottom-right': '┘' , 'left': '│' , 'left-mid': '├' , 'mid': '─' , 'mid-mid': '┼' , 'right': '│' , 'right-mid': '┤' , 'middle': '│' } , truncate: '…' , colWidths: [] , colAligns: [] , style: { 'padding-left': 1 , 'padding-right': 1 , head: ['red'] , border: ['grey'] , compact : false } , head: [] }, options); if (options && options.rows) { for (var i = 0; i < options.rows.length; i++) { this.push(options.rows[i]); } } } /** * Inherit from Array. */ Table.prototype.__proto__ = Array.prototype; /** * Width getter * * @return {Number} width * @api public */ Table.prototype.__defineGetter__('width', function (){ var str = this.toString().split("\n"); if (str.length) return str[0].length; return 0; }); /** * Render to a string. * * @return {String} table representation * @api public */ Table.prototype.render Table.prototype.toString = function (){ var ret = '' , options = this.options , style = options.style , head = options.head , chars = options.chars , truncater = options.truncate , colWidths = options.colWidths || new Array(this.head.length) , totalWidth = 0; if (!head.length && !this.length) return ''; if (!colWidths.length){ var all_rows = this.slice(0); if (head.length) { all_rows = all_rows.concat([head]) }; all_rows.forEach(function(cells){ // horizontal (arrays) if (typeof cells === 'object' && cells.length) { extractColumnWidths(cells); // vertical (objects) } else { var header_cell = Object.keys(cells)[0] , value_cell = cells[header_cell]; colWidths[0] = Math.max(colWidths[0] || 0, get_width(header_cell) || 0); // cross (objects w/ array values) if (typeof value_cell === 'object' && value_cell.length) { extractColumnWidths(value_cell, 1); } else { colWidths[1] = Math.max(colWidths[1] || 0, get_width(value_cell) || 0); } } }); }; totalWidth = (colWidths.length == 1 ? colWidths[0] : colWidths.reduce( function (a, b){ return a + b })) + colWidths.length + 1; function extractColumnWidths(arr, offset) { var offset = offset || 0; arr.forEach(function(cell, i){ colWidths[i + offset] = Math.max(colWidths[i + offset] || 0, get_width(cell) || 0); }); }; function get_width(obj) { return typeof obj == 'object' && obj.width != undefined ? obj.width : ((typeof obj == 'object' ? utils.strlen(obj.text) : utils.strlen(obj)) + (style['padding-left'] || 0) + (style['padding-right'] || 0)) } // draws a line function line (line, left, right, intersection){ var width = 0 , line = left + repeat(line, totalWidth - 2) + right; colWidths.forEach(function (w, i){ if (i == colWidths.length - 1) return; width += w + 1; line = line.substr(0, width) + intersection + line.substr(width + 1); }); return applyStyles(options.style.border, line); }; // draws the top line function lineTop (){ var l = line(chars.top , chars['top-left'] || chars.top , chars['top-right'] || chars.top , chars['top-mid']); if (l) ret += l + "\n"; }; function generateRow (items, style) { var cells = [] , max_height = 0; // prepare vertical and cross table data if (!Array.isArray(items) && typeof items === "object") { var key = Object.keys(items)[0] , value = items[key] , first_cell_head = true; if (Array.isArray(value)) { items = value; items.unshift(key); } else { items = [key, value]; } } // transform array of item strings into structure of cells items.forEach(function (item, i) { var contents = item.toString().split("\n").reduce(function (memo, l) { memo.push(string(l, i)); return memo; }, []) var height = contents.length; if (height > max_height) { max_height = height }; cells.push({ contents: contents , height: height }); }); // transform vertical cells into horizontal lines var lines = new Array(max_height); cells.forEach(function (cell, i) { cell.contents.forEach(function (line, j) { if (!lines[j]) { lines[j] = [] }; if (style || (first_cell_head && i === 0 && options.style.head)) { line = applyStyles(options.style.head, line) } lines[j].push(line); }); // populate empty lines in cell for (var j = cell.height, l = max_height; j < l; j++) { if (!lines[j]) { lines[j] = [] }; lines[j].push(string('', i)); } }); var ret = ""; lines.forEach(function (line, index) { if (ret.length > 0) { ret += "\n" + applyStyles(options.style.border, chars.left); } ret += line.join(applyStyles(options.style.border, chars.middle)) + applyStyles(options.style.border, chars.right); }); return applyStyles(options.style.border, chars.left) + ret; }; function applyStyles(styles, subject) { if (!subject) return ''; styles.forEach(function(style) { subject = colors[style](subject); }); return subject; }; // renders a string, by padding it or truncating it function string (str, index){ var str = String(typeof str == 'object' && str.text ? str.text : str) , length = utils.strlen(str) , width = colWidths[index] - (style['padding-left'] || 0) - (style['padding-right'] || 0) , align = options.colAligns[index] || 'left'; return repeat(' ', style['padding-left'] || 0) + (length == width ? str : (length < width ? pad(str, ( width + (str.length - length) ), ' ', align == 'left' ? 'right' : (align == 'middle' ? 'both' : 'left')) : (truncater ? truncate(str, width, truncater) : str)) ) + repeat(' ', style['padding-right'] || 0); }; if (head.length){ lineTop(); ret += generateRow(head, style.head) + "\n" } if (this.length) this.forEach(function (cells, i){ if (!head.length && i == 0) lineTop(); else { if (!style.compact || i<(!!head.length) ?1:0 || cells.length == 0){ var l = line(chars.mid , chars['left-mid'] , chars['right-mid'] , chars['mid-mid']); if (l) ret += l + "\n" } } if (cells.hasOwnProperty("length") && !cells.length) { return } else { ret += generateRow(cells) + "\n"; }; }); var l = line(chars.bottom , chars['bottom-left'] || chars.bottom , chars['bottom-right'] || chars.bottom , chars['bottom-mid']); if (l) ret += l; else // trim the last '\n' if we didn't add the bottom decoration ret = ret.slice(0, -1); return ret; }; /** * Module exports. */ module.exports = Table; module.exports.version = '0.0.1';