tty-table 4.2.3 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/format.js DELETED
@@ -1,282 +0,0 @@
1
- const stripAnsi = require("strip-ansi")
2
- const smartwrap = require("smartwrap")
3
- const wcwidth = require("wcwidth")
4
-
5
- const addPadding = (config, width) => {
6
- return width + config.paddingLeft + config.paddingRight
7
- }
8
-
9
- /**
10
- * Returns the widest cell give a collection of rows
11
- *
12
- * @param object columnOptions
13
- * @param array rows
14
- * @param integer columnIndex
15
- * @returns string
16
- */
17
- const getMaxLength = (columnOptions, rows, columnIndex) => {
18
- let iterable
19
-
20
- // add header value, alias to calculate width when applicable
21
- if (columnOptions && (columnOptions.value || columnOptions.alias)) {
22
- // string we use from header
23
- let val = columnOptions.alias || columnOptions.value
24
- val = val.toString()
25
- // create a row with value in the current columnIndex
26
- const headerRow = Array(rows[0].length)
27
- headerRow[columnIndex] = val
28
- // add header row to new array we will check for max value width
29
- iterable = rows.slice()
30
- iterable.push(headerRow)
31
- } else {
32
- // no header value, just use rows to derive max width
33
- iterable = rows
34
- }
35
-
36
- const widest = iterable.reduce((prev, row) => {
37
- if (row[columnIndex]) {
38
- // check cell value is object or scalar
39
- const value = (row[columnIndex].value) ? row[columnIndex].value : row[columnIndex]
40
- const width = Math.max(
41
- ...stripAnsi(value.toString()).split(/[\n\r]/).map((s) => wcwidth(s))
42
- )
43
- return (width > prev) ? width : prev
44
- }
45
- return prev
46
- }, 0)
47
-
48
- return widest
49
- }
50
-
51
- /**
52
- * Get total width available to this table instance
53
- *
54
- *
55
- */
56
- const getAvailableWidth = config => {
57
- if (process && ((process.stdout && process.stdout.columns) || (process.env && process.env.COLUMNS))) {
58
- // forked calls that do not inherit process.stdout must use process.env
59
- let viewport = (process.stdout && process.stdout.columns) ? process.stdout.columns : process.env.COLUMNS
60
- viewport = viewport - config.marginLeft
61
-
62
- // table width percentage of (viewport less margin)
63
- if (config.width !== "auto" && /^\d+%$/.test(config.width)) {
64
- return Math.min(1, (config.width.slice(0, -1) * 0.01)) * viewport
65
- }
66
-
67
- // table width fixed
68
- if (config.width !== "auto" && /^\d+$/.test(config.width)) {
69
- config.FIXED_WIDTH = true
70
- return config.width
71
- }
72
-
73
- // table width equals viewport less margin
74
- // @TODO deprecate and remove "auto", which was never documented so should not be
75
- // an issue
76
- return viewport
77
- }
78
-
79
- // browser
80
- /* istanbul ignore next */
81
- if (typeof window !== "undefined") return window.innerWidth // eslint-disable-line
82
-
83
- // process.stdout.columns does not exist. assume redirecting to write stream
84
- // use 80 columns, which is VT200 standard
85
- return config.COLUMNS - config.marginLeft
86
- }
87
-
88
- module.exports.getStringLength = string => {
89
- // stripAnsi(string.replace(/[^\x00-\xff]/g,'XX')).length
90
- return wcwidth(stripAnsi(string))
91
- }
92
-
93
- module.exports.wrapCellText = (
94
- config,
95
- cellValue,
96
- columnIndex,
97
- cellOptions,
98
- rowType
99
- ) => {
100
- // ANSI chararacters that demarcate the start/end of a line
101
- const startAnsiRegexp = /^(\033\[[0-9;]*m)+/
102
- const endAnsiRegexp = /(\033\[[0-9;]*m)+$/
103
-
104
- // coerce cell value to string
105
- let string = cellValue.toString()
106
-
107
- // store matching ANSI characters
108
- const startMatches = string.match(startAnsiRegexp) || [""]
109
-
110
- // remove ANSI start-of-line chars
111
- string = string.replace(startAnsiRegexp, "")
112
-
113
- // store matching ANSI characters so can be later re-attached
114
- const endMatches = string.match(endAnsiRegexp) || [""]
115
-
116
- // remove ANSI end-of-line chars
117
- string = string.replace(endAnsiRegexp, "")
118
-
119
- let alignTgt
120
-
121
- switch (rowType) {
122
- case ("header"):
123
- alignTgt = "headerAlign"
124
- break
125
- case ("body"):
126
- alignTgt = "align"
127
- break
128
- default:
129
- alignTgt = "footerAlign"
130
- }
131
-
132
- // equalize padding for centered lines
133
- if (cellOptions[alignTgt] === "center") {
134
- cellOptions.paddingLeft = cellOptions.paddingRight = Math.max(
135
- cellOptions.paddingRight,
136
- cellOptions.paddingLeft,
137
- 0
138
- )
139
- }
140
-
141
- const columnWidth = config.table.columnWidths[columnIndex]
142
-
143
- // innerWidth is the width available for text within the cell
144
- const innerWidth = columnWidth
145
- - cellOptions.paddingLeft
146
- - cellOptions.paddingRight
147
- - config.GUTTER
148
-
149
- if (typeof config.truncate === "string") {
150
- string = exports.truncate(string, cellOptions, innerWidth)
151
- } else {
152
- string = exports.wrap(string, cellOptions, innerWidth)
153
- }
154
-
155
- // format each line
156
- const cell = string.split("\n").map(line => {
157
- line = line.trim()
158
-
159
- const lineLength = exports.getStringLength(line)
160
-
161
- // alignment
162
- if (lineLength < columnWidth) {
163
- let emptySpace = columnWidth - lineLength
164
-
165
- switch (true) {
166
- case (cellOptions[alignTgt] === "center"):
167
- emptySpace--
168
- const padBoth = Math.floor(emptySpace / 2)
169
- const padRemainder = emptySpace % 2
170
- line = Array(padBoth + 1).join(" ")
171
- + line
172
- + Array(padBoth + 1 + padRemainder).join(" ")
173
- break
174
-
175
- case (cellOptions[alignTgt] === "right"):
176
- line = Array(emptySpace - cellOptions.paddingRight).join(" ")
177
- + line
178
- + Array(cellOptions.paddingRight + 1).join(" ")
179
- break
180
-
181
- default:
182
- line = Array(cellOptions.paddingLeft + 1).join(" ")
183
- + line
184
- + Array(emptySpace - cellOptions.paddingLeft).join(" ")
185
- }
186
- }
187
-
188
- // put ANSI color codes BACK on the beginning and end of string
189
- return startMatches[0] + line + endMatches[0]
190
- })
191
-
192
- return { cell, innerWidth }
193
- }
194
-
195
- module.exports.truncate = (string, cellOptions, maxWidth) => {
196
- const stringWidth = wcwidth(string)
197
-
198
- if (maxWidth < stringWidth) {
199
- // @TODO give user option to decide if they want to break words on wrapping
200
- string = smartwrap(string, {
201
- width: maxWidth - cellOptions.truncate.length,
202
- breakword: true
203
- }).split("\n")[0]
204
- string = string + cellOptions.truncate
205
- }
206
-
207
- return string
208
- }
209
-
210
- module.exports.wrap = (string, cellOptions, innerWidth) => {
211
- const outstring = smartwrap(string, {
212
- errorChar: cellOptions.defaultErrorValue,
213
- minWidth: 1,
214
- trim: true,
215
- width: innerWidth
216
- })
217
-
218
- return outstring
219
- }
220
-
221
- module.exports.getColumnWidths = (config, rows) => {
222
- const availableWidth = getAvailableWidth(config)
223
-
224
- // iterate over the header if we have it, iterate over the first row
225
- // if we do not (to step through the correct number of columns)
226
- const iterable = (config.table.header[0] && config.table.header[0].length > 0)
227
- ? config.table.header[0] : rows[0]
228
-
229
- let widths = iterable.map((column, columnIndex) => {
230
- let result
231
-
232
- switch (true) {
233
- // column width is a percentage of table width specified in column header
234
- case (typeof column === "object" && (/^\d+%$/.test(column.width))):
235
- result = (column.width.slice(0, -1) * 0.01) * availableWidth
236
- result = addPadding(config, result)
237
- break
238
-
239
- // column width is specified in column header
240
- case (typeof column === "object" && (/^\d+$/.test(column.width))):
241
- result = column.width
242
- break
243
-
244
- // 'auto' sets column width to its longest value in the initial data set
245
- default:
246
- const columnOptions = (config.table.header[0][columnIndex])
247
- ? config.table.header[0][columnIndex] : {}
248
- const measurableRows = (rows.length) ? rows : config.table.header[0]
249
-
250
- result = getMaxLength(columnOptions, measurableRows, columnIndex)
251
-
252
- // add spaces for padding if not centered
253
- // @TODO test with if not centered conditional
254
- result = addPadding(config, result)
255
- }
256
-
257
- // add space for gutter
258
- result = result + config.GUTTER
259
- return result
260
- })
261
-
262
- // calculate sum of all column widths (including marginLeft)
263
- const totalWidth = widths.reduce((prev, current) => prev + current)
264
-
265
- // proportionately resize columns when necessary
266
- if (totalWidth > availableWidth || config.FIXED_WIDTH) {
267
- // proportion wont be exact fit, but this method keeps us safe
268
- const proportion = (availableWidth / totalWidth).toFixed(2) - 0.01
269
- const relativeWidths = widths.map(value => Math.max(2, Math.floor(proportion * value)))
270
- if (config.FIXED_WIDTH) return relativeWidths
271
-
272
- // when proportion < 0 column cant be resized and totalWidth must overflow viewport
273
- if (proportion > 0) {
274
- const totalRelativeWidths = relativeWidths.reduce((prev, current) => prev + current)
275
- widths = (totalRelativeWidths < totalWidth) ? relativeWidths : widths
276
- }
277
- } else {
278
- widths = widths.map(Math.floor)
279
- }
280
-
281
- return widths
282
- }
package/src/main.js DELETED
@@ -1,8 +0,0 @@
1
- if (require.main === module) {
2
- // called directly in terminal
3
- /* istanbul ignore next */
4
- require("./../adapters/terminal-adapter.js")
5
- } else {
6
- // called as a module
7
- module.exports = require("./../adapters/default-adapter.js")
8
- }
package/src/render.js DELETED
@@ -1,398 +0,0 @@
1
- const Style = require("./style.js")
2
- const Format = require("./format.js")
3
-
4
- /**
5
- * Converts arrays of data into arrays of cell strings
6
- * @param {TtyTable.Config} config
7
- * @param {Array<Array<string>|object|TtyTable.Formatter>} inputData
8
- * @returns {Array<string>}
9
- */
10
- module.exports.stringifyData = (config, inputData) => {
11
- const sections = {
12
- header: [],
13
- body: [],
14
- footer: []
15
- }
16
- const marginLeft = Array(config.marginLeft + 1).join(" ")
17
- const borderStyle = config.borderCharacters[config.borderStyle]
18
- const borders = []
19
-
20
- // support backwards compatibility cli-table's multiple constructor geometries
21
- // @TODO deprecate and support only a single format
22
- const constructorType = exports.getConstructorGeometry(inputData[0] || [], config)
23
- const rows = exports.coerceConstructorGeometry(config, inputData, constructorType)
24
-
25
- // when streaming values to tty-table, we don't want column widths to change
26
- // from one rows set to the next, so we save the first set of widths and reuse
27
- if (!global.columnWidths) {
28
- global.columnWidths = {}
29
- }
30
-
31
- if (global.columnWidths[config.tableId]) {
32
- config.table.columnWidths = global.columnWidths[config.tableId]
33
- } else {
34
- global.columnWidths[config.tableId] = config.table.columnWidths = Format.getColumnWidths(config, rows)
35
- }
36
-
37
- // stringify header cells
38
- // hide header if no column names or if specified in config
39
- switch (true) {
40
- case (config.showHeader !== null && !config.showHeader): // explicitly false, hide
41
- sections.header = []
42
- break
43
-
44
- case (config.showHeader === true): // explicitly true, show
45
- case (!!config.table.header[0].find(obj => obj.value || obj.alias)): // atleast one named column, show header
46
- sections.header = config.table.header.map(row => {
47
- return exports.buildRow(config, row, "header", null, rows, inputData)
48
- })
49
- break
50
-
51
- default: // no named columns, hide
52
- sections.header = []
53
- }
54
-
55
- // stringify body cells
56
- sections.body = rows.map((row, rowIndex) => {
57
- return exports.buildRow(config, row, "body", rowIndex, rows, inputData)
58
- })
59
-
60
- // stringify footer cells
61
- sections.footer = (config.table.footer instanceof Array && config.table.footer.length > 0) ? [config.table.footer] : []
62
-
63
- sections.footer = sections.footer.map(row => {
64
- return exports.buildRow(config, row, "footer", null, rows, inputData)
65
- })
66
-
67
- // apply borders
68
- // 0=top, 1=middle, 2=bottom
69
- for (let a = 0; a < 3; a++) {
70
- // add left border
71
- borders[a] = borderStyle[a].l
72
-
73
- // add joined borders for each column
74
- config.table.columnWidths.forEach((columnWidth, index, arr) => {
75
- // Math.max because otherwise columns 1 wide wont have horizontal border
76
- borders[a] += Array(Math.max(columnWidth, 2)).join(borderStyle[a].h)
77
- borders[a] += ((index + 1 < arr.length) ? borderStyle[a].j : "")
78
- })
79
-
80
- // add right border
81
- borders[a] += borderStyle[a].r
82
-
83
- // no trailing space on footer
84
- borders[a] = (a < 2) ? `${marginLeft + borders[a]}\n` : marginLeft + borders[a]
85
- }
86
-
87
- // top horizontal border
88
- let output = borders[0]
89
-
90
- // for each section (header,body,footer)
91
- Object.keys(sections).forEach((p, i) => {
92
- // for each row in the section
93
- while (sections[p].length) {
94
- const row = sections[p].shift()
95
-
96
- // if(row.length === 0) {break}
97
-
98
- row.forEach(line => {
99
- // vertical row borders
100
- output = `${output
101
- + marginLeft
102
- // left vertical border
103
- + borderStyle[1].v
104
- // join cells on vertical border
105
- + line.join(borderStyle[1].v)
106
- // right vertical border
107
- + borderStyle[1].v
108
- // end of line
109
- }\n`
110
- })
111
-
112
- // bottom horizontal row border
113
- switch (true) {
114
- // skip if end of body and no footer
115
- case (sections[p].length === 0
116
- && i === 1
117
- && sections.footer.length === 0):
118
- break
119
-
120
- // skip if end of footer
121
- case (sections[p].length === 0
122
- && i === 2):
123
- break
124
-
125
- // skip if compact
126
- case (config.compact && p === "body" && !row.empty):
127
- break
128
-
129
- // skip if border style is "none"
130
- case (config.borderStyle === "none" && config.compact):
131
- break
132
-
133
- default:
134
- output += borders[1]
135
- }
136
- }
137
- })
138
-
139
- // bottom horizontal border
140
- output += borders[2]
141
-
142
- const finalOutput = Array(config.marginTop + 1).join("\n") + output
143
-
144
- // record the height of the output
145
- config.height = finalOutput.split(/\r\n|\r|\n/).length
146
-
147
- return finalOutput
148
- }
149
-
150
- module.exports.buildRow = (config, row, rowType, rowIndex, rowData, inputData) => {
151
- let minRowHeight = 0
152
-
153
- // tag row as empty if empty, used for `compact` option
154
- if (row.length === 0 && config.compact) {
155
- row.empty = true
156
- return row
157
- }
158
-
159
- // force row to have correct number of columns
160
- const lengthDifference = config.table.columnWidths.length - row.length
161
- if (lengthDifference > 0) {
162
- // array (row) lacks elements, add until equal
163
- row = row.concat(Array.apply(null, new Array(lengthDifference)).map(() => null))
164
- } else if (lengthDifference < 0) {
165
- // array (row) has too many elements, remove until equal
166
- row.length = config.table.columnWidths.length
167
- }
168
-
169
- // convert each element in row to cell format
170
- row = row.map((elem, elemIndex) => {
171
- const cell = exports.buildCell(config, elem, elemIndex, rowType, rowIndex, rowData, inputData)
172
- minRowHeight = (minRowHeight < cell.length) ? cell.length : minRowHeight
173
- return cell
174
- })
175
-
176
- // apply top and bottom padding to row
177
- minRowHeight = (rowType === "header") ? minRowHeight
178
- : minRowHeight + (config.paddingBottom + config.paddingTop)
179
-
180
- const linedRow = Array.apply(null, { length: minRowHeight })
181
- .map(Function.call, () => [])
182
-
183
- row.forEach(function (cell, a) {
184
- const whitespace = Array(config.table.columnWidths[a]).join(" ")
185
-
186
- if (rowType === "body") {
187
- // add whitespace for top padding
188
- for (let i = 0; i < config.paddingTop; i++) {
189
- cell.unshift(whitespace)
190
- }
191
-
192
- // add whitespace for bottom padding
193
- for (let i = 0; i < config.paddingBottom; i++) {
194
- cell.push(whitespace)
195
- }
196
- }
197
-
198
- // a `row` is divided by columns (horizontally)
199
- // a `linedRow` becomes the row divided instead into an array of vertical lines
200
- // each nested line divided by columns
201
- for (let i = 0; i < minRowHeight; i++) {
202
- linedRow[i].push((typeof cell[i] !== "undefined")
203
- ? cell[i] : whitespace)
204
- }
205
- })
206
-
207
- return linedRow
208
- }
209
-
210
- module.exports.buildCell = (config, elem, columnIndex, rowType, rowIndex, rowData, inputData) => {
211
- let cellValue = null
212
-
213
- const cellOptions = Object.assign(
214
- { reset: false },
215
- config,
216
- (rowType === "body") ? config.columnSettings[columnIndex] : {}, // ignore columnSettings for footer
217
- (typeof elem === "object") ? elem : {}
218
- )
219
-
220
- if (rowType === "header") {
221
- config.table.columns.push(cellOptions)
222
- cellValue = cellOptions.alias || cellOptions.value || ""
223
- } else {
224
- // set cellValue
225
- switch (true) {
226
- case (typeof elem === "undefined" || elem === null):
227
- // replace undefined/null elem values with placeholder
228
- cellValue = (config.errorOnNull) ? config.defaultErrorValue : config.defaultValue
229
- // @TODO add to elem defaults
230
- cellOptions.isNull = true
231
- break
232
-
233
- case (typeof elem === "object" && elem !== null && typeof elem.value !== "undefined"):
234
- cellValue = elem.value
235
- break
236
-
237
- case (typeof elem === "function"):
238
- cellValue = elem.bind({
239
- configure: function (object) {
240
- return Object.assign(cellOptions, object)
241
- },
242
- style: Style.style,
243
- resetStyle: Style.resetStyle
244
- })(
245
- (!cellOptions.isNull) ? cellValue : "",
246
- columnIndex,
247
- rowIndex,
248
- rowData,
249
- inputData
250
- )
251
- break
252
-
253
- default:
254
- // elem is assumed to be a scalar
255
- cellValue = elem
256
- }
257
-
258
- // run formatter
259
- if (typeof cellOptions.formatter === "function") {
260
- cellValue = cellOptions.formatter
261
- .bind({
262
- configure: function (object) {
263
- return Object.assign(cellOptions, object)
264
- },
265
- style: Style.style,
266
- resetStyle: Style.resetStyle
267
- })(
268
- (!cellOptions.isNull) ? cellValue : "",
269
- columnIndex,
270
- rowIndex,
271
- rowData,
272
- inputData
273
- )
274
- }
275
- }
276
-
277
- // colorize cellValue
278
- // we don't want the formatter to pass a styled cell value with ANSI codes
279
- // (in case user wants to do math or string operations to cell value), so
280
- // we apply default styles to the cell after it runs through the formatter
281
- // and omit those default styles if the user applied `this.resetStyle`
282
- if (!cellOptions.reset) {
283
- cellValue = Style.colorizeCell(cellValue, cellOptions, rowType)
284
- }
285
-
286
- // textwrap cellValue
287
- const { cell, innerWidth } = Format.wrapCellText(cellOptions, cellValue, columnIndex, cellOptions, rowType)
288
-
289
- if (rowType === "header") {
290
- config.table.columnInnerWidths.push(innerWidth)
291
- }
292
-
293
- return cell
294
- }
295
-
296
- /**
297
- * Check for a backwards compatible (cli-table) constructor
298
- */
299
- module.exports.getConstructorGeometry = (row, config) => {
300
- let type
301
-
302
- // rows passed as an object
303
- if (typeof row === "object" && !(row instanceof Array)) {
304
- const keys = Object.keys(row)
305
-
306
- if (config.adapter === "automattic") {
307
- // detected cross table
308
- const key = keys[0]
309
-
310
- if (row[key] instanceof Array) {
311
- type = "automattic-cross"
312
- } else {
313
- // detected vertical table
314
- type = "automattic-vertical"
315
- }
316
- } else {
317
- // detected horizontal table
318
- type = "o-horizontal"
319
- }
320
- } else {
321
- // rows passed as an array
322
- type = "a-horizontal"
323
- }
324
-
325
- return type
326
- }
327
-
328
- /**
329
- * Coerce backwards compatible constructor styles
330
- */
331
- module.exports.coerceConstructorGeometry = (config, rows, constructorType) => {
332
- let output = []
333
- switch (constructorType) {
334
- case ("automattic-cross"):
335
- // assign header styles to first column
336
- config.columnSettings[0] = config.columnSettings[0] || {}
337
- config.columnSettings[0].color = config.headerColor
338
-
339
- output = rows.map(obj => {
340
- const arr = []
341
- const key = Object.keys(obj)[0]
342
- arr.push(key)
343
- return arr.concat(obj[key])
344
- })
345
- break
346
-
347
- case ("automattic-vertical"):
348
- // assign header styles to first column
349
- config.columnSettings[0] = config.columnSettings[0] || {}
350
- config.columnSettings[0].color = config.headerColor
351
-
352
- output = rows.map(function (value) {
353
- const key = Object.keys(value)[0]
354
- return [key, value[key]]
355
- })
356
- break
357
-
358
- case ("o-horizontal"):
359
- // cell property names are specified in header columns
360
- if (config.table.header[0].length
361
- && config.table.header[0].every(obj => obj.value)) {
362
- output = rows.map(row => config.table.header[0]
363
- .map(obj => row[obj.value]))
364
- } // eslint-disable-line brace-style
365
- // no property names given, default to object property order
366
- else {
367
- output = rows.map(obj => Object.values(obj))
368
- }
369
- break
370
-
371
- case ("a-horizontal"):
372
- output = rows
373
- break
374
-
375
- default:
376
- }
377
-
378
- return output
379
- }
380
-
381
- // @TODO For rotating horizontal data into a vertical table
382
- // assumes all rows are same length
383
- // module.exports.verticalizeMatrix = (config, inputArray) => {
384
- //
385
- // // grow to # arrays equal to number of columns in input array
386
- // let outputArray = []
387
- // let headers = config.table.columns
388
- //
389
- // // create a row for each heading, and prepend the row
390
- // // with the heading name
391
- // headers.forEach(name => outputArray.push([name]))
392
- //
393
- // inputArray.forEach(row => {
394
- // row.forEach((element, index) => outputArray[index].push(element))
395
- // })
396
- //
397
- // return outputArray
398
- // }