mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 21:19:50 +01:00
lib: respect terminal capabilities on styleText
This PR changes styleText API to respect terminal capabilities and environment variables such as NO_COLOR, NODE_DISABLE_COLORS, and FORCE_COLOR. PR-URL: https://github.com/nodejs/node/pull/54389 Fixes: https://github.com/nodejs/node/issues/54365 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Claudio Wunder <cwunder@gnome.org> Reviewed-By: Rich Trott <rtrott@gmail.com>
This commit is contained in:
parent
4f14eb1545
commit
4a0ec20a35
@ -1802,7 +1802,7 @@ console.log(util.stripVTControlCharacters('\u001B[4mvalue\u001B[0m'));
|
||||
// Prints "value"
|
||||
```
|
||||
|
||||
## `util.styleText(format, text)`
|
||||
## `util.styleText(format, text[, options])`
|
||||
|
||||
> Stability: 1.1 - Active development
|
||||
|
||||
@ -1810,24 +1810,55 @@ console.log(util.stripVTControlCharacters('\u001B[4mvalue\u001B[0m'));
|
||||
added:
|
||||
- v21.7.0
|
||||
- v20.12.0
|
||||
changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/54389
|
||||
description: Respect isTTY and environment variables
|
||||
such as NO_COLORS, NODE_DISABLE_COLORS, and FORCE_COLOR.
|
||||
-->
|
||||
|
||||
* `format` {string | Array} A text format or an Array
|
||||
of text formats defined in `util.inspect.colors`.
|
||||
* `text` {string} The text to to be formatted.
|
||||
* `options` {Object}
|
||||
* `validateStream` {boolean} When true, `stream` is checked to see if it can handle colors. **Default:** `true`.
|
||||
* `stream` {Stream} A stream that will be validated if it can be colored. **Default:** `process.stdout`.
|
||||
|
||||
This function returns a formatted text considering the `format` passed.
|
||||
This function returns a formatted text considering the `format` passed
|
||||
for printing in a terminal, it is aware of the terminal's capabilities
|
||||
and act according to the configuration set via `NO_COLORS`,
|
||||
`NODE_DISABLE_COLORS` and `FORCE_COLOR` environment variables.
|
||||
|
||||
```mjs
|
||||
import { styleText } from 'node:util';
|
||||
const errorMessage = styleText('red', 'Error! Error!');
|
||||
console.log(errorMessage);
|
||||
import { stderr } from 'node:process';
|
||||
|
||||
const successMessage = styleText('green', 'Success!');
|
||||
console.log(successMessage);
|
||||
|
||||
const errorMessage = styleText(
|
||||
'red',
|
||||
'Error! Error!',
|
||||
// Validate if process.stderr has TTY
|
||||
{ stream: stderr },
|
||||
);
|
||||
console.error(successMessage);
|
||||
```
|
||||
|
||||
```cjs
|
||||
const { styleText } = require('node:util');
|
||||
const errorMessage = styleText('red', 'Error! Error!');
|
||||
console.log(errorMessage);
|
||||
const { stderr } = require('node:process');
|
||||
|
||||
const successMessage = styleText('green', 'Success!');
|
||||
console.log(successMessage);
|
||||
|
||||
const errorMessage = styleText(
|
||||
'red',
|
||||
'Error! Error!',
|
||||
// Validate if process.stderr has TTY
|
||||
{ stream: stderr },
|
||||
);
|
||||
console.error(successMessage);
|
||||
```
|
||||
|
||||
`util.inspect.colors` also provides text formats such as `italic`, and
|
||||
|
42
lib/util.js
42
lib/util.js
@ -56,12 +56,25 @@ const {
|
||||
} = require('internal/util/inspect');
|
||||
const { debuglog } = require('internal/util/debuglog');
|
||||
const {
|
||||
validateBoolean,
|
||||
validateFunction,
|
||||
validateNumber,
|
||||
validateString,
|
||||
validateOneOf,
|
||||
} = require('internal/validators');
|
||||
const {
|
||||
isReadableStream,
|
||||
isWritableStream,
|
||||
isNodeStream,
|
||||
} = require('internal/streams/utils');
|
||||
const types = require('internal/util/types');
|
||||
|
||||
let utilColors;
|
||||
function lazyUtilColors() {
|
||||
utilColors ??= require('internal/util/colors');
|
||||
return utilColors;
|
||||
}
|
||||
|
||||
const binding = internalBinding('util');
|
||||
|
||||
const {
|
||||
@ -92,10 +105,25 @@ function escapeStyleCode(code) {
|
||||
/**
|
||||
* @param {string | string[]} format
|
||||
* @param {string} text
|
||||
* @param {object} [options={}]
|
||||
* @param {boolean} [options.validateStream=true] - Whether to validate the stream.
|
||||
* @param {Stream} [options.stream=process.stdout] - The stream used for validation.
|
||||
* @returns {string}
|
||||
*/
|
||||
function styleText(format, text) {
|
||||
function styleText(format, text, { validateStream = true, stream = process.stdout } = {}) {
|
||||
validateString(text, 'text');
|
||||
validateBoolean(validateStream, 'options.validateStream');
|
||||
|
||||
if (validateStream) {
|
||||
if (
|
||||
!isReadableStream(stream) &&
|
||||
!isWritableStream(stream) &&
|
||||
!isNodeStream(stream)
|
||||
) {
|
||||
throw new ERR_INVALID_ARG_TYPE('stream', ['ReadableStream', 'WritableStream', 'Stream'], stream);
|
||||
}
|
||||
}
|
||||
|
||||
if (ArrayIsArray(format)) {
|
||||
let left = '';
|
||||
let right = '';
|
||||
@ -115,6 +143,18 @@ function styleText(format, text) {
|
||||
if (formatCodes == null) {
|
||||
validateOneOf(format, 'format', ObjectKeys(inspect.colors));
|
||||
}
|
||||
|
||||
// Check colorize only after validating arg type and value
|
||||
if (
|
||||
validateStream &&
|
||||
(
|
||||
!stream ||
|
||||
!lazyUtilColors().shouldColorize(stream)
|
||||
)
|
||||
) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return `${escapeStyleCode(formatCodes[0])}${text}${escapeStyleCode(formatCodes[1])}`;
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ expected.beforePreExec = new Set([
|
||||
'NativeModule internal/assert',
|
||||
'NativeModule internal/util/inspect',
|
||||
'NativeModule internal/util/debuglog',
|
||||
'NativeModule internal/streams/utils',
|
||||
'NativeModule internal/timers',
|
||||
'NativeModule events',
|
||||
'Internal Binding buffer',
|
||||
|
@ -1,7 +1,12 @@
|
||||
'use strict';
|
||||
require('../common');
|
||||
const assert = require('assert');
|
||||
const util = require('util');
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('node:assert');
|
||||
const util = require('node:util');
|
||||
const { WriteStream } = require('node:tty');
|
||||
|
||||
const styled = '\u001b[31mtest\u001b[39m';
|
||||
const noChange = 'test';
|
||||
|
||||
[
|
||||
undefined,
|
||||
@ -31,13 +36,69 @@ assert.throws(() => {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
|
||||
assert.strictEqual(util.styleText('red', 'test'), '\u001b[31mtest\u001b[39m');
|
||||
assert.strictEqual(
|
||||
util.styleText('red', 'test', { validateStream: false }),
|
||||
'\u001b[31mtest\u001b[39m',
|
||||
);
|
||||
|
||||
assert.strictEqual(util.styleText(['bold', 'red'], 'test'), '\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m');
|
||||
assert.strictEqual(util.styleText(['bold', 'red'], 'test'), util.styleText('bold', util.styleText('red', 'test')));
|
||||
assert.strictEqual(
|
||||
util.styleText(['bold', 'red'], 'test', { validateStream: false }),
|
||||
'\u001b[1m\u001b[31mtest\u001b[39m\u001b[22m',
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
util.styleText(['bold', 'red'], 'test', { validateStream: false }),
|
||||
util.styleText(
|
||||
'bold',
|
||||
util.styleText('red', 'test', { validateStream: false }),
|
||||
{ validateStream: false },
|
||||
),
|
||||
);
|
||||
|
||||
assert.throws(() => {
|
||||
util.styleText(['invalid'], 'text');
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_VALUE',
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
util.styleText('red', 'text', { stream: {} });
|
||||
}, {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
});
|
||||
|
||||
// does not throw
|
||||
util.styleText('red', 'text', { stream: {}, validateStream: false });
|
||||
|
||||
assert.strictEqual(
|
||||
util.styleText('red', 'test', { validateStream: false }),
|
||||
styled,
|
||||
);
|
||||
|
||||
const fd = common.getTTYfd();
|
||||
if (fd !== -1) {
|
||||
const writeStream = new WriteStream(fd);
|
||||
|
||||
const originalEnv = process.env;
|
||||
[
|
||||
{ isTTY: true, env: {}, expected: styled },
|
||||
{ isTTY: false, env: {}, expected: noChange },
|
||||
{ isTTY: true, env: { NODE_DISABLE_COLORS: '1' }, expected: noChange },
|
||||
{ isTTY: true, env: { NO_COLOR: '1' }, expected: noChange },
|
||||
{ isTTY: true, env: { FORCE_COLOR: '1' }, expected: styled },
|
||||
{ isTTY: true, env: { FORCE_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
|
||||
{ isTTY: false, env: { FORCE_COLOR: '1', NO_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
|
||||
{ isTTY: true, env: { FORCE_COLOR: '1', NO_COLOR: '1', NODE_DISABLE_COLORS: '1' }, expected: styled },
|
||||
].forEach((testCase) => {
|
||||
writeStream.isTTY = testCase.isTTY;
|
||||
process.env = {
|
||||
...process.env,
|
||||
...testCase.env
|
||||
};
|
||||
const output = util.styleText('red', 'test', { stream: writeStream });
|
||||
assert.strictEqual(output, testCase.expected);
|
||||
process.env = originalEnv;
|
||||
});
|
||||
} else {
|
||||
common.skip('Could not create TTY fd');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user