-
Notifications
You must be signed in to change notification settings - Fork 6
/
index.js
202 lines (183 loc) · 6.63 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import Rows from './lib/rows.js'
import Columns from './lib/columns.js'
import wrap from 'wordwrapjs'
import Cell from './lib/cell.js'
import arrayify from 'array-back'
import * as ansi from './lib/ansi.js'
import { removeEmptyColumns, getLongestWord, getLongestArray, padCell, applyDefaultValues } from './lib/util.js'
/**
* @module table-layout
*/
/**
* Recordset data in (array of objects), text table out.
* @alias module:table-layout
*/
class Table {
/**
* @param {object[]} - input data
* @param [options] {object} - optional settings
* @param [options.maxWidth] {number} - maximum width of layout
* @param [options.noWrap] {boolean} - disable wrapping on all columns
* @param [options.noTrim] {boolean} - disable line-trimming
* @param [options.break] {boolean} - enable word-breaking on all columns
* @param [options.columns] {module:table-layout~columnOption} - array of column-specific options
* @param [options.ignoreEmptyColumns] {boolean} - If set, empty columns or columns containing only whitespace are not rendered.
* @param [options.padding] {object} - Padding values to set on each column. Per-column overrides can be set in the `options.columns` array.
* @param [options.padding.left] {string} - Defaults to a single space.
* @param [options.padding.right] {string} - Defaults to a single space.
* @param [options.eol] {string} - EOL character used. Defaults to `\n`.
* @alias module:table-layout
*/
constructor (data, options = {}) {
const defaults = {
padding: {
left: ' ',
right: ' '
},
maxWidth: 80,
columns: [],
eol: '\n'
}
this.options = applyDefaultValues(options, defaults)
this.rows = null
this.columns = null
this.load(data)
}
/**
* Set the input data to display. Must be an array of objects.
* @param data {object[]}
*/
load (data) {
const options = this.options
/* remove empty columns */
if (options.ignoreEmptyColumns) {
data = removeEmptyColumns(data)
}
/* Create columns.. also removes ansi characters and measures column content width */
this.columns = Columns.getColumns(data)
/* load default column properties from options */
this.columns.maxWidth = options.maxWidth
for (const column of this.columns.list) {
column.padding = options.padding
column.noWrap = options.noWrap
column.break = options.break
if (options.break) {
/* Force column to be wrappable */
column.contentWrappable = true
}
}
/* load column properties from options.columns */
for (const optionColumn of options.columns) {
const column = this.columns.get(optionColumn.name)
if (column) {
if (optionColumn.padding) {
column.padding.left = optionColumn.padding.left
column.padding.right = optionColumn.padding.right
}
column.width = optionColumn.width
column.maxWidth = optionColumn.maxWidth
column.minWidth = optionColumn.minWidth
column.noWrap = optionColumn.noWrap
column.break = optionColumn.break
if (optionColumn.break) {
/* Force column to be wrappable */
column.contentWrappable = true
}
column.get = optionColumn.get
}
}
for (const row of arrayify(data)) {
for (const columnName in row) {
const column = this.columns.get(columnName)
/* Remove ansi characters from cell value before calculating widths */
const cell = new Cell(row[columnName], column)
let cellValue = cell.value
if (ansi.has(cellValue)) {
cellValue = ansi.remove(cellValue)
}
/* Update column content width if this if this cell is wider */
if (cellValue.length > column.contentWidth) {
column.contentWidth = cellValue.length
}
/* Update column minContentWidth if this cell has a longer word */
const longestWord = getLongestWord(cellValue)
if (longestWord > column.minContentWidth) {
column.minContentWidth = longestWord
}
if (!column.contentWrappable) {
column.contentWrappable = wrap.isWrappable(cellValue)
}
}
}
this.columns.autoSize()
this.rows = new Rows(data, this.columns)
return this
}
getWrapped () {
this.columns.autoSize()
return this.rows.list.map(row => {
const line = []
for (const [column, cell] of row.entries()) {
if (column.noWrap) {
line.push(cell.value.split(/\r\n?|\n/))
} else {
line.push(wrap.lines(cell.value, {
width: column.wrappedContentWidth,
break: column.break,
noTrim: this.options.noTrim
}))
}
}
return line
})
}
getLines () {
const wrappedLines = this.getWrapped()
const lines = []
wrappedLines.forEach(wrapped => {
const mostLines = getLongestArray(wrapped)
for (let i = 0; i < mostLines; i++) {
const line = []
wrapped.forEach(cell => {
line.push(cell[i] || '')
})
lines.push(line)
}
})
return lines
}
/**
* Identical to `.toString()` with the exception that the result will be an array of lines, rather than a single, multi-line string.
* @returns {string[]}
*/
renderLines () {
const lines = this.getLines()
return lines.map(line => {
return line.reduce((prev, cell, index) => {
const column = this.columns.list[index]
return prev + padCell(cell, column.padding, column.generatedWidth)
}, '')
})
}
/**
* Returns the input data as a text table.
* @returns {string}
*/
toString () {
return this.renderLines().join(this.options.eol) + this.options.eol
}
}
/**
* @typedef module:table-layout~columnOption
* @property name {string} - column name, must match a property name in the input
* @property [width] {number} - A specific column width. Supply either this or a min and/or max width.
* @property [minWidth] {number} - column min width
* @property [maxWidth] {number} - column max width
* @property [nowrap] {boolean} - disable wrapping for this column
* @property [break] {boolean} - enable word-breaking for this columns
* @property [padding] {object} - padding options
* @property [padding.left] {string} - a string to pad the left of each cell (default: `' '`)
* @property [padding.right] {string} - a string to pad the right of each cell (default: `' '`)
* @property [get] {function(cell)} - A getter function to return the cell value, the function receives the existing cell value. Signature: `function (cellValue) { // return the desired cell value }`
*/
export default Table