Permalink
Browse files

console: add table method

PR-URL: #18137
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
  • Loading branch information...
devsnek committed Jan 13, 2018
1 parent 83d44be commit 97ace0449292eab3f044158ba3787e6af5dd6f3a
Showing with 449 additions and 1 deletion.
  1. +41 −0 doc/api/console.md
  2. +128 −1 lib/console.js
  3. +83 −0 lib/internal/cli_table.js
  4. +1 −0 node.gyp
  5. +196 −0 test/parallel/test-console-table.js
@@ -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 }]);

This comment has been minimized.

@sant123

sant123 Apr 26, 2018

Should be

console.table([{ a: 1, b: 'Y' }, { a: 'Z', b: 2 }], ['a', 'b']);

Right?

This comment has been minimized.

@Trott

Trott Apr 26, 2018

Member

Sure seems like it. Pull request fixing that line in the docs would be welcome!

This comment has been minimized.

@sant123

This comment has been minimized.

@BridgeAR

BridgeAR Apr 26, 2018

Member

It is correct as it is right now. a and b are present as properties on the objects.

This comment has been minimized.

@sant123

sant123 Apr 26, 2018

Sure, but it doesn't print the result below.

This comment has been minimized.

@BridgeAR

BridgeAR Apr 26, 2018

Member

image

Looks fine to me and is the same behavior as in chrome.

This comment has been minimized.

@sant123

sant123 Apr 26, 2018

Not at all

image

This comment has been minimized.

@BridgeAR

BridgeAR Apr 26, 2018

Member

Oh, I checked the wrong part :D

// ┌─────────┬─────┬─────┐
// │ (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
@@ -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;
@@ -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;
@@ -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',
Oops, something went wrong.

0 comments on commit 97ace04

Please sign in to comment.