tiny-essentials
Version:
Collection of small, essential scripts designed to be used across various projects. These simple utilities are crafted for speed, ease of use, and versatility.
197 lines (171 loc) • 6.09 kB
JavaScript
;
/**
* @typedef {Record<string, string>} ColorsList
* Represents a mapping of color keys to ANSI escape codes.
*/
class ColorSafeStringify {
/**
* Currently active color configuration.
* @type {ColorsList}
*/
#colors;
/**
* Preset collections (internal and user-defined).
* @type {Record<string, ColorsList>}
* @static
*/
static #PRESETS = {
default: {
reset: '\x1b[0m',
key: '\x1b[36m', // Cyan (object keys)
string: '\x1b[32m', // Green (regular strings)
string_url: '\x1b[34m', // Blue (URLs)
string_bool: '\x1b[35m', // Magenta (boolean/null in string form)
string_number: '\x1b[33m', // Yellow (numbers in string form)
number: '\x1b[33m', // Yellow (raw numbers)
boolean: '\x1b[35m', // Magenta (true/false)
null: '\x1b[1;30m', // Gray (null)
special: '\x1b[31m', // Red (e.g., [Circular], [undefined])
func: '\x1b[90m', // Dim (function string representations)
},
solarized: {
reset: '\x1b[0m',
key: '\x1b[38;5;37m',
string: '\x1b[38;5;136m',
string_url: '\x1b[38;5;33m',
string_bool: '\x1b[38;5;166m',
string_number: '\x1b[38;5;136m',
number: '\x1b[38;5;136m',
boolean: '\x1b[38;5;166m',
null: '\x1b[38;5;241m',
special: '\x1b[38;5;160m',
func: '\x1b[38;5;244m',
},
monokai: {
reset: '\x1b[0m',
key: '\x1b[38;5;81m',
string: '\x1b[38;5;114m',
string_url: '\x1b[38;5;75m',
string_bool: '\x1b[38;5;204m',
string_number: '\x1b[38;5;221m',
number: '\x1b[38;5;221m',
boolean: '\x1b[38;5;204m',
null: '\x1b[38;5;241m',
special: '\x1b[38;5;160m',
func: '\x1b[38;5;102m',
},
};
/**
* Constructs a new instance with an optional base preset or custom override.
* @param {ColorsList} [defaultColors] - Optional override for the default color scheme.
*/
constructor(defaultColors = {}) {
this.#colors = { ...ColorSafeStringify.#PRESETS.default, ...defaultColors };
}
/**
* Internal method to apply ANSI color codes to different parts of a JSON string.
* @param {string} str - Raw JSON string to be colorized.
* @param {ColorsList} colors - ANSI color mapping to be applied to each JSON element type.
* @returns {string} Colorized JSON string.
*/
#colorizeJSON(str, colors) {
/** @type {{ marker: string, key: string }[]} */
const keyMatches = [];
// Colorize numeric values
str = str.replace(
/(?<!")\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b(?!")/g,
`${colors.number}$1${colors.reset}`,
);
// Replace keys with temporary markers for later colorization
str = str.replace(/"([^"]+)":/g, (_, key) => {
const marker = `___KEY${keyMatches.length}___`;
keyMatches.push({ marker, key });
return `${marker}:`; // Keep the colon for valid syntax
});
// Replace strings and apply specific colors based on their content
str = str.replace(/"(?:\\.|[^"\\])*?"/g, (match) => {
const val = match.slice(1, -1); // Remove surrounding quotes
if (/^(https?|ftp):\/\/[^\s]+$/i.test(val)) {
return `${colors.string_url}${match}${colors.reset}`;
}
if (/^(true|false|null)$/.test(val)) {
return `${colors.string_bool}${match}${colors.reset}`;
}
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(val)) {
return `${colors.string_number}${match}${colors.reset}`;
}
return `${colors.string}${match}${colors.reset}`;
});
// Replace markers with colorized keys
for (const { marker, key } of keyMatches) {
const regex = new RegExp(marker, 'g');
str = str.replace(regex, `${colors.key}"${key}"${colors.reset}`);
}
// Colorize boolean values
str = str.replace(/(?<!")\b(true|false)\b(?!")/g, `${colors.boolean}$1${colors.reset}`);
// Colorize null values
str = str.replace(/(?<!")\bnull\b(?!")/g, `${colors.null}null${colors.reset}`);
// Highlight special placeholder values
str = str.replace(/\[Circular\]/g, `${colors.special}[Circular]${colors.reset}`);
str = str.replace(/\[undefined\]/g, `${colors.special}[undefined]${colors.reset}`);
// Colorize function string representations
str = str.replace(/"function.*?[^\\]"/gs, `${colors.func}$&${colors.reset}`);
return str;
}
/**
* Colorizes a JSON string using the active or optionally overridden color set.
* @param {string} json - The JSON string to format.
* @param {ColorsList} [customColors] - Optional temporary color override.
* @returns {string}
*/
colorize(json, customColors = {}) {
const colors = { ...this.#colors, ...customColors };
return this.#colorizeJSON(json, colors);
}
/**
* Returns the currently active color scheme.
* @returns {ColorsList}
*/
getColors() {
return { ...this.#colors };
}
/**
* Updates the current color scheme with a partial override.
* @param {Partial<ColorsList>} newColors
*/
updateColors(newColors) {
Object.assign(this.#colors, newColors);
}
/**
* Resets the current color scheme to the default preset.
*/
resetColors() {
this.#colors = { ...ColorSafeStringify.#PRESETS.default };
}
/**
* Loads a color preset by name.
* @param {string} presetName - Name of the preset to load.
* @throws Will throw if the preset doesn't exist.
*/
loadColorPreset(presetName) {
const preset = ColorSafeStringify.#PRESETS[presetName];
if (!preset) throw new Error(`Preset "${presetName}" not found.`);
this.#colors = { ...preset };
}
/**
* Saves a new custom color preset.
* @param {string} name - Name of the new preset.
* @param {ColorsList} colors - ANSI color map to save.
*/
saveColorPreset(name, colors) {
ColorSafeStringify.#PRESETS[name] = { ...colors };
}
/**
* Returns a list of all available color preset names.
* @returns {string[]}
*/
getAvailablePresets() {
return Object.keys(ColorSafeStringify.#PRESETS);
}
}
module.exports = ColorSafeStringify;