Skip to content

Commit 77f2290

Browse files
committed
util: add colorize functionality
The colorize object allows to format strings with ansi color codes so that the string is stylized on a terminal. This is a utility function that should not yet be exposed to the users to allow some further polishing first. Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
1 parent 93728c6 commit 77f2290

File tree

4 files changed

+125
-5
lines changed

4 files changed

+125
-5
lines changed

doc/api/util.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,47 @@ The `--throw-deprecation` command-line flag and `process.throwDeprecation`
239239
property take precedence over `--trace-deprecation` and
240240
`process.traceDeprecation`.
241241

242-
## `util.format(format[, ...args])`
242+
## `util.colorize`
243+
<!-- YAML
244+
added: REPLACEME
245+
-->
246+
247+
The `util.colorize` object provides functions that add ansi color codes to the
248+
provided string. These may be used to style terminal output.
249+
250+
### `util.colorize.<style>[. ...<style>](string[, ...string])`
251+
<!-- YAML
252+
added: REPLACEME
253+
-->
254+
255+
* `string` {string} The string that is formatted by the chosen style.
256+
* Returns: {string} The formatted string
257+
258+
The API allows to be used with a builder/chaining pattern to add multiple styles
259+
in one call. Nesting color codes is supported.
260+
261+
```js
262+
const { colorize } = util;
263+
264+
console.log(
265+
`${colorize.green('Heads up')}: only the "Heads up" is green`
266+
);
267+
268+
console.log(
269+
colorize.green('green', colorize.yellow('yellow'), 'green')
270+
)
271+
272+
console.log(
273+
colorize.bold.underline.red('bold red underline')
274+
)
275+
276+
const info = colorize.italics.blue.bgYellow;
277+
console.log(
278+
info('italic blue with yellow background')
279+
)
280+
```
281+
282+
## `util.format(format[, args...])`
243283

244284
<!-- YAML
245285
added: v0.5.3

lib/internal/debugger/inspect_repl.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ const vm = require('vm');
5656
const { fileURLToPath } = require('internal/url');
5757

5858
const { customInspectSymbol } = require('internal/util');
59-
const { inspect: utilInspect } = require('internal/util/inspect');
59+
const {
60+
inspect: utilInspect,
61+
colorize
62+
} = require('internal/util/inspect');
6063
const debuglog = require('internal/util/debuglog').debuglog('inspect');
6164

6265
const SHORTCUTS = {
@@ -162,7 +165,7 @@ function markSourceColumn(sourceText, position, useColors) {
162165
// Colourize char if stdout supports colours
163166
if (useColors) {
164167
tail = RegExpPrototypeSymbolReplace(/(.+?)([^\w]|$)/, tail,
165-
'\u001b[32m$1\u001b[39m$2');
168+
`${colorize.gray('$1')}$2`);
166169
}
167170

168171
// Return source line with coloured char at `position`

lib/internal/repl/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
268268
completionPreview = suffix;
269269

270270
const result = repl.useColors ?
271-
`\u001b[90m${suffix}\u001b[39m` :
271+
colorize.gray(suffix) :
272272
` // ${suffix}`;
273273

274274
const { cursorPos, displayPos } = getPreviewPos();
@@ -443,7 +443,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
443443
}
444444

445445
const result = repl.useColors ?
446-
`\u001b[90m${inspected}\u001b[39m` :
446+
colorize.gray(inspected) :
447447
`// ${inspected}`;
448448

449449
const { cursorPos, displayPos } = getPreviewPos();

lib/internal/util/inspect.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,82 @@ defineColorAlias('inverse', 'swapColors');
439439
defineColorAlias('inverse', 'swapcolors');
440440
defineColorAlias('doubleunderline', 'doubleUnderline');
441441

442+
// TODO(BridgeAR): Consider using a class instead and require the user to instantiate it
443+
// before using to declare their color support (or use a default, if none
444+
// provided).
445+
const colorize = {};
446+
447+
function defineColorGetter({ style, codes, enumerable }) {
448+
function color(...strings) {
449+
let result = '';
450+
const ansiCodes = this.ansiCodes
451+
let string = strings[0];
452+
for (let i = 1; i < strings.length; i++) {
453+
string += ' ' + strings[i];
454+
}
455+
456+
// Fast path
457+
const searchCode = ansiCodes.length === 1 ? ansiCodes[0].end : '\u001b[';
458+
const ansiCodeStart = string.indexOf(searchCode);
459+
if (ansiCodeStart === -1) {
460+
for (const code of ansiCodes) {
461+
result += code.start;
462+
}
463+
} else {
464+
// Slow path
465+
const start = string.slice(0, ansiCodeStart - 1);
466+
let middle = string.slice(ansiCodeStart - 1, -4);
467+
const end = string.slice(-4);
468+
469+
for (const code of ansiCodes) {
470+
result += code.start;
471+
// Continue former colors by finding end points and continuing from there.
472+
middle = middle.replaceAll(code.end, `$&${code.start}`)
473+
}
474+
string = `${start}${middle}${end}`;
475+
}
476+
477+
result += string;
478+
for (let i = ansiCodes.length - 1; i >= 0; i--) {
479+
result += ansiCodes[i].end;
480+
}
481+
482+
return result;
483+
}
484+
485+
Object.defineProperty(colorize, style, {
486+
__proto__: null,
487+
get: function () {
488+
if (typeof this === 'function') {
489+
this.ansiCodes.push(codes);
490+
return this;
491+
}
492+
const ansiCodes = [codes];
493+
const context = {
494+
ansiCodes
495+
};
496+
let boundColor = color.bind(context);
497+
// Enable chaining.
498+
Object.setPrototypeOf(boundColor, colorize);
499+
boundColor.ansiCodes = ansiCodes;
500+
return boundColor;
501+
},
502+
enumerable,
503+
});
504+
}
505+
506+
for (const [style, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(inspect.colors))) {
507+
const value = descriptor.value ?? descriptor.get.call(inspect.colors);
508+
defineColorGetter({
509+
style,
510+
codes: {
511+
start: `\u001b[${value[0]}m`,
512+
end: `\u001b[${value[1]}m`
513+
},
514+
enumerable: descriptor.enumerable
515+
});
516+
}
517+
442518
// TODO(BridgeAR): Add function style support for more complex styles.
443519
// Don't use 'blue' not visible on cmd.exe
444520
inspect.styles = ObjectAssign(ObjectCreate(null), {
@@ -2290,6 +2366,7 @@ function stripVTControlCharacters(str) {
22902366
}
22912367

22922368
module.exports = {
2369+
colorize,
22932370
inspect,
22942371
format,
22952372
formatWithOptions,

0 commit comments

Comments
 (0)