1'use strict'; 2 3const stringWidth = require('string-width'); 4const stripAnsi = require('strip-ansi'); 5 6const concat = Array.prototype.concat; 7const defaults = { 8 character: ' ', 9 newline: '\n', 10 padding: 2, 11 sort: true, 12 width: 0 13}; 14 15function byPlainText(a, b) { 16 const plainA = stripAnsi(a); 17 const plainB = stripAnsi(b); 18 19 if (plainA === plainB) { 20 return 0; 21 } 22 23 if (plainA > plainB) { 24 return 1; 25 } 26 27 return -1; 28} 29 30function makeArray() { 31 return []; 32} 33 34function makeList(count) { 35 return Array.apply(null, Array(count)); 36} 37 38function padCell(fullWidth, character, value) { 39 const valueWidth = stringWidth(value); 40 const filler = makeList(fullWidth - valueWidth + 1); 41 42 return value + filler.join(character); 43} 44 45function toRows(rows, cell, i) { 46 rows[i % rows.length].push(cell); 47 48 return rows; 49} 50 51function toString(arr) { 52 return arr.join(''); 53} 54 55function columns(values, options) { 56 values = concat.apply([], values); 57 options = Object.assign({}, defaults, options); 58 59 let cells = values 60 .filter(Boolean) 61 .map(String); 62 63 if (options.sort !== false) { 64 cells = cells.sort(byPlainText); 65 } 66 67 const termWidth = options.width || process.stdout.columns; 68 const cellWidth = Math.max.apply(null, cells.map(stringWidth)) + options.padding; 69 const columnCount = Math.floor(termWidth / cellWidth) || 1; 70 const rowCount = Math.ceil(cells.length / columnCount) || 1; 71 72 if (columnCount === 1) { 73 return cells.join(options.newline); 74 } 75 76 return cells 77 .map(padCell.bind(null, cellWidth, options.character)) 78 .reduce(toRows, makeList(rowCount).map(makeArray)) 79 .map(toString) 80 .join(options.newline); 81} 82 83module.exports = columns; 84