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.
|
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)
|
### console.time(label)
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.1.104
|
added: v0.1.104
|
||||||
|
129
lib/console.js
129
lib/console.js
@ -23,11 +23,31 @@
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
isStackOverflowError,
|
isStackOverflowError,
|
||||||
codes: { ERR_CONSOLE_WRITABLE_STREAM },
|
codes: {
|
||||||
|
ERR_CONSOLE_WRITABLE_STREAM,
|
||||||
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
},
|
||||||
} = require('internal/errors');
|
} = 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 util = require('util');
|
||||||
|
const {
|
||||||
|
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
|
||||||
|
} = util.types;
|
||||||
const kCounts = Symbol('counts');
|
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()`.
|
// Track amount of indentation required via `console.group()`.
|
||||||
const kGroupIndent = Symbol('groupIndent');
|
const kGroupIndent = Symbol('groupIndent');
|
||||||
|
|
||||||
@ -242,6 +262,113 @@ Console.prototype.groupEnd = function groupEnd() {
|
|||||||
this[kGroupIndent].slice(0, this[kGroupIndent].length - 2);
|
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 = new Console(process.stdout, process.stderr);
|
||||||
module.exports.Console = Console;
|
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/zlib.js',
|
||||||
'lib/internal/async_hooks.js',
|
'lib/internal/async_hooks.js',
|
||||||
'lib/internal/buffer.js',
|
'lib/internal/buffer.js',
|
||||||
|
'lib/internal/cli_table.js',
|
||||||
'lib/internal/child_process.js',
|
'lib/internal/child_process.js',
|
||||||
'lib/internal/cluster/child.js',
|
'lib/internal/cluster/child.js',
|
||||||
'lib/internal/cluster/master.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