Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -2870,6 +2870,48 @@ Returns the `string` after replacing any surrogate code points
(or equivalently, any unpaired surrogate code units) with the
Unicode "replacement character" U+FFFD.

## `util.table(tabularData[, properties][, options])`

<!-- YAML
added: REPLACEME
-->

* `tabularData` {any} The data to render. Arrays, objects, `Map`s and `Set`s
are rendered as tables; any other value is returned as inspected text.
* `properties` {string\[]} An alternate list of properties to use as the table's
columns. By default every own enumerable property of the rows is used.
* `options` {Object} Options forwarded to [`util.inspect()`][] when formatting
each cell.
* Returns: {string}

Returns tabular data formatted with the same layout as [`console.table()`][],
but as a string instead of writing it to a stream. This is useful for logging,
writing to a file, or building output without a console.

```mjs
import { table } from 'node:util';

console.log(table([
{ name: 'alice', age: 30 },
{ name: 'bob', age: 25 },
]));
// ┌─────────┬─────────┬─────┐
// │ (index) │ name │ age │
// ├─────────┼─────────┼─────┤
// │ 0 │ 'alice' │ 30 │
// │ 1 │ 'bob' │ 25 │
// └─────────┴─────────┴─────┘
```

```cjs
const { table } = require('node:util');

console.log(table([
{ name: 'alice', age: 30 },
{ name: 'bob', age: 25 },
]));
```

## `util.transferableAbortController()`

<!-- YAML
Expand Down Expand Up @@ -3914,6 +3956,7 @@ npx codemod@latest @nodejs/util-is
[`Runtime.ScriptId`]: https://chromedevtools.github.io/devtools-protocol/1-3/Runtime/#type-ScriptId
[`assert.deepStrictEqual()`]: assert.md#assertdeepstrictequalactual-expected-message
[`console.error()`]: console.md#consoleerrordata-args
[`console.table()`]: console.md#consoletabletabulardata-properties
[`mime.toString()`]: #mimetostring
[`mimeParams.entries()`]: #mimeparamsentries
[`napi_create_external()`]: n-api.md#napi_create_external
Expand Down
126 changes: 6 additions & 120 deletions lib/internal/console/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
// console. It's exported for backwards compatibility.

const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypeForEach,
ArrayPrototypePush,
ArrayPrototypeUnshift,
Boolean,
ErrorCaptureStackTrace,
Expand All @@ -17,8 +14,6 @@ const {
ObjectDefineProperties,
ObjectDefineProperty,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectValues,
ReflectApply,
ReflectConstruct,
ReflectOwnKeys,
Expand Down Expand Up @@ -49,14 +44,12 @@ const {
validateObject,
validateOneOf,
} = require('internal/validators');
const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
const {
inspect,
formatWithOptions,
} = require('internal/util/inspect');
const {
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
isMap,
} = require('internal/util/types');
const {
CHAR_UPPERCASE_C: kTraceCount,
Expand All @@ -76,7 +69,7 @@ const kTraceConsoleCategory = 'node,node.console';
const kMaxGroupIndentation = 1000;

// Lazy loaded for startup performance.
let cliTable;
let buildTable;

let utilColors;
function lazyUtilColors() {
Expand Down Expand Up @@ -558,120 +551,13 @@ const consoleMethods = {
if (tabularData === null || typeof tabularData !== 'object')
return this.log(tabularData);

cliTable ??= require('internal/cli_table');
const final = (k, v) => this.log(cliTable(k, v));

const _inspect = (v) => {
const depth = v !== null &&
typeof v === 'object' &&
!isArray(v) &&
ObjectKeys(v).length > 2 ? -1 : 0;
const opt = {
depth,
maxArrayLength: 3,
breakLength: Infinity,
...this[kGetInspectOptions](this._stdout),
};
return inspect(v, opt);
};
const getIndexArray = (length) => ArrayFrom(
{ length }, (_, i) => _inspect(i));

const mapIter = isMapIterator(tabularData);
let isKeyValue = false;
let i = 0;
if (mapIter) {
const res = previewEntries(tabularData, true);
tabularData = res[0];
isKeyValue = res[1];
}

if (isKeyValue || isMap(tabularData)) {
const keys = [];
const values = [];
let length = 0;
if (mapIter) {
for (; i < tabularData.length / 2; ++i) {
ArrayPrototypePush(keys, _inspect(tabularData[i * 2]));
ArrayPrototypePush(values, _inspect(tabularData[i * 2 + 1]));
length++;
}
} else {
for (const { 0: k, 1: v } of tabularData) {
ArrayPrototypePush(keys, _inspect(k));
ArrayPrototypePush(values, _inspect(v));
length++;
}
}
return final([
iterKey, keyKey, valuesKey,
], [
getIndexArray(length),
keys,
values,
]);
}

const setIter = isSetIterator(tabularData);
if (setIter)
tabularData = previewEntries(tabularData);

const setlike = setIter || mapIter || isSet(tabularData);
if (setlike) {
const values = [];
let length = 0;
for (const v of tabularData) {
ArrayPrototypePush(values, _inspect(v));
length++;
}
return final([iterKey, valuesKey], [getIndexArray(length), values]);
}

const map = { __proto__: null };
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);

for (; 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) {
map[key] ??= [];
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}

const keys = ObjectKeys(map);
const values = ObjectValues(map);
if (hasPrimitives) {
ArrayPrototypePush(keys, valuesKey);
ArrayPrototypePush(values, valuesKeyArray);
}
ArrayPrototypeUnshift(keys, indexKey);
ArrayPrototypeUnshift(values, indexKeyArray);

return final(keys, values);
buildTable ??= require('internal/util/table');
return this.log(
buildTable(tabularData, properties, this[kGetInspectOptions](this._stdout)),
);
},
};

const keyKey = 'Key';
const valuesKey = 'Values';
const indexKey = '(index)';
const iterKey = '(iteration index)';

const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);

