You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
7.7 KiB
304 lines
7.7 KiB
|
|
/** |
|
* 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';
|
|
|