2 var align = require('wide-align')
3 var validate = require('aproba')
4 var objectAssign = require('object-assign')
5 var wideTruncate = require('./wide-truncate')
6 var error = require('./error')
7 var TemplateItem = require('./template-item')
9 function renderValueWithValues (values) {
10 return function (item) {
11 return renderValue(item, values)
15 var renderTemplate = module.exports = function (width, template, values) {
16 var items = prepareItems(width, template, values)
17 var rendered = items.map(renderValueWithValues(values)).join('')
18 return align.left(wideTruncate(rendered, width), width)
21 function preType (item) {
22 var cappedTypeName = item.type[0].toUpperCase() + item.type.slice(1)
23 return 'pre' + cappedTypeName
26 function postType (item) {
27 var cappedTypeName = item.type[0].toUpperCase() + item.type.slice(1)
28 return 'post' + cappedTypeName
31 function hasPreOrPost (item, values) {
32 if (!item.type) return
33 return values[preType(item)] || values[postType(item)]
36 function generatePreAndPost (baseItem, parentValues) {
37 var item = objectAssign({}, baseItem)
38 var values = Object.create(parentValues)
40 var pre = preType(item)
41 var post = postType(item)
43 template.push({value: values[pre]})
50 values[item.type] = values[item.type]
52 template.push({value: values[post]})
55 return function ($1, $2, length) {
56 return renderTemplate(length, template, values)
60 function prepareItems (width, template, values) {
61 function cloneAndObjectify (item, index, arr) {
62 var cloned = new TemplateItem(item, width)
63 var type = cloned.type
64 if (cloned.value == null) {
65 if (!(type in values)) {
66 if (cloned.default == null) {
67 throw new error.MissingTemplateValue(cloned, values)
69 cloned.value = cloned.default
72 cloned.value = values[type]
75 if (cloned.value == null || cloned.value === '') return null
77 cloned.first = index === 0
78 cloned.last = index === arr.length - 1
79 if (hasPreOrPost(cloned, values)) cloned.value = generatePreAndPost(cloned, values)
83 var output = template.map(cloneAndObjectify).filter(function (item) { return item != null })
86 var remainingSpace = width
87 var variableCount = output.length
89 function consumeSpace (length) {
90 if (length > remainingSpace) length = remainingSpace
91 outputLength += length
92 remainingSpace -= length
95 function finishSizing (item, length) {
96 if (item.finished) throw new error.Internal('Tried to finish template item that was already finished')
97 if (length === Infinity) throw new error.Internal('Length of template item cannot be infinity')
98 if (length != null) item.length = length
100 item.maxLength = null
103 if (item.length == null) item.length = item.getBaseLength()
104 if (item.length == null) throw new error.Internal('Finished template items must have a length')
105 consumeSpace(item.getLength())
108 output.forEach(function (item) {
109 if (!item.kerning) return
110 var prevPadRight = item.first ? 0 : output[item.index - 1].padRight
111 if (!item.first && prevPadRight < item.kerning) item.padLeft = item.kerning - prevPadRight
112 if (!item.last) item.padRight = item.kerning
115 // Finish any that have a fixed (literal or intuited) length
116 output.forEach(function (item) {
117 if (item.getBaseLength() == null) return
126 hunkSize = Math.round(remainingSpace / variableCount)
127 output.forEach(function (item) {
128 if (item.finished) return
129 if (!item.maxLength) return
130 if (item.getMaxLength() < hunkSize) {
131 finishSizing(item, item.maxLength)
135 } while (resizing && resized++ < output.length)
136 if (resizing) throw new error.Internal('Resize loop iterated too many times while determining maxLength')
141 hunkSize = Math.round(remainingSpace / variableCount)
142 output.forEach(function (item) {
143 if (item.finished) return
144 if (!item.minLength) return
145 if (item.getMinLength() >= hunkSize) {
146 finishSizing(item, item.minLength)
150 } while (resizing && resized++ < output.length)
151 if (resizing) throw new error.Internal('Resize loop iterated too many times while determining minLength')
153 hunkSize = Math.round(remainingSpace / variableCount)
154 output.forEach(function (item) {
155 if (item.finished) return
156 finishSizing(item, hunkSize)
162 function renderFunction (item, values, length) {
163 validate('OON', arguments)
165 return item.value(values, values[item.type + 'Theme'] || {}, length)
167 return item.value(values, {}, length)
171 function renderValue (item, values) {
172 var length = item.getBaseLength()
173 var value = typeof item.value === 'function' ? renderFunction(item, values, length) : item.value
174 if (value == null || value === '') return ''
175 var alignWith = align[item.align] || align.left
176 var leftPadding = item.padLeft ? align.left('', item.padLeft) : ''
177 var rightPadding = item.padRight ? align.right('', item.padRight) : ''
178 var truncated = wideTruncate(String(value), length)
179 var aligned = alignWith(truncated, length)
180 return leftPadding + aligned + rightPadding