mirror of
https://github.com/denoland/deno.git
synced 2025-01-03 04:48:52 -05:00
Add console.table (#1608)
This commit is contained in:
parent
7c4265159a
commit
f05fd7a1f3
7 changed files with 285 additions and 1 deletions
1
BUILD.gn
1
BUILD.gn
|
@ -53,6 +53,7 @@ ts_sources = [
|
||||||
"js/blob.ts",
|
"js/blob.ts",
|
||||||
"js/buffer.ts",
|
"js/buffer.ts",
|
||||||
"js/chmod.ts",
|
"js/chmod.ts",
|
||||||
|
"js/console_table.ts",
|
||||||
"js/compiler.ts",
|
"js/compiler.ts",
|
||||||
"js/console.ts",
|
"js/console.ts",
|
||||||
"js/copy_file.ts",
|
"js/copy_file.ts",
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { isTypedArray } from "./util";
|
import { isTypedArray } from "./util";
|
||||||
import { TextEncoder } from "./text_encoding";
|
import { TextEncoder } from "./text_encoding";
|
||||||
import { File, stdout } from "./files";
|
import { File, stdout } from "./files";
|
||||||
|
import { cliTable } from "./console_table";
|
||||||
|
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
type ConsoleContext = Set<any>;
|
type ConsoleContext = Set<any>;
|
||||||
|
@ -591,6 +592,99 @@ export class Console {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
table = (data: any, properties?: string[]): void => {
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
type Value = any;
|
||||||
|
|
||||||
|
if (properties !== undefined && !Array.isArray(properties)) {
|
||||||
|
throw new Error(
|
||||||
|
"The 'properties' argument must be of type Array\
|
||||||
|
. Received type string"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data === null || typeof data !== "object") {
|
||||||
|
return this.log(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const objectValues: { [key: string]: Value[] } = {};
|
||||||
|
const indexKeys: string[] = [];
|
||||||
|
const values: Value[] = [];
|
||||||
|
|
||||||
|
const stringifyValue = (value: Value) =>
|
||||||
|
stringifyWithQuotes(
|
||||||
|
value,
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
new Set<any>(),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
const toTable = (header: string[], body: string[][]) =>
|
||||||
|
this.log(cliTable(header, body));
|
||||||
|
const createColumn = (value: Value, shift?: number): string[] => [
|
||||||
|
...(shift ? [...new Array(shift)].map(() => "") : []),
|
||||||
|
stringifyValue(value)
|
||||||
|
];
|
||||||
|
|
||||||
|
let resultData = data;
|
||||||
|
const isSet = data instanceof Set;
|
||||||
|
const isMap = data instanceof Map;
|
||||||
|
const valuesKey = "Values";
|
||||||
|
const indexKey = isSet || isMap ? "(iteration index)" : "(index)";
|
||||||
|
|
||||||
|
if (isSet) {
|
||||||
|
resultData = [...data];
|
||||||
|
} else if (isMap) {
|
||||||
|
let idx = 0;
|
||||||
|
resultData = {};
|
||||||
|
|
||||||
|
data.forEach((k: Value, v: Value) => {
|
||||||
|
resultData[idx] = { Key: k, Values: v };
|
||||||
|
idx++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(resultData).forEach((k, idx) => {
|
||||||
|
const value = resultData[k];
|
||||||
|
|
||||||
|
if (value !== null && typeof value === "object") {
|
||||||
|
Object.keys(value).forEach(k => {
|
||||||
|
const v = value[k];
|
||||||
|
|
||||||
|
if (properties && !properties.includes(k)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectValues[k]) {
|
||||||
|
objectValues[k].push(stringifyValue(v));
|
||||||
|
} else {
|
||||||
|
objectValues[k] = createColumn(v, idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
values.push("");
|
||||||
|
} else {
|
||||||
|
values.push(stringifyValue(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
indexKeys.push(k);
|
||||||
|
});
|
||||||
|
|
||||||
|
const headerKeys = Object.keys(objectValues);
|
||||||
|
const bodyValues = Object.values(objectValues);
|
||||||
|
const header = [
|
||||||
|
indexKey,
|
||||||
|
...(properties || [
|
||||||
|
...headerKeys,
|
||||||
|
!isMap && values.length > 0 && valuesKey
|
||||||
|
])
|
||||||
|
].filter(Boolean) as string[];
|
||||||
|
const body = [indexKeys, ...bodyValues, values];
|
||||||
|
|
||||||
|
toTable(header, body);
|
||||||
|
};
|
||||||
|
|
||||||
time = (label = "default"): void => {
|
time = (label = "default"): void => {
|
||||||
label = String(label);
|
label = String(label);
|
||||||
|
|
||||||
|
|
95
js/console_table.ts
Normal file
95
js/console_table.ts
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { TextEncoder } from "./text_encoding";
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
|
const tableChars = {
|
||||||
|
middleMiddle: "─",
|
||||||
|
rowMiddle: "┼",
|
||||||
|
topRight: "┐",
|
||||||
|
topLeft: "┌",
|
||||||
|
leftMiddle: "├",
|
||||||
|
topMiddle: "┬",
|
||||||
|
bottomRight: "┘",
|
||||||
|
bottomLeft: "└",
|
||||||
|
bottomMiddle: "┴",
|
||||||
|
rightMiddle: "┤",
|
||||||
|
left: "│ ",
|
||||||
|
right: " │",
|
||||||
|
middle: " │ "
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorRegExp = /\u001b\[\d\d?m/g;
|
||||||
|
|
||||||
|
function removeColors(str: string): string {
|
||||||
|
return str.replace(colorRegExp, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function countBytes(str: string): number {
|
||||||
|
const normalized = removeColors(String(str)).normalize("NFC");
|
||||||
|
|
||||||
|
return encoder.encode(normalized).byteLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderRow(row: string[], columnWidths: number[]): string {
|
||||||
|
let out = tableChars.left;
|
||||||
|
for (let i = 0; i < row.length; i++) {
|
||||||
|
const cell = row[i];
|
||||||
|
const len = countBytes(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cliTable(head: string[], columns: string[][]): string {
|
||||||
|
const rows = [];
|
||||||
|
const columnWidths = head.map((h: string) => countBytes(h));
|
||||||
|
const longestColumn = columns.reduce(
|
||||||
|
(n: number, a: string[]) => Math.max(n, a.length),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < head.length; i++) {
|
||||||
|
const column = columns[i];
|
||||||
|
for (let j = 0; j < longestColumn; j++) {
|
||||||
|
if (rows[j] === undefined) {
|
||||||
|
rows[j] = [];
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-any
|
||||||
|
const value = ((rows[j][i] as any) = column.hasOwnProperty(j)
|
||||||
|
? column[j]
|
||||||
|
: "");
|
||||||
|
const width = columnWidths[i] || 0;
|
||||||
|
const counted = countBytes(value);
|
||||||
|
columnWidths[i] = Math.max(width, counted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const divider = columnWidths.map((i: number) =>
|
||||||
|
tableChars.middleMiddle.repeat(i + 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
let result =
|
||||||
|
`${tableChars.topLeft}${divider.join(tableChars.topMiddle)}` +
|
||||||
|
`${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` +
|
||||||
|
`${tableChars.leftMiddle}${divider.join(tableChars.rowMiddle)}` +
|
||||||
|
`${tableChars.rightMiddle}\n`;
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
result += `${renderRow(row, columnWidths)}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
result +=
|
||||||
|
`${tableChars.bottomLeft}${divider.join(tableChars.bottomMiddle)}` +
|
||||||
|
tableChars.bottomRight;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -116,7 +116,7 @@ test(function consoleTestStringifyCircular() {
|
||||||
assertEqual(
|
assertEqual(
|
||||||
stringify(console),
|
stringify(console),
|
||||||
// tslint:disable-next-line:max-line-length
|
// tslint:disable-next-line:max-line-length
|
||||||
"Console { printFunc: [Function], log: [Function], debug: [Function], info: [Function], dir: [Function], warn: [Function], error: [Function], assert: [Function], count: [Function], countReset: [Function], time: [Function], timeLog: [Function], timeEnd: [Function], group: [Function], groupCollapsed: [Function], groupEnd: [Function], clear: [Function], indentLevel: 0, collapsedAt: null }"
|
"Console { printFunc: [Function], log: [Function], debug: [Function], info: [Function], dir: [Function], warn: [Function], error: [Function], assert: [Function], count: [Function], countReset: [Function], table: [Function], time: [Function], timeLog: [Function], timeEnd: [Function], group: [Function], groupCollapsed: [Function], groupEnd: [Function], clear: [Function], indentLevel: 0, collapsedAt: null }"
|
||||||
);
|
);
|
||||||
// test inspect is working the same
|
// test inspect is working the same
|
||||||
assertEqual(inspect(nestedObj), nestedObjExpected);
|
assertEqual(inspect(nestedObj), nestedObjExpected);
|
||||||
|
@ -278,6 +278,7 @@ test(function consoleDetachedLog() {
|
||||||
const consoleAssert = console.assert;
|
const consoleAssert = console.assert;
|
||||||
const consoleCount = console.count;
|
const consoleCount = console.count;
|
||||||
const consoleCountReset = console.countReset;
|
const consoleCountReset = console.countReset;
|
||||||
|
const consoleTable = console.table;
|
||||||
const consoleTime = console.time;
|
const consoleTime = console.time;
|
||||||
const consoleTimeLog = console.timeLog;
|
const consoleTimeLog = console.timeLog;
|
||||||
const consoleTimeEnd = console.timeEnd;
|
const consoleTimeEnd = console.timeEnd;
|
||||||
|
@ -293,6 +294,7 @@ test(function consoleDetachedLog() {
|
||||||
consoleAssert(true);
|
consoleAssert(true);
|
||||||
consoleCount("Hello world");
|
consoleCount("Hello world");
|
||||||
consoleCountReset("Hello world");
|
consoleCountReset("Hello world");
|
||||||
|
consoleTable({ test: "Hello world" });
|
||||||
consoleTime("Hello world");
|
consoleTime("Hello world");
|
||||||
consoleTimeLog("Hello world");
|
consoleTimeLog("Hello world");
|
||||||
consoleTimeEnd("Hello world");
|
consoleTimeEnd("Hello world");
|
||||||
|
|
3
tests/console_table.test
Normal file
3
tests/console_table.test
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
args: tests/console_table.ts --reload
|
||||||
|
check_stderr: true
|
||||||
|
output: tests/console_table.ts.out
|
18
tests/console_table.ts
Normal file
18
tests/console_table.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
console.table({ a: "test", b: 1 });
|
||||||
|
console.table({ a: { b: 10 }, b: { b: 20, c: 30 } }, ["c"]);
|
||||||
|
console.table([1, 2, [3, [4]], [5, 6], [[7], [8]]]);
|
||||||
|
console.table(new Set([1, 2, 3, "test"]));
|
||||||
|
console.table(new Map([[1, "one"], [2, "two"]]));
|
||||||
|
console.table({
|
||||||
|
a: true,
|
||||||
|
b: { c: { d: 10 }, e: [1, 2, [5, 6]] },
|
||||||
|
f: "test",
|
||||||
|
g: new Set([1, 2, 3, "test"]),
|
||||||
|
h: new Map([[1, "one"]])
|
||||||
|
});
|
||||||
|
console.table([1, "test", false, { a: 10 }, ["test", { b: 20, c: "test" }]]);
|
||||||
|
console.table([]);
|
||||||
|
console.table({});
|
||||||
|
console.table(new Set());
|
||||||
|
console.table(new Map());
|
||||||
|
console.table("test");
|
71
tests/console_table.ts.out
Normal file
71
tests/console_table.ts.out
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
Compiling [WILDCARD].ts
|
||||||
|
┌─────────┬────────┐
|
||||||
|
│ (index) │ Values │
|
||||||
|
├─────────┼────────┤
|
||||||
|
│ a │ "test" │
|
||||||
|
│ b │ 1 │
|
||||||
|
└─────────┴────────┘
|
||||||
|
┌─────────┬────┐
|
||||||
|
│ (index) │ c │
|
||||||
|
├─────────┼────┤
|
||||||
|
│ a │ │
|
||||||
|
│ b │ 30 │
|
||||||
|
└─────────┴────┘
|
||||||
|
┌─────────┬───────┬───────┬────────┐
|
||||||
|
│ (index) │ 0 │ 1 │ Values │
|
||||||
|
├─────────┼───────┼───────┼────────┤
|
||||||
|
│ 0 │ │ │ 1 │
|
||||||
|
│ 1 │ │ │ 2 │
|
||||||
|
│ 2 │ 3 │ [ 4 ] │ │
|
||||||
|
│ 3 │ 5 │ 6 │ │
|
||||||
|
│ 4 │ [ 7 ] │ [ 8 ] │ │
|
||||||
|
└─────────┴───────┴───────┴────────┘
|
||||||
|
┌───────────────────┬────────┐
|
||||||
|
│ (iteration index) │ Values │
|
||||||
|
├───────────────────┼────────┤
|
||||||
|
│ 0 │ 1 │
|
||||||
|
│ 1 │ 2 │
|
||||||
|
│ 2 │ 3 │
|
||||||
|
│ 3 │ "test" │
|
||||||
|
└───────────────────┴────────┘
|
||||||
|
┌───────────────────┬───────┬────────┐
|
||||||
|
│ (iteration index) │ Key │ Values │
|
||||||
|
├───────────────────┼───────┼────────┤
|
||||||
|
│ 0 │ "one" │ 1 │
|
||||||
|
│ 1 │ "two" │ 2 │
|
||||||
|
└───────────────────┴───────┴────────┘
|
||||||
|
┌─────────┬───────────┬───────────────────┬────────┐
|
||||||
|
│ (index) │ c │ e │ Values │
|
||||||
|
├─────────┼───────────┼───────────────────┼────────┤
|
||||||
|
│ a │ │ │ true │
|
||||||
|
│ b │ { d: 10 } │ [ 1, 2, [Array] ] │ │
|
||||||
|
│ f │ │ │ "test" │
|
||||||
|
│ g │ │ │ │
|
||||||
|
│ h │ │ │ │
|
||||||
|
└─────────┴───────────┴───────────────────┴────────┘
|
||||||
|
┌─────────┬────────┬──────────────────────┬────┬────────┐
|
||||||
|
│ (index) │ 0 │ 1 │ a │ Values │
|
||||||
|
├─────────┼────────┼──────────────────────┼────┼────────┤
|
||||||
|
│ 0 │ │ │ │ 1 │
|
||||||
|
│ 1 │ │ │ │ "test" │
|
||||||
|
│ 2 │ │ │ │ false │
|
||||||
|
│ 3 │ │ │ 10 │ │
|
||||||
|
│ 4 │ "test" │ { b: 20, c: "test" } │ │ │
|
||||||
|
└─────────┴────────┴──────────────────────┴────┴────────┘
|
||||||
|
┌─────────┐
|
||||||
|
│ (index) │
|
||||||
|
├─────────┤
|
||||||
|
└─────────┘
|
||||||
|
┌─────────┐
|
||||||
|
│ (index) │
|
||||||
|
├─────────┤
|
||||||
|
└─────────┘
|
||||||
|
┌───────────────────┐
|
||||||
|
│ (iteration index) │
|
||||||
|
├───────────────────┤
|
||||||
|
└───────────────────┘
|
||||||
|
┌───────────────────┐
|
||||||
|
│ (iteration index) │
|
||||||
|
├───────────────────┤
|
||||||
|
└───────────────────┘
|
||||||
|
test
|
Loading…
Reference in a new issue