console: add color support
Add a way to tell `Console` instances to either always use, never use or auto-detect color support and inspect objects accordingly. PR-URL: https://github.com/nodejs/node/pull/19372 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
ce58df58d0
commit
57e8793c43
@ -87,7 +87,8 @@ changes:
|
|||||||
description: The `ignoreErrors` option was introduced.
|
description: The `ignoreErrors` option was introduced.
|
||||||
- version: REPLACEME
|
- version: REPLACEME
|
||||||
pr-url: https://github.com/nodejs/node/pull/19372
|
pr-url: https://github.com/nodejs/node/pull/19372
|
||||||
description: The `Console` constructor now supports an `options` argument.
|
description: The `Console` constructor now supports an `options` argument,
|
||||||
|
and the `colorMode` option was introduced.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `options` {Object}
|
* `options` {Object}
|
||||||
@ -95,6 +96,11 @@ changes:
|
|||||||
* `stderr` {stream.Writable}
|
* `stderr` {stream.Writable}
|
||||||
* `ignoreErrors` {boolean} Ignore errors when writing to the underlying
|
* `ignoreErrors` {boolean} Ignore errors when writing to the underlying
|
||||||
streams. **Default:** `true`.
|
streams. **Default:** `true`.
|
||||||
|
* `colorMode` {boolean|string} Set color support for this `Console` instance.
|
||||||
|
Setting to `true` enables coloring while inspecting values, setting to
|
||||||
|
`'auto'` will make color support depend on the value of the `isTTY` property
|
||||||
|
and the value returned by `getColorDepth()` on the respective stream.
|
||||||
|
**Default:** `false`
|
||||||
|
|
||||||
Creates a new `Console` with one or two writable stream instances. `stdout` is a
|
Creates a new `Console` with one or two writable stream instances. `stdout` is a
|
||||||
writable stream to print log or info output. `stderr` is used for warning or
|
writable stream to print log or info output. `stderr` is used for warning or
|
||||||
|
@ -268,8 +268,8 @@ an `inspectOptions` argument which specifies options that are passed along to
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
|
util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
|
||||||
// Returns 'See object { foo: 42 }', where `42` is colored as a number
|
// Returns 'See object { foo: 42 }', where `42` is colored as a number
|
||||||
// when printed to a terminal.
|
// when printed to a terminal.
|
||||||
```
|
```
|
||||||
|
|
||||||
## util.getSystemErrorName(err)
|
## util.getSystemErrorName(err)
|
||||||
|
@ -26,6 +26,7 @@ const {
|
|||||||
codes: {
|
codes: {
|
||||||
ERR_CONSOLE_WRITABLE_STREAM,
|
ERR_CONSOLE_WRITABLE_STREAM,
|
||||||
ERR_INVALID_ARG_TYPE,
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
ERR_INVALID_ARG_VALUE,
|
||||||
},
|
},
|
||||||
} = require('internal/errors');
|
} = require('internal/errors');
|
||||||
const { previewMapIterator, previewSetIterator } = require('internal/v8');
|
const { previewMapIterator, previewSetIterator } = require('internal/v8');
|
||||||
@ -49,24 +50,32 @@ const {
|
|||||||
} = Array;
|
} = Array;
|
||||||
|
|
||||||
// Track amount of indentation required via `console.group()`.
|
// Track amount of indentation required via `console.group()`.
|
||||||
const kGroupIndent = Symbol('groupIndent');
|
const kGroupIndent = Symbol('kGroupIndent');
|
||||||
|
|
||||||
|
const kFormatForStderr = Symbol('kFormatForStderr');
|
||||||
|
const kFormatForStdout = Symbol('kFormatForStdout');
|
||||||
|
const kGetInspectOptions = Symbol('kGetInspectOptions');
|
||||||
|
const kColorMode = Symbol('kColorMode');
|
||||||
|
|
||||||
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
|
function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
|
||||||
if (!(this instanceof Console)) {
|
if (!(this instanceof Console)) {
|
||||||
return new Console(...arguments);
|
return new Console(...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
let stdout, stderr, ignoreErrors;
|
let stdout, stderr, ignoreErrors, colorMode;
|
||||||
if (options && typeof options.write !== 'function') {
|
if (options && typeof options.write !== 'function') {
|
||||||
({
|
({
|
||||||
stdout,
|
stdout,
|
||||||
stderr = stdout,
|
stderr = stdout,
|
||||||
ignoreErrors = true
|
ignoreErrors = true,
|
||||||
|
colorMode = false
|
||||||
} = options);
|
} = options);
|
||||||
} else {
|
} else {
|
||||||
stdout = options;
|
return new Console({
|
||||||
stderr = arguments[1];
|
stdout: options,
|
||||||
ignoreErrors = arguments[2] === undefined ? true : arguments[2];
|
stderr: arguments[1],
|
||||||
|
ignoreErrors: arguments[2]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!stdout || typeof stdout.write !== 'function') {
|
if (!stdout || typeof stdout.write !== 'function') {
|
||||||
@ -94,7 +103,11 @@ function Console(options /* or: stdout, stderr, ignoreErrors = true */) {
|
|||||||
prop.value = createWriteErrorHandler(stderr);
|
prop.value = createWriteErrorHandler(stderr);
|
||||||
Object.defineProperty(this, '_stderrErrorHandler', prop);
|
Object.defineProperty(this, '_stderrErrorHandler', prop);
|
||||||
|
|
||||||
|
if (typeof colorMode !== 'boolean' && colorMode !== 'auto')
|
||||||
|
throw new ERR_INVALID_ARG_VALUE('colorMode', colorMode);
|
||||||
|
|
||||||
this[kCounts] = new Map();
|
this[kCounts] = new Map();
|
||||||
|
this[kColorMode] = colorMode;
|
||||||
|
|
||||||
Object.defineProperty(this, kGroupIndent, { writable: true });
|
Object.defineProperty(this, kGroupIndent, { writable: true });
|
||||||
this[kGroupIndent] = '';
|
this[kGroupIndent] = '';
|
||||||
@ -156,13 +169,33 @@ function write(ignoreErrors, stream, string, errorhandler, groupIndent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const kColorInspectOptions = { colors: true };
|
||||||
|
const kNoColorInspectOptions = {};
|
||||||
|
Console.prototype[kGetInspectOptions] = function(stream) {
|
||||||
|
let color = this[kColorMode];
|
||||||
|
if (color === 'auto') {
|
||||||
|
color = stream.isTTY && (
|
||||||
|
typeof stream.getColorDepth === 'function' ?
|
||||||
|
stream.getColorDepth() > 2 : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return color ? kColorInspectOptions : kNoColorInspectOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.prototype[kFormatForStdout] = function(args) {
|
||||||
|
const opts = this[kGetInspectOptions](this._stdout);
|
||||||
|
return util.formatWithOptions(opts, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
Console.prototype[kFormatForStderr] = function(args) {
|
||||||
|
const opts = this[kGetInspectOptions](this._stderr);
|
||||||
|
return util.formatWithOptions(opts, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
Console.prototype.log = function log(...args) {
|
Console.prototype.log = function log(...args) {
|
||||||
write(this._ignoreErrors,
|
write(this._ignoreErrors,
|
||||||
this._stdout,
|
this._stdout,
|
||||||
// The performance of .apply and the spread operator seems on par in V8
|
this[kFormatForStdout](args),
|
||||||
// 6.3 but the spread operator, unlike .apply(), pushes the elements
|
|
||||||
// onto the stack. That is, it makes stack overflows more likely.
|
|
||||||
util.format.apply(null, args),
|
|
||||||
this._stdoutErrorHandler,
|
this._stdoutErrorHandler,
|
||||||
this[kGroupIndent]);
|
this[kGroupIndent]);
|
||||||
};
|
};
|
||||||
@ -173,14 +206,16 @@ Console.prototype.dirxml = Console.prototype.log;
|
|||||||
Console.prototype.warn = function warn(...args) {
|
Console.prototype.warn = function warn(...args) {
|
||||||
write(this._ignoreErrors,
|
write(this._ignoreErrors,
|
||||||
this._stderr,
|
this._stderr,
|
||||||
util.format.apply(null, args),
|
this[kFormatForStderr](args),
|
||||||
this._stderrErrorHandler,
|
this._stderrErrorHandler,
|
||||||
this[kGroupIndent]);
|
this[kGroupIndent]);
|
||||||
};
|
};
|
||||||
Console.prototype.error = Console.prototype.warn;
|
Console.prototype.error = Console.prototype.warn;
|
||||||
|
|
||||||
Console.prototype.dir = function dir(object, options) {
|
Console.prototype.dir = function dir(object, options) {
|
||||||
options = Object.assign({ customInspect: false }, options);
|
options = Object.assign({
|
||||||
|
customInspect: false
|
||||||
|
}, this[kGetInspectOptions](this._stdout), options);
|
||||||
write(this._ignoreErrors,
|
write(this._ignoreErrors,
|
||||||
this._stdout,
|
this._stdout,
|
||||||
util.inspect(object, options),
|
util.inspect(object, options),
|
||||||
@ -211,7 +246,7 @@ Console.prototype.timeEnd = function timeEnd(label = 'default') {
|
|||||||
Console.prototype.trace = function trace(...args) {
|
Console.prototype.trace = function trace(...args) {
|
||||||
const err = {
|
const err = {
|
||||||
name: 'Trace',
|
name: 'Trace',
|
||||||
message: util.format.apply(null, args)
|
message: this[kFormatForStderr](args)
|
||||||
};
|
};
|
||||||
Error.captureStackTrace(err, trace);
|
Error.captureStackTrace(err, trace);
|
||||||
this.error(err.stack);
|
this.error(err.stack);
|
||||||
@ -220,7 +255,7 @@ Console.prototype.trace = function trace(...args) {
|
|||||||
Console.prototype.assert = function assert(expression, ...args) {
|
Console.prototype.assert = function assert(expression, ...args) {
|
||||||
if (!expression) {
|
if (!expression) {
|
||||||
args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`;
|
args[0] = `Assertion failed${args.length === 0 ? '' : `: ${args[0]}`}`;
|
||||||
this.warn(util.format.apply(null, args));
|
this.warn(this[kFormatForStderr](args));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
46
test/parallel/test-console-tty-colors.js
Normal file
46
test/parallel/test-console-tty-colors.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const util = require('util');
|
||||||
|
const { Writable } = require('stream');
|
||||||
|
const { Console } = require('console');
|
||||||
|
|
||||||
|
function check(isTTY, colorMode, expectedColorMode) {
|
||||||
|
const items = [
|
||||||
|
1,
|
||||||
|
{ a: 2 },
|
||||||
|
[ 'foo' ],
|
||||||
|
{ '\\a': '\\bar' }
|
||||||
|
];
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const stream = new Writable({
|
||||||
|
write: common.mustCall((chunk, enc, cb) => {
|
||||||
|
assert.strictEqual(chunk.trim(),
|
||||||
|
util.inspect(items[i++], {
|
||||||
|
colors: expectedColorMode
|
||||||
|
}));
|
||||||
|
cb();
|
||||||
|
}, items.length),
|
||||||
|
decodeStrings: false
|
||||||
|
});
|
||||||
|
stream.isTTY = isTTY;
|
||||||
|
|
||||||
|
// Set ignoreErrors to `false` here so that we see assertion failures
|
||||||
|
// from the `write()` call happen.
|
||||||
|
const testConsole = new Console({
|
||||||
|
stdout: stream,
|
||||||
|
ignoreErrors: false,
|
||||||
|
colorMode
|
||||||
|
});
|
||||||
|
for (const item of items) {
|
||||||
|
testConsole.log(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(true, 'auto', true);
|
||||||
|
check(false, 'auto', false);
|
||||||
|
check(true, true, true);
|
||||||
|
check(false, true, true);
|
||||||
|
check(true, false, false);
|
||||||
|
check(false, false, false);
|
@ -51,9 +51,11 @@ const custom_inspect = { foo: 'bar', inspect: () => 'inspect' };
|
|||||||
|
|
||||||
const strings = [];
|
const strings = [];
|
||||||
const errStrings = [];
|
const errStrings = [];
|
||||||
|
process.stdout.isTTY = false;
|
||||||
common.hijackStdout(function(data) {
|
common.hijackStdout(function(data) {
|
||||||
strings.push(data);
|
strings.push(data);
|
||||||
});
|
});
|
||||||
|
process.stderr.isTTY = false;
|
||||||
common.hijackStderr(function(data) {
|
common.hijackStderr(function(data) {
|
||||||
errStrings.push(data);
|
errStrings.push(data);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user