├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── debug.ts
├── debug_test.ts
├── demo.png
├── demo.ts
├── format.ts
├── format_test.ts
├── test.ts
└── utils.ts
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | install:
4 | - curl -L https://deno.land/x/install/install.sh | sh
5 | - export PATH="$HOME/.deno/bin:$PATH"
6 |
7 | script:
8 | - deno --version
9 | - make test
10 |
11 | cache:
12 | directories:
13 | - "$HOME/.deno"
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Nikola Ristic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: fmt test
2 |
3 | demo:
4 | DEBUG=* deno run --allow-env ./demo.ts
5 |
6 | test:
7 | DEBUG=* deno test --allow-env
8 |
9 | fmt:
10 | deno fmt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deno Debug [](https://travis-ci.org/rista404/deno-debug)
2 |
3 |
4 |
5 | Debug utility for deno.
6 |
7 | ## Usage
8 |
9 | ```javascript
10 | import debug from "https://deno.land/x/debuglog/debug.ts";
11 |
12 | // create debugger
13 | const service = debug("service");
14 |
15 | const serviceName = "app";
16 |
17 | // log
18 | service("booting %s", serviceName);
19 | ```
20 |
21 | Then run your app.
22 |
23 | ```sh
24 | > DEBUG=* deno run --allow-env app.ts
25 | ```
26 |
27 | ## Todo
28 |
29 | - [x] extending debuggers
30 | - [x] custom log functions
31 | - [x] custom formatters
32 | - [x] `log` override in all namespaces
33 | - [x] inspect opts
34 | - [x] detecting color support
35 | - [ ] non-tty env
36 | - [x] add `debug` to registry
37 |
38 | ## Notes
39 |
40 | - Currently debug assumes it is TTY and shows colors by default.
41 | - Deno's `inspect` differs from node's `util.inspect` so the output may not be
42 | the same.
43 | - We're using a custom `format` function ported from `util`. Might be cool to
44 | extract it when `util` is ported entirely.
45 | - We should cover more functionality with tests.
46 |
--------------------------------------------------------------------------------
/debug.ts:
--------------------------------------------------------------------------------
1 | const { noColor } = Deno;
2 | import { ms } from "https://deno.land/x/ms/ms.ts";
3 | import format from "./format.ts";
4 | import { coerce, regexpToNamespace, selectColor } from "./utils.ts";
5 |
6 | const DEBUG = {
7 | debug: "",
8 | };
9 |
10 | interface DebugInstance {
11 | (log: string | Error, ...args: any[]): void;
12 | namespace: string;
13 | enabled: boolean;
14 | color: number;
15 | destroy: () => boolean;
16 | extend: (namespace: string, delimiter?: string) => DebugInstance;
17 | log?: Function;
18 | }
19 |
20 | interface DebugModule {
21 | (namespace: string): DebugInstance;
22 | enable: (namespaces: any) => void;
23 | disable: () => string;
24 | enabled: (namespace: string) => boolean;
25 | names: RegExp[];
26 | skips: RegExp[];
27 | formatters: Formatters;
28 | log: Function;
29 | }
30 |
31 | interface Formatters {
32 | [key: string]: (value: any) => string;
33 | }
34 |
35 | /**
36 | * Active `debug` instances.
37 | */
38 | let instances: DebugInstance[] = [];
39 |
40 | /**
41 | * The currently active debug mode names, and names to skip.
42 | */
43 | let names: RegExp[] = [];
44 | let skips: RegExp[] = [];
45 |
46 | /**
47 | * Map of special "%n" handling functions, for the debug "format" argument.
48 | *
49 | * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
50 | */
51 | let formatters: Formatters = {};
52 |
53 | /**
54 | * Create a debugger with the given `namespace`.
55 | */
56 | function createDebug(namespace: string): DebugInstance {
57 | let prevTime: number;
58 |
59 | let debug: DebugInstance;
60 |
61 | // @ts-ignore-next-line
62 | debug = function (log: string | Error, ...args: any[]) {
63 | // Skip if debugger is disabled
64 | if (!debug.enabled) {
65 | return;
66 | }
67 |
68 | const self = debug;
69 |
70 | log = coerce(log);
71 |
72 | if (typeof log !== "string") {
73 | // Anything else let's inspect with %O
74 | args.unshift(log);
75 | log = "%O";
76 | }
77 |
78 | // Set `diff` timestamp
79 | const currTime = Number(Date.now());
80 | // Difference in miliseconds
81 | const diff = currTime - (prevTime || currTime);
82 | prevTime = currTime;
83 |
84 | // Apply all custom formatters to our arguments
85 | const customFormattedArgs = applyFormatters.call(self, log, ...args);
86 | const { namespace, color } = self;
87 |
88 | // Format the string before logging
89 | const formattedArgs = formatArgs(
90 | { namespace, color, diff },
91 | customFormattedArgs,
92 | );
93 |
94 | // Use a custom logger if defined
95 | // If not, we use the default logger
96 | const logFn = self.log || debugModule.log;
97 |
98 | // Finally, log
99 | logFn.apply(self, formattedArgs);
100 | return;
101 | };
102 |
103 | debug.namespace = namespace;
104 | debug.color = selectColor(namespace);
105 | debug.enabled = enabled(namespace);
106 | debug.destroy = destroy;
107 | debug.extend = extend;
108 |
109 | instances.push(debug);
110 |
111 | return debug;
112 | }
113 |
114 | function destroy(this: DebugInstance) {
115 | if (instances.includes(this)) {
116 | this.enabled = false;
117 | instances = instances.filter((instance) => instance !== this);
118 | return true;
119 | }
120 | return false;
121 | }
122 |
123 | /**
124 | * const server = debug('server');
125 | * const serverHttp = server.extend('http') // server:http
126 | * const serverHttpReq = serverHttp.extend('req', '-') // server:http-req
127 | */
128 | function extend(
129 | this: DebugInstance,
130 | subNamespace: string,
131 | delimiter: string = ":",
132 | ) {
133 | const newNamespace = `${this.namespace}${delimiter}${subNamespace}`;
134 | const newDebug = createDebug(newNamespace);
135 | // Pass down the custom logger
136 | newDebug.log = this.log;
137 | return newDebug;
138 | }
139 |
140 | function applyFormatters(this: DebugInstance, fmt: string, ...args: any[]) {
141 | let index = 0;
142 | const newFmt = fmt.replace(/%([a-zA-Z%])/g, (match, format) => {
143 | // If we encounter an escaped % then don't increase the array index
144 | if (match === "%%") {
145 | return match;
146 | }
147 |
148 | const formatter = formatters[format];
149 |
150 | if (typeof formatter === "function") {
151 | const value = args[index];
152 | // Remove the argument we used in the custom formatter
153 | args = [...args.slice(0, index), ...args.slice(index + 1)];
154 | return formatter.call(this, value);
155 | }
156 |
157 | index++;
158 | return match;
159 | });
160 |
161 | // Return the update fmt string and updated args
162 | return [newFmt, ...args];
163 | }
164 |
165 | /**
166 | * Returns true if the given mode name is enabled, false otherwise.
167 | */
168 | export function enabled(namespace: string): boolean {
169 | if (namespace[namespace.length - 1] === "*") {
170 | return true;
171 | }
172 |
173 | for (const skip of skips) {
174 | if (skip.test(namespace)) {
175 | return false;
176 | }
177 | }
178 | for (const name of names) {
179 | if (name.test(namespace)) {
180 | return true;
181 | }
182 | }
183 |
184 | return false;
185 | }
186 |
187 | /**
188 | * Enables a debug mode by namespaces. This can include modes
189 | * separated by a colon and wildcards.
190 | */
191 | export function enable(namespaces: any) {
192 | updateNamespacesEnv(namespaces);
193 |
194 | // Resets enabled and disable namespaces
195 | names = [];
196 | skips = [];
197 | // Splits on comma
198 | // Loops through the passed namespaces
199 | // And groups them in enabled and disabled lists
200 | (typeof namespaces === "string" ? namespaces : "")
201 | .split(/[\s,]+/)
202 | // Ignore empty strings
203 | .filter(Boolean)
204 | .map((namespace) => namespace.replace(/\*/g, ".*?"))
205 | .forEach((ns) => {
206 | // If a namespace starts with `-`, we should disable that namespace
207 | if (ns[0] === "-") {
208 | skips.push(new RegExp("^" + ns.slice(1) + "$"));
209 | } else {
210 | names.push(new RegExp("^" + ns + "$"));
211 | }
212 | });
213 |
214 | instances.forEach((instance) => {
215 | instance.enabled = enabled(instance.namespace);
216 | });
217 | }
218 |
219 | interface FormatArgsOptions {
220 | namespace: string;
221 | color: number;
222 | diff: number;
223 | }
224 |
225 | function formatArgs(
226 | { namespace, color, diff }: FormatArgsOptions,
227 | args: any[],
228 | ): any[] {
229 | const colorCode = "\u001B[3" + (color < 8 ? color : "8;5;" + color);
230 | const prefix = noColor
231 | ? ` ${namespace} `
232 | : ` ${colorCode};1m${namespace} \u001B[0m`;
233 | // Add a prefix on every line
234 | args[0] = args[0]
235 | .split("\n")
236 | .map((line: string) => `${prefix}${line}`)
237 | .join("\n");
238 |
239 | const lastArg = noColor
240 | ? `+${ms(diff)}`
241 | : `${colorCode}m+${ms(diff)}${"\u001B[0m"}`;
242 |
243 | return [...args, lastArg];
244 | }
245 |
246 | /**
247 | * Disable debug output.
248 | */
249 | export function disable(): string {
250 | const namespaces = [
251 | ...names.map(regexpToNamespace),
252 | ...skips.map(regexpToNamespace).map((namespace) => `-${namespace}`),
253 | ].join(",");
254 | enable("");
255 | return namespaces;
256 | }
257 |
258 | /**
259 | * Save `namespaces` to env.
260 | */
261 | function updateNamespacesEnv(namespaces: string): void {
262 | if (namespaces) {
263 | DEBUG.debug = namespaces;
264 | } else {
265 | DEBUG.debug = "";
266 | }
267 | }
268 |
269 | // Default logger
270 | function log(...args: any[]): void {
271 | const result = format(...args);
272 | console.log(result);
273 | }
274 |
275 | // Exports
276 |
277 | const debugModule: DebugModule = Object.assign(createDebug, {
278 | enable,
279 | disable,
280 | enabled,
281 | names,
282 | skips,
283 | formatters,
284 | log,
285 | });
286 |
287 | // Enable namespaces passed from env
288 | try {
289 | const setting = Deno.env.get("DEBUG");
290 | if (setting) {
291 | DEBUG.debug = setting;
292 | }
293 | } catch (err) {}
294 |
295 | enable(DEBUG.debug);
296 |
297 | export default debugModule;
298 |
--------------------------------------------------------------------------------
/debug_test.ts:
--------------------------------------------------------------------------------
1 | // Copied from https://github.com/defunctzombie/node-util/blob/master/test/node/format.js
2 | import {
3 | assert,
4 | assertEquals,
5 | assertStrictEquals,
6 | } from "https://deno.land/std/testing/asserts.ts";
7 | import debug from "./debug.ts";
8 |
9 | Deno.test("passes a basic sanity check", function () {
10 | const log = debug("test");
11 | log.enabled = true;
12 | log.log = () => {};
13 |
14 | log("hello world");
15 | });
16 |
17 | Deno.test("allows namespaces to be a non-string value", function () {
18 | const log = debug("test");
19 | log.enabled = true;
20 | log.log = () => {};
21 |
22 | debug.enable(true);
23 | });
24 |
25 | Deno.test("logger should handle error as the first param", function () {
26 | const log = debug("test");
27 | log.enabled = true;
28 | const messages: any[][] = [];
29 | log.log = (...args: any[]) => messages.push(args);
30 |
31 | log(new Error());
32 |
33 | assertEquals(typeof messages[0][0], "string");
34 | assertEquals(typeof messages[0][1], "string");
35 | });
36 |
37 | Deno.test("honors global debug namespace enable calls", function () {
38 | assertEquals(debug("test:12345").enabled, false);
39 | assertEquals(debug("test:67890").enabled, false);
40 |
41 | debug.enable("test:12345");
42 | assertEquals(debug("test:12345").enabled, true);
43 | assertEquals(debug("test:67890").enabled, false);
44 | });
45 |
46 | Deno.test("uses custom log function", function () {
47 | const log = debug("test");
48 | log.enabled = true;
49 |
50 | const messages = [];
51 | log.log = (...args: any[]) => messages.push(args);
52 |
53 | log("using custom log function");
54 | log("using custom log function again");
55 | log("%O", 12345);
56 |
57 | assertEquals(messages.length, 3);
58 | });
59 |
60 | // Extending
61 |
62 | Deno.test("extend should extend namespace", function () {
63 | const log = debug("foo");
64 | log.enabled = true;
65 | log.log = () => {};
66 |
67 | const logBar = log.extend("bar");
68 | assertEquals(logBar.namespace, "foo:bar");
69 | });
70 |
71 | Deno.test("extend should extend namespace with custom delimiter", function () {
72 | const log = debug("foo");
73 | log.enabled = true;
74 | log.log = () => {};
75 |
76 | const logBar = log.extend("bar", "--");
77 | assertEquals(logBar.namespace, "foo--bar");
78 | });
79 |
80 | Deno.test("extend should extend namespace with empty delimiter", function () {
81 | const log = debug("foo");
82 | log.enabled = true;
83 | log.log = () => {};
84 |
85 | const logBar = log.extend("bar", "");
86 | assertStrictEquals(logBar.namespace, "foobar");
87 | });
88 |
89 | Deno.test(
90 | "extend should keep the log function between extensions",
91 | function () {
92 | const log = debug("foo");
93 | log.log = () => {};
94 |
95 | const logBar = log.extend("bar");
96 | assertStrictEquals(log.log, logBar.log);
97 | },
98 | );
99 |
100 | // log.destroy()
101 |
102 | Deno.test("destroy works", function () {
103 | const log = debug("test");
104 | log.enabled = true;
105 |
106 | const messages = [];
107 | log.log = (...args: any[]) => messages.push(args);
108 |
109 | log("using custom log function");
110 | log("using custom log function again");
111 |
112 | log.destroy();
113 |
114 | log("using custom log function");
115 |
116 | assertEquals(messages.length, 2);
117 | });
118 |
119 | // debug.enable
120 |
121 | Deno.test("enable handles empty", function () {
122 | debug.enable("");
123 | assertEquals(debug.names, []);
124 | assertEquals(debug.skips, []);
125 | });
126 |
127 | Deno.test("enable works", function () {
128 | assertEquals(debug.enabled("test"), false);
129 |
130 | debug.enable("test");
131 | assertEquals(debug.enabled("test"), true);
132 |
133 | debug.disable();
134 | assertEquals(debug.enabled("test"), false);
135 | });
136 |
137 | // debug.disable
138 |
139 | Deno.test(
140 | "disable should keep the log function between extensions",
141 | function () {
142 | debug.enable("test,abc*,-abc");
143 | const namespaces = debug.disable();
144 | assertEquals(namespaces, "test,abc*,-abc");
145 | },
146 | );
147 |
148 | Deno.test("disable handles empty", function () {
149 | debug.enable("");
150 | const namespaces = debug.disable();
151 | assertEquals(namespaces, "");
152 | assertEquals(debug.names, []);
153 | assertEquals(debug.skips, []);
154 | });
155 |
156 | Deno.test("disable handles all", function () {
157 | debug.enable("*");
158 | const namespaces = debug.disable();
159 | assertEquals(namespaces, "*");
160 | });
161 |
162 | Deno.test("disable handles skip all", function () {
163 | debug.enable("-*");
164 | const namespaces = debug.disable();
165 | assertEquals(namespaces, "-*");
166 | });
167 |
168 | Deno.test("properly skips logging if all is disabled", function () {
169 | debug.enable("-*");
170 | const log = debug("test");
171 |
172 | const messages = [];
173 | log.log = (...args: any[]) => messages.push(args);
174 |
175 | log("using custom log function");
176 | log("using custom log function again");
177 | log("%O", 12345);
178 |
179 | assertEquals(messages.length, 0);
180 |
181 | debug.enable("test");
182 | debug.disable();
183 |
184 | log("using custom log function");
185 | log("using custom log function again");
186 | log("%O", 12345);
187 |
188 | assertEquals(messages.length, 0);
189 | });
190 |
191 | Deno.test("names+skips same with new string", function () {
192 | debug.enable("test,abc*,-abc");
193 | const oldNames = [...debug.names];
194 | const oldSkips = [...debug.skips];
195 | const namespaces = debug.disable();
196 | assertEquals(namespaces, "test,abc*,-abc");
197 | debug.enable(namespaces);
198 | assertEquals(oldNames.map(String), debug.names.map(String));
199 | assertEquals(oldSkips.map(String), debug.skips.map(String));
200 | });
201 |
202 | // custom formatters
203 |
204 | Deno.test("adds a custom formatter", function () {
205 | const log = debug("test");
206 | log.enabled = true;
207 | const messages: any[][] = [];
208 | log.log = (...args: any[]) => messages.push(args);
209 |
210 | debug.formatters.t = function (v: any) {
211 | return `test`;
212 | };
213 | debug.formatters.w = (v) => {
214 | return v + 5;
215 | };
216 | log("this is: %t", "this will be ignored");
217 | log("this is: %w", 5);
218 |
219 | assert(messages[0][0].includes("this is: test"));
220 | assert(messages[1][0].includes("this is: 10"));
221 | });
222 |
223 | Deno.test("formatters can access logger on this", function () {
224 | const log = debug("test");
225 | log.enabled = true;
226 | log.log = () => {};
227 |
228 | debug.formatters.t = function (v: any) {
229 | assertStrictEquals(this as any, log as any);
230 | return `test`;
231 | };
232 | log("this is: %t", "this will be ignored");
233 | });
234 |
235 | // Custom global logger
236 |
237 | Deno.test("overrides all per-namespace log settings", function () {
238 | const loger1 = debug("test");
239 | loger1.enabled = true;
240 | const loger2 = debug("test2");
241 | loger2.enabled = true;
242 |
243 | const messages = [];
244 |
245 | debug.log = (...args: any[]) => messages.push(args);
246 |
247 | loger1("using custom log function");
248 | loger2("using custom log function again");
249 | loger1("%O", 12345);
250 |
251 | assertEquals(messages.length, 3);
252 | });
253 |
--------------------------------------------------------------------------------
/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rista404/deno-debug/2feca67fe85af6b7c6a2473cd8ad94061daf7382/demo.png
--------------------------------------------------------------------------------
/demo.ts:
--------------------------------------------------------------------------------
1 | import debug from "./debug.ts";
2 |
3 | function sleep(ms: number) {
4 | return new Promise((r) => {
5 | setTimeout(() => r(), ms);
6 | });
7 | }
8 |
9 | // Not actually a test, more of a demo
10 | // Should improve it
11 |
12 | const msg1 = "Doing lots of work";
13 | const msg2 = "Doing lots of other work";
14 |
15 | async function demo() {
16 | const http = debug("http");
17 | const dotenv = debug("dotenv");
18 | const worker = debug("worker");
19 | const workerApi = worker.extend("api");
20 | const workerGen = worker.extend("gen");
21 |
22 | dotenv("env variables loaded");
23 | http("booting %s", `'My App'`);
24 |
25 | await sleep(200);
26 | workerApi(msg1);
27 |
28 | await sleep(44);
29 | workerGen(msg1);
30 |
31 | await sleep(99);
32 |
33 | workerApi(msg1);
34 | await sleep(1200);
35 | workerApi(msg1);
36 | await sleep(55);
37 | workerApi(msg1);
38 | await sleep(12);
39 | workerApi(msg1);
40 | await sleep(92);
41 | workerApi(msg1);
42 |
43 | await sleep(58);
44 |
45 | workerGen(msg1);
46 |
47 | workerApi(msg1);
48 |
49 | await sleep(233);
50 |
51 | workerGen(msg2);
52 |
53 | const nestedObj: any = { a: { b: { c: { d: { e: { f: 42 } } } } } };
54 | workerGen("event: %O", nestedObj);
55 | http("received event: %O", {
56 | bar: true,
57 | justify: "column",
58 | align: { center: true },
59 | });
60 | await sleep(100);
61 | http("received event: %O", {
62 | bar: true,
63 | justify: "column",
64 | align: { center: true },
65 | });
66 |
67 | // TODO
68 | // const obj = { a: 1 };
69 | // const obj2 = { b: 2 };
70 | // const weakSet = new WeakSet([obj, obj2]);
71 | // workerB("Object with hidden fields: %O", weakSet);
72 |
73 | workerApi(`${msg1}
74 | request: /multi-line-comment
75 | response: /working`);
76 |
77 | workerGen("wooooorking");
78 |
79 | await sleep(500);
80 |
81 | await sleep(220);
82 |
83 | workerGen(msg2);
84 |
85 | await sleep(88);
86 |
87 | workerGen(msg2);
88 | workerApi(msg2);
89 | workerGen(msg2);
90 | workerGen(msg2);
91 |
92 | sleep(55555);
93 | return;
94 | }
95 |
96 | demo();
97 |
--------------------------------------------------------------------------------
/format.ts:
--------------------------------------------------------------------------------
1 | // Copied from https://github.com/defunctzombie/node-util/blob/master/util.js
2 | // Modified to format %o and %O as deno objects
3 | const { inspect } = Deno;
4 | import { getInspectOpts } from "./utils.ts";
5 |
6 | const inspectOpts = getInspectOpts();
7 | const formatRegExp = /%[sdjoO%]/g;
8 |
9 | export default function format(...args: any[]) {
10 | if (typeof args[0] !== "string") {
11 | let objects = [];
12 | for (let i = 0; i < arguments.length; i++) {
13 | objects.push(inspect(arguments[i], inspectOpts));
14 | }
15 | return objects.join(" ");
16 | }
17 |
18 | let i = 1;
19 | const f = args[0];
20 | const len = args.length;
21 | let str = String(f).replace(formatRegExp, function (x) {
22 | if (x === "%%") return "%";
23 | if (i >= len) return x;
24 | switch (x) {
25 | case "%s":
26 | return String(args[i++]);
27 | case "%d":
28 | return String(Number(args[i++]));
29 | case "%j":
30 | try {
31 | return JSON.stringify(args[i++]);
32 | } catch (_) {
33 | return "[Circular]";
34 | }
35 | case "%o":
36 | case "%O":
37 | return inspect(args[i++], inspectOpts);
38 | default:
39 | return x;
40 | }
41 | });
42 | for (let x = args[i]; i < len; x = args[++i]) {
43 | if (x == null || typeof x !== "object") {
44 | str += " " + x;
45 | } else {
46 | str += " " + inspect(x);
47 | }
48 | }
49 | return str;
50 | }
51 |
--------------------------------------------------------------------------------
/format_test.ts:
--------------------------------------------------------------------------------
1 | // Copied from https://github.com/defunctzombie/node-util/blob/master/test/node/format.js
2 | import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
3 | import format from "./format.ts";
4 |
5 | Deno.test("testFormat", function () {
6 | assertEquals(format(), "");
7 | assertEquals(format(""), "");
8 | assertEquals(format([]), "[]");
9 | assertEquals(format({}), "{}");
10 | assertEquals(format(null), "null");
11 | assertEquals(format(true), "true");
12 | assertEquals(format(false), "false");
13 | assertEquals(format("test"), "test");
14 |
15 | // CHECKME this is for console.log() compatibility - but is it *right*?
16 | assertEquals(format("foo", "bar", "baz"), "foo bar baz");
17 |
18 | assertEquals(format("%d", 42.0), "42");
19 | assertEquals(format("%d", 42), "42");
20 | assertEquals(format("%s", 42), "42");
21 | assertEquals(format("%j", 42), "42");
22 |
23 | assertEquals(format("%d", "42.0"), "42");
24 | assertEquals(format("%d", "42"), "42");
25 | assertEquals(format("%s", "42"), "42");
26 | assertEquals(format("%j", "42"), '"42"');
27 |
28 | assertEquals(format("%%s%s", "foo"), "%sfoo");
29 |
30 | assertEquals(format("%s"), "%s");
31 | assertEquals(format("%s", undefined), "undefined");
32 | assertEquals(format("%s", "foo"), "foo");
33 | assertEquals(format("%s:%s"), "%s:%s");
34 | assertEquals(format("%s:%s", undefined), "undefined:%s");
35 | assertEquals(format("%s:%s", "foo"), "foo:%s");
36 | assertEquals(format("%s:%s", "foo", "bar"), "foo:bar");
37 | assertEquals(format("%s:%s", "foo", "bar", "baz"), "foo:bar baz");
38 | assertEquals(format("%%%s%%", "hi"), "%hi%");
39 | assertEquals(format("%%%s%%%%", "hi"), "%hi%%");
40 |
41 | (function () {
42 | let o: { o?: any } = {};
43 | o.o = o;
44 | assertEquals(format("%j", o), "[Circular]");
45 | })();
46 |
47 | // Ignore erros until we see should util package be ported
48 | // or a replacement found
49 |
50 | // Errors
51 | // assertEquals(format(new Error('foo')), 'Error: foo');
52 | // class CustomError extends Error {
53 | // constructor(msg) {
54 | // super(msg);
55 | // Object.defineProperty(this, 'message', { value: msg, enumerable: false });
56 | // Object.defineProperty(this, 'name', { value: 'CustomError', enumerable: false });
57 | // }
58 | // }
59 | // assertEquals(format(new CustomError('bar')), 'CustomError: bar');
60 | });
61 |
--------------------------------------------------------------------------------
/test.ts:
--------------------------------------------------------------------------------
1 | import "./debug_test.ts";
2 | import "./format_test.ts";
3 |
--------------------------------------------------------------------------------
/utils.ts:
--------------------------------------------------------------------------------
1 | const { env } = Deno;
2 | import { camelCase } from "https://raw.githubusercontent.com/denolib/camelcase/master/mod.ts";
3 |
4 | // We assume the terminal supports colors
5 | export const colors = [
6 | 20,
7 | 21,
8 | 26,
9 | 27,
10 | 32,
11 | 33,
12 | 38,
13 | 39,
14 | 40,
15 | 41,
16 | 42,
17 | 43,
18 | 44,
19 | 45,
20 | 56,
21 | 57,
22 | 62,
23 | 63,
24 | 68,
25 | 69,
26 | 74,
27 | 75,
28 | 76,
29 | 77,
30 | 78,
31 | 79,
32 | 80,
33 | 81,
34 | 92,
35 | 93,
36 | 98,
37 | 99,
38 | 112,
39 | 113,
40 | 128,
41 | 129,
42 | 134,
43 | 135,
44 | 148,
45 | 149,
46 | 160,
47 | 161,
48 | 162,
49 | 163,
50 | 164,
51 | 165,
52 | 166,
53 | 167,
54 | 168,
55 | 169,
56 | 170,
57 | 171,
58 | 172,
59 | 173,
60 | 178,
61 | 179,
62 | 184,
63 | 185,
64 | 196,
65 | 197,
66 | 198,
67 | 199,
68 | 200,
69 | 201,
70 | 202,
71 | 203,
72 | 204,
73 | 205,
74 | 206,
75 | 207,
76 | 208,
77 | 209,
78 | 214,
79 | 215,
80 | 220,
81 | 221,
82 | ];
83 |
84 | /**
85 | * Selects a color for a debug namespace
86 | */
87 | export function selectColor(namespace: string): number {
88 | let hash = 0;
89 |
90 | for (let i = 0; i < namespace.length; i++) {
91 | hash = (hash << 5) - hash + namespace.charCodeAt(i);
92 | hash |= 0; // Convert to 32bit integer
93 | }
94 |
95 | return colors[Math.abs(hash) % colors.length];
96 | }
97 |
98 | export function getInspectOpts(): Deno.InspectOptions {
99 | const currentEnv = env.toObject();
100 | const inspectOpts: Deno.InspectOptions = Object.keys(currentEnv)
101 | .filter((key) => /^debug_/i.test(key))
102 | .reduce((obj: { [key: string]: number | boolean | null }, key) => {
103 | const prop = camelCase(key.slice(6));
104 |
105 | let envVar: string = currentEnv[key];
106 | let val: boolean | number | null;
107 | if (/^(yes|on|true|enabled)$/i.test(envVar)) {
108 | val = true;
109 | } else if (/^(no|off|false|disabled)$/i.test(envVar)) {
110 | val = false;
111 | } else if (envVar === "null") {
112 | val = null;
113 | } else {
114 | val = Number(envVar);
115 | }
116 |
117 | obj[prop] = val;
118 | return obj;
119 | }, {});
120 | return inspectOpts;
121 | }
122 |
123 | /**
124 | * Coerce `val`.
125 | */
126 | export function coerce(val: any): any {
127 | if (val instanceof Error) {
128 | return val.stack || val.message;
129 | }
130 | return val;
131 | }
132 |
133 | /**
134 | * Convert regexp to namespace
135 | */
136 | export function regexpToNamespace(regexp: RegExp): string {
137 | return regexp
138 | .toString()
139 | .substring(2, regexp.toString().length - 2)
140 | .replace(/\.\*\?$/, "*");
141 | }
142 |
--------------------------------------------------------------------------------