| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 | var wrap = require('wordwrap'),  align = {    right: require('right-align'),    center: require('center-align')  },  top = 0,  right = 1,  bottom = 2,  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.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,    rows = str.split('\n'),    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 && columns[0].length > leftColumnWidth) {      leftColumnWidth = Math.min(        Math.floor(_this.width * 0.5),        columns[0].length      )    }  })  // 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: [0, r.match(/\s*$/)[0].length, 0, r.match(/^\s*/)[0].length],        width: (i === 0 && columns.length > 1) ? leftColumnWidth : undefined      }    }))  })  return this.rows[this.rows.length - 1]}UI.prototype._colFromString = function (str) {  return {    text: str  }}UI.prototype.toString = function () {  var _this = this,    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,    paddingLeft,    rrows = this._rasterize(row),    str = '',    ts,    width,    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.      for (var i = 0; i < Math.max(wrapWidth, col.length); i++) {        ts += col.charAt(i) || ' '      }      // align the string within its column.      if (row[c].align && row[c].align !== 'left' && _this.wrap) {        ts = align[row[c].align](ts.trim() + '\n' + new Array(wrapWidth + 1).join(' '))          .split('\n')[0]        if (ts.length < wrapWidth) ts += new Array(width - ts.length).join(' ')      }      // add left/right padding and print string.      paddingLeft = (row[c].padding || [0, 0, 0, 0])[left]      if (paddingLeft) str += new Array(row[c].padding[left] + 1).join(' ')      str += ts      if (row[c].padding && row[c].padding[right]) str += new Array(row[c].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], paddingLeft)      }    })    // remove trailing whitespace.    lines.push({      text: str.replace(/ +$/, ''),      span: row.span    })  })  return lines}// if the full 'source' can render in// the target line, do so.UI.prototype._renderInline = function (source, previousLine, paddingLeft) {  var target = previousLine.text,    str = ''  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  }  for (var i = 0, tc, sc; i < Math.max(source.length, target.length); i++) {    tc = target.charAt(i) || ' '    sc = source.charAt(i) || ' '    // we tried to overwrite a character in the other string.    if (tc !== ' ' && sc !== ' ') return source    // there is not enough whitespace to maintain padding.    if (sc !== ' ' && i < paddingLeft + target.length) return source    // :thumbsup:    if (tc === ' ') str += sc    else str += tc  }  previousLine.hidden = true  return str}UI.prototype._rasterize = function (row) {  var _this = this,    i,    rrow,    rrows = [],    widths = this._columnWidths(row),    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.hard(_this._negatePadding(col))(col.text).split('\n')    else wrapped = col.text.split('\n')    // 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)  return wrapWidth}UI.prototype._columnWidths = function (row) {  var _this = this,    widths = [],    unset = row.length,    unsetWidth,    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 || row[i].text.length    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 || []  return 1 + (padding[left] || 0) + (padding[right] || 0)}module.exports = function (opts) {  opts = opts || {}  return new UI({    width: (opts || {}).width || 80,    wrap: typeof opts.wrap === 'boolean' ? opts.wrap : true  })}
 |