UNPKG

jsm-treeify

Version:

A library to display JavaScript objects as colorized, tree-like structures in the console.

258 lines (257 loc) 9.71 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitize = void 0; const ansi_code_1 = require("ansi-code"); /* -------------------------------------------------------------------------- */ /* TYPES */ /* -------------------------------------------------------------------------- */ var Color; (function (Color) { Color["Gray"] = "gray"; Color["Green"] = "green"; Color["Red"] = "red"; Color["Yellow"] = "yellow"; Color["Cyan"] = "cyan"; Color["Blue"] = "blue"; Color["Reset"] = "reset"; Color["Default"] = "default"; })(Color || (Color = {})); /* -------------------------------------------------------------------------- */ /* UTILITIES */ /* -------------------------------------------------------------------------- */ /** * Sanitizes an object by removing circular references and handling edge cases * @param obj - The object to sanitize * @param maxDepth - Maximum depth to traverse (prevents infinite recursion) * @returns Sanitized object safe for JSON operations */ function sanitize(obj, maxDepth = 10) { const seen = new WeakSet(); function sanitizeRecursive(value, depth) { // Prevent infinite recursion if (depth >= maxDepth) { return '[Max Depth Reached]'; } // Handle primitives if (value === null || value === undefined) { return value; } // Handle non-object types if (typeof value !== 'object') { return value; } // Handle functions if (typeof value === 'function') { return `[Function: ${value.name || 'anonymous'}]`; } // Handle dates if (value instanceof Date) { return value; } // Handle circular references if (seen.has(value)) { return '[Circular Reference]'; } seen.add(value); try { if (Array.isArray(value)) { return value.map(item => sanitizeRecursive(item, depth + 1)); } // Handle regular objects const sanitized = {}; for (const key in value) { if (value.hasOwnProperty(key)) { sanitized[key] = sanitizeRecursive(value[key], depth + 1); } } return sanitized; } finally { seen.delete(value); } } return sanitizeRecursive(obj, 0); } exports.sanitize = sanitize; /** * Determines if a value is a simple (primitive) type * @param value - The value to check * @returns True if the value is a simple type */ function isSimpleType(value) { return (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || typeof value === 'undefined' || value === null || value instanceof Date); } /** * Determines the appropriate color for a value based on its type * @param value - The value to determine color for * @returns The color name to use */ function getValueColor(value) { if (value === null || value === undefined) { return Color.Gray; } switch (typeof value) { case 'string': return Color.Cyan; case 'number': return Color.Yellow; case 'boolean': return value ? Color.Green : Color.Red; default: if (value instanceof Date) { return Color.Blue; } return Color.Gray; } } /** * Formats a value for display in the tree * @param value - The value to format * @returns Formatted string representation */ function formatValue(value) { if (value === null) { return '(null)'; } if (value === undefined) { return '(undefined)'; } if (value instanceof Date) { return value.toISOString(); } return String(value); } /** * Transforms an object into a colored tree-like string representation. * * @param obj - The object to transform. * @param prefix - The prefix string for the current level (used for indentation). * @param isLast - Boolean indicating if the current element is the last in its level. * @param isRoot - Boolean indicating if the current element the root element. * @param options - Optional configuration for tree generation. * * @returns An array of strings representing the lines of the colored tree. */ function colorizeTree(obj, prefix = '', isLast = true, isRoot = true, options = {}) { const { maxDepth = 10, showArrayIndices = false, sortKeys = false } = options; // ANSI color codes const colors = { [Color.Gray]: ansi_code_1.font.color.gray, [Color.Green]: ansi_code_1.font.color.green, [Color.Red]: ansi_code_1.font.color.red, [Color.Yellow]: ansi_code_1.font.color.yellow, [Color.Cyan]: ansi_code_1.font.color.cyan, [Color.Blue]: ansi_code_1.font.color.blue, [Color.Reset]: ansi_code_1.font.reset, }; function colorize(text, color) { if (color === Color.Default) { return String(text); } return colors[color] + String(text) + colors[Color.Reset]; } // Check for max depth to prevent infinite recursion const currentDepth = (prefix.match(/[│├└]/g) || []).length; if (currentDepth >= maxDepth) { return [prefix + colorize('[Max Depth Reached]', Color.Gray)]; } // Handle null and undefined at the top level if (obj === null) { return [prefix + colorize('(null)', Color.Gray)]; } if (obj === undefined) { return [prefix + colorize('(undefined)', Color.Gray)]; } // Handle simple types if (isSimpleType(obj)) { const color = getValueColor(obj); const value = formatValue(obj); return [prefix + colorize(value, color)]; } // Handle objects and arrays if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return renderArray(obj, prefix, isLast, isRoot, showArrayIndices); } else { // Sanitize object to handle circular references const sanitizedObj = sanitize(obj, maxDepth); return renderObject(sanitizedObj, prefix, isLast, isRoot, sortKeys); } } // Fallback for unknown types return [prefix + colorize(String(obj), Color.Gray)]; /** * Renders an array as tree lines */ function renderArray(arr, prefix, isLast, isRoot, showIndices) { const lines = []; if (arr.length === 0) { lines.push(prefix + colorize("(empty array)", Color.Gray)); return lines; } for (let i = 0; i < arr.length; i++) { const isLastItem = i === arr.length - 1; const connector = isLastItem ? colorize('└─ ', Color.Gray) : colorize('├─ ', Color.Gray); const nextPrefix = prefix + (isLastItem ? ' ' : colorize('│ ', Color.Gray)); const indexLabel = showIndices ? colorize(`[${i}] `, Color.Gray) : ''; if (isSimpleType(arr[i])) { const color = getValueColor(arr[i]); const value = formatValue(arr[i]); lines.push(prefix + connector + indexLabel + colorize(value, color)); } else { lines.push(prefix + connector + indexLabel); lines.push(...colorizeTree(arr[i], nextPrefix, isLastItem, false, options)); } } return lines; } /** * Renders an object as tree lines */ function renderObject(obj, prefix, isLast, isRoot, shouldSortKeys) { const lines = []; let keys = Object.keys(obj); if (shouldSortKeys) { keys = keys.sort(); } if (keys.length === 0) { lines.push(prefix + colorize("(empty object)", Color.Gray)); return lines; } for (let i = 0; i < keys.length; i++) { const key = keys[i]; const value = obj[key]; const isLastKey = i === keys.length - 1; const connector = isLastKey ? colorize('└─ ', Color.Gray) : colorize('├─ ', Color.Gray); const nextPrefix = prefix + (isLastKey ? ' ' : colorize('│ ', Color.Gray)); if (value === null || value === undefined) { const displayValue = formatValue(value); lines.push(prefix + connector + colorize(key, Color.Default) + ': ' + colorize(displayValue, Color.Gray)); } else if (isSimpleType(value)) { const color = getValueColor(value); const displayValue = formatValue(value); lines.push(prefix + connector + colorize(key, Color.Default) + ': ' + colorize(displayValue, color)); } else if (Array.isArray(value) && value.length === 0) { lines.push(prefix + connector + colorize(key, Color.Default) + ': ' + colorize("(empty array)", Color.Gray)); } else if (typeof value === 'object' && Object.keys(value).length === 0) { lines.push(prefix + connector + colorize(key, Color.Default) + ': ' + colorize("(empty object)", Color.Gray)); } else { lines.push(prefix + connector + colorize(key, Color.Default) + ':'); lines.push(...colorizeTree(value, nextPrefix, isLastKey, false, options)); } } return lines; } } exports.default = colorizeTree;