aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGus Caplan <me@gus.host>2018-01-13 13:55:19 -0600
committerGus Caplan <me@gus.host>2018-03-30 19:41:41 -0500
commit97ace0449292eab3f044158ba3787e6af5dd6f3a (patch)
treecd6150997307934e3f1998252d3465709469916b /lib
parent83d44bee0134e3b8b6b98bf89e1f02981d72adee (diff)
downloadandroid-node-v8-97ace0449292eab3f044158ba3787e6af5dd6f3a.tar.gz
android-node-v8-97ace0449292eab3f044158ba3787e6af5dd6f3a.tar.bz2
android-node-v8-97ace0449292eab3f044158ba3787e6af5dd6f3a.zip
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>
Diffstat (limited to 'lib')
-rw-r--r--lib/console.js129
-rw-r--r--lib/internal/cli_table.js83
2 files changed, 211 insertions, 1 deletions
diff --git a/lib/console.js b/lib/console.js
index d70a6b30b7..456c0cc439 100644
--- a/lib/console.js
+++ b/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;
diff --git a/lib/internal/cli_table.js b/lib/internal/cli_table.js
new file mode 100644
index 0000000000..4c07d92eeb
--- /dev/null
+++ b/lib/internal/cli_table.js
@@ -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;