-
Notifications
You must be signed in to change notification settings - Fork 30.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
console: add table method #18137
console: add table method #18137
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest to have a identical behavior for The only difference necessary is the name for the index. That way the output becomes way more coherent. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: add a special case in case no keys exist from the item and to show the item in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i assume you mean for non-arrays and non-objects? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I mean e.g. an empty array ( Another point: this will not work with Set / Map / SetIterator / MapIterator. |
||
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; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we had the discussion about using Primordials or not but as long as we do not really have anything decided there: shall we really do this right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
until we come to consensus on that conversation i'm going to continue saving commonly messed-with methods