| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent c128942 commit 00705a4
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 | |
|---|---|---|---|
@@ -2520,6 +2520,9 @@ added: | |||
| 2520 | 2520 | - v21.7.0 | |
| 2521 | 2521 | - v20.12.0 | |
| 2522 | 2522 | changes: | |
| 2523 | + - version: REPLACEME | ||
| 2524 | + pr-url: https://github.com/nodejs/node/pull/61556 | ||
| 2525 | + description: Add support for hexadecimal colors. | ||
| 2523 | 2526 | - version: v24.2.0 | |
| 2524 | 2527 | pr-url: https://github.com/nodejs/node/pull/58437 | |
| 2525 | 2528 | description: Added the `'none'` format as a non-op format. | |
@@ -2537,7 +2540,8 @@ changes: | |||
| 2537 | 2540 | --> | |
| 2538 | 2541 | ||
| 2539 | 2542 | * `format` {string | Array} A text format or an Array | |
| 2540 | - of text formats defined in `util.inspect.colors`. | ||
| 2543 | + of text formats defined in `util.inspect.colors`, or a hex color in `#RGB` | ||
| 2544 | + or `#RRGGBB` form. | ||
| 2541 | 2545 | * `text` {string} The text to to be formatted. | |
| 2542 | 2546 | * `options` {Object} | |
| 2543 | 2547 | * `validateStream` {boolean} When true, `stream` is checked to see if it can handle colors. **Default:** `true`. | |
@@ -2600,6 +2604,30 @@ console.log( | |||
| 2600 | 2604 | ||
| 2601 | 2605 | The special format value `none` applies no additional styling to the text. | |
| 2602 | 2606 | ||
| 2607 | + In addition to predefined color names, `util.styleText()` supports hex color | ||
| 2608 | + strings using ANSI TrueColor (24-bit) escape sequences. Hex colors can be | ||
| 2609 | + specified in either 3-digit (`#RGB`) or 6-digit (`#RRGGBB`) format: | ||
| 2610 | + | ||
| 2611 | + ```mjs | ||
| 2612 | + import { styleText } from 'node:util'; | ||
| 2613 | + | ||
| 2614 | + // 6-digit hex color | ||
| 2615 | + console.log(styleText('#ff5733', 'Orange text')); | ||
| 2616 | + | ||
| 2617 | + // 3-digit hex color (shorthand) | ||
| 2618 | + console.log(styleText('#f00', 'Red text')); | ||
| 2619 | + ``` | ||
| 2620 | + | ||
| 2621 | + ```cjs | ||
| 2622 | + const { styleText } = require('node:util'); | ||
| 2623 | + | ||
| 2624 | + // 6-digit hex color | ||
| 2625 | + console.log(styleText('#ff5733', 'Orange text')); | ||
| 2626 | + | ||
| 2627 | + // 3-digit hex color (shorthand) | ||
| 2628 | + console.log(styleText('#f00', 'Red text')); | ||
| 2629 | + ``` | ||
| 2630 | + | ||
| 2603 | 2631 | The full list of formats can be found in [modifiers][]. | |
| 2604 | 2632 | ||
| 2605 | 2633 | ## 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