650 lines
14 KiB
JavaScript
650 lines
14 KiB
JavaScript
/**
|
|
* (c) 2013 Beau Sorensen
|
|
* MIT Licensed
|
|
* For all details and documentation:
|
|
* https://github.com/sorensen/ascii-table
|
|
*/
|
|
|
|
;(function() {
|
|
'use strict';
|
|
|
|
/*!
|
|
* Module dependencies
|
|
*/
|
|
|
|
var slice = Array.prototype.slice
|
|
, toString = Object.prototype.toString
|
|
|
|
/**
|
|
* AsciiTable constructor
|
|
*
|
|
* @param {String|Object} title or JSON table
|
|
* @param {Object} table options
|
|
* - `prefix` - string prefix added to each line on render
|
|
* @constructor
|
|
* @api public
|
|
*/
|
|
|
|
function AsciiTable(name, options) {
|
|
this.options = options || {}
|
|
this.reset(name)
|
|
}
|
|
|
|
/*!
|
|
* Current library version, should match `package.json`
|
|
*/
|
|
|
|
AsciiTable.VERSION = '0.0.8'
|
|
|
|
/*!
|
|
* Alignment constants
|
|
*/
|
|
|
|
AsciiTable.LEFT = 0
|
|
AsciiTable.CENTER = 1
|
|
AsciiTable.RIGHT = 2
|
|
|
|
/*!
|
|
* Static methods
|
|
*/
|
|
|
|
/**
|
|
* Create a new table instance
|
|
*
|
|
* @param {String|Object} title or JSON table
|
|
* @param {Object} table options
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.factory = function(name, options) {
|
|
return new AsciiTable(name, options)
|
|
}
|
|
|
|
/**
|
|
* Align the a string at the given length
|
|
*
|
|
* @param {Number} direction
|
|
* @param {String} string input
|
|
* @param {Number} string length
|
|
* @param {Number} padding character
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.align = function(dir, str, len, pad) {
|
|
if (dir === AsciiTable.LEFT) return AsciiTable.alignLeft(str, len, pad)
|
|
if (dir === AsciiTable.RIGHT) return AsciiTable.alignRight(str, len, pad)
|
|
if (dir === AsciiTable.CENTER) return AsciiTable.alignCenter(str, len, pad)
|
|
return AsciiTable.alignAuto(str, len, pad)
|
|
}
|
|
|
|
/**
|
|
* Left align a string by padding it at a given length
|
|
*
|
|
* @param {String} str
|
|
* @param {Number} string length
|
|
* @param {String} padding character (optional, default '')
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.alignLeft = function(str, len, pad) {
|
|
if (!len || len < 0) return ''
|
|
if (str === undefined || str === null) str = ''
|
|
if (typeof pad === 'undefined') pad = ' '
|
|
if (typeof str !== 'string') str = str.toString()
|
|
var alen = len + 1 - str.length
|
|
if (alen <= 0) return str
|
|
return str + Array(len + 1 - str.length).join(pad)
|
|
}
|
|
|
|
/**
|
|
* Center align a string by padding it at a given length
|
|
*
|
|
* @param {String} str
|
|
* @param {Number} string length
|
|
* @param {String} padding character (optional, default '')
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.alignCenter = function(str, len, pad) {
|
|
if (!len || len < 0) return ''
|
|
if (str === undefined || str === null) str = ''
|
|
if (typeof pad === 'undefined') pad = ' '
|
|
if (typeof str !== 'string') str = str.toString()
|
|
var nLen = str.length
|
|
, half = Math.floor(len / 2 - nLen / 2)
|
|
, odds = Math.abs((nLen % 2) - (len % 2))
|
|
, len = str.length
|
|
|
|
return AsciiTable.alignRight('', half, pad)
|
|
+ str
|
|
+ AsciiTable.alignLeft('', half + odds, pad)
|
|
}
|
|
|
|
/**
|
|
* Right align a string by padding it at a given length
|
|
*
|
|
* @param {String} str
|
|
* @param {Number} string length
|
|
* @param {String} padding character (optional, default '')
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.alignRight = function(str, len, pad) {
|
|
if (!len || len < 0) return ''
|
|
if (str === undefined || str === null) str = ''
|
|
if (typeof pad === 'undefined') pad = ' '
|
|
if (typeof str !== 'string') str = str.toString()
|
|
var alen = len + 1 - str.length
|
|
if (alen <= 0) return str
|
|
return Array(len + 1 - str.length).join(pad) + str
|
|
}
|
|
|
|
/**
|
|
* Auto align string value based on object type
|
|
*
|
|
* @param {Any} object to string
|
|
* @param {Number} string length
|
|
* @param {String} padding character (optional, default '')
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.alignAuto = function(str, len, pad) {
|
|
if (str === undefined || str === null) str = ''
|
|
var type = toString.call(str)
|
|
pad || (pad = ' ')
|
|
len = +len
|
|
if (type !== '[object String]') {
|
|
str = str.toString()
|
|
}
|
|
if (str.length < len) {
|
|
switch(type) {
|
|
case '[object Number]': return AsciiTable.alignRight(str, len, pad)
|
|
default: return AsciiTable.alignLeft(str, len, pad)
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
|
|
/**
|
|
* Fill an array at a given size with the given value
|
|
*
|
|
* @param {Number} array size
|
|
* @param {Any} fill value
|
|
* @return {Array} filled array
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.arrayFill = function(len, fill) {
|
|
var arr = new Array(len)
|
|
for (var i = 0; i !== len; i++) {
|
|
arr[i] = fill;
|
|
}
|
|
return arr
|
|
}
|
|
|
|
/*!
|
|
* Instance methods
|
|
*/
|
|
|
|
/**
|
|
* Reset the table state back to defaults
|
|
*
|
|
* @param {String|Object} title or JSON table
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.reset =
|
|
AsciiTable.prototype.clear = function(name) {
|
|
this.__name = ''
|
|
this.__nameAlign = AsciiTable.CENTER
|
|
this.__rows = []
|
|
this.__maxCells = 0
|
|
this.__aligns = []
|
|
this.__colMaxes = []
|
|
this.__spacing = 1
|
|
this.__heading = null
|
|
this.__headingAlign = AsciiTable.CENTER
|
|
this.setBorder()
|
|
|
|
if (toString.call(name) === '[object String]') {
|
|
this.__name = name
|
|
} else if (toString.call(name) === '[object Object]') {
|
|
this.fromJSON(name)
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Set the table border
|
|
*
|
|
* @param {String} horizontal edges (optional, default `|`)
|
|
* @param {String} vertical edges (optional, default `-`)
|
|
* @param {String} top corners (optional, default `.`)
|
|
* @param {String} bottom corners (optional, default `'`)
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setBorder = function(edge, fill, top, bottom) {
|
|
this.__border = true
|
|
if (arguments.length === 1) {
|
|
fill = top = bottom = edge
|
|
}
|
|
this.__edge = edge || '|'
|
|
this.__fill = fill || '-'
|
|
this.__top = top || '.'
|
|
this.__bottom = bottom || "'"
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Remove all table borders
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.removeBorder = function() {
|
|
this.__border = false
|
|
this.__edge = ' '
|
|
this.__fill = ' '
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Set the column alignment at a given index
|
|
*
|
|
* @param {Number} column index
|
|
* @param {Number} alignment direction
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setAlign = function(idx, dir) {
|
|
this.__aligns[idx] = dir
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Set the title of the table
|
|
*
|
|
* @param {String} title
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setTitle = function(name) {
|
|
this.__name = name
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Get the title of the table
|
|
*
|
|
* @return {String} title
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.getTitle = function() {
|
|
return this.__name
|
|
}
|
|
|
|
/**
|
|
* Set table title alignment
|
|
*
|
|
* @param {Number} direction
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setTitleAlign = function(dir) {
|
|
this.__nameAlign = dir
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* AsciiTable sorting shortcut to sort rows
|
|
*
|
|
* @param {Function} sorting method
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.sort = function(method) {
|
|
this.__rows.sort(method)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Sort rows based on sort method for given column
|
|
*
|
|
* @param {Number} column index
|
|
* @param {Function} sorting method
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.sortColumn = function(idx, method) {
|
|
this.__rows.sort(function(a, b) {
|
|
return method(a[idx], b[idx])
|
|
})
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Set table heading for columns
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setHeading = function(row) {
|
|
if (arguments.length > 1 || toString.call(row) !== '[object Array]') {
|
|
row = slice.call(arguments)
|
|
}
|
|
this.__heading = row
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Get table heading for columns
|
|
*
|
|
* @return {Array} copy of headings
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.getHeading = function() {
|
|
return this.__heading.slice()
|
|
}
|
|
|
|
/**
|
|
* Set heading alignment
|
|
*
|
|
* @param {Number} direction
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setHeadingAlign = function(dir) {
|
|
this.__headingAlign = dir
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add a row of information to the table
|
|
*
|
|
* @param {...|Array} argument values in order of columns
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.addRow = function(row) {
|
|
if (arguments.length > 1 || toString.call(row) !== '[object Array]') {
|
|
row = slice.call(arguments)
|
|
}
|
|
this.__maxCells = Math.max(this.__maxCells, row.length)
|
|
this.__rows.push(row)
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Get a copy of all rows of the table
|
|
*
|
|
* @return {Array} copy of rows
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.getRows = function() {
|
|
return this.__rows.slice().map(function(row) {
|
|
return row.slice()
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Add rows in the format of a row matrix
|
|
*
|
|
* @param {Array} row matrix
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.addRowMatrix = function(rows) {
|
|
for (var i = 0; i < rows.length; i++) {
|
|
this.addRow(rows[i])
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Add rows from the given data array, processed by the callback function rowCallback.
|
|
*
|
|
* @param {Array} data
|
|
* @param (Function) rowCallback
|
|
* @param (Boolean) asMatrix - controls if the row created by rowCallback should be assigned as row matrix
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.addData = function(data, rowCallback, asMatrix) {
|
|
if (toString.call(data) !== '[object Array]') {
|
|
return this;
|
|
}
|
|
for (var index = 0, limit = data.length; index < limit; index++) {
|
|
var row = rowCallback(data[index]);
|
|
if(asMatrix) {
|
|
this.addRowMatrix(row);
|
|
} else {
|
|
this.addRow(row);
|
|
}
|
|
}
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Reset the current row state
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.clearRows = function() {
|
|
this.__rows = []
|
|
this.__maxCells = 0
|
|
this.__colMaxes = []
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Apply an even spaced column justification
|
|
*
|
|
* @param {Boolean} on / off
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.setJustify = function(val) {
|
|
arguments.length === 0 && (val = true)
|
|
this.__justify = !!val
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Convert the current instance to a JSON structure
|
|
*
|
|
* @return {Object} json representation
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.toJSON = function() {
|
|
return {
|
|
title: this.getTitle()
|
|
, heading: this.getHeading()
|
|
, rows: this.getRows()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populate the table from a JSON object
|
|
*
|
|
* @param {Object} json representation
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.parse =
|
|
AsciiTable.prototype.fromJSON = function(obj) {
|
|
return this
|
|
.clear()
|
|
.setTitle(obj.title)
|
|
.setHeading(obj.heading)
|
|
.addRowMatrix(obj.rows)
|
|
}
|
|
|
|
/**
|
|
* Render the table with the current information
|
|
*
|
|
* @return {String} formatted table
|
|
* @api public
|
|
*/
|
|
|
|
AsciiTable.prototype.render =
|
|
AsciiTable.prototype.valueOf =
|
|
AsciiTable.prototype.toString = function() {
|
|
var self = this
|
|
, body = []
|
|
, mLen = this.__maxCells
|
|
, max = AsciiTable.arrayFill(mLen, 0)
|
|
, total = mLen * 3
|
|
, rows = this.__rows
|
|
, justify
|
|
, border = this.__border
|
|
, all = this.__heading
|
|
? [this.__heading].concat(rows)
|
|
: rows
|
|
|
|
// Calculate max table cell lengths across all rows
|
|
for (var i = 0; i < all.length; i++) {
|
|
var row = all[i]
|
|
for (var k = 0; k < mLen; k++) {
|
|
var cell = row[k]
|
|
max[k] = Math.max(max[k], cell ? cell.toString().length : 0)
|
|
}
|
|
}
|
|
this.__colMaxes = max
|
|
justify = this.__justify ? Math.max.apply(null, max) : 0
|
|
|
|
// Get
|
|
max.forEach(function(x) {
|
|
total += justify ? justify : x + self.__spacing
|
|
})
|
|
justify && (total += max.length)
|
|
total -= this.__spacing
|
|
|
|
// Heading
|
|
border && body.push(this._seperator(total - mLen + 1, this.__top))
|
|
if (this.__name) {
|
|
body.push(this._renderTitle(total - mLen + 1))
|
|
border && body.push(this._seperator(total - mLen + 1))
|
|
}
|
|
if (this.__heading) {
|
|
body.push(this._renderRow(this.__heading, ' ', this.__headingAlign))
|
|
body.push(this._rowSeperator(mLen, this.__fill))
|
|
}
|
|
for (var i = 0; i < this.__rows.length; i++) {
|
|
body.push(this._renderRow(this.__rows[i], ' '))
|
|
}
|
|
border && body.push(this._seperator(total - mLen + 1, this.__bottom))
|
|
|
|
var prefix = this.options.prefix || ''
|
|
return prefix + body.join('\n' + prefix)
|
|
}
|
|
|
|
/**
|
|
* Create a line seperator
|
|
*
|
|
* @param {Number} string size
|
|
* @param {String} side values (default '|')
|
|
* @api private
|
|
*/
|
|
|
|
AsciiTable.prototype._seperator = function(len, sep) {
|
|
sep || (sep = this.__edge)
|
|
return sep + AsciiTable.alignRight(sep, len, this.__fill)
|
|
}
|
|
|
|
/**
|
|
* Create a row seperator
|
|
*
|
|
* @return {String} seperator
|
|
* @api private
|
|
*/
|
|
|
|
AsciiTable.prototype._rowSeperator = function() {
|
|
var blanks = AsciiTable.arrayFill(this.__maxCells, this.__fill)
|
|
return this._renderRow(blanks, this.__fill)
|
|
}
|
|
|
|
/**
|
|
* Render the table title in a centered box
|
|
*
|
|
* @param {Number} string size
|
|
* @return {String} formatted title
|
|
* @api private
|
|
*/
|
|
|
|
AsciiTable.prototype._renderTitle = function(len) {
|
|
var name = ' ' + this.__name + ' '
|
|
, str = AsciiTable.align(this.__nameAlign, name, len - 1, ' ')
|
|
return this.__edge + str + this.__edge
|
|
}
|
|
|
|
/**
|
|
* Render an invdividual row
|
|
*
|
|
* @param {Array} row
|
|
* @param {String} column seperator
|
|
* @param {Number} total row alignment (optional, default `auto`)
|
|
* @return {String} formatted row
|
|
* @api private
|
|
*/
|
|
|
|
AsciiTable.prototype._renderRow = function(row, str, align) {
|
|
var tmp = ['']
|
|
, max = this.__colMaxes
|
|
|
|
for (var k = 0; k < this.__maxCells; k++) {
|
|
var cell = row[k]
|
|
, just = this.__justify ? Math.max.apply(null, max) : max[k]
|
|
// , pad = k === this.__maxCells - 1 ? just : just + this.__spacing
|
|
, pad = just
|
|
, cAlign = this.__aligns[k]
|
|
, use = align
|
|
, method = 'alignAuto'
|
|
|
|
if (typeof align === 'undefined') use = cAlign
|
|
|
|
if (use === AsciiTable.LEFT) method = 'alignLeft'
|
|
if (use === AsciiTable.CENTER) method = 'alignCenter'
|
|
if (use === AsciiTable.RIGHT) method = 'alignRight'
|
|
|
|
tmp.push(AsciiTable[method](cell, pad, str))
|
|
}
|
|
var front = tmp.join(str + this.__edge + str)
|
|
front = front.substr(1, front.length)
|
|
return front + str + this.__edge
|
|
}
|
|
|
|
/*!
|
|
* Aliases
|
|
*/
|
|
|
|
// Create method shortcuts to all alignment methods for each direction
|
|
;['Left', 'Right', 'Center'].forEach(function(dir) {
|
|
var constant = AsciiTable[dir.toUpperCase()]
|
|
|
|
;['setAlign', 'setTitleAlign', 'setHeadingAlign'].forEach(function(method) {
|
|
// Call the base method with the direction constant as the last argument
|
|
AsciiTable.prototype[method + dir] = function() {
|
|
var args = slice.call(arguments).concat(constant)
|
|
return this[method].apply(this, args)
|
|
}
|
|
})
|
|
})
|
|
|
|
/*!
|
|
* Module exports.
|
|
*/
|
|
|
|
if (typeof exports !== 'undefined') {
|
|
module.exports = AsciiTable
|
|
} else {
|
|
this.AsciiTable = AsciiTable
|
|
}
|
|
|
|
}).call(this);
|