You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					179 lines
				
				5.5 KiB
			
		
		
			
		
	
	
					179 lines
				
				5.5 KiB
			| 
											3 years ago
										 | 'use strict' | ||
|  | var align = require('wide-align') | ||
|  | var validate = require('aproba') | ||
|  | 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 = Object.assign({}, 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 remainingSpace = width | ||
|  |   var variableCount = output.length | ||
|  | 
 | ||
|  |   function consumeSpace (length) { | ||
|  |     if (length > remainingSpace) length = remainingSpace | ||
|  |     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 | ||
|  | } |