| 'use strict' | 
|   | 
| const stringWidth = require('string-width') | 
| const stripAnsi = require('strip-ansi') | 
| const wrap = require('wrap-ansi') | 
|   | 
| const align = { | 
|   right: alignRight, | 
|   center: alignCenter | 
| } | 
| const top = 0 | 
| const right = 1 | 
| const bottom = 2 | 
| const left = 3 | 
|   | 
| class UI { | 
|   constructor (opts) { | 
|     this.width = opts.width | 
|     this.wrap = opts.wrap | 
|     this.rows = [] | 
|   } | 
|   | 
|   span (...args) { | 
|     const cols = this.div(...args) | 
|     cols.span = true | 
|   } | 
|   | 
|   resetOutput () { | 
|     this.rows = [] | 
|   } | 
|   | 
|   div (...args) { | 
|     if (args.length === 0) { | 
|       this.div('') | 
|     } | 
|   | 
|     if (this.wrap && this._shouldApplyLayoutDSL(...args)) { | 
|       return this._applyLayoutDSL(args[0]) | 
|     } | 
|   | 
|     const cols = args.map(arg => { | 
|       if (typeof arg === 'string') { | 
|         return this._colFromString(arg) | 
|       } | 
|   | 
|       return arg | 
|     }) | 
|   | 
|     this.rows.push(cols) | 
|     return cols | 
|   } | 
|   | 
|   _shouldApplyLayoutDSL (...args) { | 
|     return args.length === 1 && typeof args[0] === 'string' && | 
|       /[\t\n]/.test(args[0]) | 
|   } | 
|   | 
|   _applyLayoutDSL (str) { | 
|     const rows = str.split('\n').map(row => row.split('\t')) | 
|     let 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(columns => { | 
|       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(columns => { | 
|       this.div(...columns.map((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] | 
|   } | 
|   | 
|   _colFromString (text) { | 
|     return { | 
|       text, | 
|       padding: this._measurePadding(text) | 
|     } | 
|   } | 
|   | 
|   _measurePadding (str) { | 
|     // measure padding without ansi escape codes | 
|     const noAnsi = stripAnsi(str) | 
|     return [0, noAnsi.match(/\s*$/)[0].length, 0, noAnsi.match(/^\s*/)[0].length] | 
|   } | 
|   | 
|   toString () { | 
|     const lines = [] | 
|   | 
|     this.rows.forEach(row => { | 
|       this.rowToString(row, lines) | 
|     }) | 
|   | 
|     // don't display any lines with the | 
|     // hidden flag set. | 
|     return lines | 
|       .filter(line => !line.hidden) | 
|       .map(line => line.text) | 
|       .join('\n') | 
|   } | 
|   | 
|   rowToString (row, lines) { | 
|     this._rasterize(row).forEach((rrow, r) => { | 
|       let str = '' | 
|       rrow.forEach((col, c) => { | 
|         const { width } = row[c] // the width with padding. | 
|         const wrapWidth = this._negatePadding(row[c]) // the width without padding. | 
|   | 
|         let ts = col // temporary string used during alignment/padding. | 
|   | 
|         if (wrapWidth > stringWidth(col)) { | 
|           ts += ' '.repeat(wrapWidth - stringWidth(col)) | 
|         } | 
|   | 
|         // 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 += ' '.repeat(width - stringWidth(ts) - 1) | 
|           } | 
|         } | 
|   | 
|         // apply border and padding to string. | 
|         const padding = row[c].padding || [0, 0, 0, 0] | 
|         if (padding[left]) { | 
|           str += ' '.repeat(padding[left]) | 
|         } | 
|   | 
|         str += addBorder(row[c], ts, '| ') | 
|         str += ts | 
|         str += addBorder(row[c], ts, ' |') | 
|         if (padding[right]) { | 
|           str += ' '.repeat(padding[right]) | 
|         } | 
|   | 
|         // 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 | 
|   } | 
|   | 
|   // if the full 'source' can render in | 
|   // the target line, do so. | 
|   _renderInline (source, previousLine) { | 
|     const leadingWhitespace = source.match(/^ */)[0].length | 
|     const target = previousLine.text | 
|     const 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() + ' '.repeat(leadingWhitespace - targetTextWidth) + source.trimLeft() | 
|   } | 
|   | 
|   _rasterize (row) { | 
|     const rrows = [] | 
|     const widths = this._columnWidths(row) | 
|     let wrapped | 
|   | 
|     // word wrap all columns, and create | 
|     // a data-structure that is easy to rasterize. | 
|     row.forEach((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('.' + '-'.repeat(this._negatePadding(col) + 2) + '.') | 
|         wrapped.push("'" + '-'.repeat(this._negatePadding(col) + 2) + "'") | 
|       } | 
|   | 
|       // add top and bottom padding. | 
|       if (col.padding) { | 
|         wrapped.unshift(...new Array(col.padding[top] || 0).fill('')) | 
|         wrapped.push(...new Array(col.padding[bottom] || 0).fill('')) | 
|       } | 
|   | 
|       wrapped.forEach((str, r) => { | 
|         if (!rrows[r]) { | 
|           rrows.push([]) | 
|         } | 
|   | 
|         const rrow = rrows[r] | 
|   | 
|         for (let i = 0; i < c; i++) { | 
|           if (rrow[i] === undefined) { | 
|             rrow.push('') | 
|           } | 
|         } | 
|   | 
|         rrow.push(str) | 
|       }) | 
|     }) | 
|   | 
|     return rrows | 
|   } | 
|   | 
|   _negatePadding (col) { | 
|     let wrapWidth = col.width | 
|     if (col.padding) { | 
|       wrapWidth -= (col.padding[left] || 0) + (col.padding[right] || 0) | 
|     } | 
|   | 
|     if (col.border) { | 
|       wrapWidth -= 4 | 
|     } | 
|   | 
|     return wrapWidth | 
|   } | 
|   | 
|   _columnWidths (row) { | 
|     if (!this.wrap) { | 
|       return row.map(col => { | 
|         return col.width || stringWidth(col.text) | 
|       }) | 
|     } | 
|   | 
|     let unset = row.length | 
|     let remainingWidth = this.width | 
|   | 
|     // column widths can be set in config. | 
|     const widths = row.map(col => { | 
|       if (col.width) { | 
|         unset-- | 
|         remainingWidth -= col.width | 
|         return col.width | 
|       } | 
|   | 
|       return undefined | 
|     }) | 
|   | 
|     // any unset widths should be calculated. | 
|     const unsetWidth = unset ? Math.floor(remainingWidth / unset) : 0 | 
|   | 
|     return widths.map((w, i) => { | 
|       if (w === undefined) { | 
|         return Math.max(unsetWidth, _minWidth(row[i])) | 
|       } | 
|   | 
|       return w | 
|     }) | 
|   } | 
| } | 
|   | 
| function addBorder (col, ts, style) { | 
|   if (col.border) { | 
|     if (/[.']-+[.']/.test(ts)) { | 
|       return '' | 
|     } | 
|   | 
|     if (ts.trim().length !== 0) { | 
|       return style | 
|     } | 
|   | 
|     return '  ' | 
|   } | 
|   | 
|   return '' | 
| } | 
|   | 
| // calculates the minimum width of | 
| // a column, based on padding preferences. | 
| function _minWidth (col) { | 
|   const padding = col.padding || [] | 
|   const minWidth = 1 + (padding[left] || 0) + (padding[right] || 0) | 
|   if (col.border) { | 
|     return minWidth + 4 | 
|   } | 
|   | 
|   return minWidth | 
| } | 
|   | 
| function getWindowWidth () { | 
|   /* istanbul ignore next: depends on terminal */ | 
|   if (typeof process === 'object' && process.stdout && process.stdout.columns) { | 
|     return process.stdout.columns | 
|   } | 
| } | 
|   | 
| function alignRight (str, width) { | 
|   str = str.trim() | 
|   const strWidth = stringWidth(str) | 
|   | 
|   if (strWidth < width) { | 
|     return ' '.repeat(width - strWidth) + str | 
|   } | 
|   | 
|   return str | 
| } | 
|   | 
| function alignCenter (str, width) { | 
|   str = str.trim() | 
|   const strWidth = stringWidth(str) | 
|   | 
|   /* istanbul ignore next */ | 
|   if (strWidth >= width) { | 
|     return str | 
|   } | 
|   | 
|   return ' '.repeat((width - strWidth) >> 1) + str | 
| } | 
|   | 
| module.exports = function (opts = {}) { | 
|   return new UI({ | 
|     width: opts.width || getWindowWidth() || /* istanbul ignore next */ 80, | 
|     wrap: opts.wrap !== false | 
|   }) | 
| } |