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.
		
		
		
		
			
				
					182 lines
				
				5.6 KiB
			
		
		
			
		
	
	
					182 lines
				
				5.6 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								'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
							 | 
						||
| 
								 | 
							
								}
							 |