shelving
Version:
Toolkit for using data in JavaScript.
93 lines (92 loc) • 3.69 kB
JavaScript
/** Debug a random value as a string. */
export function debug(value, depth = 1) {
if (value === null)
return "null";
if (value === undefined)
return "undefined";
if (typeof value === "boolean")
return value ? "true" : "false";
if (typeof value === "string")
return debugString(value);
if (typeof value === "number")
return value.toString();
if (typeof value === "symbol")
return value.toString();
if (typeof value === "function")
return `function ${value.name || ""}()`;
if (typeof value === "object") {
if (value instanceof Date)
return value.toISOString();
if (value instanceof Error)
return value.toString();
// biome-ignore lint/suspicious/useIsArray: Intential in this context.
if (value instanceof Array)
return debugArray(value, depth);
if (value instanceof Map)
return debugMap(value, depth);
if (value instanceof Set)
return debugSet(value, depth);
return debugObject(value, depth);
}
return typeof value;
}
/** Debug a string. */
export const debugString = (value) => `"${value.replace(ESCAPE_REGEXP, _escapeChar)}"`;
// biome-ignore lint/suspicious/noControlCharactersInRegex: Intentional.
const ESCAPE_REGEXP = /[\x00-\x08\x0B-\x1F\x7F-\x9F"\\]/g; // Match control characters, `"` double quote, `\` backslash.
const ESCAPE_LIST = {
'"': '\\"',
"\\": "\\\\",
"\r": "\\r",
"\n": "\\n",
"\t": "\\t",
"\b": "\\b",
"\f": "\\f",
"\v": "\\v",
};
const _escapeChar = (char) => ESCAPE_LIST[char] || `\\x${char.charCodeAt(0).toString(16).padStart(2, "00")}`;
/** Debug an array. */
export function debugArray(value, depth = 1) {
const prototype = Object.getPrototypeOf(value);
const name = prototype === Array.prototype ? "" : prototype.constructor.name || "";
const items = depth > 0 && value.length ? value.map(v => debug(v, depth - 1)).join(",\n\t") : "";
return `${name ? `${name} ` : ""}${value.length ? `[\n\t${items}\n]` : "[]"}`;
}
/** Debug a set. */
export function debugSet(value, depth = 1) {
const prototype = Object.getPrototypeOf(value);
const name = prototype === Set.prototype ? "" : prototype.constructor.name || "Set";
const items = depth > 0 && value.size
? Array.from(value)
.map(v => debug(v, depth - 1))
.join(",\n\t")
: "";
return `${name}(value.size) ${items ? `{\n\t${items}\n}` : "{}"}`;
}
/** Debug a map. */
export function debugMap(value, depth = 1) {
const prototype = Object.getPrototypeOf(value);
const name = prototype === Map.prototype ? "" : prototype.constructor.name || "Map";
const entries = depth > 0 && value.size
? Array.from(value)
.map(([k, v]) => `${debug(k)}: ${debug(v, depth - 1)}`)
.join(",\n\t")
: "";
return `${name}(value.size) ${entries ? `{\n\t${entries}\n}` : "{}"}`;
}
/** Debug an object. */
export function debugObject(value, depth = 1) {
const prototype = Object.getPrototypeOf(value);
const name = prototype === Object.prototype ? "" : prototype.constructor.name || "";
const entries = depth > 0
? Object.entries(value)
.map(([k, v]) => `${debug(k)}: ${debug(v, depth - 1)}`)
.join(",\n\t")
: "";
return `${name ? `${name} ` : ""}${entries ? `{\n\t${entries}\n}` : "{}"}`;
}
/** If a string is multiline, push it onto the next line and prepend a tab to each line.. */
export function indent(str) {
const lines = str.split("\n");
return lines.length > 1 ? `\n${lines.join("\n\t")}` : ` ${str}`;
}