Skip to content

Commit a615c41

Browse files
committed
Introduce new API
- New API to expose endpoint for already serialized data for better performance. ``` serializeData tableFromSerializedData table maxColumnWidth ``` - Use JSON.stringify to serialize values - Add two dependencies - Ramda - To make code more functional and easier to follow - core-js - To use String.repeat so we don't exceed call stack size Adjust line width when splitting into lines Add test for really long lines Fix typo
1 parent b82eb0b commit a615c41

File tree

6 files changed

+274
-258
lines changed

6 files changed

+274
-258
lines changed

README.md

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,43 @@ so it can be pasted into the medium of choice.
66
The configuration is very limited by design, all that's configurable in the
77
current version is the maximun width of the columns.
88

9-
The API exposes only two methods: `run(rows, [options])` where `rows` is expected to be
9+
The API exposes only two methods to render a table: `table(rows, [maxColWidth])` where `rows` is expected to be
1010
an array with an index for every row, and each row is also expected to be an array
11-
with one index for every column, and `getMaxColumnWidth(rows)` to get the width of the
12-
widest column.
11+
with one index for every column. The data in the columns can be of any Javascript type
12+
since it will be serialized before printed.
13+
14+
The second method to generate a table is: `tableFromSerializedData(serializedRows, [maxColWidth])`
15+
where `serializedRows` is expected to be in the same format as the previously described method,
16+
but all data must already be serialized. This method should be used when the data stays the same
17+
but are generated with multiple maxColWidths.
18+
19+
To serialize the data, the method `serializeData(rows)` is exposed. For the moment, all it does is to
20+
table `JSON.stringify` on the data.
21+
22+
To get the width of the widest column (can be used to set the max value on a slider), `maxColumnWidth(rows)`
23+
is exposed. The rows should not already be serialized when calling this method.
24+
1325
All rows should have the same number of columns, and the first row is expected to
1426
be the header column with titles for each column.
1527

1628
```javascript
1729
[
1830
['first column', 'second column'], // title row
1931
['my data row 1 col 1', 'my data row 1 col 2'], // first data row
20-
['my data row 2 col 1', 'my data row 2 col 2'], // second data row
32+
['my data row 2 col 1', 'my data row 2 col 2'] // second data row
2133
]
2234
```
2335

2436
With default max width, the above would produce:
2537

2638
```
27-
+===================+===================+
28-
|first column |second column |
29-
+===================+===================+
30-
|my data row 1 col 1|my data row 1 col 2|
31-
+-------------------+-------------------+
32-
|my data row 2 col 1|my data row 2 col 2|
33-
+-------------------+-------------------+
39+
╒═════════════════════╤═════════════════════╕
40+
│"first column" │"second column"
41+
╞═════════════════════╪═════════════════════╡
42+
│"my data row 1 col 1"│"my data row 1 col 2"│
43+
├─────────────────────┼─────────────────────┤
44+
│"my data row 2 col 1"│"my data row 2 col 2"│
45+
└─────────────────────┴─────────────────────┘
3446
```
3547

3648
## Installation
@@ -65,10 +77,10 @@ import AsciiTable from 'ascii-data-table'
6577
const items = [['x', 'y'], ['a', 'b'], ['c', 'd']]
6678

6779
// Not required, default is 30
68-
const options = {maxColumnWidth: 15}
80+
const maxColumnWidth = 15
6981

7082
// Render and save in 'res'
71-
const res = AsciiTable.run(items, options)
83+
const res = AsciiTable.table(items, maxColumnWidth)
7284
```
7385

7486
**In ES 5.5**
@@ -81,20 +93,29 @@ var AsciiTable = require('ascii-data-table').default
8193
//var AsciiTable = require('lib/ascii-data-table').default
8294

8395
var items = [['x', 'y'], ['a', 'b'], ['c', 'd']]
84-
var res = AsciiTable.run(items)
96+
var res = AsciiTable.table(items)
8597
```
8698

8799
### In web browsers
88100
A bundle for web browsers is created and can be found in `lib`.
89101

