1<a name="table"></a>
2# Table
3
4[![GitSpo Mentions](https://gitspo.com/badges/mentions/gajus/table?style=flat-square)](https://gitspo.com/mentions/gajus/table)
5[![Travis build status](http://img.shields.io/travis/gajus/table/master.svg?style=flat-square)](https://travis-ci.org/gajus/table)
6[![Coveralls](https://img.shields.io/coveralls/gajus/table.svg?style=flat-square)](https://coveralls.io/github/gajus/table)
7[![NPM version](http://img.shields.io/npm/v/table.svg?style=flat-square)](https://www.npmjs.org/package/table)
8[![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
9[![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
10
11* [Table](#table)
12    * [Features](#table-features)
13    * [Install](#table-install)
14    * [Usage](#table-usage)
15        * [Cell Content Alignment](#table-usage-cell-content-alignment)
16        * [Column Width](#table-usage-column-width)
17        * [Custom Border](#table-usage-custom-border)
18        * [Draw Horizontal Line](#table-usage-draw-horizontal-line)
19        * [Single Line Mode](#table-usage-single-line-mode)
20        * [Padding Cell Content](#table-usage-padding-cell-content)
21        * [Predefined Border Templates](#table-usage-predefined-border-templates)
22        * [Streaming](#table-usage-streaming)
23        * [Text Truncation](#table-usage-text-truncation)
24        * [Text Wrapping](#table-usage-text-wrapping)
25
26
27Produces a string that represents array data in a text table.
28
29![Demo of table displaying a list of missions to the Moon.](./.README/demo.png)
30
31<a name="table-features"></a>
32## Features
33
34* Works with strings containing [fullwidth](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms) characters.
35* Works with strings containing [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code).
36* Configurable border characters.
37* Configurable content alignment per column.
38* Configurable content padding per column.
39* Configurable column width.
40* Text wrapping.
41
42<a name="table-install"></a>
43## Install
44
45```bash
46npm install table
47
48```
49
50[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/gajus)
51[![Become a Patron](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/gajus)
52
53<a name="table-usage"></a>
54## Usage
55
56Table data is described using an array (rows) of array (cells).
57
58```js
59import {
60  table
61} from 'table';
62
63// Using commonjs?
64// const {table} = require('table');
65
66let data,
67    output;
68
69data = [
70    ['0A', '0B', '0C'],
71    ['1A', '1B', '1C'],
72    ['2A', '2B', '2C']
73];
74
75/**
76 * @typedef {string} table~cell
77 */
78
79/**
80 * @typedef {table~cell[]} table~row
81 */
82
83/**
84 * @typedef {Object} table~columns
85 * @property {string} alignment Cell content alignment (enum: left, center, right) (default: left).
86 * @property {number} width Column width (default: auto).
87 * @property {number} truncate Number of characters are which the content will be truncated (default: Infinity).
88 * @property {number} paddingLeft Cell content padding width left (default: 1).
89 * @property {number} paddingRight Cell content padding width right (default: 1).
90 */
91
92/**
93 * @typedef {Object} table~border
94 * @property {string} topBody
95 * @property {string} topJoin
96 * @property {string} topLeft
97 * @property {string} topRight
98 * @property {string} bottomBody
99 * @property {string} bottomJoin
100 * @property {string} bottomLeft
101 * @property {string} bottomRight
102 * @property {string} bodyLeft
103 * @property {string} bodyRight
104 * @property {string} bodyJoin
105 * @property {string} joinBody
106 * @property {string} joinLeft
107 * @property {string} joinRight
108 * @property {string} joinJoin
109 */
110
111/**
112 * Used to dynamically tell table whether to draw a line separating rows or not.
113 * The default behavior is to always return true.
114 *
115 * @typedef {function} drawHorizontalLine
116 * @param {number} index
117 * @param {number} size
118 * @return {boolean}
119 */
120
121/**
122 * @typedef {Object} table~config
123 * @property {table~border} border
124 * @property {table~columns[]} columns Column specific configuration.
125 * @property {table~columns} columnDefault Default values for all columns. Column specific settings overwrite the default values.
126 * @property {table~drawHorizontalLine} drawHorizontalLine
127 */
128
129/**
130 * Generates a text table.
131 *
132 * @param {table~row[]} rows
133 * @param {table~config} config
134 * @return {String}
135 */
136output = table(data);
137
138console.log(output);
139```
140
141```
142╔════╤════╤════╗
143║ 0A │ 0B │ 0C ║
144╟────┼────┼────╢
145║ 1A │ 1B │ 1C ║
146╟────┼────┼────╢
147║ 2A │ 2B │ 2C ║
148╚════╧════╧════╝
149
150```
151
152
153<a name="table-usage-cell-content-alignment"></a>
154### Cell Content Alignment
155
156`{string} config.columns[{number}].alignment` property controls content horizontal alignment within a cell.
157
158Valid values are: "left", "right" and "center".
159
160```js
161let config,
162  data,
163  output;
164
165data = [
166  ['0A', '0B', '0C'],
167  ['1A', '1B', '1C'],
168  ['2A', '2B', '2C']
169];
170
171config = {
172  columns: {
173    0: {
174      alignment: 'left',
175      width: 10
176    },
177    1: {
178      alignment: 'center',
179      width: 10
180    },
181    2: {
182      alignment: 'right',
183      width: 10
184    }
185  }
186};
187
188output = table(data, config);
189
190console.log(output);
191```
192
193```
194╔════════════╤════════════╤════════════╗
195║ 0A         │     0B     │         0C ║
196╟────────────┼────────────┼────────────╢
197║ 1A         │     1B     │         1C ║
198╟────────────┼────────────┼────────────╢
199║ 2A         │     2B     │         2C ║
200╚════════════╧════════════╧════════════╝
201```
202
203<a name="table-usage-column-width"></a>
204### Column Width
205
206`{number} config.columns[{number}].width` property restricts column width to a fixed width.
207
208```js
209let data,
210  output,
211  options;
212
213data = [
214  ['0A', '0B', '0C'],
215  ['1A', '1B', '1C'],
216  ['2A', '2B', '2C']
217];
218
219options = {
220  columns: {
221    1: {
222      width: 10
223    }
224  }
225};
226
227output = table(data, options);
228
229console.log(output);
230```
231
232```
233╔════╤════════════╤════╗
234║ 0A │ 0B         │ 0C ║
235╟────┼────────────┼────╢
236║ 1A │ 1B         │ 1C ║
237╟────┼────────────┼────╢
238║ 2A │ 2B         │ 2C ║
239╚════╧════════════╧════╝
240```
241
242<a name="table-usage-custom-border"></a>
243### Custom Border
244
245`{object} config.border` property describes characters used to draw the table border.
246
247```js
248let config,
249  data,
250  output;
251
252data = [
253  ['0A', '0B', '0C'],
254  ['1A', '1B', '1C'],
255  ['2A', '2B', '2C']
256];
257
258config = {
259  border: {
260    topBody: `─`,
261    topJoin: `┬`,
262    topLeft: `┌`,
263    topRight: `┐`,
264
265    bottomBody: `─`,
266    bottomJoin: `┴`,
267    bottomLeft: `└`,
268    bottomRight: `┘`,
269
270    bodyLeft: `│`,
271    bodyRight: `│`,
272    bodyJoin: `│`,
273
274    joinBody: `─`,
275    joinLeft: `├`,
276    joinRight: `┤`,
277    joinJoin: `┼`
278  }
279};
280
281output = table(data, config);
282
283console.log(output);
284```
285
286```
287┌────┬────┬────┐
288│ 0A │ 0B │ 0C │
289├────┼────┼────┤
290│ 1A │ 1B │ 1C │
291├────┼────┼────┤
292│ 2A │ 2B │ 2C │
293└────┴────┴────┘
294```
295
296<a name="table-usage-draw-horizontal-line"></a>
297### Draw Horizontal Line
298
299`{function} config.drawHorizontalLine` property is a function that is called for every non-content row in the table. The result of the function `{boolean}` determines whether a row is drawn.
300
301```js
302let data,
303  output,
304  options;
305
306data = [
307  ['0A', '0B', '0C'],
308  ['1A', '1B', '1C'],
309  ['2A', '2B', '2C'],
310  ['3A', '3B', '3C'],
311  ['4A', '4B', '4C']
312];
313
314options = {
315  /**
316    * @typedef {function} drawHorizontalLine
317    * @param {number} index
318    * @param {number} size
319    * @return {boolean}
320    */
321  drawHorizontalLine: (index, size) => {
322    return index === 0 || index === 1 || index === size - 1 || index === size;
323  }
324};
325
326output = table(data, options);
327
328console.log(output);
329
330```
331
332```
333╔════╤════╤════╗
334║ 0A │ 0B │ 0C ║
335╟────┼────┼────╢
336║ 1A │ 1B │ 1C ║
337║ 2A │ 2B │ 2C ║
338║ 3A │ 3B │ 3C ║
339╟────┼────┼────╢
340║ 4A │ 4B │ 4C ║
341╚════╧════╧════╝
342
343```
344
345<a name="table-usage-single-line-mode"></a>
346### Single Line Mode
347
348Horizontal lines inside the table are not drawn.
349
350```js
351import {
352  table,
353  getBorderCharacters
354} from 'table';
355
356const data = [
357  ['-rw-r--r--', '1', 'pandorym', 'staff', '1529', 'May 23 11:25', 'LICENSE'],
358  ['-rw-r--r--', '1', 'pandorym', 'staff', '16327', 'May 23 11:58', 'README.md'],
359  ['drwxr-xr-x', '76', 'pandorym', 'staff', '2432', 'May 23 12:02', 'dist'],
360  ['drwxr-xr-x', '634', 'pandorym', 'staff', '20288', 'May 23 11:54', 'node_modules'],
361  ['-rw-r--r--', '1,', 'pandorym', 'staff', '525688', 'May 23 11:52', 'package-lock.json'],
362  ['-rw-r--r--@', '1', 'pandorym', 'staff', '2440', 'May 23 11:25', 'package.json'],
363  ['drwxr-xr-x', '27', 'pandorym', 'staff', '864', 'May 23 11:25', 'src'],
364  ['drwxr-xr-x', '20', 'pandorym', 'staff', '640', 'May 23 11:25', 'test'],
365];
366
367const config = {
368  singleLine: true
369};
370
371const output = table(data, config);
372console.log(output);
373```
374
375```
376╔═════════════╤═════╤══════════╤═══════╤════════╤══════════════╤═══════════════════╗
377║ -rw-r--r--  │ 1   │ pandorym │ staff │ 1529   │ May 23 11:25 │ LICENSE           ║
378║ -rw-r--r--  │ 1   │ pandorym │ staff │ 16327  │ May 23 11:58 │ README.md379║ drwxr-xr-x  │ 76  │ pandorym │ staff │ 2432   │ May 23 12:02 │ dist              ║
380║ drwxr-xr-x  │ 634 │ pandorym │ staff │ 20288  │ May 23 11:54 │ node_modules      ║
381║ -rw-r--r--  │ 1,  │ pandorym │ staff │ 525688 │ May 23 11:52 │ package-lock.json382║ -rw-r--r--@ │ 1   │ pandorym │ staff │ 2440   │ May 23 11:25 │ package.json383║ drwxr-xr-x  │ 27  │ pandorym │ staff │ 864    │ May 23 11:25 │ src               ║
384║ drwxr-xr-x  │ 20  │ pandorym │ staff │ 640    │ May 23 11:25 │ test              ║
385╚═════════════╧═════╧══════════╧═══════╧════════╧══════════════╧═══════════════════╝
386```
387
388<a name="table-usage-padding-cell-content"></a>
389### Padding Cell Content
390
391`{number} config.columns[{number}].paddingLeft` and `{number} config.columns[{number}].paddingRight` properties control content padding within a cell. Property value represents a number of whitespaces used to pad the content.
392
393```js
394let config,
395  data,
396  output;
397
398data = [
399  ['0A', 'AABBCC', '0C'],
400  ['1A', '1B', '1C'],
401  ['2A', '2B', '2C']
402];
403
404config = {
405  columns: {
406    0: {
407      paddingLeft: 3
408    },
409    1: {
410      width: 2,
411      paddingRight: 3
412    }
413  }
414};
415
416output = table(data, config);
417
418console.log(output);
419```
420
421```
422╔══════╤══════╤════╗
423║   0A │ AA   │ 0C ║
424║      │ BB   │    ║
425║      │ CC   │    ║
426╟──────┼──────┼────╢
427║   1A │ 1B   │ 1C ║
428╟──────┼──────┼────╢
429║   2A │ 2B   │ 2C ║
430╚══════╧══════╧════╝
431```
432
433<a name="table-usage-predefined-border-templates"></a>
434### Predefined Border Templates
435
436You can load one of the predefined border templates using `getBorderCharacters` function.
437
438```js
439import {
440  table,
441  getBorderCharacters
442} from 'table';
443
444let config,
445  data;
446
447data = [
448  ['0A', '0B', '0C'],
449  ['1A', '1B', '1C'],
450  ['2A', '2B', '2C']
451];
452
453config = {
454  border: getBorderCharacters(`name of the template`)
455};
456
457table(data, config);
458```
459
460```
461# honeywell
462
463╔════╤════╤════╗
464║ 0A │ 0B │ 0C ║
465╟────┼────┼────╢
466║ 1A │ 1B │ 1C ║
467╟────┼────┼────╢
468║ 2A │ 2B │ 2C ║
469╚════╧════╧════╝
470
471# norc
472
473┌────┬────┬────┐
474│ 0A │ 0B │ 0C │
475├────┼────┼────┤
476│ 1A │ 1B │ 1C │
477├────┼────┼────┤
478│ 2A │ 2B │ 2C │
479└────┴────┴────┘
480
481# ramac (ASCII; for use in terminals that do not support Unicode characters)
482
483+----+----+----+
484| 0A | 0B | 0C |
485|----|----|----|
486| 1A | 1B | 1C |
487|----|----|----|
488| 2A | 2B | 2C |
489+----+----+----+
490
491# void (no borders; see "bordless table" section of the documentation)
492
493 0A  0B  0C
494
495 1A  1B  1C
496
497 2A  2B  2C
498
499```
500
501Raise [an issue](https://github.com/gajus/table/issues) if you'd like to contribute a new border template.
502
503<a name="table-usage-predefined-border-templates-borderless-table"></a>
504#### Borderless Table
505
506Simply using "void" border character template creates a table with a lot of unnecessary spacing.
507
508To create a more plesant to the eye table, reset the padding and remove the joining rows, e.g.
509
510```js
511let output;
512
513output = table(data, {
514    border: getBorderCharacters(`void`),
515    columnDefault: {
516        paddingLeft: 0,
517        paddingRight: 1
518    },
519    drawHorizontalLine: () => {
520        return false
521    }
522});
523
524console.log(output);
525```
526
527```
5280A 0B 0C
5291A 1B 1C
5302A 2B 2C
531```
532
533<a name="table-usage-streaming"></a>
534### Streaming
535
536`table` package exports `createStream` function used to draw a table and append rows.
537
538`createStream` requires `{number} columnDefault.width` and `{number} columnCount` configuration properties.
539
540```js
541import {
542  createStream
543} from 'table';
544
545let config,
546  stream;
547
548config = {
549  columnDefault: {
550    width: 50
551  },
552  columnCount: 1
553};
554
555stream = createStream(config);
556
557setInterval(() => {
558  stream.write([new Date()]);
559}, 500);
560```
561
562![Streaming current date.](./.README/streaming.gif)
563
564`table` package uses ANSI escape codes to overwrite the output of the last line when a new row is printed.
565
566The underlying implementation is explained in this [Stack Overflow answer](http://stackoverflow.com/a/32938658/368691).
567
568Streaming supports all of the configuration properties and functionality of a static table (such as auto text wrapping, alignment and padding), e.g.
569
570```js
571import {
572  createStream
573} from 'table';
574
575import _ from 'lodash';
576
577let config,
578  stream,
579  i;
580
581config = {
582  columnDefault: {
583    width: 50
584  },
585  columnCount: 3,
586  columns: {
587    0: {
588      width: 10,
589      alignment: 'right'
590    },
591    1: {
592      alignment: 'center',
593    },
594    2: {
595      width: 10
596    }
597  }
598};
599
600stream = createStream(config);
601
602i = 0;
603
604setInterval(() => {
605  let random;
606
607  random = _.sample('abcdefghijklmnopqrstuvwxyz', _.random(1, 30)).join('');
608
609  stream.write([i++, new Date(), random]);
610}, 500);
611```
612
613![Streaming random data.](./.README/streaming-random.gif)
614
615<a name="table-usage-text-truncation"></a>
616### Text Truncation
617
618To handle a content that overflows the container width, `table` package implements [text wrapping](#table-usage-text-wrapping). However, sometimes you may want to truncate content that is too long to be displayed in the table.
619
620`{number} config.columns[{number}].truncate` property (default: `Infinity`) truncates the text at the specified length.
621
622```js
623let config,
624  data,
625  output;
626
627data = [
628  ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus pulvinar nibh sed mauris convallis dapibus. Nunc venenatis tempus nulla sit amet viverra.']
629];
630
631config = {
632  columns: {
633    0: {
634      width: 20,
635      truncate: 100
636    }
637  }
638};
639
640output = table(data, config);
641
642console.log(output);
643```
644
645```
646╔══════════════════════╗
647║ Lorem ipsum dolor si ║
648║ t amet, consectetur  ║
649║ adipiscing elit. Pha ║
650║ sellus pulvinar nibh ║
651║ sed mauris conva...  ║
652╚══════════════════════╝
653```
654
655<a name="table-usage-text-wrapping"></a>
656### Text Wrapping
657
658`table` package implements auto text wrapping, i.e. text that has width greater than the container width will be separated into multiple lines, e.g.
659
660```js
661let config,
662  data,
663  output;
664
665data = [
666    ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus pulvinar nibh sed mauris convallis dapibus. Nunc venenatis tempus nulla sit amet viverra.']
667];
668
669config = {
670  columns: {
671    0: {
672      width: 20
673    }
674  }
675};
676
677output = table(data, config);
678
679console.log(output);
680```
681
682```
683╔══════════════════════╗
684║ Lorem ipsum dolor si ║
685║ t amet, consectetur  ║
686║ adipiscing elit. Pha ║
687║ sellus pulvinar nibh ║
688║ sed mauris convallis ║
689║ dapibus. Nunc venena ║
690║ tis tempus nulla sit ║
691║ amet viverra.        ║
692╚══════════════════════╝
693```
694
695When `wrapWord` is `true` the text is broken at the nearest space or one of the special characters ("-", "_", "\", "/", ".", ",", ";"), e.g.
696
697```js
698let config,
699  data,
700  output;
701
702data = [
703  ['Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus pulvinar nibh sed mauris convallis dapibus. Nunc venenatis tempus nulla sit amet viverra.']
704];
705
706config = {
707  columns: {
708    0: {
709      width: 20,
710      wrapWord: true
711    }
712  }
713};
714
715output = table(data, config);
716
717console.log(output);
718```
719
720```
721╔══════════════════════╗
722║ Lorem ipsum dolor    ║
723║ sit amet,            ║
724║ consectetur          ║
725║ adipiscing elit.     ║
726║ Phasellus pulvinar   ║
727║ nibh sed mauris      ║
728║ convallis dapibus.   ║
729║ Nunc venenatis       ║
730║ tempus nulla sit     ║
731║ amet viverra.        ║
732╚══════════════════════╝
733
734```
735
736