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.filter(Boolean).map(String);
60
61	if (options.sort !== false) {
62		cells = cells.sort(byPlainText);
63	}
64
65	const termWidth = options.width || process.stdout.columns;
66	const cellWidth =
67		Math.max.apply(null, cells.map(stringWidth)) + options.padding;
68	const columnCount = Math.floor(termWidth / cellWidth) || 1;
69	const rowCount = Math.ceil(cells.length / columnCount) || 1;
70
71	if (columnCount === 1) {
72		return cells.join(options.newline);
73	}
74
75	return cells
76		.map(padCell.bind(null, cellWidth, options.character))
77		.reduce(toRows, makeList(rowCount).map(makeArray))
78		.map(toString)
79		.join(options.newline);
80}
81
82module.exports = columns;
83