xref: /third_party/node/lib/internal/cli_table.js (revision 1cb0ef41)
1'use strict';
2
3const {
4  ArrayPrototypeJoin,
5  ArrayPrototypeMap,
6  MathCeil,
7  MathMax,
8  MathMaxApply,
9  ObjectPrototypeHasOwnProperty,
10  StringPrototypeRepeat,
11} = primordials;
12
13const { getStringWidth } = require('internal/util/inspect');
14
15// The use of Unicode characters below is the only non-comment use of non-ASCII
16// Unicode characters in Node.js built-in modules. If they are ever removed or
17// rewritten with \u escapes, then a test will need to be (re-)added to Node.js
18// core to verify that Unicode characters work in built-ins.
19// Refs: https://github.com/nodejs/node/issues/10673
20const tableChars = {
21  /* eslint-disable node-core/non-ascii-character */
22  middleMiddle: '─',
23  rowMiddle: '┼',
24  topRight: '┐',
25  topLeft: '┌',
26  leftMiddle: '├',
27  topMiddle: '┬',
28  bottomRight: '┘',
29  bottomLeft: '└',
30  bottomMiddle: '┴',
31  rightMiddle: '┤',
32  left: '│ ',
33  right: ' │',
34  middle: ' │ ',
35  /* eslint-enable node-core/non-ascii-character */
36};
37
38const renderRow = (row, columnWidths) => {
39  let out = tableChars.left;
40  for (let i = 0; i < row.length; i++) {
41    const cell = row[i];
42    const len = getStringWidth(cell);
43    const needed = (columnWidths[i] - len) / 2;
44    // round(needed) + ceil(needed) will always add up to the amount
45    // of spaces we need while also left justifying the output.
46    out += StringPrototypeRepeat(' ', needed) + cell +
47           StringPrototypeRepeat(' ', MathCeil(needed));
48    if (i !== row.length - 1)
49      out += tableChars.middle;
50  }
51  out += tableChars.right;
52  return out;
53};
54
55const table = (head, columns) => {
56  const rows = [];
57  const columnWidths = ArrayPrototypeMap(head, (h) => getStringWidth(h));
58  const longestColumn = MathMaxApply(ArrayPrototypeMap(columns, (a) =>
59    a.length));
60
61  for (let i = 0; i < head.length; i++) {
62    const column = columns[i];
63    for (let j = 0; j < longestColumn; j++) {
64      if (rows[j] === undefined)
65        rows[j] = [];
66      const value = rows[j][i] =
67        ObjectPrototypeHasOwnProperty(column, j) ? column[j] : '';
68      const width = columnWidths[i] || 0;
69      const counted = getStringWidth(value);
70      columnWidths[i] = MathMax(width, counted);
71    }
72  }
73
74  const divider = ArrayPrototypeMap(columnWidths, (i) =>
75    StringPrototypeRepeat(tableChars.middleMiddle, i + 2));
76
77  let result = tableChars.topLeft +
78               ArrayPrototypeJoin(divider, tableChars.topMiddle) +
79               tableChars.topRight + '\n' +
80               renderRow(head, columnWidths) + '\n' +
81               tableChars.leftMiddle +
82               ArrayPrototypeJoin(divider, tableChars.rowMiddle) +
83               tableChars.rightMiddle + '\n';
84
85  for (const row of rows)
86    result += `${renderRow(row, columnWidths)}\n`;
87
88  result += tableChars.bottomLeft +
89            ArrayPrototypeJoin(divider, tableChars.bottomMiddle) +
90            tableChars.bottomRight;
91
92  return result;
93};
94
95module.exports = table;
96