|
1 | | -const getArray = (len) => Array.apply(null, Array(len)).map((_, i) => i) |
| 1 | +import R from 'ramda' |
| 2 | +import repeat from 'core-js/library/fn/string/repeat' |
2 | 3 |
|
3 | | -const stringifyVal = (val) => { |
4 | | - if (Array.isArray(val)) return stringifyArray(val) |
5 | | - if (typeof val === 'number') return val |
6 | | - if (typeof val === 'string') return val |
7 | | - if (typeof val === 'boolean') return val |
8 | | - if (val === null) return '(null)' |
9 | | - if (typeof val === 'object') return stringifyObject(val) |
10 | | -} |
11 | | - |
12 | | -const stringifyArray = (arr) => '[' + arr.map(stringifyVal).join(', ') + ']' |
13 | | - |
14 | | -const stringifyObject = (obj) => { |
15 | | - if (typeof obj !== 'object') return obj |
16 | | - return '{' + Object.keys(obj).map((key) => key + ': ' + stringifyVal(obj[key])).join(', ') + '}' |
17 | | -} |
18 | | - |
19 | | -const stringifyLines = (rows) => { |
| 4 | +const len = (val) => typeof val === 'undefined' ? 0 : ('' + val).length |
| 5 | +const padString = (character, width) => !width ? '' : repeat(character, width) |
| 6 | +const stringifyRows = (rows) => { |
20 | 7 | if (!Array.isArray(rows) || !rows.length) return [] |
21 | | - return rows.map((row) => row.map(stringifyVal)) |
22 | | -} |
23 | | - |
24 | | -const getRowIndexForLine = (rowHeights, lineNumber) => { |
25 | | - return rowHeights.reduce((meta, height, rowIndex) => { |
26 | | - if (meta.remainingLines < 0) return meta |
27 | | - meta.rowIndex = rowIndex |
28 | | - if (meta.remainingLines < height) { |
29 | | - meta.lineIndex = meta.remainingLines |
30 | | - } |
31 | | - meta.remainingLines = meta.remainingLines - height |
32 | | - return meta |
33 | | - }, { |
34 | | - rowIndex: 0, |
35 | | - lineIndex: 0, |
36 | | - remainingLines: lineNumber |
37 | | - }) |
38 | | -} |
39 | | - |
40 | | -const getLineFromCol = (prev, _, colIndex) => { |
41 | | - const linesStr = prev.colsLines[prev.rowIndex][colIndex] |
42 | | - .filter((_, lineIndex) => lineIndex === prev.lineIndex) |
43 | | - .map((line) => line + padString(' ', prev.colWidths[colIndex] - line.length)) |
44 | | - prev.lines.push(linesStr) |
45 | | - return prev |
| 8 | + return rows.map((row) => row.map(JSON.stringify)) |
46 | 9 | } |
47 | | - |
48 | | -const getColLines = (col, colWidth, rowHeight) => { |
49 | | - let colLines = getLinesFromString(col).reduce((final, curr) => { |
50 | | - return final.concat(('' + curr).match(new RegExp('.{1,' + colWidth + '}', 'g')) || ['']) |
51 | | - }, []) |
52 | | - return colLines.concat(getArray(rowHeight - colLines.length).map((a) => ' ')) |
| 10 | +const insertColSeparators = (arr) => '│' + arr.join('│') + '│' |
| 11 | +const getTopSeparatorLine = (colWidths) => getSeparatorLine('═', '╒', '╤', '╕', colWidths) |
| 12 | +const getThickSeparatorLine = (colWidths) => getSeparatorLine('═', '╞', '╪', '╡', colWidths) |
| 13 | +const getThinSeparatorLine = (colWidths) => getSeparatorLine('─', '├', '┼', '┤', colWidths) |
| 14 | +const getBottomSeparatorLine = (colWidths) => getSeparatorLine('─', '└', '┴', '┘', colWidths) |
| 15 | +const getSeparatorLine = (horChar, leftChar, crossChar, rightChar, colWidths) => { |
| 16 | + return leftChar + colWidths.map(function (w) { |
| 17 | + return padString(horChar, w) |
| 18 | + }).join(crossChar) + rightChar |
53 | 19 | } |
54 | 20 |
|
55 | | -const getLinesFromString = (str) => { |
56 | | - if (typeof str !== 'string') return [str] |
57 | | - return str.indexOf('\n') > -1 ? str.split('\n') : [str] |
| 21 | +const colWidths = (maxWidth, minWidth, input) => { |
| 22 | + if (!Array.isArray(input)) { |
| 23 | + return 0 |
| 24 | + } |
| 25 | + return input[0].map((_, i) => { |
| 26 | + const tCol = R.pluck(i, input).map((col) => len(col)) |
| 27 | + const measuredMax = Math.max(R.apply(Math.max, tCol), minWidth) |
| 28 | + return measuredMax > maxWidth && maxWidth > 0 ? maxWidth : measuredMax |
| 29 | + }) |
58 | 30 | } |
59 | 31 |
|
60 | | -const getColWidths = (rows) => { |
61 | | - return getArray(rows[0].length).map((i) => { |
62 | | - return rows.reduce((prev, curr) => { |
63 | | - const lines = getLinesFromString(curr[i]) |
64 | | - const currMax = lines.reduce((max, line) => { |
65 | | - return Math.max(max, ('' + line).length) |
66 | | - }, 0) |
67 | | - return Math.max(prev, currMax) |
68 | | - }, 0) |
| 32 | +const rowHeights = (maxWidth, input) => { |
| 33 | + return input.map((row) => { |
| 34 | + const maxLen = R.apply(Math.max, row.map((col) => len(col))) |
| 35 | + const numLines = Math.ceil(maxLen / maxWidth) |
| 36 | + return numLines |
69 | 37 | }) |
70 | 38 | } |
71 | 39 |
|
72 | | -const renderForWidth = (rows, maxColWidth = 30, minColWidth = 3) => { |
73 | | - if (!Array.isArray(rows) || !rows.length) return '' |
74 | | - const colWidths = getColWidths(rows).map((colWidth) => { |
75 | | - return Math.max(Math.min(colWidth, maxColWidth), minColWidth) |
| 40 | +const splitRowsToLines = (maxWidth, heights, widths, input) => { |
| 41 | + return input.map((row, i) => { |
| 42 | + return row.map((col, colIndex) => { |
| 43 | + let lines = R.splitEvery(maxWidth, col) |
| 44 | + const lastLinesLen = len(R.last(lines)) |
| 45 | + if (lastLinesLen < widths[colIndex]) { |
| 46 | + lines[lines.length - 1] = lines[lines.length - 1] + padString(' ', widths[colIndex] - lastLinesLen) |
| 47 | + } |
| 48 | + while (lines.length < heights[i]) { |
| 49 | + lines = [].concat(...lines, [padString(' ', widths[colIndex])]) |
| 50 | + } |
| 51 | + return lines |
| 52 | + }) |
76 | 53 | }) |
77 | | - const rowHeights = rows.map((row) => { |
78 | | - return row.reduce((prev, curr, colIndex) => { |
79 | | - const lines = getLinesFromString(curr) |
80 | | - return lines.reduce((tot, line) => { |
81 | | - return tot + Math.max(1, Math.max(prev, Math.ceil(('' + line).length / colWidths[colIndex]))) |
82 | | - }, 0) |
83 | | - }, 0) |
84 | | - }) |
85 | | - const totalLines = rowHeights.reduce((tot, curr) => tot + curr, 0) |
86 | | - const colsLines = rows.reduce((colLines, row, rowIndex) => { |
87 | | - const cols = row.map((col, colIndex) => getColLines(col, colWidths[colIndex], rowHeights[rowIndex])) |
88 | | - return colLines.concat([cols]) |
89 | | - }, []) |
90 | | - let output = getArray(totalLines).reduce((out, _, i) => { |
91 | | - const lineMeta = getRowIndexForLine(rowHeights, i) |
92 | | - const rowLines = rows[lineMeta.rowIndex].reduce(getLineFromCol, { |
93 | | - lines: [], |
94 | | - lineIndex: lineMeta.lineIndex, |
95 | | - colWidths: colWidths, |
96 | | - colsLines: colsLines, |
97 | | - rowIndex: lineMeta.rowIndex |
98 | | - }).lines.join('│') |
99 | | - out.push('│' + rowLines + '│') |
100 | | - return out |
101 | | - }, []) |
102 | | - output = insertRowSeparators(output, rowHeights, colWidths) |
103 | | - return output.join('\n') |
104 | 54 | } |
105 | 55 |
|
106 | | -const insertRowSeparators = (lines, rowHeights, colWidths) => { |
107 | | - return rowHeights.reduce((out, rowHeight, rowIndex) => { |
108 | | - out.curr.push.apply(out.curr, out.feeder.splice(0, rowHeight)) |
109 | | - if (rowIndex === 0) { |
110 | | - out.curr.push(getThickSeparatorLine(colWidths)) |
111 | | - } else if (rowIndex === rowHeights.length - 1) { |
112 | | - out.curr.push(getBottomSeparatorLine(colWidths)) |
113 | | - } else { |
114 | | - out.curr.push(getThinSeparatorLine(colWidths)) |
| 56 | +const createLines = (rows) => { |
| 57 | + return rows.reduce((lines, row) => { |
| 58 | + if (!Array.isArray(row)) { |
| 59 | + return [].concat(lines, row) |
115 | 60 | } |
116 | | - return out |
117 | | - }, { |
118 | | - feeder: lines, |
119 | | - curr: [getTopSeparatorLine(colWidths)] |
120 | | - }).curr |
121 | | -} |
122 | | - |
123 | | -const getTopSeparatorLine = (colWidths) => getSeparatorLine('═', '╒', '╤', '╕', colWidths) |
124 | | -const getThickSeparatorLine = (colWidths) => getSeparatorLine('═', '╞', '╪', '╡', colWidths) |
125 | | -const getThinSeparatorLine = (colWidths) => getSeparatorLine('─', '├', '┼', '┤', colWidths) |
126 | | -const getBottomSeparatorLine = (colWidths) => getSeparatorLine('─', '└', '┴', '┘', colWidths) |
127 | | -const getSeparatorLine = (horChar, leftChar, crossChar, rightChar, colWidths) => { |
128 | | - return leftChar + colWidths.map(function (w) { |
129 | | - return padString(horChar, w) |
130 | | - }).join(crossChar) + rightChar |
| 61 | + const tRow = R.transpose(row).map(insertColSeparators) |
| 62 | + return [].concat(lines, tRow) |
| 63 | + }, []) |
131 | 64 | } |
132 | 65 |
|
133 | | -const padString = (character, width) => { |
134 | | - if (width < 1) return '' |
135 | | - return getArray(width).map(() => character).join('') |
| 66 | +const renderForWidth = (rows, maxColWidth = 30, minColWidth = 3) => { |
| 67 | + if (!Array.isArray(rows) || !rows.length) { |
| 68 | + return '' |
| 69 | + } |
| 70 | + maxColWidth = parseInt(maxColWidth) |
| 71 | + const widths = colWidths(maxColWidth, minColWidth, rows) |
| 72 | + const heights = rowHeights(maxColWidth, rows) |
| 73 | + const norm = splitRowsToLines(maxColWidth, heights, widths, rows) |
| 74 | + const header = createLines([R.head(norm)]) |
| 75 | + const separated = R.intersperse(getThinSeparatorLine(widths), R.tail(norm)) |
| 76 | + const lines = createLines(separated) |
| 77 | + return [ |
| 78 | + getTopSeparatorLine(widths), |
| 79 | + ...header, |
| 80 | + getThickSeparatorLine(widths), |
| 81 | + ...lines, |
| 82 | + getBottomSeparatorLine(widths) |
| 83 | + ].join('\n') |
136 | 84 | } |
137 | 85 |
|
138 | 86 | export default { |
139 | | - run: (rows, options = {maxColumnWidth: 30}) => renderForWidth(stringifyLines(rows), options.maxColumnWidth), |
140 | | - getMaxColumnWidth: (rows) => getColWidths(stringifyLines(rows)).reduce((max, colWidth) => Math.max(max, colWidth), 0) |
| 87 | + serializeData: (rows) => stringifyRows(rows), |
| 88 | + tableFromSerializedData: (serializedRows, maxColumnWidth = 30) => renderForWidth(serializedRows, maxColumnWidth), |
| 89 | + table: (rows, maxColumnWidth = 30) => renderForWidth(stringifyRows(rows), maxColumnWidth), |
| 90 | + maxColumnWidth: (rows) => R.apply(Math.max, colWidths(0, 0, stringifyRows(rows))) |
141 | 91 | } |
142 | | - |
0 commit comments