summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Golovin <golovim@gmail.com>2019-01-29 00:41:29 +0300
committerRyan Dahl <ry@tinyclouds.org>2019-01-28 16:41:29 -0500
commitf05fd7a1f3a1114b3d525613d96f85967c224815 (patch)
tree6d0acc586f8bfebe427ad96d4e78e15898c13130
parent7c4265159a26d2abff51c088f485fe09e9766856 (diff)
Add console.table (#1608)
-rw-r--r--BUILD.gn1
-rw-r--r--js/console.ts94
-rw-r--r--js/console_table.ts95
-rw-r--r--js/console_test.ts4
-rw-r--r--tests/console_table.test3
-rw-r--r--tests/console_table.ts18
-rw-r--r--tests/console_table.ts.out71
7 files changed, 285 insertions, 1 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 405bc5ca3..72517055c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -53,6 +53,7 @@ ts_sources = [
"js/blob.ts",
"js/buffer.ts",
"js/chmod.ts",
+ "js/console_table.ts",
"js/compiler.ts",
"js/console.ts",
"js/copy_file.ts",
diff --git a/js/console.ts b/js/console.ts
index 60d4101f1..0797ab8e9 100644
--- a/js/console.ts
+++ b/js/console.ts
@@ -2,6 +2,7 @@
import { isTypedArray } from "./util";
import { TextEncoder } from "./text_encoding";
import { File, stdout } from "./files";
+import { cliTable } from "./console_table";
// tslint:disable-next-line:no-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 => {
label = String(label);
diff --git a/js/console_table.ts b/js/console_table.ts
new file mode 100644
index 000000000..f04e5915a
--- /dev/null
+++ b/js/console_table.ts
@@ -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;
+}
diff --git a/js/console_test.ts b/js/console_test.ts
index e34e25d8f..ee13c8229 100644
--- a/js/console_test.ts
+++ b/js/console_test.ts
@@ -116,7 +116,7 @@ test(function consoleTestStringifyCircular() {
assertEqual(
stringify(console),
// 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
assertEqual(inspect(nestedObj), nestedObjExpected);
@@ -278,6 +278,7 @@ test(function consoleDetachedLog() {
const consoleAssert = console.assert;
const consoleCount = console.count;
const consoleCountReset = console.countReset;
+ const consoleTable = console.table;
const consoleTime = console.time;
const consoleTimeLog = console.timeLog;
const consoleTimeEnd = console.timeEnd;
@@ -293,6 +294,7 @@ test(function consoleDetachedLog() {
consoleAssert(true);
consoleCount("Hello world");
consoleCountReset("Hello world");
+ consoleTable({ test: "Hello world" });
consoleTime("Hello world");
consoleTimeLog("Hello world");
consoleTimeEnd("Hello world");
diff --git a/tests/console_table.test b/tests/console_table.test
new file mode 100644
index 000000000..4f2c5c4ac
--- /dev/null
+++ b/tests/console_table.test
@@ -0,0 +1,3 @@
+args: tests/console_table.ts --reload
+check_stderr: true
+output: tests/console_table.ts.out
diff --git a/tests/console_table.ts b/tests/console_table.ts
new file mode 100644
index 000000000..6ff391d99
--- /dev/null
+++ b/tests/console_table.ts
@@ -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");
diff --git a/tests/console_table.ts.out b/tests/console_table.ts.out
new file mode 100644
index 000000000..b4651bc65
--- /dev/null
+++ b/tests/console_table.ts.out
@@ -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