Skip to content

Commit

Permalink
feat: align cell content (#1)
Browse files Browse the repository at this point in the history
* Align cells vertically by padding cell content

* Throw on unknown alignment

* node 4 compat

* node 4 compab and style
  • Loading branch information
tjconcept authored and haltcase committed Aug 28, 2017
1 parent 2fcaa33 commit cc5b883
Show file tree
Hide file tree
Showing 4 changed files with 1,024 additions and 65 deletions.
30 changes: 15 additions & 15 deletions fixtures/inputs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ module.exports = {
}
],
expected: [
'name | repo | desc',
'----- | ----- | -----',
'trilogy | [citycide/trilogy](//github.com/citycide/trilogy) | No-hassle SQLite with type-casting schema models and support for native & pure JS backends.',
'strat | [citycide/strat](//github.com/citycide/strat) | Functional-ish JavaScript string formatting, with inspirations from Python.',
'tablemark | [citycide/tablemark](//github.com/citycide/tablemark) | Generate markdown tables from JSON data.'
'| name | repo | desc |',
'| --------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------- |',
'| trilogy | [citycide/trilogy](//github.com/citycide/trilogy) | No-hassle SQLite with type-casting schema models and support for native & pure JS backends. |',
'| strat | [citycide/strat](//github.com/citycide/strat) | Functional-ish JavaScript string formatting, with inspirations from Python. |',
'| tablemark | [citycide/tablemark](//github.com/citycide/tablemark) | Generate markdown tables from JSON data. |',
].join(os.EOL) + os.EOL
},
alignments: {
Expand All @@ -43,11 +43,11 @@ module.exports = {
]
},
expected: [
'name | age | isCool',
':---- | ----: | :---:',
'Bob | 21 | false',
'Sarah | 22 | true',
'Lee | 23 | true'
'| name | age | isCool |',
'| :---- | ----: | :----: |',
'| Bob | 21 | false |',
'| Sarah | 22 | true |',
'| Lee | 23 | true |',
].join(os.EOL) + os.EOL
},
columns: {
Expand All @@ -64,11 +64,11 @@ module.exports = {
]
},
expected: [
'word | number | boolean',
'----- | ----- | -----',
'Bob | 21 | false',
'Sarah | 22 | true',
'Lee | 23 | true'
'| word | number | boolean |',
'| ----- | ------ | ------- |',
'| Bob | 21 | false |',
'| Sarah | 22 | true |',
'| Lee | 23 | true |',
].join(os.EOL) + os.EOL
}
}
123 changes: 88 additions & 35 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use strict'

const os = require('os')
const isPlainObject = require('is-plain-obj')

const ALIGN = {
LEFT: ':----',
CENTER: ':---:',
RIGHT: '----:'
}
const ALIGN = [ 'LEFT', 'CENTER', 'RIGHT' ]

const columnWidthMin = 5

module.exports = (input, options) => {
if (!Array.isArray(input)) {
Expand All @@ -19,48 +16,104 @@ module.exports = (input, options) => {
options = Object.assign({}, options)

let table = ''
let titles = []
let alignments = []

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

if (!options.columns || !Array.isArray(options.columns)) {
// use the keys from the first object
titles = keys
alignments = new Array(titles.length).fill('')
} else {
titles = keys.map((key, i) => {
let column = options.columns[i]

if (isPlainObject(column)) {
alignments[i] = column.align
return column.name || key
} else {
alignments[i] = ''
return column || key
let titles = keys.map((key, i) => {
if (Array.isArray(options.columns) && options.columns[i]) {
if (typeof options.columns[i] === 'string') {
return options.columns[i]
} else if (options.columns[i].name) {
return options.columns[i].name
}
}

return key
})

let widths = input.reduce(
(sizes, item) => keys.map(
(key, i) => Math.max(
columnWidthMin,
typeof item[key] === 'undefined' ? 0 : String(item[key]).length,
sizes[i]
)
),
titles.map(t => t.length)
)

let alignments = keys.map((key, i) => {
if (Array.isArray(options.columns)
&& options.columns[i]
&& options.columns[i].align
) {
let align = String(options.columns[i].align).toUpperCase()

if (ALIGN.indexOf(align) === -1) {
throw new TypeError(`Unknown alignment, got ${options.columns[i].align}`)
}
})
}

return align
}
})

// header line
table += titles.join(' | ')
table += os.EOL
table += row(titles.map((title, i) => {
return pad(alignments[i], widths[i], title)
}))

// header separator
table += alignments.map(v => {
let s = String(v).toUpperCase()
return ALIGN[s] || '-----'
}).join(' | ')
table += os.EOL
table += row(alignments.map((align, i) => {
return (align === 'LEFT' || align === 'CENTER' ? ':' : '-')
+ repeat('-', widths[i] - 2)
+ (align === 'RIGHT' || align === 'CENTER' ? ':' : '-')
}))

// table body
input.forEach(item => {
table += keys.map(key => {
table += row(keys.map((key, i) => {
let v = item[key]
if (typeof v === 'undefined') return ''
return String(v)
}).join(' | ') + os.EOL
let s = typeof v === 'undefined' ? '' : String(v)

return pad(alignments[i], widths[i], s)
}))
})

return table
}

function pad(alignment, target, value){
if (!alignment || alignment === 'LEFT') {
return padEnd(value, target)
}

if (alignment === 'RIGHT') {
return padStart(value, target)
}

// CENTER
let remainder = (target - value.length) % 2
let sides = (target - value.length - remainder) / 2

return repeat(' ', sides) + value + repeat(' ', sides + remainder)
}

function row(v){
if (Array.isArray(v)) {
v = v.join(' | ')
}

return '| ' + v + ' |' + os.EOL
}

function repeat(what, times){
return new Array(times).fill(what).join('')
}

function padStart(what, target, start){
return repeat(' ', target - what.length) + what
}

function padEnd(what, target, start){
return what + repeat(' ', target - what.length)
}

0 comments on commit cc5b883

Please sign in to comment.