UNPKG

13.5 kBJavaScriptView Raw
1/**
2 * (c) 2013 Beau Sorensen
3 * MIT Licensed
4 * For all details and documentation:
5 * https://github.com/sorensen/ascii-table
6 */
7
8;(function() {
9'use strict';
10
11/*!
12 * Module dependencies
13 */
14
15var slice = Array.prototype.slice
16 , toString = Object.prototype.toString
17
18/**
19 * AsciiTable constructor
20 *
21 * @param {String|Object} title or JSON table
22 * @param {Object} table options
23 * - `prefix` - string prefix added to each line on render
24 * @constructor
25 * @api public
26 */
27
28function AsciiTable(name, options) {
29 this.options = options || {}
30 this.reset(name)
31}
32
33/*!
34 * Current library version, should match `package.json`
35 */
36
37AsciiTable.VERSION = '0.0.6'
38
39/*!
40 * Alignment constants
41 */
42
43AsciiTable.LEFT = 0
44AsciiTable.CENTER = 1
45AsciiTable.RIGHT = 2
46
47/*!
48 * Static methods
49 */
50
51/**
52 * Create a new table instance
53 *
54 * @param {String|Object} title or JSON table
55 * @param {Object} table options
56 * @api public
57 */
58
59AsciiTable.factory = function(name, options) {
60 return new AsciiTable(name, options)
61}
62
63/**
64 * Align the a string at the given length
65 *
66 * @param {Number} direction
67 * @param {String} string input
68 * @param {Number} string length
69 * @param {Number} padding character
70 * @api public
71 */
72
73AsciiTable.align = function(dir, str, len, pad) {
74 if (dir === AsciiTable.LEFT) return AsciiTable.alignLeft(str, len, pad)
75 if (dir === AsciiTable.RIGHT) return AsciiTable.alignRight(str, len, pad)
76 if (dir === AsciiTable.CENTER) return AsciiTable.alignCenter(str, len, pad)
77 return AsciiTable.alignAuto(str, len, pad)
78}
79
80/**
81 * Left align a string by padding it at a given length
82 *
83 * @param {String} str
84 * @param {Number} string length
85 * @param {String} padding character (optional, default '')
86 * @api public
87 */
88
89AsciiTable.alignLeft = function(str, len, pad) {
90 if (!len || len < 0) return ''
91 if (typeof str === 'undefined') str = ''
92 if (typeof pad === 'undefined') pad = ' '
93 if (typeof str !== 'string') str = str.toString()
94 var alen = len + 1 - str.length
95 if (alen <= 0) return str
96 return str + Array(len + 1 - str.length).join(pad)
97}
98
99/**
100 * Center align a string by padding it at a given length
101 *
102 * @param {String} str
103 * @param {Number} string length
104 * @param {String} padding character (optional, default '')
105 * @api public
106 */
107
108AsciiTable.alignCenter = function(str, len, pad) {
109 if (!len || len < 0) return ''
110 if (typeof str === 'undefined') str = ''
111 if (typeof pad === 'undefined') pad = ' '
112 if (typeof str !== 'string') str = str.toString()
113 var nLen = str.length
114 , half = Math.floor(len / 2 - nLen / 2)
115 , odds = Math.abs((nLen % 2) - (len % 2))
116 , len = str.length
117
118 return AsciiTable.alignRight('', half, pad)
119 + str
120 + AsciiTable.alignLeft('', half + odds, pad)
121}
122
123/**
124 * Right align a string by padding it at a given length
125 *
126 * @param {String} str
127 * @param {Number} string length
128 * @param {String} padding character (optional, default '')
129 * @api public
130 */
131
132AsciiTable.alignRight = function(str, len, pad) {
133 if (!len || len < 0) return ''
134 if (typeof str === 'undefined') str = ''
135 if (typeof pad === 'undefined') pad = ' '
136 if (typeof str !== 'string') str = str.toString()
137 var alen = len + 1 - str.length
138 if (alen <= 0) return str
139 return Array(len + 1 - str.length).join(pad) + str
140}
141
142/**
143 * Auto align string value based on object type
144 *
145 * @param {Any} object to string
146 * @param {Number} string length
147 * @param {String} padding character (optional, default '')
148 * @api public
149 */
150
151AsciiTable.alignAuto = function(str, len, pad) {
152 if (typeof str === 'undefined') str = ''
153 var type = toString.call(str)
154 pad || (pad = ' ')
155 len = +len
156 if (type !== '[object String]') {
157 str = str.toString()
158 }
159 if (str.length < len) {
160 switch(type) {
161 case '[object Number]': return AsciiTable.alignRight(str, len, pad)
162 default: return AsciiTable.alignLeft(str, len, pad)
163 }
164 }
165 return str
166}
167
168/**
169 * Fill an array at a given size with the given value
170 *
171 * @param {Number} array size
172 * @param {Any} fill value
173 * @return {Array} filled array
174 * @api public
175 */
176
177AsciiTable.arrayFill = function(len, fill) {
178 var arr = new Array(len)
179 for (var i = 0; i !== len; i++) {
180 arr[i] = fill;
181 }
182 return arr
183}
184
185/*!
186 * Instance methods
187 */
188
189/**
190 * Reset the table state back to defaults
191 *
192 * @param {String|Object} title or JSON table
193 * @api public
194 */
195
196AsciiTable.prototype.reset =
197AsciiTable.prototype.clear = function(name) {
198 this.__name = ''
199 this.__nameAlign = AsciiTable.CENTER
200 this.__rows = []
201 this.__maxCells = 0
202 this.__aligns = []
203 this.__colMaxes = []
204 this.__spacing = 1
205 this.__heading = null
206 this.__headingAlign = AsciiTable.CENTER
207 this.setBorder()
208
209 if (toString.call(name) === '[object String]') {
210 this.__name = name
211 } else if (toString.call(name) === '[object Object]') {
212 this.fromJSON(name)
213 }
214 return this
215}
216
217/**
218 * Set the table border
219 *
220 * @param {String} horizontal edges (optional, default `|`)
221 * @param {String} vertical edges (optional, default `-`)
222 * @param {String} top corners (optional, default `.`)
223 * @param {String} bottom corners (optional, default `'`)
224 * @api public
225 */
226
227AsciiTable.prototype.setBorder = function(edge, fill, top, bottom) {
228 this.__border = true
229 if (arguments.length === 1) {
230 fill = top = bottom = edge
231 }
232 this.__edge = edge || '|'
233 this.__fill = fill || '-'
234 this.__top = top || '.'
235 this.__bottom = bottom || "'"
236 return this
237}
238
239/**
240 * Remove all table borders
241 *
242 * @api public
243 */
244
245AsciiTable.prototype.removeBorder = function() {
246 this.__border = false
247 this.__edge = ' '
248 this.__fill = ' '
249 return this
250}
251
252/**
253 * Set the column alignment at a given index
254 *
255 * @param {Number} column index
256 * @param {Number} alignment direction
257 * @api public
258 */
259
260AsciiTable.prototype.setAlign = function(idx, dir) {
261 this.__aligns[idx] = dir
262 return this
263}
264
265/**
266 * Set the title of the table
267 *
268 * @param {String} title
269 * @api public
270 */
271
272AsciiTable.prototype.setTitle = function(name) {
273 this.__name = name
274 return this
275}
276
277/**
278 * Get the title of the table
279 *
280 * @return {String} title
281 * @api public
282 */
283
284AsciiTable.prototype.getTitle = function() {
285 return this.__name
286}
287
288/**
289 * Set table title alignment
290 *
291 * @param {Number} direction
292 * @api public
293 */
294
295AsciiTable.prototype.setTitleAlign = function(dir) {
296 this.__nameAlign = dir
297 return this
298}
299
300/**
301 * AsciiTable sorting shortcut to sort rows
302 *
303 * @param {Function} sorting method
304 * @api public
305 */
306
307AsciiTable.prototype.sort = function(method) {
308 this.__rows.sort(method)
309 return this
310}
311
312/**
313 * Sort rows based on sort method for given column
314 *
315 * @param {Number} column index
316 * @param {Function} sorting method
317 * @api public
318 */
319
320AsciiTable.prototype.sortColumn = function(idx, method) {
321 this.__rows.sort(function(a, b) {
322 return method(a[idx], b[idx])
323 })
324 return this
325}
326
327/**
328 * Set table heading for columns
329 *
330 * @api public
331 */
332
333AsciiTable.prototype.setHeading = function(row) {
334 if (arguments.length > 1 || toString.call(row) !== '[object Array]') {
335 row = slice.call(arguments)
336 }
337 this.__heading = row
338 return this
339}
340
341/**
342 * Get table heading for columns
343 *
344 * @return {Array} copy of headings
345 * @api public
346 */
347
348AsciiTable.prototype.getHeading = function() {
349 return this.__heading.slice()
350}
351
352/**
353 * Set heading alignment
354 *
355 * @param {Number} direction
356 * @api public
357 */
358
359AsciiTable.prototype.setHeadingAlign = function(dir) {
360 this.__headingAlign = dir
361 return this
362}
363
364/**
365 * Add a row of information to the table
366 *
367 * @param {...|Array} argument values in order of columns
368 * @api public
369 */
370
371AsciiTable.prototype.addRow = function(row) {
372 if (arguments.length > 1 || toString.call(row) !== '[object Array]') {
373 row = slice.call(arguments)
374 }
375 this.__maxCells = Math.max(this.__maxCells, row.length)
376 this.__rows.push(row)
377 return this
378}
379
380/**
381 * Get a copy of all rows of the table
382 *
383 * @return {Array} copy of rows
384 * @api public
385 */
386
387AsciiTable.prototype.getRows = function() {
388 return this.__rows.slice().map(function(row) {
389 return row.slice()
390 })
391}
392
393/**
394 * Add rows in the format of a row matrix
395 *
396 * @param {Array} row matrix
397 * @api public
398 */
399
400AsciiTable.prototype.addRowMatrix = function(rows) {
401 for (var i = 0; i < rows.length; i++) {
402 this.addRow(rows[i])
403 }
404 return this
405}
406
407/**
408 * Reset the current row state
409 *
410 * @api public
411 */
412
413AsciiTable.prototype.clearRows = function() {
414 this.__rows = []
415 this.__maxCells = 0
416 this.__colMaxes = []
417 return this
418}
419
420/**
421 * Apply an even spaced column justification
422 *
423 * @param {Boolean} on / off
424 * @api public
425 */
426
427AsciiTable.prototype.setJustify = function(val) {
428 arguments.length === 0 && (val = true)
429 this.__justify = !!val
430 return this
431}
432
433/**
434 * Convert the current instance to a JSON structure
435 *
436 * @return {Object} json representation
437 * @api public
438 */
439
440AsciiTable.prototype.toJSON = function() {
441 return {
442 title: this.getTitle()
443 , heading: this.getHeading()
444 , rows: this.getRows()
445 }
446}
447
448/**
449 * Populate the table from a JSON object
450 *
451 * @param {Object} json representation
452 * @api public
453 */
454
455AsciiTable.prototype.parse =
456AsciiTable.prototype.fromJSON = function(obj) {
457 return this
458 .clear()
459 .setTitle(obj.title)
460 .setHeading(obj.heading)
461 .addRowMatrix(obj.rows)
462}
463
464/**
465 * Render the table with the current information
466 *
467 * @return {String} formatted table
468 * @api public
469 */
470
471AsciiTable.prototype.render =
472AsciiTable.prototype.valueOf =
473AsciiTable.prototype.toString = function() {
474 var self = this
475 , body = []
476 , mLen = this.__maxCells
477 , max = AsciiTable.arrayFill(mLen, 0)
478 , total = mLen * 3
479 , rows = this.__rows
480 , justify
481 , border = this.__border
482 , all = this.__heading
483 ? [this.__heading].concat(rows)
484 : rows
485
486 // Calculate max table cell lengths across all rows
487 for (var i = 0; i < all.length; i++) {
488 var row = all[i]
489 for (var k = 0; k < mLen; k++) {
490 var cell = row[k]
491 max[k] = Math.max(max[k], cell ? cell.toString().length : 0)
492 }
493 }
494 this.__colMaxes = max
495 justify = this.__justify ? Math.max.apply(null, max) : 0
496
497 // Get
498 max.forEach(function(x) {
499 total += justify ? justify : x + self.__spacing
500 })
501 justify && (total += max.length)
502 total -= this.__spacing
503
504 // Heading
505 border && body.push(this._seperator(total - mLen + 1, this.__top))
506 if (this.__name) {
507 body.push(this._renderTitle(total - mLen + 1))
508 border && body.push(this._seperator(total - mLen + 1))
509 }
510 if (this.__heading) {
511 body.push(this._renderRow(this.__heading, ' ', this.__headingAlign))
512 body.push(this._rowSeperator(mLen, this.__fill))
513 }
514 for (var i = 0; i < this.__rows.length; i++) {
515 body.push(this._renderRow(this.__rows[i], ' '))
516 }
517 border && body.push(this._seperator(total - mLen + 1, this.__bottom))
518
519 var prefix = this.options.prefix || ''
520 return prefix + body.join('\n' + prefix)
521}
522
523/**
524 * Create a line seperator
525 *
526 * @param {Number} string size
527 * @param {String} side values (default '|')
528 * @api private
529 */
530
531AsciiTable.prototype._seperator = function(len, sep) {
532 sep || (sep = this.__edge)
533 return sep + AsciiTable.alignRight(sep, len, this.__fill)
534}
535
536/**
537 * Create a row seperator
538 *
539 * @return {String} seperator
540 * @api private
541 */
542
543AsciiTable.prototype._rowSeperator = function() {
544 var blanks = AsciiTable.arrayFill(this.__maxCells, this.__fill)
545 return this._renderRow(blanks, this.__fill)
546}
547
548/**
549 * Render the table title in a centered box
550 *
551 * @param {Number} string size
552 * @return {String} formatted title
553 * @api private
554 */
555
556AsciiTable.prototype._renderTitle = function(len) {
557 var name = ' ' + this.__name + ' '
558 , str = AsciiTable.align(this.__nameAlign, name, len - 1, ' ')
559 return this.__edge + str + this.__edge
560}
561
562/**
563 * Render an invdividual row
564 *
565 * @param {Array} row
566 * @param {String} column seperator
567 * @param {Number} total row alignment (optional, default `auto`)
568 * @return {String} formatted row
569 * @api private
570 */
571
572AsciiTable.prototype._renderRow = function(row, str, align) {
573 var tmp = ['']
574 , max = this.__colMaxes
575
576 for (var k = 0; k < this.__maxCells; k++) {
577 var cell = row[k]
578 , just = this.__justify ? Math.max.apply(null, max) : max[k]
579 // , pad = k === this.__maxCells - 1 ? just : just + this.__spacing
580 , pad = just
581 , cAlign = this.__aligns[k]
582 , use = align
583 , method = 'alignAuto'
584
585 if (typeof align === 'undefined') use = cAlign
586
587 if (use === AsciiTable.LEFT) method = 'alignLeft'
588 if (use === AsciiTable.CENTER) method = 'alignCenter'
589 if (use === AsciiTable.RIGHT) method = 'alignRight'
590
591 tmp.push(AsciiTable[method](cell, pad, str))
592 }
593 var front = tmp.join(str + this.__edge + str)
594 front = front.substr(1, front.length)
595 return front + str + this.__edge
596}
597
598/*!
599 * Aliases
600 */
601
602// Create method shortcuts to all alignment methods for each direction
603;['Left', 'Right', 'Center'].forEach(function(dir) {
604 var constant = AsciiTable[dir.toUpperCase()]
605
606 ;['setAlign', 'setTitleAlign', 'setHeadingAlign'].forEach(function(method) {
607 // Call the base method with the direction constant as the last argument
608 AsciiTable.prototype[method + dir] = function() {
609 var args = slice.call(arguments).concat(constant)
610 return this[method].apply(this, args)
611 }
612 })
613})
614
615/*!
616 * Module exports.
617 */
618
619if (typeof exports !== 'undefined') {
620 module.exports = AsciiTable
621} else {
622 this.AsciiTable = AsciiTable
623}
624
625}).call(this);