diff options
Diffstat (limited to 'deps/npm/node_modules/cli-table3/src')
-rw-r--r-- | deps/npm/node_modules/cli-table3/src/cell.js | 371 | ||||
-rw-r--r-- | deps/npm/node_modules/cli-table3/src/layout-manager.js | 232 | ||||
-rw-r--r-- | deps/npm/node_modules/cli-table3/src/table.js | 77 | ||||
-rw-r--r-- | deps/npm/node_modules/cli-table3/src/utils.js | 303 |
4 files changed, 983 insertions, 0 deletions
diff --git a/deps/npm/node_modules/cli-table3/src/cell.js b/deps/npm/node_modules/cli-table3/src/cell.js new file mode 100644 index 0000000000..2db7f74d72 --- /dev/null +++ b/deps/npm/node_modules/cli-table3/src/cell.js @@ -0,0 +1,371 @@ +const utils = require('./utils'); + +class Cell { + /** + * A representation of a cell within the table. + * Implementations must have `init` and `draw` methods, + * as well as `colSpan`, `rowSpan`, `desiredHeight` and `desiredWidth` properties. + * @param options + * @constructor + */ + constructor(options) { + this.setOptions(options); + + /** + * Each cell will have it's `x` and `y` values set by the `layout-manager` prior to + * `init` being called; + * @type {Number} + */ + this.x = null; + this.y = null; + } + + setOptions(options) { + if (['boolean', 'number', 'string'].indexOf(typeof options) !== -1) { + options = { content: '' + options }; + } + options = options || {}; + this.options = options; + let content = options.content; + if (['boolean', 'number', 'string'].indexOf(typeof content) !== -1) { + this.content = String(content); + } else if (!content) { + this.content = ''; + } else { + throw new Error('Content needs to be a primitive, got: ' + typeof content); + } + this.colSpan = options.colSpan || 1; + this.rowSpan = options.rowSpan || 1; + } + + mergeTableOptions(tableOptions, cells) { + this.cells = cells; + + let optionsChars = this.options.chars || {}; + let tableChars = tableOptions.chars; + let chars = (this.chars = {}); + CHAR_NAMES.forEach(function(name) { + setOption(optionsChars, tableChars, name, chars); + }); + + this.truncate = this.options.truncate || tableOptions.truncate; + + let style = (this.options.style = this.options.style || {}); + let tableStyle = tableOptions.style; + setOption(style, tableStyle, 'padding-left', this); + setOption(style, tableStyle, 'padding-right', this); + this.head = style.head || tableStyle.head; + this.border = style.border || tableStyle.border; + + let fixedWidth = tableOptions.colWidths[this.x]; + if (tableOptions.wordWrap && fixedWidth) { + fixedWidth -= this.paddingLeft + this.paddingRight; + if (this.colSpan) { + let i = 1; + while (i < this.colSpan) { + fixedWidth += tableOptions.colWidths[this.x + i]; + i++; + } + } + this.lines = utils.colorizeLines(utils.wordWrap(fixedWidth, this.content)); + } else { + this.lines = utils.colorizeLines(this.content.split('\n')); + } + + this.desiredWidth = utils.strlen(this.content) + this.paddingLeft + this.paddingRight; + this.desiredHeight = this.lines.length; + } + + /** + * Initializes the Cells data structure. + * + * @param tableOptions - A fully populated set of tableOptions. + * In addition to the standard default values, tableOptions must have fully populated the + * `colWidths` and `rowWidths` arrays. Those arrays must have lengths equal to the number + * of columns or rows (respectively) in this table, and each array item must be a Number. + * + */ + init(tableOptions) { + let x = this.x; + let y = this.y; + this.widths = tableOptions.colWidths.slice(x, x + this.colSpan); + this.heights = tableOptions.rowHeights.slice(y, y + this.rowSpan); + this.width = this.widths.reduce(sumPlusOne, -1); + this.height = this.heights.reduce(sumPlusOne, -1); + + this.hAlign = this.options.hAlign || tableOptions.colAligns[x]; + this.vAlign = this.options.vAlign || tableOptions.rowAligns[y]; + + this.drawRight = x + this.colSpan == tableOptions.colWidths.length; + } + + /** + * Draws the given line of the cell. + * This default implementation defers to methods `drawTop`, `drawBottom`, `drawLine` and `drawEmpty`. + * @param lineNum - can be `top`, `bottom` or a numerical line number. + * @param spanningCell - will be a number if being called from a RowSpanCell, and will represent how + * many rows below it's being called from. Otherwise it's undefined. + * @returns {String} The representation of this line. + */ + draw(lineNum, spanningCell) { + if (lineNum == 'top') return this.drawTop(this.drawRight); + if (lineNum == 'bottom') return this.drawBottom(this.drawRight); + let padLen = Math.max(this.height - this.lines.length, 0); + let padTop; + switch (this.vAlign) { + case 'center': + padTop = Math.ceil(padLen / 2); + break; + case 'bottom': + padTop = padLen; + break; + default: + padTop = 0; + } + if (lineNum < padTop || lineNum >= padTop + this.lines.length) { + return this.drawEmpty(this.drawRight, spanningCell); + } + let forceTruncation = this.lines.length > this.height && lineNum + 1 >= this.height; + return this.drawLine(lineNum - padTop, this.drawRight, forceTruncation, spanningCell); + } + + /** + * Renders the top line of the cell. + * @param drawRight - true if this method should render the right edge of the cell. + * @returns {String} + */ + drawTop(drawRight) { + let content = []; + if (this.cells) { + //TODO: cells should always exist - some tests don't fill it in though + this.widths.forEach(function(width, index) { + content.push(this._topLeftChar(index)); + content.push(utils.repeat(this.chars[this.y == 0 ? 'top' : 'mid'], width)); + }, this); + } else { + content.push(this._topLeftChar(0)); + content.push(utils.repeat(this.chars[this.y == 0 ? 'top' : 'mid'], this.width)); + } + if (drawRight) { + content.push(this.chars[this.y == 0 ? 'topRight' : 'rightMid']); + } + return this.wrapWithStyleColors('border', content.join('')); + } + + _topLeftChar(offset) { + let x = this.x + offset; + let leftChar; + if (this.y == 0) { + leftChar = x == 0 ? 'topLeft' : offset == 0 ? 'topMid' : 'top'; + } else { + if (x == 0) { + leftChar = 'leftMid'; + } else { + leftChar = offset == 0 ? 'midMid' : 'bottomMid'; + if (this.cells) { + //TODO: cells should always exist - some tests don't fill it in though + let spanAbove = this.cells[this.y - 1][x] instanceof Cell.ColSpanCell; + if (spanAbove) { + leftChar = offset == 0 ? 'topMid' : 'mid'; + } + if (offset == 0) { + let i = 1; + while (this.cells[this.y][x - i] instanceof Cell.ColSpanCell) { + i++; + } + if (this.cells[this.y][x - i] instanceof Cell.RowSpanCell) { + leftChar = 'leftMid'; + } + } + } + } + } + return this.chars[leftChar]; + } + + wrapWithStyleColors(styleProperty, content) { + if (this[styleProperty] && this[styleProperty].length) { + try { + let colors = require('colors/safe'); + for (let i = this[styleProperty].length - 1; i >= 0; i--) { + colors = colors[this[styleProperty][i]]; + } + return colors(content); + } catch (e) { + return content; + } + } else { + return content; + } + } + + /** + * Renders a line of text. + * @param lineNum - Which line of text to render. This is not necessarily the line within the cell. + * There may be top-padding above the first line of text. + * @param drawRight - true if this method should render the right edge of the cell. + * @param forceTruncationSymbol - `true` if the rendered text should end with the truncation symbol even + * if the text fits. This is used when the cell is vertically truncated. If `false` the text should + * only include the truncation symbol if the text will not fit horizontally within the cell width. + * @param spanningCell - a number of if being called from a RowSpanCell. (how many rows below). otherwise undefined. + * @returns {String} + */ + drawLine(lineNum, drawRight, forceTruncationSymbol, spanningCell) { + let left = this.chars[this.x == 0 ? 'left' : 'middle']; + if (this.x && spanningCell && this.cells) { + let cellLeft = this.cells[this.y + spanningCell][this.x - 1]; + while (cellLeft instanceof ColSpanCell) { + cellLeft = this.cells[cellLeft.y][cellLeft.x - 1]; + } + if (!(cellLeft instanceof RowSpanCell)) { + left = this.chars['rightMid']; + } + } + let leftPadding = utils.repeat(' ', this.paddingLeft); + let right = drawRight ? this.chars['right'] : ''; + let rightPadding = utils.repeat(' ', this.paddingRight); + let line = this.lines[lineNum]; + let len = this.width - (this.paddingLeft + this.paddingRight); + if (forceTruncationSymbol) line += this.truncate || '…'; + let content = utils.truncate(line, len, this.truncate); + content = utils.pad(content, len, ' ', this.hAlign); + content = leftPadding + content + rightPadding; + return this.stylizeLine(left, content, right); + } + + stylizeLine(left, content, right) { + left = this.wrapWithStyleColors('border', left); + right = this.wrapWithStyleColors('border', right); + if (this.y === 0) { + content = this.wrapWithStyleColors('head', content); + } + return left + content + right; + } + + /** + * Renders the bottom line of the cell. + * @param drawRight - true if this method should render the right edge of the cell. + * @returns {String} + */ + drawBottom(drawRight) { + let left = this.chars[this.x == 0 ? 'bottomLeft' : 'bottomMid']; + let content = utils.repeat(this.chars.bottom, this.width); + let right = drawRight ? this.chars['bottomRight'] : ''; + return this.wrapWithStyleColors('border', left + content + right); + } + + /** + * Renders a blank line of text within the cell. Used for top and/or bottom padding. + * @param drawRight - true if this method should render the right edge of the cell. + * @param spanningCell - a number of if being called from a RowSpanCell. (how many rows below). otherwise undefined. + * @returns {String} + */ + drawEmpty(drawRight, spanningCell) { + let left = this.chars[this.x == 0 ? 'left' : 'middle']; + if (this.x && spanningCell && this.cells) { + let cellLeft = this.cells[this.y + spanningCell][this.x - 1]; + while (cellLeft instanceof ColSpanCell) { + cellLeft = this.cells[cellLeft.y][cellLeft.x - 1]; + } + if (!(cellLeft instanceof RowSpanCell)) { + left = this.chars['rightMid']; + } + } + let right = drawRight ? this.chars['right'] : ''; + let content = utils.repeat(' ', this.width); + return this.stylizeLine(left, content, right); + } +} + +class ColSpanCell { + /** + * A Cell that doesn't do anything. It just draws empty lines. + * Used as a placeholder in column spanning. + * @constructor + */ + constructor() {} + + draw() { + return ''; + } + + init() {} + + mergeTableOptions() {} +} + +class RowSpanCell { + /** + * A placeholder Cell for a Cell that spans multiple rows. + * It delegates rendering to the original cell, but adds the appropriate offset. + * @param originalCell + * @constructor + */ + constructor(originalCell) { + this.originalCell = originalCell; + } + + init(tableOptions) { + let y = this.y; + let originalY = this.originalCell.y; + this.cellOffset = y - originalY; + this.offset = findDimension(tableOptions.rowHeights, originalY, this.cellOffset); + } + + draw(lineNum) { + if (lineNum == 'top') { + return this.originalCell.draw(this.offset, this.cellOffset); + } + if (lineNum == 'bottom') { + return this.originalCell.draw('bottom'); + } + return this.originalCell.draw(this.offset + 1 + lineNum); + } + + mergeTableOptions() {} +} + +// HELPER FUNCTIONS +function setOption(objA, objB, nameB, targetObj) { + let nameA = nameB.split('-'); + if (nameA.length > 1) { + nameA[1] = nameA[1].charAt(0).toUpperCase() + nameA[1].substr(1); + nameA = nameA.join(''); + targetObj[nameA] = objA[nameA] || objA[nameB] || objB[nameA] || objB[nameB]; + } else { + targetObj[nameB] = objA[nameB] || objB[nameB]; + } +} + +function findDimension(dimensionTable, startingIndex, span) { + let ret = dimensionTable[startingIndex]; + for (let i = 1; i < span; i++) { + ret += 1 + dimensionTable[startingIndex + i]; + } + return ret; +} + +function sumPlusOne(a, b) { + return a + b + 1; +} + +let CHAR_NAMES = [ + 'top', + 'top-mid', + 'top-left', + 'top-right', + 'bottom', + 'bottom-mid', + 'bottom-left', + 'bottom-right', + 'left', + 'left-mid', + 'mid', + 'mid-mid', + 'right', + 'right-mid', + 'middle', +]; +module.exports = Cell; +module.exports.ColSpanCell = ColSpanCell; +module.exports.RowSpanCell = RowSpanCell; diff --git a/deps/npm/node_modules/cli-table3/src/layout-manager.js b/deps/npm/node_modules/cli-table3/src/layout-manager.js new file mode 100644 index 0000000000..908040e7e5 --- /dev/null +++ b/deps/npm/node_modules/cli-table3/src/layout-manager.js @@ -0,0 +1,232 @@ +const objectAssign = require('object-assign'); +const Cell = require('./cell'); +const { ColSpanCell, RowSpanCell } = Cell; + +(function() { + function layoutTable(table) { + table.forEach(function(row, rowIndex) { + row.forEach(function(cell, columnIndex) { + cell.y = rowIndex; + cell.x = columnIndex; + for (let y = rowIndex; y >= 0; y--) { + let row2 = table[y]; + let xMax = y === rowIndex ? columnIndex : row2.length; + for (let x = 0; x < xMax; x++) { + let cell2 = row2[x]; + while (cellsConflict(cell, cell2)) { + cell.x++; + } + } + } + }); + }); + } + + function maxWidth(table) { + let mw = 0; + table.forEach(function(row) { + row.forEach(function(cell) { + mw = Math.max(mw, cell.x + (cell.colSpan || 1)); + }); + }); + return mw; + } + + function maxHeight(table) { + return table.length; + } + + function cellsConflict(cell1, cell2) { + let yMin1 = cell1.y; + let yMax1 = cell1.y - 1 + (cell1.rowSpan || 1); + let yMin2 = cell2.y; + let yMax2 = cell2.y - 1 + (cell2.rowSpan || 1); + let yConflict = !(yMin1 > yMax2 || yMin2 > yMax1); + + let xMin1 = cell1.x; + let xMax1 = cell1.x - 1 + (cell1.colSpan || 1); + let xMin2 = cell2.x; + let xMax2 = cell2.x - 1 + (cell2.colSpan || 1); + let xConflict = !(xMin1 > xMax2 || xMin2 > xMax1); + + return yConflict && xConflict; + } + + function conflictExists(rows, x, y) { + let i_max = Math.min(rows.length - 1, y); + let cell = { x: x, y: y }; + for (let i = 0; i <= i_max; i++) { + let row = rows[i]; + for (let j = 0; j < row.length; j++) { + if (cellsConflict(cell, row[j])) { + return true; + } + } + } + return false; + } + + function allBlank(rows, y, xMin, xMax) { + for (let x = xMin; x < xMax; x++) { + if (conflictExists(rows, x, y)) { + return false; + } + } + return true; + } + + function addRowSpanCells(table) { + table.forEach(function(row, rowIndex) { + row.forEach(function(cell) { + for (let i = 1; i < cell.rowSpan; i++) { + let rowSpanCell = new RowSpanCell(cell); + rowSpanCell.x = cell.x; + rowSpanCell.y = cell.y + i; + rowSpanCell.colSpan = cell.colSpan; + insertCell(rowSpanCell, table[rowIndex + i]); + } + }); + }); + } + + function addColSpanCells(cellRows) { + for (let rowIndex = cellRows.length - 1; rowIndex >= 0; rowIndex--) { + let cellColumns = cellRows[rowIndex]; + for (let columnIndex = 0; columnIndex < cellColumns.length; columnIndex++) { + let cell = cellColumns[columnIndex]; + for (let k = 1; k < cell.colSpan; k++) { + let colSpanCell = new ColSpanCell(); + colSpanCell.x = cell.x + k; + colSpanCell.y = cell.y; + cellColumns.splice(columnIndex + 1, 0, colSpanCell); + } + } + } + } + + function insertCell(cell, row) { + let x = 0; + while (x < row.length && row[x].x < cell.x) { + x++; + } + row.splice(x, 0, cell); + } + + function fillInTable(table) { + let h_max = maxHeight(table); + let w_max = maxWidth(table); + for (let y = 0; y < h_max; y++) { + for (let x = 0; x < w_max; x++) { + if (!conflictExists(table, x, y)) { + let opts = { x: x, y: y, colSpan: 1, rowSpan: 1 }; + x++; + while (x < w_max && !conflictExists(table, x, y)) { + opts.colSpan++; + x++; + } + let y2 = y + 1; + while (y2 < h_max && allBlank(table, y2, opts.x, opts.x + opts.colSpan)) { + opts.rowSpan++; + y2++; + } + + let cell = new Cell(opts); + cell.x = opts.x; + cell.y = opts.y; + insertCell(cell, table[y]); + } + } + } + } + + function generateCells(rows) { + return rows.map(function(row) { + if (!Array.isArray(row)) { + let key = Object.keys(row)[0]; + row = row[key]; + if (Array.isArray(row)) { + row = row.slice(); + row.unshift(key); + } else { + row = [key, row]; + } + } + return row.map(function(cell) { + return new Cell(cell); + }); + }); + } + + function makeTableLayout(rows) { + let cellRows = generateCells(rows); + layoutTable(cellRows); + fillInTable(cellRows); + addRowSpanCells(cellRows); + addColSpanCells(cellRows); + return cellRows; + } + + module.exports = { + makeTableLayout: makeTableLayout, + layoutTable: layoutTable, + addRowSpanCells: addRowSpanCells, + maxWidth: maxWidth, + fillInTable: fillInTable, + computeWidths: makeComputeWidths('colSpan', 'desiredWidth', 'x', 1), + computeHeights: makeComputeWidths('rowSpan', 'desiredHeight', 'y', 1), + }; +})(); + +function makeComputeWidths(colSpan, desiredWidth, x, forcedMin) { + return function(vals, table) { + let result = []; + let spanners = []; + table.forEach(function(row) { + row.forEach(function(cell) { + if ((cell[colSpan] || 1) > 1) { + spanners.push(cell); + } else { + result[cell[x]] = Math.max(result[cell[x]] || 0, cell[desiredWidth] || 0, forcedMin); + } + }); + }); + + vals.forEach(function(val, index) { + if (typeof val === 'number') { + result[index] = val; + } + }); + + //spanners.forEach(function(cell){ + for (let k = spanners.length - 1; k >= 0; k--) { + let cell = spanners[k]; + let span = cell[colSpan]; + let col = cell[x]; + let existingWidth = result[col]; + let editableCols = typeof vals[col] === 'number' ? 0 : 1; + for (let i = 1; i < span; i++) { + existingWidth += 1 + result[col + i]; + if (typeof vals[col + i] !== 'number') { + editableCols++; + } + } + if (cell[desiredWidth] > existingWidth) { + let i = 0; + while (editableCols > 0 && cell[desiredWidth] > existingWidth) { + if (typeof vals[col + i] !== 'number') { + let dif = Math.round((cell[desiredWidth] - existingWidth) / editableCols); + existingWidth += dif; + result[col + i] += dif; + editableCols--; + } + i++; + } + } + } + + objectAssign(vals, result); + for (let j = 0; j < vals.length; j++) { + vals[j] = Math.max(forcedMin, vals[j] || 0); + } + }; +} diff --git a/deps/npm/node_modules/cli-table3/src/table.js b/deps/npm/node_modules/cli-table3/src/table.js new file mode 100644 index 0000000000..af4f146463 --- /dev/null +++ b/deps/npm/node_modules/cli-table3/src/table.js @@ -0,0 +1,77 @@ +const utils = require('./utils'); +const tableLayout = require('./layout-manager'); + +class Table extends Array { + constructor(options) { + super(); + + this.options = utils.mergeOptions(options); + } + + toString() { + let array = this; + let headersPresent = this.options.head && this.options.head.length; + if (headersPresent) { + array = [this.options.head]; + if (this.length) { + array.push.apply(array, this); + } + } else { + this.options.style.head = []; + } + + let cells = tableLayout.makeTableLayout(array); + + cells.forEach(function(row) { + row.forEach(function(cell) { + cell.mergeTableOptions(this.options, cells); + }, this); + }, this); + + tableLayout.computeWidths(this.options.colWidths, cells); + tableLayout.computeHeights(this.options.rowHeights, cells); + + cells.forEach(function(row) { + row.forEach(function(cell) { + cell.init(this.options); + }, this); + }, this); + + let result = []; + + for (let rowIndex = 0; rowIndex < cells.length; rowIndex++) { + let row = cells[rowIndex]; + let heightOfRow = this.options.rowHeights[rowIndex]; + + if (rowIndex === 0 || !this.options.style.compact || (rowIndex == 1 && headersPresent)) { + doDraw(row, 'top', result); + } + + for (let lineNum = 0; lineNum < heightOfRow; lineNum++) { + doDraw(row, lineNum, result); + } + + if (rowIndex + 1 == cells.length) { + doDraw(row, 'bottom', result); + } + } + + return result.join('\n'); + } + + get width() { + let str = this.toString().split('\n'); + return str[0].length; + } +} + +function doDraw(row, lineNum, result) { + let line = []; + row.forEach(function(cell) { + line.push(cell.draw(lineNum)); + }); + let str = line.join(''); + if (str.length) result.push(str); +} + +module.exports = Table; diff --git a/deps/npm/node_modules/cli-table3/src/utils.js b/deps/npm/node_modules/cli-table3/src/utils.js new file mode 100644 index 0000000000..c9149558e2 --- /dev/null +++ b/deps/npm/node_modules/cli-table3/src/utils.js @@ -0,0 +1,303 @@ +const objectAssign = require('object-assign'); +const stringWidth = require('string-width'); + +function codeRegex(capture) { + return capture ? /\u001b\[((?:\d*;){0,5}\d*)m/g : /\u001b\[(?:\d*;){0,5}\d*m/g; +} + +function strlen(str) { + let code = codeRegex(); + let stripped = ('' + str).replace(code, ''); + let split = stripped.split('\n'); + return split.reduce(function(memo, s) { + return stringWidth(s) > memo ? stringWidth(s) : memo; + }, 0); +} + +function repeat(str, times) { + return Array(times + 1).join(str); +} + +function pad(str, len, pad, dir) { + let length = strlen(str); + if (len + 1 >= length) { + let padlen = len - length; + switch (dir) { + case 'right': { + str = repeat(pad, padlen) + str; + break; + } + case 'center': { + let right = Math.ceil(padlen / 2); + let left = padlen - right; + str = repeat(pad, left) + str + repeat(pad, right); + break; + } + default: { + str = str + repeat(pad, padlen); + break; + } + } + } + return str; +} + +let codeCache = {}; + +function addToCodeCache(name, on, off) { + on = '\u001b[' + on + 'm'; + off = '\u001b[' + off + 'm'; + codeCache[on] = { set: name, to: true }; + codeCache[off] = { set: name, to: false }; + codeCache[name] = { on: on, off: off }; +} + +//https://github.com/Marak/colors.js/blob/master/lib/styles.js +addToCodeCache('bold', 1, 22); +addToCodeCache('italics', 3, 23); +addToCodeCache('underline', 4, 24); +addToCodeCache('inverse', 7, 27); +addToCodeCache('strikethrough', 9, 29); + +function updateState(state, controlChars) { + let controlCode = controlChars[1] ? parseInt(controlChars[1].split(';')[0]) : 0; + if ((controlCode >= 30 && controlCode <= 39) || (controlCode >= 90 && controlCode <= 97)) { + state.lastForegroundAdded = controlChars[0]; + return; + } + if ((controlCode >= 40 && controlCode <= 49) || (controlCode >= 100 && controlCode <= 107)) { + state.lastBackgroundAdded = controlChars[0]; + return; + } + if (controlCode === 0) { + for (let i in state) { + /* istanbul ignore else */ + if (state.hasOwnProperty(i)) { + delete state[i]; + } + } + return; + } + let info = codeCache[controlChars[0]]; + if (info) { + state[info.set] = info.to; + } +} + +function readState(line) { + let code = codeRegex(true); + let controlChars = code.exec(line); + let state = {}; + while (controlChars !== null) { + updateState(state, controlChars); + controlChars = code.exec(line); + } + return state; +} + +function unwindState(state, ret) { + let lastBackgroundAdded = state.lastBackgroundAdded; + let lastForegroundAdded = state.lastForegroundAdded; + + delete state.lastBackgroundAdded; + delete state.lastForegroundAdded; + + Object.keys(state).forEach(function(key) { + if (state[key]) { + ret += codeCache[key].off; + } + }); + + if (lastBackgroundAdded && lastBackgroundAdded != '\u001b[49m') { + ret += '\u001b[49m'; + } + if (lastForegroundAdded && lastForegroundAdded != '\u001b[39m') { + ret += '\u001b[39m'; + } + + return ret; +} + +function rewindState(state, ret) { + let lastBackgroundAdded = state.lastBackgroundAdded; + let lastForegroundAdded = state.lastForegroundAdded; + + delete state.lastBackgroundAdded; + delete state.lastForegroundAdded; + + Object.keys(state).forEach(function(key) { + if (state[key]) { + ret = codeCache[key].on + ret; + } + }); + + if (lastBackgroundAdded && lastBackgroundAdded != '\u001b[49m') { + ret = lastBackgroundAdded + ret; + } + if (lastForegroundAdded && lastForegroundAdded != '\u001b[39m') { + ret = lastForegroundAdded + ret; + } + + return ret; +} + +function truncateWidth(str, desiredLength) { + if (str.length === strlen(str)) { + return str.substr(0, desiredLength); + } + + while (strlen(str) > desiredLength) { + str = str.slice(0, -1); + } + + return str; +} + +function truncateWidthWithAnsi(str, desiredLength) { + let code = codeRegex(true); + let split = str.split(codeRegex()); + let splitIndex = 0; + let retLen = 0; + let ret = ''; + let myArray; + let state = {}; + + while (retLen < desiredLength) { + myArray = code.exec(str); + let toAdd = split[splitIndex]; + splitIndex++; + if (retLen + strlen(toAdd) > desiredLength) { + toAdd = truncateWidth(toAdd, desiredLength - retLen); + } + ret += toAdd; + retLen += strlen(toAdd); + + if (retLen < desiredLength) { + if (!myArray) { + break; + } // full-width chars may cause a whitespace which cannot be filled + ret += myArray[0]; + updateState(state, myArray); + } + } + + return unwindState(state, ret); +} + +function truncate(str, desiredLength, truncateChar) { + truncateChar = truncateChar || '…'; + let lengthOfStr = strlen(str); + if (lengthOfStr <= desiredLength) { + return str; + } + desiredLength -= strlen(truncateChar); + + let ret = truncateWidthWithAnsi(str, desiredLength); + + return ret + truncateChar; +} + +function defaultOptions() { + return { + chars: { + top: '─', + 'top-mid': '┬', + 'top-left': '┌', + 'top-right': '┐', + bottom: '─', + 'bottom-mid': '┴', + 'bottom-left': '└', + 'bottom-right': '┘', + left: '│', + 'left-mid': '├', + mid: '─', + 'mid-mid': '┼', + right: '│', + 'right-mid': '┤', + middle: '│', + }, + truncate: '…', + colWidths: [], + rowHeights: [], + colAligns: [], + rowAligns: [], + style: { + 'padding-left': 1, + 'padding-right': 1, + head: ['red'], + border: ['grey'], + compact: false, + }, + head: [], + }; +} + +function mergeOptions(options, defaults) { + options = options || {}; + defaults = defaults || defaultOptions(); + let ret = objectAssign({}, defaults, options); + ret.chars = objectAssign({}, defaults.chars, options.chars); + ret.style = objectAssign({}, defaults.style, options.style); + return ret; +} + +function wordWrap(maxLength, input) { + let lines = []; + let split = input.split(/(\s+)/g); + let line = []; + let lineLength = 0; + let whitespace; + for (let i = 0; i < split.length; i += 2) { + let word = split[i]; + let newLength = lineLength + strlen(word); + if (lineLength > 0 && whitespace) { + newLength += whitespace.length; + } + if (newLength > maxLength) { + if (lineLength !== 0) { + lines.push(line.join('')); + } + line = [word]; + lineLength = strlen(word); + } else { + line.push(whitespace || '', word); + lineLength = newLength; + } + whitespace = split[i + 1]; + } + if (lineLength) { + lines.push(line.join('')); + } + return lines; +} + +function multiLineWordWrap(maxLength, input) { + let output = []; + input = input.split('\n'); + for (let i = 0; i < input.length; i++) { + output.push.apply(output, wordWrap(maxLength, input[i])); + } + return output; +} + +function colorizeLines(input) { + let state = {}; + let output = []; + for (let i = 0; i < input.length; i++) { + let line = rewindState(state, input[i]); + state = readState(line); + let temp = objectAssign({}, state); + output.push(unwindState(temp, line)); + } + return output; +} + +module.exports = { + strlen: strlen, + repeat: repeat, + pad: pad, + truncate: truncate, + mergeOptions: mergeOptions, + wordWrap: multiLineWordWrap, + colorizeLines: colorizeLines, +}; |