Skip to content

Commit

Permalink
feat: support text wrapping (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjconcept authored Sep 20, 2017
1 parent f2e9385 commit 304d59d
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 22 deletions.
75 changes: 75 additions & 0 deletions fixtures/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,80 @@ module.exports = {
'| Sarah | 22 | Yes |',
'| Lee | 23 | Yes |'
].join(os.EOL) + os.EOL
},
wrap: {
input: [
{ name: 'Benjamin', age: 21, isCool: false },
{ name: 'Sarah', age: 22, isCool: true },
{ name: 'Lee', age: 23, isCool: true }
],
options: {
wrap: { width: 5 }
},
expected: [
// headers wrap, soft wrap
'| Name | Age | Is |',
' cool ',
'| ----- | ----- | ----- |',
// hard wrap
'| Benja | 21 | false |',
' min ',
// no wrap
'| Sarah | 22 | true |',
'| Lee | 23 | true |'
].join(os.EOL) + os.EOL
},
newlines: {
input: [
{ name: 'Benjamin\nor Ben', age: 21, isCool: false },
{ name: 'Sarah', age: 22, isCool: true },
{ name: 'Lee', age: 23, isCool: true }
],
expected: [
'| Name | Age | Is cool |',
'| -------- | ----- | ------- |',
'| Benjamin | 21 | false |',
' or Ben ',
'| Sarah | 22 | true |',
'| Lee | 23 | true |'
].join(os.EOL) + os.EOL
},
wrapAndNewlines: {
input: [
{ name: 'Benjamin or\nBen', age: 21, isCool: false },
{ name: 'Sarah', age: 22, isCool: true },
{ name: 'Lee', age: 23, isCool: true }
],
options: {
wrap: { width: 8 }
},
expected: [
'| Name | Age | Is cool |',
'| -------- | ----- | ------- |',
'| Benjamin | 21 | false |',
' or ',
' Ben ',
'| Sarah | 22 | true |',
'| Lee | 23 | true |'
].join(os.EOL) + os.EOL
},
gutters: {
input: [
{ name: 'Benjamin', age: 21, isCool: false },
{ name: 'Sarah', age: 22, isCool: true },
{ name: 'Lee', age: 23, isCool: true }
],
options: {
wrap: { width: 5, gutters: true }
},
expected: [
'| Name | Age | Is |',
'| | | cool |',
'| ----- | ----- | ----- |',
'| Benja | 21 | false |',
'| min | | |',
'| Sarah | 22 | true |',
'| Lee | 23 | true |'
].join(os.EOL) + os.EOL
}
}
85 changes: 69 additions & 16 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

const os = require('os')
const sentence = require('sentence-case')
const split = require('split-text-to-chunks')

const columnWidthMin = 5
const width = split.width
const columnsWidthMin = 5
const ALIGN = [ 'LEFT', 'CENTER', 'RIGHT' ]

module.exports = (input, options) => {
Expand All @@ -13,9 +15,17 @@ module.exports = (input, options) => {

options = Object.assign({
stringify: v => typeof v === 'undefined' ? '' : String(v)
}, options)
}, options, {
wrap: Object.assign({
width: Infinity,
gutters: false
}, options && options.wrap)
})

const stringify = options.stringify
const columnsMaxWidth = options.wrap.width
const gutters = options.wrap.gutters

const keys = Object.keys(input[0])

const titles = keys.map((key, i) => {
Expand All @@ -34,9 +44,9 @@ module.exports = (input, options) => {

const widths = input.reduce(
(sizes, item) => keys.map(
(key, i) => Math.max(columnWidthMin, stringify(item[key]).length, sizes[i])
(key, i) => Math.max(width(stringify(item[key]), columnsMaxWidth), sizes[i])
),
titles.map(t => t.length)
titles.map(t => Math.max(columnsWidthMin, width(t, columnsMaxWidth)))
)

const alignments = keys.map((key, i) => {
Expand All @@ -58,29 +68,76 @@ module.exports = (input, options) => {
let table = ''

// header line
table += row(titles.map(
(title, i) => pad(alignments[i], widths[i], title)
))
table += row(alignments, widths, titles, gutters)

// header separator
table += row(alignments.map(
table += line(alignments.map(
(align, i) => (
(align === 'LEFT' || align === 'CENTER' ? ':' : '-') +
repeat('-', widths[i] - 2) +
(align === 'RIGHT' || align === 'CENTER' ? ':' : '-')
)
))
), true)

// table body
table += input.map(
item => row(keys.map(
(key, i) => pad(alignments[i], widths[i], stringify(item[key]))
))
(item, i) => row(alignments, widths, keys.map(
key => stringify(item[key])
), gutters)
).join('')

return table
}

function row (alignments, widths, columns, gutters) {
const width = columns.length
const values = new Array(width)
const first = new Array(width)
let height = 1

for (let h = 0; h < width; h++) {
const cells = values[h] = split(columns[h], widths[h])
if (cells.length > height) height = cells.length
first[h] = pad(alignments[h], widths[h], cells[0])
}

if (height === 1) return line(first, true)

const lines = new Array(height)
lines[0] = line(first, true)

for (let v = 1; v < height; v++) {
lines[v] = new Array(width)
}

for (let h = 0; h < width; h++) {
const cells = values[h]
let v = 1

for (;v < cells.length; v++) {
lines[v][h] = pad(alignments[h], widths[h], cells[v])
}

for (;v < height; v++) {
lines[v][h] = repeat(' ', widths[h])
}
}

for (let h = 1; h < height; h++) {
lines[h] = line(lines[h], gutters)
}

return lines.join('')
}

function line (columns, gutters) {
return (
(gutters ? '| ' : ' ') +
columns.join((gutters ? ' | ' : ' ')) +
(gutters ? ' |' : ' ') + os.EOL
)
}

function pad (alignment, width, what) {
if (!alignment || alignment === 'LEFT') {
return padEnd(what, width)
Expand All @@ -97,10 +154,6 @@ function pad (alignment, width, what) {
return repeat(' ', sides) + what + repeat(' ', sides + remainder)
}

function row (cells) {
return '| ' + cells.join(' | ') + ' |' + os.EOL
}

function repeat (what, times) {
return new Array(times).fill(what).join('')
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"standard": "^10.0.2"
},
"dependencies": {
"sentence-case": "^2.1.1"
"sentence-case": "^2.1.1",
"split-text-to-chunks": "^1.0.0"
}
}
51 changes: 46 additions & 5 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,18 +79,59 @@ tablemark(input, [options = {}])
- `{Array<Object>} input`: the data to table-ify
- `{Object} [options = {}]`

| key | type | default | description |
| :-----------: | :----------: | :-----: | ---------------------------------------- |
| `columns` | `<Array>` | - | Array of column descriptors. |
| `caseHeaders` | `<Boolean>` | `true` | Sentence case headers derived from keys. |
| `stringify` | `<Function>` | - | Provide a custom "toString" function. |
| key | type | default | description |
| :------------: | :----------: | :--------: | -------------------------------------------- |
| `columns` | `<Array>` | - | Array of column descriptors. |
| `caseHeaders` | `<Boolean>` | `true` | Sentence case headers derived from keys. |
| `stringify` | `<Function>` | - | Provide a custom "toString" function. |
| `wrap.width` | `<Number>` | `Infinity` | Wrap texts at this length. |
| `wrap.gutters` | `<Boolean>` | `false` | Add sides (`| <content> |`) to wrapped rows. |

The `columns` array can either contain objects, in which case their
`name` and `align` properties will be used to alter the display of
the column in the table, or any other type which will be coerced
to a string if necessary and used as a replacement for the column
name.

## text wrapping

To output valid [GitHub Flavored Markdown](https://github.github.com/gfm/) a
cell must not contain newlines. Consider replacing those with `<br />` (e.g.
using the `stringify` option).

Set the `wrap.width` option to wrap any content at that length onto a new
adjacent line:

```js
tablemark([
{ star: false, name: 'Benjamin' },
{ star: true, name: 'Jet Li' }
], { wrap: { width: 5 } })

// | Star | Name |
// | ----- | ----- |
// | false | Benja |
// min
// | true | Jet |
// Li
```

Enable `wrap.gutters` to add pipes on all lines:

```js
tablemark([
{ star: false, name: 'Benjamin' },
{ star: true, name: 'Jet Li' }
], { wrap: { width: 5, gutters: true } })

// | Star | Name |
// | ----- | ----- |
// | false | Benja |
// | | min |
// | true | Jet |
// | | Li |
```

## see also

- [`tablemark-cli`](https://github.com/citycide/tablemark-cli): use this module from the command line
Expand Down
20 changes: 20 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,23 @@ test('can use custom stringify function', t => {
const result = fn(cases.coerce.input, cases.coerce.options)
t.is(result, cases.coerce.expected)
})

test('text wrapping', t => {
const result = fn(cases.wrap.input, cases.wrap.options)
t.is(result, cases.wrap.expected)
})

test('newlines', t => {
const result = fn(cases.newlines.input, cases.newlines.options)
t.is(result, cases.newlines.expected)
})

test('text wrapping and newlines combined', t => {
const result = fn(cases.wrapAndNewlines.input, cases.wrapAndNewlines.options)
t.is(result, cases.wrapAndNewlines.expected)
})

test('gutters', t => {
const result = fn(cases.gutters.input, cases.gutters.options)
t.is(result, cases.gutters.expected)
})

0 comments on commit 304d59d

Please sign in to comment.