diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8ef11b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +# Set default charset (override if necessary) +charset = utf-8 +indent_style = space +indent_size = 2 diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8351c19 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +14 diff --git a/lib/prettyjson.js b/lib/prettyjson.js index a06231d..2ea96cb 100644 --- a/lib/prettyjson.js +++ b/lib/prettyjson.js @@ -1,7 +1,7 @@ 'use strict'; // ### Module dependencies -var colors = require('colors/safe'); +var chalk = require('chalk'); var Utils = require('./utils'); var conflictChars = /[^\w\s\n\r\v\t\.,]/i; @@ -9,12 +9,12 @@ var conflictChars = /[^\w\s\n\r\v\t\.,]/i; exports.version = require('../package.json').version; // Helper function to detect if an object should be printed or ignored -var isPrintable = function(input, options) { +var isPrintable = function (input, options) { return input !== undefined || options.renderUndefined; }; // Helper function to detect if an object can be directly serializable -var isSerializable = function(input, onlyPrimitives, options) { +var isSerializable = function (input, onlyPrimitives, options) { if ( typeof input === 'boolean' || typeof input === 'number' || @@ -38,32 +38,104 @@ var isSerializable = function(input, onlyPrimitives, options) { return false; }; -var addColorToData = function(input, options) { +var supportedColorStyleRenderers = [ + 'black', + 'red', + 'green', + 'yellow', + 'blue', + 'cyan', + 'magenta', + 'white', + 'gray', + 'grey', + 'blackBright', + 'redBright', + 'greenBright', + 'yellowBright', + 'blueBright', + 'cyanBright', + 'magentaBright', + 'whiteBright', + 'bgBlack', + 'bgRed', + 'bgGreen', + 'bgYellow', + 'bgBlue', + 'bgCyan', + 'bgMagenta', + 'bgWhite', + 'bgGray', + 'bgGrey', + 'bgBlackBright', + 'bgRedBright', + 'bgGreenBright', + 'bgYellowBright', + 'bgBlueBright', + 'bgCyanBright', + 'bgMagentaBright', + 'bgWhiteBright' +]; + +/** + * + * @param {string} colorString + * @param {PrettyJSONOptions} options + */ +var lookupColorRenderer = function (colorString, options) { + if (options.noColor) { + return chalk; + } + + if (supportedColorStyleRenderers.indexOf(colorString) === -1) { + if (options.unknownColorHandler === 'hex') { + return chalk.hex(colorString); + } else if (typeof options.unknownColorHandler === 'function') { + return options.unknownColorHandler; + } else if ('ignore' === options.unknownColorHandler) { + return chalk; + } else if ('warn' === options.unknownColorHandler) { + console.warn( + 'Unknown color style: ', + colorString, + 'Use one of the following: ', + supportedColorStyleRenderers.join(', ') + + '\nor set options.unknownColorHandler to "hex"', + 'or a function (to provide a custom color renderer)' + ); + + return chalk; + } + } + return chalk[colorString] || chalk; +}; + +var addColorToData = function (input, options) { if (options.noColor) { return input; } if (typeof input === 'string') { // Print strings in regular terminal color - return options.stringColor ? colors[options.stringColor](input) : input; + return lookupColorRenderer(options.stringColor, options)(input); } var sInput = input + ''; if (input === true) { - return colors.green(sInput); + return lookupColorRenderer(options.trueColor, options)(sInput); } if (input === false) { - return colors.red(sInput); + return lookupColorRenderer(options.falseColor, options)(sInput); } if (input === null || input === undefined) { - return colors.grey(sInput); + return lookupColorRenderer(options.nullUndefinedColor, options)(sInput); } if (typeof input === 'number') { if (input >= 0) { - return colors[options.positiveNumberColor](sInput); + return lookupColorRenderer(options.positiveNumberColor, options)(sInput); } else { - return colors[options.negativeNumberColor](sInput); + return lookupColorRenderer(options.negativeNumberColor, options)(sInput); } } if (typeof input === 'function') { @@ -77,26 +149,20 @@ var addColorToData = function(input, options) { return sInput; }; -var colorMultilineString = function(options, line) { - if (options.multilineStringColor === null || options.noColor) { - return line; - } else { - return colors[options.multilineStringColor](line); - } +var colorMultilineString = function (options, line) { + return lookupColorRenderer(options.multilineStringColor, options)(line); }; -var indentLines = function(string, spaces, options){ +var indentLines = function (string, spaces, options) { var lines = string.split('\n'); - lines = lines.map(function(line){ + lines = lines.map(function (line) { return Utils.indent(spaces) + colorMultilineString(options, line); }); return lines.join('\n'); }; -var renderToArray = function(data, options, indentation) { - - if (typeof data === 'string' && data.match(conflictChars) && - options.escape) { +var renderToArray = function (data, options, indentation) { + if (typeof data === 'string' && data.match(conflictChars) && options.escape) { data = JSON.stringify(data); } @@ -125,16 +191,14 @@ var renderToArray = function(data, options, indentation) { var outputArray = []; - data.forEach(function(element) { + data.forEach(function (element) { if (!isPrintable(element, options)) { return; } - // Prepend the dash at the begining of each array's element line + // Prepend the dash at the beginning of each array's element line var line = '- '; - if (!options.noColor) { - line = colors[options.dashColor](line); - } + line = lookupColorRenderer(options.dashColor, options)(line); line = Utils.indent(indentation) + line; // If the element of the array is a string, bool, number, or null @@ -143,13 +207,15 @@ var renderToArray = function(data, options, indentation) { line += renderToArray(element, options, 0)[0]; outputArray.push(line); - // If the element is an array or object, render it in next line + // If the element is an array or object, render it in next line } else { outputArray.push(line); outputArray.push.apply( outputArray, renderToArray( - element, options, indentation + options.defaultIndentation + element, + options, + indentation + options.defaultIndentation ) ); } @@ -175,16 +241,14 @@ var renderToArray = function(data, options, indentation) { var key; var output = []; - Object.getOwnPropertyNames(data).forEach(function(i) { + Object.getOwnPropertyNames(data).forEach(function (i) { if (!isPrintable(data[i], options)) { return; } // Prepend the index at the beginning of the line - key = (i + ': '); - if (!options.noColor) { - key = colors[options.keysColor](key); - } + key = i + ': '; + key = lookupColorRenderer(options.keysColor, options)(key); key = Utils.indent(indentation) + key; // If the value is serializable, render it in the same line @@ -208,36 +272,48 @@ var renderToArray = function(data, options, indentation) { }); return output; }; - -// ### Render function -// *Parameters:* -// -// * **`data`**: Data to render -// * **`options`**: Hash with different options to configure the parser -// * **`indentation`**: Base indentation of the parsed output -// -// *Example of options hash:* -// -// { -// emptyArrayMsg: '(empty)', // Rendered message on empty strings -// keysColor: 'blue', // Color for keys in hashes -// dashColor: 'red', // Color for the dashes in arrays -// stringColor: 'grey', // Color for strings -// multilineStringColor: 'cyan' // Color for multiline strings -// defaultIndentation: 2 // Indentation on nested objects -// } -exports.render = function render(data, options, indentation) { - // Default values - indentation = indentation || 0; +/** + * @typedef {Object} PrettyJSONOptions + * @property {string} [stringColor=null] Color for strings + * @property {string} [multilineStringColor=null] Color for multiline strings + * @property {string} [keysColor="green"] Color for keys in hashes + * @property {string} [dashColor="green"] Color for dashes in arrays + * @property {string} [numberColor="blue"] Default color for numbers + * @property {string} [positiveNumberColor=numberColor|"blue"] + * @property {string} [negativeNumberColor=numberColor|"blue"] + * @property {string} [trueColor="green"] Color for === true + * @property {string} [falseColor="red"] Color for === false + * @property {string} [nullUndefinedColor="grey"] Color for null || undefined + * @property {"ignore"|"warn"|"hex"|Function} [unknownColorHandler="warn"] + * If an unknown color is encountered, this handler will be called + * with the color name and it should return a function that renders + * the content. + * + * @property {number} [defaultIndentation=2] Indentation spaces per object level + * @property {string} [emptyArrayMsg="(empty array)"] Replace empty strings with + * @property {boolean} [noColor] Flag to disable colors + * @property {boolean} [noAlign] Flag to disable alignment + * @property {boolean} [escape] Flag to escape printed content + * @property {boolean} [noColor] Flag to disable colors + */ +/** + * Mutating function that ensures we have a valid options object + * @param {PrettyJSONOptions} options + * @returns PrettyJSONOptions + */ +var validateOptionsAndSetDefaults = function (options) { options = options || {}; options.emptyArrayMsg = options.emptyArrayMsg || '(empty array)'; options.keysColor = options.keysColor || 'green'; options.dashColor = options.dashColor || 'green'; + options.trueColor = options.trueColor || 'green'; + options.falseColor = options.falseColor || 'red'; + options.nullUndefinedColor = options.nullUndefinedColor || 'grey'; options.numberColor = options.numberColor || 'blue'; - options.positiveNumberColor = options.positiveNumberColor - || options.numberColor; - options.negativeNumberColor = options.negativeNumberColor - || options.numberColor; + options.positiveNumberColor = + options.positiveNumberColor || options.numberColor; + options.negativeNumberColor = + options.negativeNumberColor || options.numberColor; options.defaultIndentation = options.defaultIndentation || 2; options.noColor = !!options.noColor; options.noAlign = !!options.noAlign; @@ -245,28 +321,68 @@ exports.render = function render(data, options, indentation) { options.renderUndefined = !!options.renderUndefined; options.stringColor = options.stringColor || null; - options.multilineStringColor = options.multilineStringColor || null; + options.multilineStringColor = + options.multilineStringColor || options.stringColor || null; + + options.unknownColorHandler = options.unknownColorHandler || 'warn'; + if (typeof options.unknownColorHandler === 'string') { + if (['warn', 'hex', 'ignore'].indexOf(options.unknownColorHandler) === -1) { + throw new Error('Unknown color handler: ' + options.unknownColorHandler); + } + } else if (typeof options.unknownColorHandler !== 'function') { + throw new Error( + 'Invalid options.unknownColorHandler: ' + options.unknownColorHandler + ); + } + + return options; +}; +/** + * ### Render function + * *Parameters:* + * + * @param {*} data: Data to render + * @param {PrettyJSONOptions} options: Hash of different options + * @param {*} indentation **`indentation`**: Base indentation of the output + * + * *Example of options hash:* + * + * { + * emptyArrayMsg: '(empty)', // Rendered message on empty strings + * keysColor: 'blue', // Color for keys in hashes + * dashColor: 'red', // Color for the dashes in arrays + * stringColor: 'grey', // Color for strings + * multilineStringColor: 'cyan' // Color for multiline strings + * defaultIndentation: 2 // Indentation on nested objects + * } + * @returns string with the rendered data + */ +exports.render = function render(data, options, indentation) { + // Default values + indentation = indentation || 0; + options = validateOptionsAndSetDefaults(options); return renderToArray(data, options, indentation).join('\n'); }; -// ### Render from string function -// *Parameters:* -// -// * **`data`**: Data to render as a string -// * **`options`**: Hash with different options to configure the parser -// * **`indentation`**: Base indentation of the parsed output -// -// *Example of options hash:* -// -// { -// emptyArrayMsg: '(empty)', // Rendered message on empty strings -// keysColor: 'blue', // Color for keys in hashes -// dashColor: 'red', // Color for the dashes in arrays -// defaultIndentation: 2 // Indentation on nested objects -// } +/** + * ### Render from string function + * *Parameters:* + * + * @param {*} data: Data to render + * @param {PrettyJSONOptions} options: Hash of different options + * @param {*} indentation **`indentation`**: Base indentation of the output + * + * *Example of options hash:* + * + * { + * emptyArrayMsg: '(empty)', // Rendered message on empty strings + * keysColor: 'blue', // Color for keys in hashes + * dashColor: 'red', // Color for the dashes in arrays + * defaultIndentation: 2 // Indentation on nested objects + * } + */ exports.renderString = function renderString(data, options, indentation) { - var output = ''; var parsedData; // If the input is not a string or if it's empty, just return an empty string @@ -294,7 +410,7 @@ exports.renderString = function renderString(data, options, indentation) { parsedData = JSON.parse(data); } catch (e) { // Return an error in case of an invalid JSON - return colors.red('Error:') + ' Not valid JSON!'; + return chalk.red('Error:') + ' Not valid JSON!'; } // Call the real render() method diff --git a/package-lock.json b/package-lock.json index 8954c22..c0fa1d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,10 +24,12 @@ "dev": true }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } }, "argparse": { "version": "1.0.10", @@ -124,16 +126,12 @@ "dev": true }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "cli": { @@ -162,10 +160,18 @@ } } }, - "colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { "version": "1.0.8", @@ -523,6 +529,33 @@ "commander": "^2.9.0", "is-my-json-valid": "^2.12.4", "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } } }, "has-ansi": { @@ -1250,10 +1283,19 @@ "dev": true }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + } + } }, "tough-cookie": { "version": "2.3.4", diff --git a/package.json b/package.json index bebaa3f..c9c9e54 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "prettyjson": "./bin/prettyjson" }, "dependencies": { - "colors": "1.4.0", + "chalk": "^4.1.2", "minimist": "^1.2.0" }, "devDependencies": { diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..41c08aa --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,16 @@ +module.exports = { + overrides: [ + { + // prettier will strip newlines out of package.json files unless you tell it to use the json parser + files: ['package.json', '**/package.json'], + options: { parser: 'json' } + } + ], + printWidth: 80, + semi: true, + singleQuote: true, + trailingComma: 'none', + tabWidth: 2, + useTabs: false, + xmlWhitespaceSensitivity: 'ignore' +};