| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 28346d9 commit b85c73f
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,7 +7,7 @@ const assert = require('node:assert'); | |||
| 7 | 7 | ||
| 8 | 8 | const bench = common.createBenchmark(main, { | |
| 9 | 9 | messageType: ['string', 'number', 'boolean', 'invalid'], | |
| 10 | - format: ['red', 'italic', 'invalid'], | ||
| 10 | + format: ['red', 'italic', 'invalid', '#ff0000'], | ||
| 11 | 11 | validateStream: [1, 0], | |
| 12 | 12 | n: [1e3], | |
| 13 | 13 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2531,6 +2531,9 @@ added: | |||
| 2531 | 2531 | - v21.7.0 | |
| 2532 | 2532 | - v20.12.0 | |
| 2533 | 2533 | changes: | |
| 2534 | + - version: REPLACEME | ||
| 2535 | + pr-url: https://github.com/nodejs/node/pull/61556 | ||
| 2536 | + description: Add support for hexadecimal colors. | ||
| 2534 | 2537 | - version: | |
| 2535 | 2538 | - v24.2.0 | |
| 2536 | 2539 | - v22.17.0 | |
@@ -2550,7 +2553,8 @@ changes: | |||
| 2550 | 2553 | --> | |
| 2551 | 2554 | ||
| 2552 | 2555 | * `format` {string | Array} A text format or an Array | |
| 2553 | - of text formats defined in `util.inspect.colors`. | ||
| 2556 | + of text formats defined in `util.inspect.colors`, or a hex color in `#RGB` | ||
| 2557 | + or `#RRGGBB` form. | ||
| 2554 | 2558 | * `text` {string} The text to to be formatted. | |
| 2555 | 2559 | * `options` {Object} | |
| 2556 | 2560 | * `validateStream` {boolean} When true, `stream` is checked to see if it can handle colors. **Default:** `true`. | |
@@ -2613,6 +2617,30 @@ console.log( | |||
| 2613 | 2617 | ||
| 2614 | 2618 | The special format value `none` applies no additional styling to the text. | |
| 2615 | 2619 | ||
| 2620 | + In addition to predefined color names, `util.styleText()` supports hex color | ||
| 2621 | + strings using ANSI TrueColor (24-bit) escape sequences. Hex colors can be | ||
| 2622 | + specified in either 3-digit (`#RGB`) or 6-digit (`#RRGGBB`) format: | ||
| 2623 | + | ||
| 2624 | + ```mjs | ||
| 2625 | + import { styleText } from 'node:util'; | ||
| 2626 | + | ||
| 2627 | + // 6-digit hex color | ||
| 2628 | + console.log(styleText('#ff5733', 'Orange text')); | ||
| 2629 | + | ||
| 2630 | + // 3-digit hex color (shorthand) | ||
| 2631 | + console.log(styleText('#f00', 'Red text')); | ||
| 2632 | + ``` | ||
| 2633 | + | ||
| 2634 | + ```cjs | ||
| 2635 | + const { styleText } = require('node:util'); | ||
| 2636 | + | ||
| 2637 | + // 6-digit hex color | ||
| 2638 | + console.log(styleText('#ff5733', 'Orange text')); | ||
| 2639 | + | ||
| 2640 | + // 3-digit hex color (shorthand) | ||
| 2641 | + console.log(styleText('#f00', 'Red text')); | ||
| 2642 | + ``` | ||
| 2643 | + | ||
| 2616 | 2644 | The full list of formats can be found in [modifiers][]. | |
| 2617 | 2645 | ||
| 2618 | 2646 | ## Class: `util.TextDecoder` | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -37,6 +37,8 @@ const { | |||
| 37 | 37 | ObjectSetPrototypeOf, | |
| 38 | 38 | ObjectValues, | |
| 39 | 39 | ReflectApply, | |
| 40 | + RegExpPrototypeExec, | ||
| 41 | + StringPrototypeSlice, | ||
| 40 | 42 | StringPrototypeToWellFormed, | |
| 41 | 43 | } = primordials; | |
| 42 | 44 | ||
@@ -46,10 +48,12 @@ const { | |||
| 46 | 48 | codes: { | |
| 47 | 49 | ERR_FALSY_VALUE_REJECTION, | |
| 48 | 50 | ERR_INVALID_ARG_TYPE, | |
| 51 | + ERR_INVALID_ARG_VALUE, | ||
| 49 | 52 | ERR_OUT_OF_RANGE, | |
| 50 | 53 | }, | |
| 51 | 54 | isErrorStackTraceLimitWritable, | |
| 52 | 55 | } = require('internal/errors'); | |
| 56 | + const { Buffer } = require('buffer'); | ||
| 53 | 57 | const { | |
| 54 | 58 | format, | |
| 55 | 59 | formatWithOptions, | |
@@ -156,6 +160,51 @@ function replaceCloseCode(str, closeSeq, openSeq, keepClose) { | |||
| 156 | 160 | return result + str.slice(lastIndex); | |
| 157 | 161 | } | |
| 158 | 162 | ||
| 163 | + // Matches #RGB or #RRGGBB | ||
| 164 | + const hexColorRegExp = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; | ||
| 165 | + | ||
| 166 | + /** | ||
| 167 | + * Validates whether a string is a valid hex color code. | ||
| 168 | + * @param {string} hex The hex string to validate (e.g., '#fff' or '#ffffff') | ||
| 169 | + * @returns {boolean} True if valid hex color, false otherwise | ||
| 170 | + */ | ||
| 171 | + function isValidHexColor(hex) { | ||
| 172 | + return typeof hex === 'string' && RegExpPrototypeExec(hexColorRegExp, hex) !== null; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + /** | ||
| 176 | + * Parses a hex color string into RGB components. | ||
| 177 | + * Supports both 3-digit (#RGB) and 6-digit (#RRGGBB) formats. | ||
| 178 | + * @param {string} hex A valid hex color string | ||
| 179 | + * @returns {Buffer} The RGB components | ||
| 180 | + */ | ||
| 181 | + function hexToRgb(hex) { | ||
| 182 | + // Normalize to 6 digits | ||
| 183 | + let hexStr; | ||
| 184 | + if (hex.length === 4) { | ||
| 185 | + // Expand #RGB to #RRGGBB | ||
| 186 | + hexStr = hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3]; | ||
| 187 | + } else if (hex.length === 7) { | ||
| 188 | + hexStr = StringPrototypeSlice(hex, 1); | ||
| 189 | + } else { | ||
| 190 | + throw new ERR_OUT_OF_RANGE('hex', '#RGB or #RRGGBB', hex); | ||
| 191 | + } | ||
| 192 | + | ||
| 193 | + // TODO(araujogui): use Uint8Array.fromHex | ||
| 194 | + return Buffer.from(hexStr, 'hex'); | ||
| 195 | + } | ||
| 196 | + | ||
| 197 | + /** | ||
| 198 | + * Generates the ANSI TrueColor (24-bit) escape sequence for a foreground color. | ||
| 199 | + * @param {number} r Red component (0-255) | ||
| 200 | + * @param {number} g Green component (0-255) | ||
| 201 | + * @param {number} b Blue component (0-255) | ||
| 202 | + * @returns {string} The ANSI escape sequence | ||
| 203 | + */ | ||
| 204 | + function rgbToAnsi24Bit(r, g, b) { | ||
| 205 | + return `38;2;${r};${g};${b}`; | ||
| 206 | + } | ||
| 207 | + | ||
| 159 | 208 | /** | |
| 160 | 209 | * @param {string | string[]} format | |
| 161 | 210 | * @param {string} text | |
@@ -205,8 +254,25 @@ function styleText(format, text, options) { | |||
| 205 | 254 | ||
| 206 | 255 | for (const key of formatArray) { | |
| 207 | 256 | if (key === 'none') continue; | |
| 257 | + | ||
| 258 | + if (isValidHexColor(key)) { | ||
| 259 | + if (skipColorize) continue; | ||
| 260 | + const { 0: r, 1: g, 2: b } = hexToRgb(key); | ||
| 261 | + const openSeq = kEscape + rgbToAnsi24Bit(r, g, b) + kEscapeEnd; | ||
| 262 | + const closeSeq = kEscape + '39' + kEscapeEnd; | ||
| 263 | + openCodes += openSeq; | ||
| 264 | + closeCodes = closeSeq + closeCodes; | ||
| 265 | + processedText = replaceCloseCode(processedText, closeSeq, openSeq, false); | ||
| 266 | + continue; | ||
| 267 | + } | ||
| 268 | + | ||
| 208 | 269 | const style = cache[key]; | |
| 209 | 270 | if (style === undefined) { | |
| 271 | + // Check if it looks like an invalid hex color (starts with #) | ||
| 272 | + if (typeof key === 'string' && key[0] === '#') { | ||
| 273 | + throw new ERR_INVALID_ARG_VALUE('format', key, | ||
| 274 | + 'must be a valid hex color (#RGB or #RRGGBB)'); | ||
| 275 | + } | ||
| 210 | 276 | validateOneOf(key, 'format', ObjectGetOwnPropertyNames(inspect.colors)); | |
| 211 | 277 | } | |
| 212 | 278 | openCodes += style.openSeq; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments