mirror of
				https://kkgithub.com/actions/setup-python.git
				synced 2025-11-04 04:31:51 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
var stringWidth = require('string-width')
 | 
						|
var stripAnsi = require('strip-ansi')
 | 
						|
var wrap = require('wrap-ansi')
 | 
						|
var align = {
 | 
						|
  right: alignRight,
 | 
						|
  center: alignCenter
 | 
						|
}
 | 
						|
var top = 0
 | 
						|
var right = 1
 | 
						|
var bottom = 2
 | 
						|
var left = 3
 | 
						|
 | 
						|
function 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
 | 
						|
  })
 | 
						|
}
 |