├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples └── revs.js ├── lib ├── index.js └── utils.js ├── package.json └── test ├── index.mocha.js └── newline.mocha.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "13" 5 | - "12" 6 | - "10" 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.0 4 | 5 | - add borders: false option 6 | - Adapt test to mocha 7 | - Replace manual padding with .padEnd 8 | - Allow only Node 8 9 | - Update travis test suite 10 | - Upgrade chalk to 3.0.0 11 | 12 | 0.3.1 / 2014-10-22 13 | ================== 14 | 15 | * fix example for new paths 16 | * Readme badges 17 | * Lighter production installs 18 | * Safe colors 19 | * In addition to 256-xterm ansi colors, handle 24-bit colors 20 | * set up .travis.yml 21 | 22 | 0.3.0 / 2014-02-02 23 | ================== 24 | 25 | * Switch version of colors to avoid npm broken-ness 26 | * Handle custom colored strings correctly 27 | * Removing var completely as return var width caused other problems. 28 | * Fixing global leak of width variable. 29 | * Omit horizontal decoration lines if empty 30 | * Add a test for the the compact mode 31 | * Make line() return the generated string instead of appending it to ret 32 | * Customize the vertical cell separator separately from the right one 33 | * Allow newer versions of colors to be used 34 | * Added test for bordercolor 35 | * Add bordercolor in style options and enable deepcopy of options 36 | 37 | 0.2.0 / 2012-10-21 38 | ================== 39 | 40 | * test: avoid module dep in tests 41 | * fix type bug on integer vertical table value 42 | * handle newlines in vertical and cross tables 43 | * factor out common style setting function 44 | * handle newlines in body cells 45 | * fix render bug when no header provided 46 | * correctly calculate width of cells with newlines 47 | * handles newlines in header cells 48 | * ability to create cross tables 49 | * changing table chars to ones that windows supports 50 | * allow empty arguments to Table constructor 51 | * fix headless tables containing empty first row 52 | * add vertical tables 53 | * remove reference to require.paths 54 | * compact style for dense tables 55 | * fix toString without col widths by cloning array 56 | * [api]: Added abiltity to strip out ANSI color escape codes when calculating cell padding 57 | 58 | 0.0.1 / 2011-01-03 59 | ================== 60 | 61 | Initial release 62 | 63 | 64 | ## Jun 28, 2017 65 | 66 | Fork of `Automattic/cli-table` 67 | 68 | - Merges [cli-table/#83](https://github.com/Automattic/cli-table/pull/83) (test in [6d5d4b](https://github.com/keymetrics/cli-table/commit/6d5d4b293295e312ad1370e28f409e5a3ff3fc47)) to add array method names in the data set. 69 | - Releases a fix on null/undefined values, [cli-table/#71](https://github.com/Automattic/cli-table/pull/71). 70 | - Lint the code using [standard](https://github.com/standard/standard). 71 | - Use `chalk` instead of `colors`. 72 | - Bump version to stable `1.0.0` 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2017 Automattic 4 | Copyright (c) 2017 Keymetrics 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # cli tableau 3 | 4 | 5 | Build Status 6 | 7 | 8 | 9 | ### Horizontal Tables 10 | ```javascript 11 | var Table = require('cli-tableau'); 12 | 13 | var table = new Table({ 14 | head: ['TH 1 label', 'TH 2 label'], 15 | colWidths: [100, 200], 16 | borders: false 17 | }); 18 | 19 | table.push( 20 | ['First value', 'Second value'], 21 | ['First value', 'Second value'] 22 | ); 23 | 24 | console.log(table.toString()); 25 | ``` 26 | 27 | ### Vertical Tables 28 | 29 | ```javascript 30 | var Table = require('cli-tableau'); 31 | var table = new Table(); 32 | 33 | table.push( 34 | { 'Some key': 'Some value' }, 35 | { 'Another key': 'Another value' } 36 | ); 37 | 38 | console.log(table.toString()); 39 | ``` 40 | 41 | ### Cross Tables 42 | Cross tables are very similar to vertical tables, with two key differences: 43 | 44 | 1. They require a `head` setting when instantiated that has an empty string as the first header 45 | 2. The individual rows take the general form of { "Header": ["Row", "Values"] } 46 | 47 | ```javascript 48 | var Table = require('cli-tableau'); 49 | var table = new Table({ head: ["", "Top Header 1", "Top Header 2"] }); 50 | 51 | table.push( 52 | { 'Left Header 1': ['Value Row 1 Col 1', 'Value Row 1 Col 2'] }, 53 | { 'Left Header 2': ['Value Row 2 Col 1', 'Value Row 2 Col 2'] } 54 | ); 55 | 56 | console.log(table.toString()); 57 | ``` 58 | 59 | ### Custom styles 60 | 61 | The ```chars``` property controls how the table is drawn: 62 | ```javascript 63 | var table = new Table({ 64 | chars: { 65 | 'top': '═' , 'top-mid': '╤' , 'top-left': '╔' , 'top-right': '╗', 66 | 'bottom': '═' , 'bottom-mid': '╧' , 'bottom-left': '╚' , 'bottom-right': '╝', 67 | 'left': '║' , 'left-mid': '╟' , 'mid': '─' , 'mid-mid': '┼', 68 | 'right': '║' , 'right-mid': '╢' , 'middle': '│' 69 | } 70 | }); 71 | 72 | table.push( 73 | ['foo', 'bar', 'baz'], 74 | ['frob', 'bar', 'quuz'] 75 | ); 76 | 77 | console.log(table.toString()); 78 | // Outputs: 79 | // 80 | //╔══════╤═════╤══════╗ 81 | //║ foo │ bar │ baz ║ 82 | //╟──────┼─────┼──────╢ 83 | //║ frob │ bar │ quuz ║ 84 | //╚══════╧═════╧══════╝ 85 | ``` 86 | 87 | Empty decoration lines will be skipped, to avoid vertical separator rows just 88 | set the 'mid', 'left-mid', 'mid-mid', 'right-mid' to the empty string: 89 | ```javascript 90 | var table = new Table({ chars: {'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''} }); 91 | table.push( 92 | ['foo', 'bar', 'baz'], 93 | ['frobnicate', 'bar', 'quuz'] 94 | ); 95 | 96 | console.log(table.toString()); 97 | // Outputs: (note the lack of the horizontal line between rows) 98 | //┌────────────┬─────┬──────┐ 99 | //│ foo │ bar │ baz │ 100 | //│ frobnicate │ bar │ quuz │ 101 | //└────────────┴─────┴──────┘ 102 | ``` 103 | 104 | By setting all chars to empty with the exception of 'middle' being set to a 105 | single space and by setting padding to zero, it's possible to get the most 106 | compact layout with no decorations: 107 | ```javascript 108 | var table = new Table({ 109 | chars: { 110 | 'top': '' , 'top-mid': '' , 'top-left': '' , 'top-right': '', 111 | 'bottom': '' , 'bottom-mid': '' , 'bottom-left': '' , 'bottom-right': '', 112 | 'left': '' , 'left-mid': '' , 'mid': '' , 'mid-mid': '', 113 | 'right': '' , 'right-mid': '' , 'middle': ' ' 114 | }, 115 | style: { 'padding-left': 0, 'padding-right': 0 } 116 | }); 117 | 118 | table.push( 119 | ['foo', 'bar', 'baz'], 120 | ['frobnicate', 'bar', 'quuz'] 121 | ); 122 | 123 | console.log(table.toString()); 124 | // Outputs: 125 | //foo bar baz 126 | //frobnicate bar quuz 127 | ``` 128 | 129 | ## Credits 130 | 131 | - Guillermo Rauch <guillermo@learnboost.com> ([Guille](http://github.com/guille)) 132 | -------------------------------------------------------------------------------- /examples/revs.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module requirements. 4 | */ 5 | 6 | var Table = require('../lib') 7 | 8 | /** 9 | * Example. 10 | */ 11 | 12 | /* col widths */ 13 | var table = new Table({ 14 | head: ['Rel', 'Change', 'By', 'When'], 15 | colWidths: [6, 21, 25, 17] 16 | }) 17 | 18 | table.push( 19 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] 20 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 21 | ) 22 | 23 | console.log(table.toString()) 24 | 25 | /* compact */ 26 | var table2 = new Table({ 27 | head: ['Rel', 'Change', 'By', 'When'], 28 | colWidths: [6, 21, 25, 17], 29 | style: {compact: true, 'padding-left': 1} 30 | }) 31 | 32 | table2.push( 33 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] 34 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 35 | , [] 36 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 37 | ) 38 | 39 | console.log(table.toString()) 40 | 41 | /* headless */ 42 | var headlessTable = new Table() 43 | headlessTable.push(['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago']) 44 | console.log(headlessTable.toString()) 45 | 46 | /* vertical */ 47 | var verticalTable = new Table() 48 | verticalTable.push({'Some Key': 'Some Value'}, {'Another much longer key': 'And its corresponding longer value'}) 49 | 50 | console.log(verticalTable.toString()) 51 | 52 | /* cross */ 53 | var crossTable = new Table({head: ['', 'Header #1', 'Header #2']}) 54 | crossTable.push({'Header #3': ['Value 1', 'Value 2']}, {'Header #4': ['Value 3', 'Value 4']}) 55 | console.log(crossTable.toString()) 56 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var colors = require('chalk') 7 | var utils = require('./utils') 8 | var repeat = utils.repeat 9 | var truncate = utils.truncate 10 | 11 | /** 12 | * Table constructor 13 | * 14 | * @param {Object} options 15 | * @api public 16 | */ 17 | 18 | function Table (options) { 19 | this.options = utils.options({ 20 | chars: { 21 | 'top': '─', 22 | 'top-mid': '┬', 23 | 'top-left': '┌', 24 | 'top-right': '┐', 25 | 'bottom': '─', 26 | 'bottom-mid': '┴', 27 | 'bottom-left': '└', 28 | 'bottom-right': '┘', 29 | 'left': '│', 30 | 'left-mid': '├', 31 | 'mid': '─', 32 | 'mid-mid': '┼', 33 | 'right': '│', 34 | 'right-mid': '┤', 35 | 'middle': '│' 36 | }, 37 | truncate: '…', 38 | colWidths: [], 39 | colAligns: [], 40 | style: { 41 | 'padding-left': 1, 42 | 'padding-right': 1, 43 | head: ['red'], 44 | border: ['grey'], 45 | compact: false 46 | }, 47 | head: [] 48 | }, options) 49 | 50 | if (options.borders == false) { 51 | this.options.chars = { 52 | 'top': '', 53 | 'top-mid': '', 54 | 'top-left': '', 55 | 'top-right': '', 56 | 'bottom': '', 57 | 'bottom-mid': '', 58 | 'bottom-left': '', 59 | 'bottom-right': '', 60 | 'left': '', 61 | 'left-mid': '', 62 | 'mid': '', 63 | 'mid-mid': '', 64 | 'right': '', 65 | 'right-mid': '', 66 | 'middle': '' 67 | } 68 | } 69 | }; 70 | 71 | /** 72 | * Inherit from Array. 73 | */ 74 | 75 | Table.prototype = new Array 76 | 77 | /** 78 | * Width getter 79 | * 80 | * @return {Number} width 81 | * @api public 82 | */ 83 | 84 | Table.prototype.__defineGetter__('width', function () { 85 | var str = this.toString().split('\n') 86 | if (str.length) return str[0].length 87 | return 0 88 | }) 89 | 90 | /** 91 | * Render to a string. 92 | * 93 | * @return {String} table representation 94 | * @api public 95 | */ 96 | 97 | Table.prototype.render = 98 | Table.prototype.toString = function () { 99 | var ret = '' 100 | var options = this.options 101 | var style = options.style 102 | var head = options.head 103 | var chars = options.chars 104 | var truncater = options.truncate 105 | var colWidths = options.colWidths || new Array(this.head.length) 106 | var totalWidth = 0 107 | 108 | if (!head.length && !this.length) return '' 109 | 110 | if (!colWidths.length) { 111 | var everyRows = this.slice(0) 112 | if (head.length) { everyRows = everyRows.concat([head]) }; 113 | 114 | everyRows.forEach(function (cells) { 115 | // horizontal (arrays) 116 | if (Array.isArray(cells) && cells.length) { 117 | extractColumnWidths(cells) 118 | 119 | // vertical (objects) 120 | } else { 121 | var headerCell = Object.keys(cells)[0] 122 | var valueCell = cells[headerCell] 123 | 124 | colWidths[0] = Math.max(colWidths[0] || 0, getWidth(headerCell) || 0) 125 | 126 | // cross (objects w/ array values) 127 | if (Array.isArray(valueCell) && valueCell.length) { 128 | extractColumnWidths(valueCell, 1) 129 | } else { 130 | colWidths[1] = Math.max(colWidths[1] || 0, getWidth(valueCell) || 0) 131 | } 132 | } 133 | }) 134 | }; 135 | 136 | totalWidth = (colWidths.length === 1 ? colWidths[0] : colWidths.reduce( 137 | function (a, b) { 138 | return a + b 139 | })) + colWidths.length + 1 140 | 141 | function extractColumnWidths (arr, offset) { 142 | offset = offset || 0 143 | arr.forEach(function (cell, i) { 144 | colWidths[i + offset] = Math.max(colWidths[i + offset] || 0, getWidth(cell) || 0) 145 | }) 146 | }; 147 | 148 | function getWidth (obj) { 149 | return typeof obj === 'object' && obj && obj.width !== undefined 150 | ? obj.width 151 | : ((typeof obj === 'object' && obj !== null ? utils.strlen(obj.text) : utils.strlen(obj)) + (style['padding-left'] || 0) + (style['padding-right'] || 0)) 152 | } 153 | 154 | // draws a line 155 | function line (line, left, right, intersection) { 156 | var width = 0 157 | line = left + repeat(line, totalWidth - 2) + right 158 | 159 | colWidths.forEach(function (w, i) { 160 | if (i === colWidths.length - 1) return 161 | width += w + 1 162 | line = line.substr(0, width) + intersection + line.substr(width + 1) 163 | }) 164 | 165 | return applyStyles(options.style.border, line) 166 | }; 167 | 168 | // draws the top line 169 | function lineTop () { 170 | var l = line(chars.top, 171 | chars['top-left'] || chars.top, 172 | chars['top-right'] || chars.top, 173 | chars['top-mid']) 174 | if (l) { 175 | ret += l + '\n' 176 | } 177 | }; 178 | 179 | function generateRow (items, style) { 180 | var cells = [] 181 | var maxHeight = 0 182 | 183 | // prepare vertical and cross table data 184 | if (!Array.isArray(items) && typeof items === 'object') { 185 | var key = Object.keys(items)[0] 186 | var value = items[key] 187 | var firstCellHead = true 188 | 189 | if (Array.isArray(value)) { 190 | items = value 191 | items.unshift(key) 192 | } else { 193 | items = [key, value] 194 | } 195 | } 196 | 197 | // transform array of item strings into structure of cells 198 | items.forEach(function (item, i) { 199 | var contents = (item === null || item === undefined ? '' : item).toString().split('\n').reduce(function (memo, l) { 200 | memo.push(string(l, i)) 201 | return memo 202 | }, []) 203 | 204 | var height = contents.length 205 | if (height > maxHeight) { maxHeight = height }; 206 | 207 | cells.push({ contents: contents, height: height }) 208 | }) 209 | 210 | // transform vertical cells into horizontal lines 211 | var lines = new Array(maxHeight) 212 | cells.forEach(function (cell, i) { 213 | cell.contents.forEach(function (line, j) { 214 | if (!lines[j]) { lines[j] = [] }; 215 | if (style || (firstCellHead && i === 0 && options.style.head)) { 216 | line = applyStyles(options.style.head, line) 217 | } 218 | 219 | lines[j].push(line) 220 | }) 221 | 222 | // populate empty lines in cell 223 | for (var j = cell.height, l = maxHeight; j < l; j++) { 224 | if (!lines[j]) { lines[j] = [] }; 225 | lines[j].push(string('', i)) 226 | } 227 | }) 228 | 229 | var ret = '' 230 | lines.forEach(function (line, index) { 231 | if (ret.length > 0) { 232 | ret += '\n' + applyStyles(options.style.border, chars.left) 233 | } 234 | 235 | ret += line.join(applyStyles(options.style.border, chars.middle)) + applyStyles(options.style.border, chars.right) 236 | }) 237 | 238 | return applyStyles(options.style.border, chars.left) + ret 239 | }; 240 | 241 | function applyStyles (styles, subject) { 242 | if (!subject) { 243 | return '' 244 | } 245 | 246 | styles.forEach(function (style) { 247 | subject = colors[style](subject) 248 | }) 249 | 250 | return subject 251 | }; 252 | 253 | // renders a string, by padding it or truncating it 254 | function string (str, index) { 255 | str = String(typeof str === 'object' && str.text ? str.text : str) 256 | var length = utils.strlen(str) 257 | var width = colWidths[index] - (style['padding-left'] || 0) - (style['padding-right'] || 0) 258 | var align = options.colAligns[index] || 'left' 259 | 260 | return repeat(' ', style['padding-left'] || 0) + 261 | (length === width ? str 262 | : (length < width 263 | ? str.padEnd((width + (str.length - length)), ' ', align === 'left' ? 'right' 264 | : (align === 'middle' ? 'both' : 'left')) 265 | : (truncater ? truncate(str, width, truncater) : str)) 266 | ) + 267 | repeat(' ', style['padding-right'] || 0) 268 | }; 269 | 270 | if (head.length) { 271 | lineTop() 272 | 273 | ret += generateRow(head, style.head) + '\n' 274 | } 275 | 276 | if (this.length) { 277 | this.forEach(function (cells, i) { 278 | if (!head.length && i === 0) { lineTop() } else { 279 | if (!style.compact || i < (!!head.length) ? 1 : 0 || cells.length === 0) { 280 | var l = line(chars.mid 281 | , chars['left-mid'] 282 | , chars['right-mid'] 283 | , chars['mid-mid']) 284 | if (l) { ret += l + '\n' } 285 | } 286 | } 287 | 288 | if (Array.isArray(cells) && !cells.length) { 289 | return 290 | } else { 291 | ret += generateRow(cells) + '\n' 292 | }; 293 | }) 294 | } 295 | 296 | var l = line(chars.bottom, 297 | chars['bottom-left'] || chars.bottom, 298 | chars['bottom-right'] || chars.bottom, 299 | chars['bottom-mid']) 300 | if (l) { 301 | ret += l 302 | } else { 303 | // trim the last '\n' if we didn't add the bottom decoration 304 | ret = ret.slice(0, -1) 305 | } 306 | 307 | return ret 308 | } 309 | 310 | /** 311 | * Module exports. 312 | */ 313 | 314 | module.exports = Table 315 | 316 | module.exports.version = '2.0.0' 317 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Repeats a string. 4 | * 5 | * @param {String} char(s) 6 | * @param {Number} number of times 7 | * @return {String} repeated string 8 | */ 9 | 10 | exports.repeat = function (str, times) { 11 | return Array(times + 1).join(str) 12 | } 13 | 14 | /** 15 | * Truncates a string 16 | * 17 | * @api public 18 | */ 19 | 20 | exports.truncate = function (str, length, chr) { 21 | chr = chr || '…' 22 | return str.length >= length ? str.substr(0, length - chr.length) + chr : str 23 | } 24 | 25 | /** 26 | * Copies and merges options with defaults. 27 | * 28 | * @param {Object} defaults 29 | * @param {Object} supplied options 30 | * @return {Object} new (merged) object 31 | */ 32 | 33 | function options (defaults, opts) { 34 | for (var p in opts) { 35 | if (opts[p] && opts[p].constructor && opts[p].constructor === Object) { 36 | defaults[p] = defaults[p] || {} 37 | options(defaults[p], opts[p]) 38 | } else { 39 | defaults[p] = opts[p] 40 | } 41 | } 42 | 43 | return defaults 44 | }; 45 | exports.options = options 46 | 47 | // 48 | // For consideration of terminal "color" programs like colors.js, 49 | // which can add ANSI escape color codes to strings, 50 | // we destyle the ANSI color escape codes for padding calculations. 51 | // 52 | // see: http://en.wikipedia.org/wiki/ANSI_escape_code 53 | // 54 | exports.strlen = function (str) { 55 | var code = /\u001b\[(?:\d*;){0,5}\d*m/g 56 | var stripped = ('' + (str != null ? str : '')).replace(code, '') 57 | var split = stripped.split('\n') 58 | return split.reduce(function (memo, s) { return (s.length > memo) ? s.length : memo }, 0) 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-tableau", 3 | "description": "Pretty unicode tables for the CLI", 4 | "version": "2.0.1", 5 | "engines": { 6 | "node": ">=8.10.0" 7 | }, 8 | "author": { 9 | "name": "Guillermo Rauch", 10 | "email": "guillermo@learnboost.com" 11 | }, 12 | "contributors": [ 13 | { 14 | "name": "Sonny Michaud", 15 | "email": "Michaud.sonny@gmail.com" 16 | } 17 | ], 18 | "maintainers": [ 19 | { 20 | "name": "soyuka", 21 | "email": "soyuka@gmail.com" 22 | } 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/Keymetrics/cli-table.git" 27 | }, 28 | "keywords": [ 29 | "cli", 30 | "colors", 31 | "table" 32 | ], 33 | "dependencies": { 34 | "chalk": "3.0.0" 35 | }, 36 | "devDependencies": { 37 | "should": "~0.6", 38 | "mocha": "^7.1.1" 39 | }, 40 | "main": "lib", 41 | "files": [ 42 | "lib" 43 | ], 44 | "scripts": { 45 | "test": "mocha test/*" 46 | }, 47 | "licence": "MIT" 48 | } 49 | -------------------------------------------------------------------------------- /test/index.mocha.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * Module requirements. 5 | */ 6 | 7 | require('should') 8 | 9 | var Table = require('../') 10 | 11 | /** 12 | * Tests. 13 | */ 14 | 15 | describe('Table', function() { 16 | it('should test complete table ', () => { 17 | var table = new Table({ 18 | head: ['Rel', 'Change', 'By', 'When'], 19 | style: { 20 | 'padding-left': 1, 21 | 'padding-right': 1, 22 | head: [], 23 | border: [] 24 | }, 25 | colWidths: [6, 21, 25, 17] 26 | }) 27 | 28 | table.push( 29 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'], 30 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 31 | ) 32 | 33 | var expected = [ 34 | '┌──────┬─────────────────────┬─────────────────────────┬─────────────────┐', 35 | '│ Rel │ Change │ By │ When │', 36 | '├──────┼─────────────────────┼─────────────────────────┼─────────────────┤', 37 | '│ v0.1 │ Testing something … │ rauchg@gmail.com │ 7 minutes ago │', 38 | '├──────┼─────────────────────┼─────────────────────────┼─────────────────┤', 39 | '│ v0.1 │ Testing something … │ rauchg@gmail.com │ 8 minutes ago │', 40 | '└──────┴─────────────────────┴─────────────────────────┴─────────────────┘' 41 | ] 42 | 43 | table.toString().should.eql(expected.join('\n')) 44 | table.render().should.eql(expected.join('\n')) 45 | }) 46 | 47 | it('test width property', function () { 48 | var table = new Table({ 49 | head: ['Cool'], 50 | style: { 51 | head: [], 52 | border: [] 53 | } 54 | }) 55 | 56 | table.width.should.eql(8) 57 | }) 58 | 59 | it('test vertical table output', function () { 60 | var table = new Table({ style: {'padding-left': 0, 'padding-right': 0, head: [], border: []} }) // clear styles to prevent color output 61 | 62 | table.push( 63 | {'v0.1': 'Testing something cool'}, 64 | {'v0.1': 'Testing something cool'} 65 | ) 66 | 67 | var expected = [ 68 | '┌────┬──────────────────────┐', 69 | '│v0.1│Testing something cool│', 70 | '├────┼──────────────────────┤', 71 | '│v0.1│Testing something cool│', 72 | '└────┴──────────────────────┘' 73 | ] 74 | 75 | table.toString().should.eql(expected.join('\n')) 76 | }) 77 | 78 | it('test cross table output', function () { 79 | var table = new Table({ head: ['', 'Header 1', 'Header 2'], style: {'padding-left': 0, 'padding-right': 0, head: [], border: []} }) // clear styles to prevent color output 80 | 81 | table.push( 82 | {'Header 3': ['v0.1', 'Testing something cool']}, 83 | {'Header 4': ['v0.1', 'Testing something cool']} 84 | ) 85 | 86 | var expected = [ 87 | '┌────────┬────────┬──────────────────────┐', 88 | '│ │Header 1│Header 2 │', 89 | '├────────┼────────┼──────────────────────┤', 90 | '│Header 3│v0.1 │Testing something cool│', 91 | '├────────┼────────┼──────────────────────┤', 92 | '│Header 4│v0.1 │Testing something cool│', 93 | '└────────┴────────┴──────────────────────┘' 94 | ] 95 | 96 | table.toString().should.eql(expected.join('\n')) 97 | }) 98 | 99 | it('test table colors', function () { 100 | var table = new Table({ 101 | head: ['Rel', 'By'], 102 | style: {head: ['red'], border: ['grey']} 103 | }) 104 | 105 | var off = '\u001b[39m' 106 | var red = '\u001b[31m' 107 | var orange = '\u001b[38;5;221m' 108 | var grey = '\u001b[90m' 109 | 110 | c256s = orange + 'v0.1' + off 111 | 112 | table.push([c256s, 'rauchg@gmail.com']) 113 | 114 | var expected = [ 115 | grey + '┌──────┬──────────────────┐' + off, 116 | grey + '│' + off + red + ' Rel ' + off + grey + '│' + off + red + ' By ' + off + grey + '│' + off, 117 | grey + '├──────┼──────────────────┤' + off, 118 | grey + '│' + off + ' ' + c256s + ' ' + grey + '│' + off + ' rauchg@gmail.com ' + grey + '│' + off, 119 | grey + '└──────┴──────────────────┘' + off 120 | ] 121 | 122 | table.toString().should.eql(expected.join('\n')) 123 | }) 124 | 125 | it('test custom chars', function () { 126 | var table = new Table({ 127 | chars: { 128 | 'top': '═', 129 | 'top-mid': '╤', 130 | 'top-left': '╔', 131 | 'top-right': '╗', 132 | 'bottom': '═', 133 | 'bottom-mid': '╧', 134 | 'bottom-left': '╚', 135 | 'bottom-right': '╝', 136 | 'left': '║', 137 | 'left-mid': '╟', 138 | 'right': '║', 139 | 'right-mid': '╢' 140 | }, 141 | style: { 142 | head: [], 143 | border: [] 144 | } 145 | }) 146 | 147 | table.push(['foo', 'bar', 'baz'], ['frob', 'bar', 'quuz']) 148 | 149 | var expected = [ 150 | '╔══════╤═════╤══════╗', 151 | '║ foo │ bar │ baz ║', 152 | '╟──────┼─────┼──────╢', 153 | '║ frob │ bar │ quuz ║', 154 | '╚══════╧═════╧══════╝' 155 | ] 156 | 157 | table.toString().should.eql(expected.join('\n')) 158 | }) 159 | 160 | it('test compact shortand', function () { 161 | var table = new Table({ 162 | style: { 163 | head: [], 164 | border: [], 165 | compact: true 166 | } 167 | }) 168 | 169 | table.push(['foo', 'bar', 'baz'], ['frob', 'bar', 'quuz']) 170 | 171 | var expected = [ 172 | '┌──────┬─────┬──────┐', 173 | '│ foo │ bar │ baz │', 174 | '│ frob │ bar │ quuz │', 175 | '└──────┴─────┴──────┘' 176 | ] 177 | 178 | table.toString().should.eql(expected.join('\n')) 179 | }) 180 | 181 | it('test compact empty mid line', function () { 182 | var table = new Table({ 183 | chars: { 184 | 'mid': '', 185 | 'left-mid': '', 186 | 'mid-mid': '', 187 | 'right-mid': '' 188 | }, 189 | style: { 190 | head: [], 191 | border: [] 192 | } 193 | }) 194 | 195 | table.push(['foo', 'bar', 'baz'], ['frob', 'bar', 'quuz']) 196 | 197 | var expected = [ 198 | '┌──────┬─────┬──────┐', 199 | '│ foo │ bar │ baz │', 200 | '│ frob │ bar │ quuz │', 201 | '└──────┴─────┴──────┘' 202 | ] 203 | 204 | table.toString().should.eql(expected.join('\n')) 205 | }) 206 | 207 | it('test decoration lines disabled', function () { 208 | var table = new Table({ 209 | chars: { 210 | 'top': '', 211 | 'top-mid': '', 212 | 'top-left': '', 213 | 'top-right': '', 214 | 'bottom': '', 215 | 'bottom-mid': '', 216 | 'bottom-left': '', 217 | 'bottom-right': '', 218 | 'left': '', 219 | 'left-mid': '', 220 | 'mid': '', 221 | 'mid-mid': '', 222 | 'right': '', 223 | 'right-mid': '', 224 | 'middle': ' ' // a single space 225 | }, 226 | style: { 227 | head: [], 228 | border: [], 229 | 'padding-left': 0, 230 | 'padding-right': 0 231 | } 232 | }) 233 | 234 | table.push(['foo', 'bar', 'baz'], ['frobnicate', 'bar', 'quuz']) 235 | 236 | var expected = [ 237 | 'foo bar baz ', 238 | 'frobnicate bar quuz' 239 | ] 240 | 241 | table.toString().should.eql(expected.join('\n')) 242 | }) 243 | 244 | it('test with null/undefined as values or column names', function () { 245 | var table = new Table({ 246 | head: [null, undefined, 0, ''], 247 | style: { 248 | head: [], 249 | border: [] 250 | } 251 | }) 252 | 253 | table.push( 254 | [null, undefined, 0, ''] 255 | ) 256 | 257 | var expected = [ 258 | '┌──┬──┬───┬──┐', 259 | '│ │ │ 0 │ │', 260 | '├──┼──┼───┼──┤', 261 | '│ │ │ 0 │ │', 262 | '└──┴──┴───┴──┘' 263 | ] 264 | 265 | table.toString().should.eql(expected.join('\n')) 266 | }) 267 | 268 | it('test with toString things', function() { 269 | 270 | var table = new Table({ 271 | head: ['Some test toString', 'foo'], 272 | style: { 273 | head: [], 274 | border: [] 275 | } 276 | }) 277 | 278 | table.push([{}, {toString: function() { return 'foo'}}]) 279 | 280 | var expected = [ 281 | '┌────────────────────┬─────┐', 282 | '│ Some test toString │ foo │', 283 | '├────────────────────┼─────┤', 284 | '│ [object Object] │ foo │', 285 | '└────────────────────┴─────┘' 286 | ] 287 | 288 | table.toString().should.eql(expected.join('\n')) 289 | }) 290 | 291 | it('test with array method values', function() { 292 | 293 | var table = new Table({ 294 | head: ['pop', 'push', 'slice'], 295 | style: { 296 | head: [], 297 | border: [] 298 | } 299 | }) 300 | 301 | table.push(['push', 'slice', 'pop']) 302 | 303 | var expected = [ 304 | '┌──────┬───────┬───────┐', 305 | '│ pop │ push │ slice │', 306 | '├──────┼───────┼───────┤', 307 | '│ push │ slice │ pop │', 308 | '└──────┴───────┴───────┘' 309 | ] 310 | 311 | table.toString().should.eql(expected.join('\n')) 312 | }) 313 | }) 314 | -------------------------------------------------------------------------------- /test/newline.mocha.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module requirements. 3 | */ 4 | 5 | require('should') 6 | 7 | var Table = require('../') 8 | 9 | /** 10 | * Tests. 11 | */ 12 | 13 | describe('', function() { 14 | it('test table with newlines in headers', function () { 15 | var table = new Table({ 16 | head: ['Test', '1\n2\n3'], 17 | style: { 18 | 'padding-left': 1, 19 | 'padding-right': 1, 20 | head: [], 21 | border: [] 22 | } 23 | }) 24 | 25 | var expected = [ 26 | '┌──────┬───┐', 27 | '│ Test │ 1 │', 28 | '│ │ 2 │', 29 | '│ │ 3 │', 30 | '└──────┴───┘' 31 | ] 32 | 33 | table.toString().should.eql(expected.join('\n')) 34 | }) 35 | 36 | it('test column width is accurately reflected when newlines are present', function () { 37 | var table = new Table({ head: ['Test\nWidth'], style: {head: [], border: []} }) 38 | table.width.should.eql(9) 39 | }) 40 | 41 | it('test newlines in body cells', function () { 42 | var table = new Table({style: {head: [], border: []}}) 43 | 44 | table.push(['something\nwith\nnewlines']) 45 | 46 | var expected = [ 47 | '┌───────────┐', 48 | '│ something │', 49 | '│ with │', 50 | '│ newlines │', 51 | '└───────────┘' 52 | ] 53 | 54 | table.toString().should.eql(expected.join('\n')) 55 | }) 56 | 57 | it('test newlines in vertical cell header and body', function () { 58 | var table = new Table({ style: {'padding-left': 0, 'padding-right': 0, head: [], border: []} }) 59 | 60 | table.push( 61 | {'v\n0.1': 'Testing\nsomething cool'} 62 | ) 63 | 64 | var expected = [ 65 | '┌───┬──────────────┐', 66 | '│v │Testing │', 67 | '│0.1│something cool│', 68 | '└───┴──────────────┘' 69 | ] 70 | 71 | table.toString().should.eql(expected.join('\n')) 72 | }) 73 | 74 | it('test newlines in cross table header and body', function () { 75 | var table = new Table({ head: ['', 'Header\n1'], style: {'padding-left': 0, 'padding-right': 0, head: [], border: []} }) 76 | 77 | table.push({ 'Header\n2': ['Testing\nsomething\ncool'] }) 78 | 79 | var expected = [ 80 | '┌──────┬─────────┐', 81 | '│ │Header │', 82 | '│ │1 │', 83 | '├──────┼─────────┤', 84 | '│Header│Testing │', 85 | '│2 │something│', 86 | '│ │cool │', 87 | '└──────┴─────────┘' 88 | ] 89 | 90 | table.toString().should.eql(expected.join('\n')) 91 | }) 92 | }) 93 | --------------------------------------------------------------------------------