182 lines
5.6 KiB
JavaScript
182 lines
5.6 KiB
JavaScript
|
'use strict'
|
||
|
var align = require('wide-align')
|
||
|
var validate = require('aproba')
|
||
|
var objectAssign = require('object-assign')
|
||
|
var wideTruncate = require('./wide-truncate')
|
||
|
var error = require('./error')
|
||
|
var TemplateItem = require('./template-item')
|
||
|
|
||
|
function renderValueWithValues (values) {
|
||
|
return function (item) {
|
||
|
return renderValue(item, values)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var renderTemplate = module.exports = function (width, template, values) {
|
||
|
var items = prepareItems(width, template, values)
|
||
|
var rendered = items.map(renderValueWithValues(values)).join('')
|
||
|
return align.left(wideTruncate(rendered, width), width)
|
||
|
}
|
||
|
|
||
|
function preType (item) {
|
||
|
var cappedTypeName = item.type[0].toUpperCase() + item.type.slice(1)
|
||
|
return 'pre' + cappedTypeName
|
||
|
}
|
||
|
|
||
|
function postType (item) {
|
||
|
var cappedTypeName = item.type[0].toUpperCase() + item.type.slice(1)
|
||
|
return 'post' + cappedTypeName
|
||
|
}
|
||
|
|
||
|
function hasPreOrPost (item, values) {
|
||
|
if (!item.type) return
|
||
|
return values[preType(item)] || values[postType(item)]
|
||
|
}
|
||
|
|
||
|
function generatePreAndPost (baseItem, parentValues) {
|
||
|
var item = objectAssign({}, baseItem)
|
||
|
var values = Object.create(parentValues)
|
||
|
var template = []
|
||
|
var pre = preType(item)
|
||
|
var post = postType(item)
|
||
|
if (values[pre]) {
|
||
|
template.push({value: values[pre]})
|
||
|
values[pre] = null
|
||
|
}
|
||
|
item.minLength = null
|
||
|
item.length = null
|
||
|
item.maxLength = null
|
||
|
template.push(item)
|
||
|
values[item.type] = values[item.type]
|
||
|
if (values[post]) {
|
||
|
template.push({value: values[post]})
|
||
|
values[post] = null
|
||
|
}
|
||
|
return function ($1, $2, length) {
|
||
|
return renderTemplate(length, template, values)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function prepareItems (width, template, values) {
|
||
|
function cloneAndObjectify (item, index, arr) {
|
||
|
var cloned = new TemplateItem(item, width)
|
||
|
var type = cloned.type
|
||
|
if (cloned.value == null) {
|
||
|
if (!(type in values)) {
|
||
|
if (cloned.default == null) {
|
||
|
throw new error.MissingTemplateValue(cloned, values)
|
||
|
} else {
|
||
|
cloned.value = cloned.default
|
||
|
}
|
||
|
} else {
|
||
|
cloned.value = values[type]
|
||
|
}
|
||
|
}
|
||
|
if (cloned.value == null || cloned.value === '') return null
|
||
|
cloned.index = index
|
||
|
cloned.first = index === 0
|
||
|
cloned.last = index === arr.length - 1
|
||
|
if (hasPreOrPost(cloned, values)) cloned.value = generatePreAndPost(cloned, values)
|
||
|
return cloned
|
||
|
}
|
||
|
|
||
|
var output = template.map(cloneAndObjectify).filter(function (item) { return item != null })
|
||
|
|
||
|
var outputLength = 0
|
||
|
var remainingSpace = width
|
||
|
var variableCount = output.length
|
||
|
|
||
|
function consumeSpace (length) {
|
||
|
if (length > remainingSpace) length = remainingSpace
|
||
|
outputLength += length
|
||
|
remainingSpace -= length
|
||
|
}
|
||
|
|
||
|
function finishSizing (item, length) {
|
||
|
if (item.finished) throw new error.Internal('Tried to finish template item that was already finished')
|
||
|
if (length === Infinity) throw new error.Internal('Length of template item cannot be infinity')
|
||
|
if (length != null) item.length = length
|
||
|
item.minLength = null
|
||
|
item.maxLength = null
|
||
|
--variableCount
|
||
|
item.finished = true
|
||
|
if (item.length == null) item.length = item.getBaseLength()
|
||
|
if (item.length == null) throw new error.Internal('Finished template items must have a length')
|
||
|
consumeSpace(item.getLength())
|
||
|
}
|
||
|
|
||
|
output.forEach(function (item) {
|
||
|
if (!item.kerning) return
|
||
|
var prevPadRight = item.first ? 0 : output[item.index - 1].padRight
|
||
|
if (!item.first && prevPadRight < item.kerning) item.padLeft = item.kerning - prevPadRight
|
||
|
if (!item.last) item.padRight = item.kerning
|
||
|
})
|
||
|
|
||
|
// Finish any that have a fixed (literal or intuited) length
|
||
|
output.forEach(function (item) {
|
||
|
if (item.getBaseLength() == null) return
|
||
|
finishSizing(item)
|
||
|
})
|
||
|
|
||
|
var resized = 0
|
||
|
var resizing
|
||
|
var hunkSize
|
||
|
do {
|
||
|
resizing = false
|
||
|
hunkSize = Math.round(remainingSpace / variableCount)
|
||
|
output.forEach(function (item) {
|
||
|
if (item.finished) return
|
||
|
if (!item.maxLength) return
|
||
|
if (item.getMaxLength() < hunkSize) {
|
||
|
finishSizing(item, item.maxLength)
|
||
|
resizing = true
|
||
|
}
|
||
|
})
|
||
|
} while (resizing && resized++ < output.length)
|
||
|
if (resizing) throw new error.Internal('Resize loop iterated too many times while determining maxLength')
|
||
|
|
||
|
resized = 0
|
||
|
do {
|
||
|
resizing = false
|
||
|
hunkSize = Math.round(remainingSpace / variableCount)
|
||
|
output.forEach(function (item) {
|
||
|
if (item.finished) return
|
||
|
if (!item.minLength) return
|
||
|
if (item.getMinLength() >= hunkSize) {
|
||
|
finishSizing(item, item.minLength)
|
||
|
resizing = true
|
||
|
}
|
||
|
})
|
||
|
} while (resizing && resized++ < output.length)
|
||
|
if (resizing) throw new error.Internal('Resize loop iterated too many times while determining minLength')
|
||
|
|
||
|
hunkSize = Math.round(remainingSpace / variableCount)
|
||
|
output.forEach(function (item) {
|
||
|
if (item.finished) return
|
||
|
finishSizing(item, hunkSize)
|
||
|
})
|
||
|
|
||
|
return output
|
||
|
}
|
||
|
|
||
|
function renderFunction (item, values, length) {
|
||
|
validate('OON', arguments)
|
||
|
if (item.type) {
|
||
|
return item.value(values, values[item.type + 'Theme'] || {}, length)
|
||
|
} else {
|
||
|
return item.value(values, {}, length)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function renderValue (item, values) {
|
||
|
var length = item.getBaseLength()
|
||
|
var value = typeof item.value === 'function' ? renderFunction(item, values, length) : item.value
|
||
|
if (value == null || value === '') return ''
|
||
|
var alignWith = align[item.align] || align.left
|
||
|
var leftPadding = item.padLeft ? align.left('', item.padLeft) : ''
|
||
|
var rightPadding = item.padRight ? align.right('', item.padRight) : ''
|
||
|
var truncated = wideTruncate(String(value), length)
|
||
|
var aligned = alignWith(truncated, length)
|
||
|
return leftPadding + aligned + rightPadding
|
||
|
}
|