123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- /**
- * Module dependencies.
- */
- var colors = require('chalk')
- var utils = require('./utils')
- var repeat = utils.repeat
- var truncate = utils.truncate
- /**
- * 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.borders == false) {
- this.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': ''
- }
- }
- };
- /**
- * Inherit from Array.
- */
- Table.prototype = new Array
- /**
- * 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 = ''
- var options = this.options
- var style = options.style
- var head = options.head
- var chars = options.chars
- var truncater = options.truncate
- var colWidths = options.colWidths || new Array(this.head.length)
- var totalWidth = 0
- if (!head.length && !this.length) return ''
- if (!colWidths.length) {
- var everyRows = this.slice(0)
- if (head.length) { everyRows = everyRows.concat([head]) };
- everyRows.forEach(function (cells) {
- // horizontal (arrays)
- if (Array.isArray(cells) && cells.length) {
- extractColumnWidths(cells)
- // vertical (objects)
- } else {
- var headerCell = Object.keys(cells)[0]
- var valueCell = cells[headerCell]
- colWidths[0] = Math.max(colWidths[0] || 0, getWidth(headerCell) || 0)
- // cross (objects w/ array values)
- if (Array.isArray(valueCell) && valueCell.length) {
- extractColumnWidths(valueCell, 1)
- } else {
- colWidths[1] = Math.max(colWidths[1] || 0, getWidth(valueCell) || 0)
- }
- }
- })
- };
- totalWidth = (colWidths.length === 1 ? colWidths[0] : colWidths.reduce(
- function (a, b) {
- return a + b
- })) + colWidths.length + 1
- function extractColumnWidths (arr, offset) {
- offset = offset || 0
- arr.forEach(function (cell, i) {
- colWidths[i + offset] = Math.max(colWidths[i + offset] || 0, getWidth(cell) || 0)
- })
- };
- function getWidth (obj) {
- return typeof obj === 'object' && obj && obj.width !== undefined
- ? obj.width
- : ((typeof obj === 'object' && obj !== null ? 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 = []
- var maxHeight = 0
- // prepare vertical and cross table data
- if (!Array.isArray(items) && typeof items === 'object') {
- var key = Object.keys(items)[0]
- var value = items[key]
- var firstCellHead = 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 === null || item === undefined ? '' : item).toString().split('\n').reduce(function (memo, l) {
- memo.push(string(l, i))
- return memo
- }, [])
- var height = contents.length
- if (height > maxHeight) { maxHeight = height };
- cells.push({ contents: contents, height: height })
- })
- // transform vertical cells into horizontal lines
- var lines = new Array(maxHeight)
- cells.forEach(function (cell, i) {
- cell.contents.forEach(function (line, j) {
- if (!lines[j]) { lines[j] = [] };
- if (style || (firstCellHead && 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 = maxHeight; 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) {
- str = String(typeof str === 'object' && str.text ? str.text : str)
- var length = utils.strlen(str)
- var width = colWidths[index] - (style['padding-left'] || 0) - (style['padding-right'] || 0)
- var align = options.colAligns[index] || 'left'
- return repeat(' ', style['padding-left'] || 0) +
- (length === width ? str
- : (length < width
- ? str.padEnd((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 (Array.isArray(cells) && !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 = '2.0.0'
|