| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 | var stringWidth = require('string-width')var stripAnsi = require('strip-ansi')var wrap = require('wrap-ansi')var align = {  right: alignRight,  center: alignCenter}var top = 0var right = 1var bottom = 2var left = 3function UI (opts) {  this.width = opts.width  this.wrap = opts.wrap  this.rows = []}UI.prototype.span = function () {  var cols = this.div.apply(this, arguments)  cols.span = true}UI.prototype.resetOutput = function () {  this.rows = []}UI.prototype.div = function () {  if (arguments.length === 0) this.div('')  if (this.wrap && this._shouldApplyLayoutDSL.apply(this, arguments)) {    return this._applyLayoutDSL(arguments[0])  }  var cols = []  for (var i = 0, arg; (arg = arguments[i]) !== undefined; i++) {    if (typeof arg === 'string') cols.push(this._colFromString(arg))    else cols.push(arg)  }  this.rows.push(cols)  return cols}UI.prototype._shouldApplyLayoutDSL = function () {  return arguments.length === 1 && typeof arguments[0] === 'string' &&    /[\t\n]/.test(arguments[0])}UI.prototype._applyLayoutDSL = function (str) {  var _this = this  var rows = str.split('\n')  var leftColumnWidth = 0  // simple heuristic for layout, make sure the  // second column lines up along the left-hand.  // don't allow the first column to take up more  // than 50% of the screen.  rows.forEach(function (row) {    var columns = row.split('\t')    if (columns.length > 1 && stringWidth(columns[0]) > leftColumnWidth) {      leftColumnWidth = Math.min(        Math.floor(_this.width * 0.5),        stringWidth(columns[0])      )    }  })  // generate a table:  //  replacing ' ' with padding calculations.  //  using the algorithmically generated width.  rows.forEach(function (row) {    var columns = row.split('\t')    _this.div.apply(_this, columns.map(function (r, i) {      return {        text: r.trim(),        padding: _this._measurePadding(r),        width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined      }    }))  })  return this.rows[this.rows.length - 1]}UI.prototype._colFromString = function (str) {  return {    text: str,    padding: this._measurePadding(str)  }}UI.prototype._measurePadding = function (str) {  // measure padding without ansi escape codes  var noAnsi = stripAnsi(str)  return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length]}UI.prototype.toString = function () {  var _this = this  var lines = []  _this.rows.forEach(function (row, i) {    _this.rowToString(row, lines)  })  // don't display any lines with the  // hidden flag set.  lines = lines.filter(function (line) {    return !line.hidden  })  return lines.map(function (line) {    return line.text  }).join('\n')}UI.prototype.rowToString = function (row, lines) {  var _this = this  var padding  var rrows = this._rasterize(row)  var str = ''  var ts  var width  var wrapWidth  rrows.forEach(function (rrow, r) {    str = ''    rrow.forEach(function (col, c) {      ts = '' // temporary string used during alignment/padding.      width = row[c].width // the width with padding.      wrapWidth = _this._negatePadding(row[c]) // the width without padding.      ts += col      for (var i = 0; i < wrapWidth - stringWidth(col); i++) {        ts += ' '      }      // align the string within its column.      if (row[c].align && row[c].align !== 'left' && _this.wrap) {        ts = align[row[c].align](ts, wrapWidth)        if (stringWidth(ts) < wrapWidth) ts += new Array(width - stringWidth(ts)).join(' ')      }      // apply border and padding to string.      padding = row[c].padding || [0, 0, 0, 0]      if (padding[left]) str += new Array(padding[left] + 1).join(' ')      str += addBorder(row[c], ts, '| ')      str += ts      str += addBorder(row[c], ts, ' |')      if (padding[right]) str += new Array(padding[right] + 1).join(' ')      // if prior row is span, try to render the      // current row on the prior line.      if (r === 0 && lines.length > 0) {        str = _this._renderInline(str, lines[lines.length - 1])      }    })    // remove trailing whitespace.    lines.push({      text: str.replace(/ +$/, ''),      span: row.span    })  })  return lines}function addBorder (col, ts, style) {  if (col.border) {    if (/[.']-+[.']/.test(ts)) return ''    else if (ts.trim().length) return style    else return '  '  }  return ''}// if the full 'source' can render in// the target line, do so.UI.prototype._renderInline = function (source, previousLine) {  var leadingWhitespace = source.match(/^ */)[0].length  var target = previousLine.text  var targetTextWidth = stringWidth(target.trimRight())  if (!previousLine.span) return source  // if we're not applying wrapping logic,  // just always append to the span.  if (!this.wrap) {    previousLine.hidden = true    return target + source  }  if (leadingWhitespace < targetTextWidth) return source  previousLine.hidden = true  return target.trimRight() + new Array(leadingWhitespace - targetTextWidth + 1).join(' ') + source.trimLeft()}UI.prototype._rasterize = function (row) {  var _this = this  var i  var rrow  var rrows = []  var widths = this._columnWidths(row)  var wrapped  // word wrap all columns, and create  // a data-structure that is easy to rasterize.  row.forEach(function (col, c) {    // leave room for left and right padding.    col.width = widths[c]    if (_this.wrap) wrapped = wrap(col.text, _this._negatePadding(col), { hard: true }).split('\n')    else wrapped = col.text.split('\n')    if (col.border) {      wrapped.unshift('.' + new Array(_this._negatePadding(col) + 3).join('-') + '.')      wrapped.push("'" + new Array(_this._negatePadding(col) + 3).join('-') + "'")    }    // add top and bottom padding.    if (col.padding) {      for (i = 0; i < (col.padding[top] || 0); i++) wrapped.unshift('')      for (i = 0; i < (col.padding[bottom] || 0); i++) wrapped.push('')    }    wrapped.forEach(function (str, r) {      if (!rrows[r]) rrows.push([])      rrow = rrows[r]      for (var i = 0; i < c; i++) {        if (rrow[i] === undefined) rrow.push('')      }      rrow.push(str)    })  })  return rrows}UI.prototype._negatePadding = function (col) {  var wrapWidth = col.width  if (col.padding) wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0)  if (col.border) wrapWidth -= 4  return wrapWidth}UI.prototype._columnWidths = function (row) {  var _this = this  var widths = []  var unset = row.length  var unsetWidth  var remainingWidth = this.width  // column widths can be set in config.  row.forEach(function (col, i) {    if (col.width) {      unset--      widths[i] = col.width      remainingWidth -= col.width    } else {      widths[i] = undefined    }  })  // any unset widths should be calculated.  if (unset) unsetWidth = Math.floor(remainingWidth / unset)  widths.forEach(function (w, i) {    if (!_this.wrap) widths[i] = row[i].width || stringWidth(row[i].text)    else if (w === undefined) widths[i] = Math.max(unsetWidth, _minWidth(row[i]))  })  return widths}// calculates the minimum width of// a column, based on padding preferences.function _minWidth (col) {  var padding = col.padding || []  var minWidth = 1 + (padding[left] || 0) + (padding[right] || 0)  if (col.border) minWidth += 4  return minWidth}function getWindowWidth () {  if (typeof process === 'object' && process.stdout && process.stdout.columns) return process.stdout.columns}function alignRight (str, width) {  str = str.trim()  var padding = ''  var strWidth = stringWidth(str)  if (strWidth < width) {    padding = new Array(width - strWidth + 1).join(' ')  }  return padding + str}function alignCenter (str, width) {  str = str.trim()  var padding = ''  var strWidth = stringWidth(str.trim())  if (strWidth < width) {    padding = new Array(parseInt((width - strWidth) / 2, 10) + 1).join(' ')  }  return padding + str}module.exports = function (opts) {  opts = opts || {}  return new UI({    width: (opts || {}).width || getWindowWidth() || 80,    wrap: typeof opts.wrap === 'boolean' ? opts.wrap : true  })}
 |