function noop() {}

for (const method of ReflectOwnKeys(consoleMethods))
Expand Down
150 changes: 150 additions & 0 deletions lib/internal/util/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use strict';

const {
ArrayFrom,
ArrayIsArray,
ArrayPrototypePush,
ArrayPrototypeUnshift,
ObjectKeys,
ObjectPrototypeHasOwnProperty,
ObjectValues,
SetPrototypeForEach,
} = primordials;

const { previewEntries } = internalBinding('util');
const { Buffer: { isBuffer } } = require('buffer');
const { inspect } = require('internal/util/inspect');
const {
isTypedArray, isSet, isMap, isSetIterator, isMapIterator,
} = require('internal/util/types');

const keyKey = 'Key';
const valuesKey = 'Values';
const indexKey = '(index)';
const iterKey = '(iteration index)';

const isArray = (v) => ArrayIsArray(v) || isTypedArray(v) || isBuffer(v);

/**
* Builds the rows of a table from tabular data and renders them into a string.
* Shared by `console.table` and `util.table`.
* @param {any} tabularData The data to tabulate.
* @param {string[]} [properties] The subset of properties to include as columns.
* @param {object} [inspectOptions] Base options merged into each cell's inspect call.
* @returns {string} The rendered table.
*/
function table(tabularData, properties, inspectOptions) {
// Lazy require to avoid a cycle with the console bootstrap path.
const cliTable = require('internal/cli_table');

const _inspect = (v) => {
const depth = v !== null &&
typeof v === 'object' &&
!isArray(v) &&
ObjectKeys(v).length > 2 ? -1 : 0;
const opt = {
depth,
maxArrayLength: 3,
breakLength: Infinity,
...inspectOptions,
};
return inspect(v, opt);
};
const getIndexArray = (length) => ArrayFrom(
{ length }, (_, i) => _inspect(i));

const mapIter = isMapIterator(tabularData);
let isMapLike;
if (mapIter) {
const res = previewEntries(tabularData, true);
tabularData = res[0];
isMapLike = res[1];
} else {
isMapLike = isMap(tabularData);
}

if (isMapLike) {
const keys = [];
const values = [];
let length = 0;
if (mapIter) {
for (let i = 0; i < tabularData.length / 2; ++i) {
ArrayPrototypePush(keys, _inspect(tabularData[i * 2]));
ArrayPrototypePush(values, _inspect(tabularData[i * 2 + 1]));
length++;
}
} else {
for (const { 0: k, 1: v } of tabularData) {
ArrayPrototypePush(keys, _inspect(k));
ArrayPrototypePush(values, _inspect(v));
length++;
}
}
return cliTable([
iterKey, keyKey, valuesKey,
], [
getIndexArray(length),
keys,
values,
]);
}

const setIter = isSetIterator(tabularData);
if (setIter)
tabularData = previewEntries(tabularData);

const setlike = setIter || mapIter || isSet(tabularData);
if (setlike) {
const values = [];
if (setIter || mapIter) {
// `previewEntries` already yielded an array, so read it by index.
for (let j = 0; j < tabularData.length; j++)
ArrayPrototypePush(values, _inspect(tabularData[j]));
} else {
// A real Set: iterate with `SetPrototypeForEach` to avoid the iterator
// protocol and a second pass over the data.
SetPrototypeForEach(tabularData,
(v) => ArrayPrototypePush(values, _inspect(v)));
}
return cliTable([iterKey, valuesKey], [getIndexArray(values.length), values]);
}
Comment thread
aduh95 marked this conversation as resolved.

const map = { __proto__: null };
let hasPrimitives = false;
const valuesKeyArray = [];
const indexKeyArray = ObjectKeys(tabularData);

for (let 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 (let k = 0; k < keys.length; k++) {
const key = keys[k];
map[key] ??= [];
if ((primitive && properties) ||
!ObjectPrototypeHasOwnProperty(item, key))
map[key][i] = '';
else
map[key][i] = _inspect(item[key]);
}
}
}

const keys = ObjectKeys(map);
const values = ObjectValues(map);
if (hasPrimitives) {
ArrayPrototypePush(keys, valuesKey);
ArrayPrototypePush(values, valuesKeyArray);
}
ArrayPrototypeUnshift(keys, indexKey);
ArrayPrototypeUnshift(values, indexKeyArray);

return cliTable(keys, values);
}

module.exports = table;
Loading
Loading