diff --git a/src/common.js b/src/common.js index 0b6d0b64..2c66873a 100644 --- a/src/common.js +++ b/src/common.js @@ -36,6 +36,122 @@ function setup(env) { */ createDebug.formatters = {}; + /** + * Map of formatting handling functions, for output formatting. + * m and _time are special hardcoded keys. + */ + createDebug.outputFormatters = {}; + + /** + * Map %m to applying formatters to arguments + */ + + createDebug.outputFormatters.m = function (_, args) { + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + /** + * Note: This only inspects the first argument, + * so if debug({foo: "bar"}, {foo: "bar"}) is passed + * only the first object will be colored by node's formatters.O + */ + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return match; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(this, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + return args; + }; + + /** + * Map %+ to humanize()'s defaults (1000ms diff => "1s") + */ + + createDebug.outputFormatters['+'] = function () { + return '+' + createDebug.humanize(this.diff); + }; + + /** + * Map %d to returning milliseconds + */ + + createDebug.outputFormatters.d = function () { + return '+' + this.diff + 'ms'; + }; + + /** + * Map %n to outputting namespace prefix + */ + + createDebug.outputFormatters.n = function () { + return this.namespace; + }; + + /** + * Map %_time to handling time...? + */ + + createDebug.outputFormatters._time = function (format) { + // Browser doesn't have date + return new Date().toISOString(); + }; + + /** + * Map of meta-formatters which are applied to outputFormatters + */ + createDebug.metaFormatters = {}; + + /** + * Map %J* to `JSON.stringify()` + */ + + createDebug.outputFormatters.J = function (v) { + return JSON.stringify(v); + }; + + /** + * Map %c* to to `applyColor()` + */ + + createDebug.outputFormatters.c = function (v) { + if (this.useColors) { + return this.applyColor(v); + } else { + return v; + } + }; + + /** + * Map %C* to to `applyColor(arg, bold = true)` (node) + */ + + createDebug.outputFormatters.C = function (v) { + if (this.useColors) { + return this.applyColor(v, true); + } else { + return v; + } + }; + /** * Selects a color for a debug namespace * @param {String} namespace The namespace string for the for the debug instance to be colored @@ -80,44 +196,67 @@ function setup(env) { self.curr = curr; prevTime = curr; - args[0] = createDebug.coerce(args[0]); + // Apply relevant `outputFormatters` to `format` + const reg = /%([a-zA-Z+]+|[a-zA-Z]*?\{.+\})/; + let formattedArgs = []; + let res; + let outputFormat = self.format; // Make a copy of the format + while (res = outputFormat.match(reg)) { + let [matched, formatToken] = res; + let formatter; + let formatted; + + // Split out the part before the matched format token + const split = outputFormat.slice(0, res.index); + outputFormat = outputFormat.slice(res.index + matched.length); + + // And add it to the arguments + if (split.length > 0) { + formattedArgs.push(split); + } - if (typeof args[0] !== 'string') { - // Anything else let's inspect with %O - args.unshift('%O'); - } + const metaFormatters = []; + // Extract metaformatters + while (formatToken.length > 1 && !formatToken.startsWith('{')) { + const metaFormatterToken = formatToken.slice(0, 1); + formatToken = formatToken.slice(1); + metaFormatters.push(createDebug.outputFormatters[metaFormatterToken]); + } - // Apply any `formatters` transformations - let index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { - // If we encounter an escaped % then don't increase the array index - if (match === '%%') { - return match; + // Not really sure how to handle time at this point + if (formatToken.startsWith('{')) { + formatter = createDebug.outputFormatters._time; + } else { + formatter = createDebug.outputFormatters[formatToken]; } - index++; - const formatter = createDebug.formatters[format]; if (typeof formatter === 'function') { - const val = args[index]; - match = formatter.call(self, val); - - // Now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; + formatted = formatter.call(self, formatToken, args); + + // Apply metaFormatters + metaFormatters.forEach(metaFormatter => { + if (typeof metaFormatter === 'function') { + formatted = metaFormatter.call(self, formatted); + } + }); + + if (Array.isArray(formatted)) { // Intended to concatenate %m's args in the middle of the format + formattedArgs = formattedArgs.concat(formatted); + } else { + formattedArgs.push(formatted); + } } - return match; - }); - - // Apply env-specific formatting (colors, etc.) - createDebug.formatArgs.call(self, args); + } const logFn = self.log || createDebug.log; - logFn.apply(self, args); + logFn.apply(self, formattedArgs); } debug.namespace = namespace; debug.enabled = createDebug.enabled(namespace); debug.useColors = createDebug.useColors(); + debug.format = createDebug.getFormat() || '%{H:M-Z}%n%m%+'; // ' %n%m%+' debug.color = selectColor(namespace); + debug.applyColor = createDebug.applyColor.bind(debug); debug.destroy = destroy; debug.extend = extend; // Debug.formatArgs = formatArgs; diff --git a/src/node.js b/src/node.js index 5e1f1541..9075c07c 100644 --- a/src/node.js +++ b/src/node.js @@ -11,10 +11,11 @@ const util = require('util'); exports.init = init; exports.log = log; -exports.formatArgs = formatArgs; +exports.applyColor = applyColor; exports.save = save; exports.load = load; exports.useColors = useColors; +exports.getFormat = getFormat; /** * Colors. @@ -137,7 +138,10 @@ exports.inspectOpts = Object.keys(process.env).filter(key => { } else if (val === 'null') { val = null; } else { - val = Number(val); + const asNumber = Number(val); + if (!isNaN(asNumber)) { + val = asNumber; + } } obj[prop] = val; @@ -155,31 +159,40 @@ function useColors() { } /** - * Adds ANSI color escape codes if enabled. - * - * @api public + * If DEBUG_FORMAT if specified, returns it. + * Otherwise, returns a format matching previous version's based on DEBUG_COLORS and DEBUG_HIDE_DATE */ -function formatArgs(args) { - const {namespace: name, useColors} = this; - - if (useColors) { - const c = this.color; - const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); - const prefix = ` ${colorCode};1m${name} \u001B[0m`; +function getFormat() { + const useColors = 'colors' in exports.inspectOpts ? + Boolean(exports.inspectOpts.colors) : + tty.isatty(process.stderr.fd); - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); + if ('format' in exports.inspectOpts) { + return exports.inspectOpts.format; } else { - args[0] = getDate() + name + ' ' + args[0]; + if (useColors) { + return ' %Cn%m%c+'; // ' %n %m %+' + } else if (exports.inspectOpts.hideDate) { + return '%n%m'; // '%n %m' + } else { + return '%{%FT%T.%LZ%M-Z}%n%m'; // '%{%FT%T.%LZ%M-Z} %n %m' + } } } -function getDate() { - if (exports.inspectOpts.hideDate) { - return ''; - } - return new Date().toISOString() + ' '; +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function applyColor(str, bold = false) { + // I think doing this each time is a waste, colorCode could be stored in some variable? + const c = this.color; + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); + + return colorCode + (bold ? ';1' : '') + 'm' + str + '\u001B[0m'; } /**