💾TableMaker
print a table for 2D array of strings (with column settings, optional icons, separators).
replit: TableMaker (with separators)
const {log} = console;
// ⭐ TableMaker
class TableMaker {
// • ⭐ data : 2D array of strings
// • ⭐ columns: column settings (array of [header, align])
// • table settings: ({spacing: 3, icon: null})
constructor(data, columns, {
spacing = 3, // ⭐ between columns
padding = 0, // ⭐ inside column
maxStringLength = 24,
icon = null, // ⭐ choose icon for each row (function)
iconDescription = `<icon description here>`,
} = {}) {
this.data = data;
this.columnSettings = columns;
this.spacing = spacing;
this.padding = padding;
this.maxStringLength = maxStringLength;
this.icon = icon;
this.iconDescription = iconDescription;
// number of rows/cols (header not included)
this.rows = this.data.length; // ❗ may include separators
this.cols = this.columnSettings.length;
// truncate strings if needed
this._truncateData();
// init column widths
this._calculateColumnWidths(); // this._widths
// convert data rows to lines
// ⭐ (1st pass) this.divider still undefined
this._convertDataToLines(); // this._lines
// setup divider
const w = this._lines[0].length;
this.divider = '─'.repeat(w+1);
// convert data rows to lines
// ⭐ (2nd pass) this.divider is defined!
this._convertDataToLines(); // this._lines
}
// truncate data string if needed
_truncateData() {
for (let row of this.data) {
if (row === '---') continue; // skip separator
const max = this.maxStringLength;
for(let [j, s] of row.entries()) {
if (j >= this.cols) break; // skip extra values
if (s.length > max) row[j] = s.slice(0, max-4) + `...`;
}
}
}
// calculate width of every column
_calculateColumnWidths() {
// header widths first
let widths = this.columnSettings.map(s => s[0].length);
// iterate over rows
for (const row of this.data) {
if (row === '---') continue; // ❗ skip separators
for (const [j, s] of row.entries()) {
if (j >= this.cols) break; // ❗ extra values ignored
const len = s.length;
if (len > widths[j]) widths[j] = len;
}
}
this._widths = widths;
}
// align text with `width`, `align`
_alignText(text, width, align = TableMaker.align.left) {
let result;
// truncate text if needed
const max = this.maxStringLength;
if (text.length > max) text = text.slice(0, max-3) + '...';
switch (align) {
case TableMaker.align.left:
result = text.padEnd(width, ' ');
break;
case TableMaker.align.right:
result = text.padStart(width, ' ');
break;
case TableMaker.align.center:
const len = text.length;
const padstart = Math.floor((width - len) / 2);
const padend = width - padstart - len;
result = ' '.repeat(padstart) + text + ' '.repeat(padend);
break;
default:
result = text.padEnd(width, ' ');
}
return result;
}
// convert data rows to lines
_convertDataToLines() {
this._lines = this.data.map(row => this._rowToString(row));
}
// data row -> string (with/out icon)
_rowToString(row) {
if (row === '---') return this.divider; // ❗ separator
const pad = ' '.repeat(this.padding);
const spaces = ' '.repeat(this.spacing);
const rowIcon = this.icon ?. (row);
return (rowIcon ? rowIcon + ' ' : '') + row
.slice(0, this.cols) // ❗ extra values ignored
.map((v, j) => pad
+ this._alignText(v, this._widths[j], this.columnSettings[j][1])
+ pad
)
.join(spaces);
}
// header row -> string
_header() {
const pad = ' '.repeat(this.padding);
const spaces = ' '.repeat(this.spacing);
const iconPad = this.icon ? `┌── ${this.iconDescription}\n│\n│ ` : '';
const row = this.columnSettings.map(s => s[0]);
return iconPad + row
.map((v, j) => pad
+ this._alignText(v, this._widths[j])
+ pad)
.join(spaces);
}
// table -> string
toString() {
const divider = this.divider;
log(`table width: ${divider.length}`);
return this._header() + '\n'
+ divider + '\n'
+ this._lines.join('\n') + '\n'
+ divider + '\n';
}
// print to console
print() {
console.log(this.toString());
}
}
// column alignment
TableMaker.align = {
left: 0, center: 1, right: 2,
dot: 3, // not implemented (may have bugs)
};
// icons
TableMaker.icon = {
diamond: '🔸',
star : '⭐️',
check : '✅',
cross : '❌',
};
// export
module.exports = { TableMaker };
replit: TableMaker (with separators)
const {log} = console;
const { testCases } = require('./testCases.js');
const { isPrimitive } = require('./helpers/isObject.js');
const { TableMaker } = require('./helpers/TableMaker.js');
// ⭐ data (with extra `value`)
const data = testCases.map(testcase => {
if (testcase === '---') return '---';
// test cases: [value, expr, desc]
const value = testcase[0];
const expr = testcase[1] || String(value);
const desc = testcase[2] || '';
const toNum = String(+value);
const toStr = `"${String(value)}"`;
// [expr, desc, num, str, value] (extra `value` is for icon chooser)
return [ expr, desc, toNum, toStr, value];
})
// ⭐ column settings (4 columns)
const columns = [
['value', ],
['desc', ],
['-> number', TableMaker.align.right],
['-> string', ],
];
// ⭐ icon chooser
const icon = (row) => isPrimitive(row[4]) ? '✅' : '❌';
// ⭐ TableMaker
const table = new TableMaker(data, columns, {
icon: icon,
iconDescription: '(primitive?)'
});
table.print();
📃 結果:
┌── (primitive?)
│
│ value desc -> number -> string
────────────────────────────────────────────────────────────────────
✅ null 0 "null"
────────────────────────────────────────────────────────────────────
✅ undefined NaN "undefined"
────────────────────────────────────────────────────────────────────
✅ 0 0 "0"
✅ Infinity Infinity "Infinity"
✅ NaN NaN "NaN"
────────────────────────────────────────────────────────────────────
✅ "" empty str 0 ""
✅ "1.2" numeric 1.2 "1.2"
✅ "one" non-numeric NaN "one"
────────────────────────────────────────────────────────────────────
✅ true 1 "true"
✅ false 0 "false"
────────────────────────────────────────────────────────────────────
❌ {a:1} NaN "[object Object]"
❌ [] empty arr 0 ""
❌ [6] one numeric 6 "6"
❌ ['a'] any other NaN "a"
❌ new Date 1667313821772 "Tue Nov 01 2022 14:...
❌ /regex/ NaN "/regex/"
────────────────────────────────────────────────────────────────────
❌ () => {} NaN "() => {}"
❌ class {} NaN "class {}"
────────────────────────────────────────────────────────────────────
Last updated