summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
Diffstat (limited to 'js')
-rw-r--r--js/console.ts94
-rw-r--r--js/console_table.ts95
-rw-r--r--js/console_test.ts4
3 files changed, 192 insertions, 1 deletions
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");