├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── Contributing.md ├── License.md ├── Readme.md ├── __snapshots__ └── test.js.snap ├── index.d.ts ├── index.js ├── jsconfig.json ├── package-lock.json ├── package.json └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.{json,yml,md,babelrc,eslintrc,remarkrc}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/* -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tamia", 3 | "parserOptions": { 4 | "sourceType": "script" 5 | }, 6 | "rules": { 7 | "strict": [2, "global"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | Thumbs.db 4 | .idea/ 5 | .vscode/ 6 | *.sublime-project 7 | *.sublime-workspace 8 | *.log 9 | .eslintcache 10 | coverage/ 11 | Changelog.md -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | __tests__/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | node_js: 6 | - 4 7 | - 6 8 | - 8 9 | after_success: 10 | - npm install -g semantic-release 11 | - npm install --no-save semantic-release-tamia 12 | - semantic-release pre && npm publish && semantic-release post 13 | branches: 14 | except: 15 | - /^v\d+\.\d+\.\d+$/ 16 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | I love pull requests. And following this simple guidelines will make your pull request easier to merge. 4 | 5 | ## Submitting pull requests 6 | 7 | 1. Create a new branch, please don’t work in master directly. 8 | 2. Add failing tests (if there’re any tests in project) for the change you want to make. Run tests (see below) to see the tests fail. 9 | 3. Hack on. 10 | 4. Run tests to see if the tests pass. Repeat steps 2–4 until done. 11 | 5. Update the documentation to reflect any changes. 12 | 6. Push to your fork and submit a pull request. 13 | 14 | ## JavaScript code style 15 | 16 | [See here](https://github.com/tamiadev/eslint-config-tamia#code-style-at-a-glance). 17 | 18 | ## Other notes 19 | 20 | - If you have commit access to repository and want to make big change or not sure about something, make a new branch and open pull request. 21 | - Don’t commit generated files: compiled from Stylus CSS, minified JavaScript, etc. 22 | - Don’t change version number and change log. 23 | - Install [EditorConfig](http://editorconfig.org/) plugin for your code editor. 24 | - Feel free to [ask me](http://sapegin.me) anything you need. 25 | 26 | ## Building and running tests 27 | 28 | Install dependencies: 29 | 30 | ```bash 31 | npm install 32 | ``` 33 | 34 | Run tests: 35 | 36 | ```bash 37 | npm test 38 | ``` 39 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright 2017 Artem Sapegin (http://sapegin.me), contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # q-i: Node.js objects inspector with color highlighting 2 | 3 | [![npm](https://img.shields.io/npm/v/q-i.svg)](https://www.npmjs.com/package/q-i) 4 | [![Build Status](https://travis-ci.org/sapegin/q-i.svg)](https://travis-ci.org/sapegin/q-i) 5 | 6 | Useful for debugging big objects, like webpack configuration. 7 | 8 | ![](https://d3vv6lp55qjaqc.cloudfront.net/items/0S1R2F1u1i1E2h2z0R41/q-i.png) 9 | 10 | [![Washing your code. A book on clean code for frontend developers](https://sapegin.me/images/washing-code-github.jpg)](https://sapegin.me/book/) 11 | 12 | ## Features 13 | 14 | * Compact and readable output 15 | * No unnecessary quotes 16 | * Color highlighted 17 | * Collapses huge arrays and objects (more than 30 items by default) 18 | 19 | ## Installation 20 | 21 | ```bash 22 | npm install q-i 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```js 28 | const { print, stringify } = require('q-i'); 29 | 30 | const obj = { a: { x: 41, y: { z: 42 } } }; 31 | 32 | print(obj); 33 | console.log(stringify(obj)); 34 | /* => 35 | { 36 | a: { 37 | x: 41, 38 | y: { 39 | z: 42 40 | } 41 | } 42 | } 43 | */ 44 | ``` 45 | 46 | ## Options 47 | 48 | ```js 49 | print(obj, { maxItems: Infinity }) 50 | stringify(obj, { maxItems: Infinity }) 51 | ``` 52 | 53 | ### `maxItems` (default: 30) 54 | 55 | Collapse arrays with more than `maxItems` items and objects with more than `maxItems` keys. 56 | 57 | ## Change log 58 | 59 | The change log can be found on the [Releases page](https://github.com/sapegin/q-i/releases). 60 | 61 | ## Contributing 62 | 63 | Everyone is welcome to contribute. Please take a moment to review the [contributing guidelines](Contributing.md). 64 | 65 | ## Sponsoring 66 | 67 | This software has been developed with lots of coffee, buy me one more cup to keep it going. 68 | 69 | Buy Me A Coffee 70 | 71 | ## Authors and license 72 | 73 | [Artem Sapegin](http://sapegin.me) and [contributors](https://github.com/sapegin/q-i/graphs/contributors). 74 | 75 | MIT License, see the included [License.md](License.md) file. 76 | -------------------------------------------------------------------------------- /__snapshots__/test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`stringify() should accept custom max items 1`] = ` 4 | "{ 5 | a: { 6 | k1: 0, 7 | k2: 0 8 | }, 9 | b: Object {4} 10 | }" 11 | `; 12 | 13 | exports[`stringify() should collapse arrays with more than 30 items 1`] = ` 14 | "{ 15 | a: [ 16 | 1, 17 | 2 18 | ], 19 | b: b,Array[31] 20 | }" 21 | `; 22 | 23 | exports[`stringify() should collapse functions longer than one line 1`] = ` 24 | "{ 25 | a: Function fn, 26 | c: x => x * 2 27 | }" 28 | `; 29 | 30 | exports[`stringify() should collapse objects with more than 30 keys 1`] = ` 31 | "{ 32 | a: { 33 | k1: 0, 34 | k2: 0 35 | }, 36 | b: Object {31} 37 | }" 38 | `; 39 | 40 | exports[`stringify() should color highlight basic types 1`] = ` 41 | "{ 42 | a: undefined, 43 | b: null, 44 | c: 0, 45 | d: 42, 46 | e: '42', 47 | f: false, 48 | g: true, 49 | h: /pizza/, 50 | i: [ 51 | 1, 52 | 2 53 | ], 54 | j: { 55 | x: 1, 56 | y: 2 57 | } 58 | }" 59 | `; 60 | 61 | exports[`stringify() should print contructor names for classes 1`] = ` 62 | "{ 63 | x: Pizza { 64 | name: 'Quattro Formaggi' 65 | } 66 | }" 67 | `; 68 | 69 | exports[`stringify() should stringify objects without constructor 1`] = ` 70 | "{ 71 | a: {} 72 | }" 73 | `; 74 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | maxItems?: number, 3 | } 4 | 5 | declare module 'q-i' { 6 | declare function print(object: any, options?: Options) : void; 7 | declare function stringify(object: any, options?: Options) : string; 8 | } 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isPlainObject = require('is-plain-object'); 4 | const stringifyObject = require('stringify-object'); 5 | const ansi = require('ansi-styles'); 6 | 7 | const internals = ['Array', 'Object', 'Function']; 8 | 9 | const style = (v, s) => `${ansi[s].open}${v}${ansi[s].close}`; 10 | 11 | const color = (v, c) => `${ansi.color[c].open}${v}${ansi.color.close}`; 12 | 13 | const getArraySize = o => Array.isArray(o) && o.length; 14 | 15 | const getObjectSize = o => isPlainObject(o) && Object.keys(o).length; 16 | 17 | const getFunctionSize = o => typeof o === 'function' && o.toString().split('\n').length; 18 | 19 | const getConstructor = v => { 20 | if (v === null) { 21 | return 'Null'; 22 | } 23 | 24 | if (v === undefined) { 25 | return 'Undefined'; 26 | } 27 | 28 | return v.constructor && v.constructor.name; 29 | }; 30 | 31 | const printers = { 32 | Null: v => style(v, 'italic'), 33 | Undefined: v => style(v, 'italic'), 34 | Boolean: v => color(v, 'magenta'), 35 | Number: v => color(v, 'cyan'), 36 | String: v => color(v, 'green'), 37 | RegExp: v => color(v, 'yellow'), 38 | }; 39 | 40 | function stringify(object, options) { 41 | const maxItems = (options && options.maxItems) || 30; 42 | const maxLines = (options && options.maxLines) || 1; 43 | 44 | return stringifyObject(object, { 45 | indent: ' ', 46 | transform: (obj, key, originalResult) => { 47 | const value = obj[key]; 48 | 49 | const arraySize = getArraySize(value); 50 | if (arraySize > maxItems) { 51 | return [key, style(`Array[${arraySize}]`, 'dim')]; 52 | } 53 | 54 | const objectSize = getObjectSize(value); 55 | if (objectSize > maxItems) { 56 | return style(`Object {${objectSize}}`, 'dim'); 57 | } 58 | 59 | const functionSize = getFunctionSize(value); 60 | if (functionSize > maxLines) { 61 | return style(`Function ${value.name || ''}`, 'dim'); 62 | } 63 | 64 | const ctr = getConstructor(value); 65 | 66 | if (printers[ctr]) { 67 | return printers[ctr](originalResult); 68 | } 69 | 70 | if (ctr && internals.indexOf(ctr) === -1) { 71 | return `${ctr} ${originalResult}`; 72 | } 73 | 74 | return originalResult; 75 | }, 76 | }); 77 | } 78 | 79 | function print(object, options) { 80 | // eslint-disable-next-line no-console 81 | console.log(stringify(object, options)); 82 | } 83 | 84 | module.exports = { 85 | print, 86 | stringify, 87 | }; 88 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext" 5 | ] 6 | }, 7 | "exclude": [ 8 | "node_modules" 9 | ] 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "q-i", 3 | "version": "2.0.1", 4 | "description": "Node.js objects inspector with color highlighting", 5 | "author": { 6 | "name": "Artem Sapegin", 7 | "url": "http://sapegin.me" 8 | }, 9 | "homepage": "https://github.com/sapegin/q-i", 10 | "repository": "sapegin/q-i", 11 | "license": "MIT", 12 | "engines": { 13 | "node": ">=4" 14 | }, 15 | "main": "index.js", 16 | "files": [ 17 | "index.js" 18 | ], 19 | "scripts": { 20 | "lint": "eslint . --cache --fix", 21 | "pretest": "npm run lint", 22 | "test:jest": "jest", 23 | "test:watch": "jest --watch", 24 | "test:coverage": "jest --coverage", 25 | "test": "npm run test:jest", 26 | "precommit": "lint-staged" 27 | }, 28 | "keywords": [ 29 | "node", 30 | "inspect", 31 | "inspector", 32 | "object", 33 | "print", 34 | "printer", 35 | "pretty", 36 | "stringify", 37 | "json", 38 | "color", 39 | "syntax", 40 | "highlight" 41 | ], 42 | "devDependencies": { 43 | "eslint": "^8.29.0", 44 | "eslint-config-tamia": "^4.2.3", 45 | "eslint-plugin-prettier": "^2.3.1", 46 | "husky": "^0.14.3", 47 | "jest": "^29.3.1", 48 | "lint-staged": "^4.2.1", 49 | "prettier": "^1.7.0" 50 | }, 51 | "dependencies": { 52 | "ansi-styles": "^3.2.0", 53 | "is-plain-object": "^2.0.4", 54 | "stringify-object": "^3.2.0" 55 | }, 56 | "lint-staged": { 57 | "*.js": [ 58 | "eslint --fix", 59 | "git add" 60 | ] 61 | }, 62 | "release": { 63 | "analyzeCommits": "semantic-release-tamia/analyzeCommits", 64 | "generateNotes": "semantic-release-tamia/generateNotes", 65 | "verifyRelease": "semantic-release-tamia/verifyRelease" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint-disable id-blacklist, no-console */ 3 | 4 | const qi = require('./index'); 5 | 6 | describe('stringify()', () => { 7 | it('should color highlight basic types', () => { 8 | const result = qi.stringify({ 9 | a: undefined, 10 | b: null, 11 | c: 0, 12 | d: 42, 13 | e: '42', 14 | f: false, 15 | g: true, 16 | h: /pizza/, 17 | i: [1, 2], 18 | j: { x: 1, y: 2 }, 19 | }); 20 | expect(result).toMatchSnapshot(); 21 | }); 22 | 23 | it('should print contructor names for classes', () => { 24 | class Pizza { 25 | constructor(options) { 26 | this.name = options.name; 27 | } 28 | } 29 | const result = qi.stringify({ 30 | x: new Pizza({ name: 'Quattro Formaggi' }), 31 | }); 32 | expect(result).toMatchSnapshot(); 33 | }); 34 | 35 | it('should collapse functions longer than one line', () => { 36 | function fn(x) { 37 | return x * 2; 38 | } 39 | const result = qi.stringify({ 40 | a: fn, 41 | c: x => x * 2, 42 | }); 43 | expect(result).toMatchSnapshot(); 44 | }); 45 | 46 | it('should collapse arrays with more than 30 items', () => { 47 | const result = qi.stringify({ 48 | a: [1, 2], 49 | b: [ 50 | 1, 51 | 2, 52 | 3, 53 | 4, 54 | 5, 55 | 6, 56 | 7, 57 | 8, 58 | 9, 59 | 10, 60 | 11, 61 | 12, 62 | 13, 63 | 14, 64 | 15, 65 | 16, 66 | 17, 67 | 18, 68 | 19, 69 | 20, 70 | 21, 71 | 22, 72 | 23, 73 | 24, 74 | 25, 75 | 26, 76 | 27, 77 | 28, 78 | 29, 79 | 30, 80 | 31, 81 | ], 82 | }); 83 | expect(result).toMatchSnapshot(); 84 | }); 85 | 86 | it('should collapse objects with more than 30 keys', () => { 87 | const result = qi.stringify({ 88 | a: { k1: 0, k2: 0 }, 89 | b: { 90 | k1: 0, 91 | k2: 0, 92 | k3: 0, 93 | k4: 0, 94 | k5: 0, 95 | k6: 0, 96 | k7: 0, 97 | k8: 0, 98 | k9: 0, 99 | k10: 0, 100 | k11: 0, 101 | k12: 0, 102 | k13: 0, 103 | k14: 0, 104 | k15: 0, 105 | k16: 0, 106 | k17: 0, 107 | k18: 0, 108 | k19: 0, 109 | k20: 0, 110 | k21: 0, 111 | k22: 0, 112 | k23: 0, 113 | k24: 0, 114 | k25: 0, 115 | k26: 0, 116 | k27: 0, 117 | k28: 0, 118 | k29: 0, 119 | k30: 0, 120 | k31: 0, 121 | }, 122 | }); 123 | expect(result).toMatchSnapshot(); 124 | }); 125 | 126 | it('should stringify objects without constructor', () => { 127 | const result = qi.stringify({ 128 | a: Object.create(null), 129 | }); 130 | expect(result).toMatchSnapshot(); 131 | }); 132 | 133 | it('should accept custom max items', () => { 134 | const result = qi.stringify( 135 | { 136 | a: { k1: 0, k2: 0 }, 137 | b: { 138 | k1: 0, 139 | k2: 0, 140 | k3: 0, 141 | k4: 0, 142 | }, 143 | }, 144 | { maxItems: 3 } 145 | ); 146 | expect(result).toMatchSnapshot(); 147 | }); 148 | }); 149 | 150 | describe('print()', () => { 151 | const console$log = console.log; 152 | beforeEach(() => { 153 | console.log = jest.fn(); 154 | }); 155 | afterEach(() => { 156 | console.log = console$log; 157 | }); 158 | 159 | it('should print an object', () => { 160 | qi.print({ 161 | a: 42, 162 | }); 163 | expect(console.log).toBeCalledWith(expect.stringMatching('42')); 164 | }); 165 | 166 | it('should accept custom max items', () => { 167 | qi.print( 168 | { 169 | a: { k1: 0, k2: 0 }, 170 | b: { 171 | k1: 0, 172 | k2: 0, 173 | k3: 0, 174 | k4: 0, 175 | }, 176 | }, 177 | { maxItems: 3 } 178 | ); 179 | expect(console.log).toBeCalledWith(expect.stringMatching('Object \\{4\\}')); 180 | }); 181 | }); 182 | --------------------------------------------------------------------------------