-
-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Improve performance * Fix a couple of bugs (such as trailing whitespace) * Remove minimum of three character in alignment row cells — this isn’t needed in Markdown anymore * Don’t double pad empty cells * Fix tests Related to remarkjs/remark-lint#217.
- Loading branch information
Showing
4 changed files
with
283 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,206 +1,249 @@ | ||
'use strict' | ||
|
||
var repeat = require('repeat-string') | ||
|
||
module.exports = markdownTable | ||
|
||
var trailingWhitespace = / +$/ | ||
|
||
// Characters. | ||
var space = ' ' | ||
var lineFeed = '\n' | ||
var dash = '-' | ||
var colon = ':' | ||
var lowercaseC = 'c' | ||
var lowercaseL = 'l' | ||
var lowercaseR = 'r' | ||
var verticalBar = '|' | ||
|
||
var minCellSize = 3 | ||
var x = 0 | ||
var C = 67 | ||
var L = 76 | ||
var R = 82 | ||
var c = 99 | ||
var l = 108 | ||
var r = 114 | ||
|
||
// Create a table from a matrix of strings. | ||
function markdownTable(table, options) { | ||
var settings = options || {} | ||
var padding = settings.padding === false ? '' : space | ||
var between = padding + verticalBar + padding | ||
var start = settings.delimiterStart === false ? '' : verticalBar + padding | ||
var end = settings.delimiterEnd === false ? '' : padding + verticalBar | ||
var alignment = settings.align | ||
var calculateStringLength = settings.stringLength || lengthNoop | ||
var cellCount = 0 | ||
var padding = settings.padding !== false | ||
var start = settings.delimiterStart !== false | ||
var end = settings.delimiterEnd !== false | ||
var align = (settings.align || []).concat() | ||
var alignDelimiters = settings.alignDelimiters !== false | ||
var alignments = [] | ||
var stringLength = settings.stringLength || defaultStringLength | ||
var rowIndex = -1 | ||
var rowLength = table.length | ||
var cellMatrix = [] | ||
var sizeMatrix = [] | ||
var row = [] | ||
var sizes = [] | ||
var align | ||
var rule | ||
var rows | ||
var row | ||
var longestCellByColumn = [] | ||
var mostCellsPerRow = 0 | ||
var cells | ||
var index | ||
var position | ||
var columnIndex | ||
var columnLength | ||
var largest | ||
var size | ||
var value | ||
var spacing | ||
var cell | ||
var lines | ||
var line | ||
var before | ||
var after | ||
var code | ||
|
||
alignment = alignment ? alignment.concat() : [] | ||
|
||
// This is a superfluous loop if we don’t align delimiters, but otherwise we’d | ||
// do superfluous work when aligning, so optimize for aligning. | ||
while (++rowIndex < rowLength) { | ||
row = table[rowIndex] | ||
cells = table[rowIndex] | ||
columnIndex = -1 | ||
columnLength = cells.length | ||
row = [] | ||
sizes = [] | ||
|
||
if (columnLength > mostCellsPerRow) { | ||
mostCellsPerRow = columnLength | ||
} | ||
|
||
index = -1 | ||
while (++columnIndex < columnLength) { | ||
cell = serialize(cells[columnIndex]) | ||
|
||
if (row.length > cellCount) { | ||
cellCount = row.length | ||
} | ||
if (alignDelimiters === true) { | ||
size = stringLength(cell) | ||
sizes[columnIndex] = size | ||
|
||
while (++index < cellCount) { | ||
position = row[index] ? row[index].length : null | ||
largest = longestCellByColumn[columnIndex] | ||
|
||
if (!sizes[index]) { | ||
sizes[index] = minCellSize | ||
if (largest === undefined || size > largest) { | ||
longestCellByColumn[columnIndex] = size | ||
} | ||
} | ||
|
||
if (position > sizes[index]) { | ||
sizes[index] = position | ||
} | ||
row.push(cell) | ||
} | ||
} | ||
|
||
if (typeof alignment === 'string') { | ||
alignment = pad(cellCount, alignment).split('') | ||
cellMatrix[rowIndex] = row | ||
sizeMatrix[rowIndex] = sizes | ||
} | ||
|
||
// Make sure only valid alignments are used. | ||
index = -1 | ||
|
||
while (++index < cellCount) { | ||
align = alignment[index] | ||
// Figure out which alignments to use. | ||
columnIndex = -1 | ||
columnLength = mostCellsPerRow | ||
|
||
if (typeof align === 'string') { | ||
align = align.charAt(0).toLowerCase() | ||
if (typeof align === 'object' && 'length' in align) { | ||
while (++columnIndex < columnLength) { | ||
alignments[columnIndex] = toAlignment(align[columnIndex]) | ||
} | ||
} else { | ||
code = toAlignment(align) | ||
|
||
if (align !== lowercaseL && align !== lowercaseR && align !== lowercaseC) { | ||
align = '' | ||
while (++columnIndex < columnLength) { | ||
alignments[columnIndex] = code | ||
} | ||
|
||
alignment[index] = align | ||
} | ||
|
||
rowIndex = -1 | ||
rows = [] | ||
|
||
while (++rowIndex < rowLength) { | ||
row = table[rowIndex] | ||
|
||
index = -1 | ||
cells = [] | ||
// Inject the alignment row. | ||
columnIndex = -1 | ||
columnLength = mostCellsPerRow | ||
row = [] | ||
sizes = [] | ||
|
||
while (++index < cellCount) { | ||
cells[index] = stringify(row[index]) | ||
while (++columnIndex < columnLength) { | ||
code = alignments[columnIndex] | ||
before = '' | ||
after = '' | ||
|
||
if (code === l) { | ||
before = colon | ||
} else if (code === r) { | ||
after = colon | ||
} else if (code === c) { | ||
before = colon | ||
after = colon | ||
} | ||
|
||
rows[rowIndex] = cells | ||
} | ||
// There *must* be at least one hyphen-minus in each alignment cell. | ||
size = alignDelimiters | ||
? Math.max( | ||
1, | ||
longestCellByColumn[columnIndex] - before.length - after.length | ||
) | ||
: 1 | ||
|
||
sizes = [] | ||
rowIndex = -1 | ||
|
||
while (++rowIndex < rowLength) { | ||
cells = rows[rowIndex] | ||
cell = before + repeat(dash, size) + after | ||
|
||
index = -1 | ||
if (alignDelimiters === true) { | ||
size = before.length + size + after.length | ||
|
||
while (++index < cellCount) { | ||
value = cells[index] | ||
|
||
if (!sizes[index]) { | ||
sizes[index] = minCellSize | ||
if (size > longestCellByColumn[columnIndex]) { | ||
longestCellByColumn[columnIndex] = size | ||
} | ||
|
||
size = calculateStringLength(value) | ||
|
||
if (size > sizes[index]) { | ||
sizes[index] = size | ||
} | ||
sizes[columnIndex] = size | ||
} | ||
|
||
row[columnIndex] = cell | ||
} | ||
|
||
// Inject the alignment row. | ||
cellMatrix.splice(1, 0, row) | ||
sizeMatrix.splice(1, 0, sizes) | ||
|
||
rowIndex = -1 | ||
rowLength = cellMatrix.length | ||
lines = [] | ||
|
||
while (++rowIndex < rowLength) { | ||
cells = rows[rowIndex] | ||
row = cellMatrix[rowIndex] | ||
sizes = sizeMatrix[rowIndex] | ||
columnIndex = -1 | ||
columnLength = mostCellsPerRow | ||
line = [] | ||
|
||
while (++columnIndex < columnLength) { | ||
cell = row[columnIndex] || '' | ||
before = '' | ||
after = '' | ||
|
||
if (alignDelimiters === true) { | ||
size = longestCellByColumn[columnIndex] - (sizes[columnIndex] || 0) | ||
code = alignments[columnIndex] | ||
|
||
if (code === r) { | ||
before = repeat(space, size) | ||
} else if (code === c) { | ||
if (size % 2 === 0) { | ||
before = repeat(space, size / 2) | ||
after = before | ||
} else { | ||
before = repeat(space, size / 2 + 0.5) | ||
after = repeat(space, size / 2 - 0.5) | ||
} | ||
} else { | ||
after = repeat(space, size) | ||
} | ||
} | ||
|
||
index = -1 | ||
if (start === true && columnIndex === 0) { | ||
line.push(verticalBar) | ||
} | ||
|
||
if (settings.alignDelimiters !== false) { | ||
while (++index < cellCount) { | ||
value = cells[index] | ||
if ( | ||
padding === true && | ||
// Don’t add the opening space if we’re not aligning and the cell is | ||
// empty: there will be a closing space. | ||
!(alignDelimiters === false && cell === '') && | ||
(start === true || columnIndex !== 0) | ||
) { | ||
line.push(space) | ||
} | ||
|
||
position = sizes[index] - (calculateStringLength(value) || 0) | ||
spacing = pad(position) | ||
if (alignDelimiters === true) { | ||
line.push(before) | ||
} | ||
|
||
if (alignment[index] === lowercaseR) { | ||
value = spacing + value | ||
} else if (alignment[index] === lowercaseC) { | ||
position /= 2 | ||
line.push(cell) | ||
|
||
if (position % 1 === 0) { | ||
before = position | ||
after = position | ||
} else { | ||
before = position + 0.5 | ||
after = position - 0.5 | ||
} | ||
if (alignDelimiters === true) { | ||
line.push(after) | ||
} | ||
|
||
value = pad(before) + value + pad(after) | ||
} else { | ||
value += spacing | ||
} | ||
if (padding === true) { | ||
line.push(space) | ||
} | ||
|
||
cells[index] = value | ||
if (end === true || columnIndex !== columnLength - 1) { | ||
line.push(verticalBar) | ||
} | ||
} | ||
|
||
rows[rowIndex] = cells.join(between) | ||
} | ||
line = line.join('') | ||
|
||
index = -1 | ||
rule = [] | ||
|
||
while (++index < cellCount) { | ||
// When `pad` is false, make the rule the same size as the first row. | ||
if (settings.alignDelimiters === false) { | ||
value = table[0][index] | ||
spacing = calculateStringLength(stringify(value)) | ||
spacing = spacing > minCellSize ? spacing : minCellSize | ||
} else { | ||
spacing = sizes[index] | ||
if (end === false) { | ||
line = line.replace(trailingWhitespace, '') | ||
} | ||
|
||
align = alignment[index] | ||
|
||
// When `align` is left, don't add colons. | ||
value = align === lowercaseR || align === '' ? dash : colon | ||
value += pad(spacing - 2, dash) | ||
value += align !== lowercaseL && align !== '' ? colon : dash | ||
|
||
rule[index] = value | ||
lines.push(line) | ||
} | ||
|
||
rows.splice(1, 0, rule.join(between)) | ||
|
||
return start + rows.join(end + lineFeed + start) + end | ||
return lines.join(lineFeed) | ||
} | ||
|
||
function stringify(value) { | ||
function serialize(value) { | ||
return value === null || value === undefined ? '' : String(value) | ||
} | ||
|
||
// Get the length of `value`. | ||
function lengthNoop(value) { | ||
return String(value).length | ||
function defaultStringLength(value) { | ||
return value.length | ||
} | ||
|
||
// Get a string consisting of `length` `character`s. | ||
function pad(length, character) { | ||
return new Array(length + 1).join(character || space) | ||
function toAlignment(value) { | ||
var code = typeof value === 'string' ? value.charCodeAt(0) : x | ||
|
||
return code === L || code === l | ||
? l | ||
: code === R || code === r | ||
? r | ||
: code === C || code === c | ||
? c | ||
: x | ||
} |
Oops, something went wrong.