UNPKG

luhn-generator

Version:

A generator of numbers that passes the validation of Luhn algorithm or Luhn formula, also known as the 'modulus 10' or 'mod 10' algorithm

325 lines (267 loc) 8.28 kB
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 }) }