90102
```html
91-
<script type="text/javascript" src="/components/lib/bundle.js"></script>
92-
<script type="text/javascript">
93-
var items = [['x', 'y'], ['a', 'b'], ['c', 'd']]
94-
var output = AsciiTable.run(items)
95-
document.getElementById('my-table').innerHTML = output
96-
console.log(output)
97-
</script>
103+
<html>
104+
<head>
105+
<script type="text/javascript" src="/components/lib/bundle.js"></script>
106+
<script type="text/javascript">
107+
function load() {
108+
var items = [['x', 'y'], ['a', 'b'], ['c', 'd']]
109+
var output = AsciiTable.table(items)
110+
document.getElementById('my-table').innerHTML = output
111+
console.log(output)
112+
}
113+
</script>
114+
</head>
115+
<body onload="load()">
116+
<pre id="my-table">loading...</pre>
117+
</body>
118+
</html>
98119
```
99120

100121
### For React >= 0.14
@@ -137,7 +158,7 @@ assumes there's a global variable named `angular` available.
137158
.module('myApp', ['AsciiTableModule'])
138159
.controller('TableController', ['$scope', 'AsciiTable', function($scope, AsciiTable){
139160
var items = [['x', 'y'], ['a', 'b'], ['c', 'd']]
140-
$scope.data = AsciiTable.run(items)
161+
$scope.data = AsciiTable.table(items)
141162
}])
142163
</script>
143164
</head>
@@ -152,11 +173,11 @@ You can try online here: [Online demo](https://oskarhane-dropshare-eu.s3-eu-cent
152173
In the `examples` folder there are examples for node and web browser environments.
153174
One cool thing in the browser demo is that you can hook up a range slider to the maximun
154175
width of the columns, giving this effect:
155-
![slider-gif-demo](https://oskarhane-dropshare-eu.s3-eu-central-1.amazonaws.com/ascii-data-table-slider-lfbBzm2sql/ascii-data-table-slider.gif)
176+
![slider-gif-demo](https://oskarhane-dropshare-eu.s3-eu-central-1.amazonaws.com/adt-2-Q2Qhvevx2E/adt-2.gif)
156177

157178
## Testing
158-
Run `npm test` to execute test in both Node.js and browser environments.
159-
Run `npm run test:watch` to have tests run on file changes.
179+
table `npm test` to execute test in both Node.js and browser environments.
180+
table `npm table test:watch` to have tests table on file changes.
160181

161182
## Contributing
162-
All bug reports, feature requests and pull requests are welcome. This project uses the [Javascript Standard Style](http://standardjs.com) and a lint check will run before all tests and builds.
183+
All bug reports, feature requests and pull requests are welcome. This project uses the [Javascript Standard Style](http://standardjs.com) and a lint check will table before all tests and builds.

examples/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
['Sleepless in Seattle', 'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?']
1414
]
1515
function renderTable(id, data, maxWidth) {
16-
var output = AsciiTable.run(data, {maxColumnWidth: maxWidth})
16+
var output = AsciiTable.table(data, maxWidth)
1717
document.getElementById(id).innerHTML = output
1818
//console.log(output) <- Cool, but spams the console
1919
}
@@ -22,7 +22,7 @@
2222
document.getElementById('currentWidth').innerHTML = w
2323
}
2424
function setSliderMax() {
25-
var maxWidth = AsciiTable.getMaxColumnWidth(data)
25+
var maxWidth = AsciiTable.maxColumnWidth(data)
2626
document.getElementById('width-slider').max = maxWidth
2727
}
2828
</script>

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,9 @@
5858
"copy data",
5959
"table data",
6060
"draw ascii"
61-
]
61+
],
62+
"dependencies": {
63+
"core-js": "^2.4.1",
64+
"ramda": "^0.22.1"
65+
}
6266
}

src/ascii-data-table.js

Lines changed: 71 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,91 @@
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'
23

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) => {
207
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))
469
}
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
5319
}
5420

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+
})
5830
}
5931

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
6937
})
7038
}
7139

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+
})
7653
})
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')
10454
}
10555

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)
11560
}
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+
}, [])
13164
}
13265

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')
13684
}
13785

13886
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)))
14191
}
142-

0 commit comments

Comments
 (0)