console: add table method
PR-URL: https://github.com/nodejs/node/pull/18137 Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
parent
83d44bee01
commit
97ace04492
@ -332,6 +332,47 @@ console.log('count:', count);
|
||||
|
||||
See [`util.format()`][] for more information.
|
||||
|
||||
### console.table(tabularData[, properties])
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `tabularData` {any}
|
||||
* `properties` {string[]} Alternate properties for constructing the table.
|
||||
|
||||
Try to construct a table with the columns of the properties of `tabularData`
|
||||
(or use `properties`) and rows of `tabularData` and logit. Falls back to just
|
||||
logging the argument if it can’t be parsed as tabular.
|
||||
|
||||
```js
|
||||
// These can't be parsed as tabular data
|
||||
console.table(Symbol());
|
||||
// Symbol()
|
||||
|
||||
console.table(undefined);
|
||||
// undefined
|
||||
```
|
||||
|
||||
```js
|
||||
console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }]);
|
||||
// ┌─────────┬─────┬─────┐
|
||||
// │ (index) │ a │ b │
|
||||
// ├─────────┼─────┼─────┤
|
||||
// │ 0 │ 1 │ 'Y' │
|
||||
// │ 1 │ 'Z' │ 2 │
|
||||
// └─────────┴─────┴─────┘
|
||||
```
|
||||
|
||||
```js
|
||||
console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ['a']);
|
||||
// ┌─────────┬─────┐
|
||||
// │ (index) │ a │
|
||||
// ├─────────┼─────┤
|
||||
// │ 0 │ 1 │
|
||||
// │ 1 │ 'Z' │
|
||||
// └─────────┴─────┘
|
||||
```
|
||||
|
||||
### console.time(label)
|
||||
<!-- YAML
|
||||
added: v0.1.104
|
||||
|
129
lib/console.js
129
lib/console.js
@ -23,11 +23,31 @@
|
||||
|
||||
const {
|
||||
isStackOverflowError,
|
||||
codes: { ERR_CONSOLE_WRITABLE_STREAM },
|
||||
codes: {
|
||||
ERR_CONSOLE_WRITABLE_STREAM,
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
},
|
||||
} = require('internal/errors');
|
||||
const { previewMapIterator, previewSetIterator } = require('internal/v8');
|
||||
const { Buffer: { isBuffer } } = require('buffer');
|
||||
const cliTable = require('internal/cli_table');
|
||||
const util = require('util');
|
||||
const {
|
||||
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
|
||||
} = util.types;
|
||||
const kCounts = Symbol('counts');
|
||||
|
||||
const {
|
||||
keys: ObjectKeys,
|
||||
values: ObjectValues,
|
||||
} = Object;
|
||||
const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
|
||||
|
||||
const {
|
||||
isArray: ArrayIsArray,
|
||||
from: ArrayFrom,
|
||||
} = Array;
|
||||
|
||||
// Track amount of indentation required via `console.group()`.
|
||||
const kGroupIndent = Symbol('groupIndent');
|
||||
|
||||
@ -242,6 +262,113 @@ Console.prototype.groupEnd = function groupEnd() {
|
||||
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
|
||||
};
|
||||
|
||||
const keyKey = 'Key';
|
||||
const valuesKey = 'Values';
|
||||
const indexKey = '(index)';
|
||||
const iterKey = '(iteration index)';
|
||||
|
||||
|
||||
const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);
|
||||
const inspect = (v) => {
|
||||
const opt = { depth: 0, maxArrayLength: 3 };
|
||||
if (v !== null && typeof v === 'object' &&
|
||||
!isArray(v) && ObjectKeys(v).length > 2)
|
||||
opt.depth = -1;
|
||||
return util.inspect(v, opt);
|
||||
};
|
||||
|
||||
const getIndexArray = (length) => ArrayFrom({ length }, (_, i) => inspect(i));
|
||||
|
||||
// https://console.spec.whatwg.org/#table
|
||||
Console.prototype.table = function(tabularData, properties) {
|
||||
if (properties !== undefined && !ArrayIsArray(properties))
|
||||
throw new ERR_INVALID_ARG_TYPE('properties', 'Array', properties);
|
||||
|
||||
if (tabularData == null ||
|
||||
(typeof tabularData !== 'object' && typeof tabularData !== 'function'))
|
||||
return this.log(tabularData);
|
||||
|
||||
const final = (k, v) => this.log(cliTable(k, v));
|
||||
|
||||
const mapIter = isMapIterator(tabularData);
|
||||
if (mapIter)
|
||||
tabularData = previewMapIterator(tabularData);
|
||||
|
||||
if (mapIter || isMap(tabularData)) {
|
||||
const keys = [];
|
||||
const values = [];
|
||||
let length = 0;
|
||||
for (const [k, v] of tabularData) {
|
||||
keys.push(inspect(k));
|
||||
values.push(inspect(v));
|
||||
length++;
|
||||
}
|
||||
return final([
|
||||
iterKey, keyKey, valuesKey
|
||||
], [
|
||||
getIndexArray(length),
|
||||
keys,
|
||||
values,
|
||||
]);
|
||||
}
|
||||
|
||||
const setIter = isSetIterator(tabularData);
|
||||
if (setIter)
|
||||
tabularData = previewSetIterator(tabularData);
|
||||
|
||||
const setlike = setIter || isSet(tabularData);
|
||||
if (setlike ||
|
||||
(properties === undefined &&
|
||||
(isArray(tabularData) || isTypedArray(tabularData)))) {
|
||||
const values = [];
|
||||
let length = 0;
|
||||
for (const v of tabularData) {
|
||||
values.push(inspect(v));
|
||||
length++;
|
||||
}
|
||||
return final([setlike ? iterKey : indexKey, valuesKey], [
|
||||
getIndexArray(length),
|
||||
values,
|
||||
]);
|
||||
}
|
||||
|
||||
const map = {};
|
||||
let hasPrimitives = false;
|
||||
const valuesKeyArray = [];
|
||||
const indexKeyArray = ObjectKeys(tabularData);
|
||||
|
||||
for (var i = 0; i < indexKeyArray.length; i++) {
|
||||
const item = tabularData[indexKeyArray[i]];
|
||||
const primitive = item === null ||
|
||||
(typeof item !== 'function' && typeof item !== 'object');
|
||||
if (properties === undefined && primitive) {
|
||||
hasPrimitives = true;
|
||||
valuesKeyArray[i] = inspect(item);
|
||||
} else {
|
||||
const keys = properties || ObjectKeys(item);
|
||||
for (const key of keys) {
|
||||
if (map[key] === undefined)
|
||||
map[key] = [];
|
||||
if ((primitive && properties) || !hasOwnProperty(item, key))
|
||||
map[key][i] = '';
|
||||
else
|
||||
map[key][i] = item == null ? item : inspect(item[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const keys = ObjectKeys(map);
|
||||
const values = ObjectValues(map);
|
||||
if (hasPrimitives) {
|
||||
keys.push(valuesKey);
|
||||
values.push(valuesKeyArray);
|
||||
}
|
||||
keys.unshift(indexKey);
|
||||
values.unshift(indexKeyArray);
|
||||
|
||||
return final(keys, values);
|
||||
};
|
||||
|
||||
module.exports = new Console(process.stdout, process.stderr);
|
||||
module.exports.Console = Console;
|
||||
|
||||
|
83
lib/internal/cli_table.js
Normal file
83
lib/internal/cli_table.js
Normal file
@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
const { Buffer } = require('buffer');
|
||||
const { removeColors } = require('internal/util');
|
||||
const HasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
|
||||
|
||||
const tableChars = {
|
||||
/* eslint-disable node-core/non-ascii-character */
|
||||
middleMiddle: '─',
|
||||
rowMiddle: '┼',
|
||||
topRight: '┐',
|
||||
topLeft: '┌',
|
||||
leftMiddle: '├',
|
||||
topMiddle: '┬',
|
||||
bottomRight: '┘',
|
||||
bottomLeft: '└',
|
||||
bottomMiddle: '┴',
|
||||
rightMiddle: '┤',
|
||||
left: '│ ',
|
||||
right: ' │',
|
||||
middle: ' │ ',
|
||||
/* eslint-enable node-core/non-ascii-character */
|
||||
};
|
||||
|
||||
const countSymbols = (string) => {
|
||||
const normalized = removeColors(string).normalize('NFC');
|
||||
return Buffer.from(normalized, 'UCS-2').byteLength / 2;
|
||||
};
|
||||
|
||||
const renderRow = (row, columnWidths) => {
|
||||
let out = tableChars.left;
|
||||
for (var i = 0; i < row.length; i++) {
|
||||
const cell = row[i];
|
||||
const len = countSymbols(cell);
|
||||
const needed = (columnWidths[i] - len) / 2;
|
||||
// round(needed) + ceil(needed) will always add up to the amount
|
||||
// of spaces we need while also left justifying the output.
|
||||
out += `${' '.repeat(needed)}${cell}${' '.repeat(Math.ceil(needed))}`;
|
||||
if (i !== row.length - 1)
|
||||
out += tableChars.middle;
|
||||
}
|
||||
out += tableChars.right;
|
||||
return out;
|
||||
};
|
||||
|
||||
const table = (head, columns) => {
|
||||
const rows = [];
|
||||
const columnWidths = head.map((h) => countSymbols(h));
|
||||
const longestColumn = columns.reduce((n, a) => Math.max(n, a.length), 0);
|
||||
|
||||
for (var i = 0; i < head.length; i++) {
|
||||
const column = columns[i];
|
||||
for (var j = 0; j < longestColumn; j++) {
|
||||
if (!rows[j])
|
||||
rows[j] = [];
|
||||
const v = rows[j][i] = HasOwnProperty(column, j) ? column[j] : '';
|
||||
const width = columnWidths[i] || 0;
|
||||
const counted = countSymbols(v);
|
||||
columnWidths[i] = Math.max(width, counted);
|
||||
}
|
||||
}
|
||||
|
||||
const divider = columnWidths.map((i) =>
|
||||
tableChars.middleMiddle.repeat(i + 2));
|
||||
|
||||
const tl = tableChars.topLeft;
|
||||
const tr = tableChars.topRight;
|
||||
const lm = tableChars.leftMiddle;
|
||||
let result = `${tl}${divider.join(tableChars.topMiddle)}${tr}
|
||||
${renderRow(head, columnWidths)}
|
||||
${lm}${divider.join(tableChars.rowMiddle)}${tableChars.rightMiddle}
|
||||
`;
|
||||
|
||||
for (const row of rows)
|
||||
result += `${renderRow(row, columnWidths)}\n`;
|
||||
|
||||
result += `${tableChars.bottomLeft}${
|
||||
divider.join(tableChars.bottomMiddle)}${tableChars.bottomRight}`;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = table;
|
1
node.gyp
1
node.gyp
@ -81,6 +81,7 @@
|
||||
'lib/zlib.js',
|
||||
'lib/internal/async_hooks.js',
|
||||
'lib/internal/buffer.js',
|
||||
'lib/internal/cli_table.js',
|
||||
'lib/internal/child_process.js',
|
||||
'lib/internal/cluster/child.js',
|
||||
'lib/internal/cluster/master.js',
|
||||
|
196
test/parallel/test-console-table.js
Normal file
196
test/parallel/test-console-table.js
Normal file
@ -0,0 +1,196 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const { Console } = require('console');
|
||||
|
||||
const queue = [];
|
||||
|
||||
const console = new Console({ write: (x) => {
|
||||
queue.push(x);
|
||||
}, removeListener: () => {} }, process.stderr, false);
|
||||
|
||||
function test(data, only, expected) {
|
||||
if (arguments.length === 2) {
|
||||
expected = only;
|
||||
only = undefined;
|
||||
}
|
||||
console.table(data, only);
|
||||
assert.strictEqual(queue.shift(), expected.trimLeft());
|
||||
}
|
||||
|
||||
common.expectsError(() => console.table([], false), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
|
||||
test(null, 'null\n');
|
||||
test(undefined, 'undefined\n');
|
||||
test(false, 'false\n');
|
||||
test('hi', 'hi\n');
|
||||
test(Symbol(), 'Symbol()\n');
|
||||
|
||||
test([1, 2, 3], `
|
||||
┌─────────┬────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼────────┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 2 │
|
||||
│ 2 │ 3 │
|
||||
└─────────┴────────┘
|
||||
`);
|
||||
|
||||
test([Symbol(), 5, [10]], `
|
||||
┌─────────┬──────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼──────────┤
|
||||
│ 0 │ Symbol() │
|
||||
│ 1 │ 5 │
|
||||
│ 2 │ [ 10 ] │
|
||||
└─────────┴──────────┘
|
||||
`);
|
||||
|
||||
test([undefined, 5], `
|
||||
┌─────────┬───────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼───────────┤
|
||||
│ 0 │ undefined │
|
||||
│ 1 │ 5 │
|
||||
└─────────┴───────────┘
|
||||
`);
|
||||
|
||||
test({ a: 1, b: Symbol(), c: [10] }, `
|
||||
┌─────────┬────┬──────────┐
|
||||
│ (index) │ 0 │ Values │
|
||||
├─────────┼────┼──────────┤
|
||||
│ a │ │ 1 │
|
||||
│ b │ │ Symbol() │
|
||||
│ c │ 10 │ │
|
||||
└─────────┴────┴──────────┘
|
||||
`);
|
||||
|
||||
test(new Map([ ['a', 1], [Symbol(), [2]] ]), `
|
||||
┌───────────────────┬──────────┬────────┐
|
||||
│ (iteration index) │ Key │ Values │
|
||||
├───────────────────┼──────────┼────────┤
|
||||
│ 0 │ 'a' │ 1 │
|
||||
│ 1 │ Symbol() │ [ 2 ] │
|
||||
└───────────────────┴──────────┴────────┘
|
||||
`);
|
||||
|
||||
test(new Set([1, 2, Symbol()]), `
|
||||
┌───────────────────┬──────────┐
|
||||
│ (iteration index) │ Values │
|
||||
├───────────────────┼──────────┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 2 │
|
||||
│ 2 │ Symbol() │
|
||||
└───────────────────┴──────────┘
|
||||
`);
|
||||
|
||||
test({ a: 1, b: 2 }, ['a'], `
|
||||
┌─────────┬───┐
|
||||
│ (index) │ a │
|
||||
├─────────┼───┤
|
||||
│ a │ │
|
||||
│ b │ │
|
||||
└─────────┴───┘
|
||||
`);
|
||||
|
||||
test([{ a: 1, b: 2 }, { a: 3, c: 4 }], ['a'], `
|
||||
┌─────────┬───┐
|
||||
│ (index) │ a │
|
||||
├─────────┼───┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 3 │
|
||||
└─────────┴───┘
|
||||
`);
|
||||
|
||||
test(new Map([[1, 1], [2, 2], [3, 3]]).entries(), `
|
||||
┌───────────────────┬─────┬────────┐
|
||||
│ (iteration index) │ Key │ Values │
|
||||
├───────────────────┼─────┼────────┤
|
||||
│ 0 │ 1 │ 1 │
|
||||
│ 1 │ 2 │ 2 │
|
||||
│ 2 │ 3 │ 3 │
|
||||
└───────────────────┴─────┴────────┘
|
||||
`);
|
||||
|
||||
test(new Set([1, 2, 3]).values(), `
|
||||
┌───────────────────┬────────┐
|
||||
│ (iteration index) │ Values │
|
||||
├───────────────────┼────────┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 2 │
|
||||
│ 2 │ 3 │
|
||||
└───────────────────┴────────┘
|
||||
`);
|
||||
|
||||
|
||||
test({ a: { a: 1, b: 2, c: 3 } }, `
|
||||
┌─────────┬───┬───┬───┐
|
||||
│ (index) │ a │ b │ c │
|
||||
├─────────┼───┼───┼───┤
|
||||
│ a │ 1 │ 2 │ 3 │
|
||||
└─────────┴───┴───┴───┘
|
||||
`);
|
||||
|
||||
test({ a: [1, 2] }, `
|
||||
┌─────────┬───┬───┐
|
||||
│ (index) │ 0 │ 1 │
|
||||
├─────────┼───┼───┤
|
||||
│ a │ 1 │ 2 │
|
||||
└─────────┴───┴───┘
|
||||
`);
|
||||
|
||||
test({ a: [1, 2, 3, 4, 5], b: 5, c: { e: 5 } }, `
|
||||
┌─────────┬───┬───┬───┬───┬───┬───┬────────┐
|
||||
│ (index) │ 0 │ 1 │ 2 │ 3 │ 4 │ e │ Values │
|
||||
├─────────┼───┼───┼───┼───┼───┼───┼────────┤
|
||||
│ a │ 1 │ 2 │ 3 │ 4 │ 5 │ │ │
|
||||
│ b │ │ │ │ │ │ │ 5 │
|
||||
│ c │ │ │ │ │ │ 5 │ │
|
||||
└─────────┴───┴───┴───┴───┴───┴───┴────────┘
|
||||
`);
|
||||
|
||||
test(new Uint8Array([1, 2, 3]), `
|
||||
┌─────────┬────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼────────┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 2 │
|
||||
│ 2 │ 3 │
|
||||
└─────────┴────────┘
|
||||
`);
|
||||
|
||||
test(Buffer.from([1, 2, 3]), `
|
||||
┌─────────┬────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼────────┤
|
||||
│ 0 │ 1 │
|
||||
│ 1 │ 2 │
|
||||
│ 2 │ 3 │
|
||||
└─────────┴────────┘
|
||||
`);
|
||||
|
||||
test({ a: undefined }, ['x'], `
|
||||
┌─────────┬───┐
|
||||
│ (index) │ x │
|
||||
├─────────┼───┤
|
||||
│ a │ │
|
||||
└─────────┴───┘
|
||||
`);
|
||||
|
||||
test([], `
|
||||
┌─────────┬────────┐
|
||||
│ (index) │ Values │
|
||||
├─────────┼────────┤
|
||||
└─────────┴────────┘
|
||||
`);
|
||||
|
||||
test(new Map(), `
|
||||
┌───────────────────┬─────┬────────┐
|
||||
│ (iteration index) │ Key │ Values │
|
||||
├───────────────────┼─────┼────────┤
|
||||
└───────────────────┴─────┴────────┘
|
||||
`);
|
Loading…
x
Reference in New Issue
Block a user