UNPKG

symfony-style-console

Version:

Use the style and utilities of the Symfony Console in Node.js

553 lines (552 loc) 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.time = exports.wordwrap = exports.safeReplace = exports.trimEnd = exports.stripTags = exports.chunkString = exports.arrayFill = exports.arrayReplaceRecursive = exports.strPad = exports.formatTime = exports.arrContains = exports.flipObject = exports.lengthWithoutDecoration = exports.removeDecoration = exports.formatMemory = exports.sprintf = exports.range = exports.countOccurences = void 0; /** * Count occurrences of `needle` in `haystack`. Doesn't count overlapped substrings. * * @param haystack The string to search in * @param needle The substring to search for * @param offset The offset where to start counting. If the offset is negative, counting starts from the end of the string. * @param length The maximum length after the specified offset to search for the substring. A negative length counts from the end of haystack. */ function countOccurences(haystack, needle, offset, length) { if (offset === void 0) { offset = 0; } if (length === void 0) { length = 0; } var count = 0; if (!needle.length) { return false; } offset--; while ((offset = haystack.indexOf(needle, offset + 1)) !== -1) { if (length > 0 && offset + needle.length > length) { return false; } count++; } return count; } exports.countOccurences = countOccurences; /** * Generates an array containing the integer values starting with `from` and ending with `to`. * * @param from The lower bound * @param to The upper bound */ function range(from, to) { from = Math.round(from); to = Math.round(to); var arr = []; if (from < to) { for (var i = from; i <= to; i++) arr.push(i); } else { for (var i = to; i >= from; i--) arr.push(i); } return arr; } exports.range = range; /** * Formats a string. * * This is a port of PHP's [`sprintf`](https://secure.php.net/manual/de/function.sprintf.php) function. * * @param format The formatting template * @param args The values to merge into the template */ function sprintf(format) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } var regex = /%%|%(\d+\$)?([-+'#0 ]*)(\*\d+\$|\*|\d+)?(?:\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g; var a = args.map(function (arg) { return String(arg); }); var i = 0; var _pad = function (str, len, chr, leftJustify) { if (chr === void 0) { chr = ' '; } var padding = str.length >= len ? '' : new Array((1 + len - str.length) >>> 0).join(chr); return leftJustify ? str + padding : padding + str; }; var justify = function (value, prefix, leftJustify, minWidth, zeroPad, customPadChar) { var diff = minWidth - value.length; if (diff > 0) { if (leftJustify || !zeroPad) { value = _pad(value, minWidth, customPadChar, leftJustify); } else { value = [ value.slice(0, prefix.length), _pad('', diff, '0', true), value.slice(prefix.length) ].join(''); } } return value; }; var prefixValues = { '2': '0b', '8': '0', '16': '0x' }; var _formatBaseX = function (value, base, prefix, leftJustify, minWidth, precision, zeroPad) { // Note: casts negative numbers to positive ones value = +value; var number = value >>> 0; prefix = (prefix && number && prefixValues[String(base)]) || ''; var strValue = prefix + _pad(number.toString(+base), precision || 0, '0', false); return justify(strValue, prefix, leftJustify, minWidth, zeroPad); }; // _formatString() var _formatString = function (value, leftJustify, minWidth, precision, zeroPad, customPadChar) { if (precision !== null && precision !== undefined) { value = value.slice(0, precision); } return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar); }; // doFormat() // RegEx replacer var doFormat = function (substring, valueIndex, flags, minWidth, precision, type) { var number, prefix, method, textTransform, value; if (substring === '%%') { return '%'; } // parse flags var leftJustify = false; var positivePrefix = ''; var zeroPad = false; var prefixBaseX = false; var customPadChar = ' '; var flagsl = flags.length; var j; for (j = 0; j < flagsl; j++) { switch (flags.charAt(j)) { case ' ': positivePrefix = ' '; break; case '+': positivePrefix = '+'; break; case '-': leftJustify = true; break; case "'": customPadChar = flags.charAt(j + 1); break; case '0': zeroPad = true; customPadChar = '0'; break; case '#': prefixBaseX = true; break; } } // parameters may be null, undefined, empty-string or real valued // we want to ignore null, undefined and empty-string values if (typeof minWidth === 'number') { } else if (!minWidth) { minWidth = 0; } else if (minWidth === '*') { minWidth = +a[i++]; } else if (minWidth.charAt(0) === '*') { minWidth = +a[+minWidth.slice(1, -1)]; } else { minWidth = +minWidth; } // Note: undocumented perl feature: if (minWidth < 0) { minWidth = -minWidth; leftJustify = true; } if (!isFinite(minWidth)) { throw new Error('sprintf: (minimum-)width must be finite'); } if (typeof precision === 'number') { } else if (!precision) { precision = 'fFeE'.indexOf(type) > -1 ? 6 : type === 'd' ? 0 : undefined; } else if (precision === '*') { precision = +a[i++]; } else if (precision.charAt(0) === '*') { precision = +a[+precision.slice(1, -1)]; } else { precision = +precision; } // grab value using valueIndex if required? value = valueIndex ? a[+String(valueIndex).slice(0, -1)] : a[i++]; switch (type) { case 's': return _formatString(value, leftJustify, minWidth, precision, zeroPad, customPadChar); case 'c': return _formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad); case 'b': return _formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad); case 'o': return _formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad); case 'x': return _formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad); case 'X': return _formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase(); case 'u': return _formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad); case 'i': case 'd': number = +value || 0; // Plain Math.round doesn't just truncate number = Math.round(number - (number % 1)); prefix = number < 0 ? '-' : positivePrefix; value = prefix + _pad(String(Math.abs(number)), precision, '0', false); return justify(value, prefix, leftJustify, minWidth, zeroPad); case 'e': case 'E': case 'f': // @todo: Should handle locales (as per setlocale) case 'F': case 'g': case 'G': number = +value; prefix = number < 0 ? '-' : positivePrefix; var methods = [ 'toExponential', 'toFixed', 'toPrecision' ]; method = methods['efg'.indexOf(type.toLowerCase())]; var transforms = ['toString', 'toUpperCase']; textTransform = transforms['eEfFgG'.indexOf(type) % 2]; value = prefix + Math.abs(number)[method](precision); return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform](); default: return substring; } }; return format.replace(regex, doFormat); } exports.sprintf = sprintf; /** * Creates a human-friendly representation of a number of bytes. * * @param memory The number of bytes to format */ function formatMemory(memory) { if (memory >= 1024 * 1024 * 1024) { return sprintf('%.1f GiB', memory / 1024 / 1024 / 1024); } if (memory >= 1024 * 1024) { return sprintf('%.1f MiB', memory / 1024 / 1024); } if (memory >= 1024) { return sprintf('%d KiB', memory / 1024); } return sprintf('%d B', memory); } exports.formatMemory = formatMemory; /** * Removes `<...>` formatting and ANSI escape sequences from a string. * * @param formatter The formatter instance in charge to resolve `<...>` formatting * @param str The string to perform the removing on */ function removeDecoration(formatter, str) { var isDecorated = formatter.isDecorated(); formatter.setDecorated(false); // Resolve <...> formatting str = formatter.format(str); // Remove ANSI-formatted characters str = str.replace(/\033\[[^m]*m/g, ''); formatter.setDecorated(isDecorated); return str; } exports.removeDecoration = removeDecoration; /** * Get the length of a string ignoring `<...>` formatting and ANSI escape sequences. * * @param formatter The formatter instance in charge to resolve `<...>` formatting * @param str The string whose length to determine */ function lengthWithoutDecoration(formatter, str) { return removeDecoration(formatter, str).length; } exports.lengthWithoutDecoration = lengthWithoutDecoration; /** * Creates an object literal with key/value pairs of the given `obj` swapped. * * @param obj The object whose keys and values to use */ function flipObject(obj) { var flipped = Object.create(null); for (var key in obj) flipped[obj[key]] = key; return flipped; } exports.flipObject = flipObject; /** * Checks if `item` is contained by `arr`. * * @param arr The array to search in * @param item The item to search for */ function arrContains(arr, item) { if (Array.prototype.includes) { return arr.includes(item); } else { return arr.indexOf(item) !== -1; } } exports.arrContains = arrContains; /** * Creates a human-friendly representation of a number of seconds. * * @param secs The number of seconds to format */ function formatTime(secs) { var timeFormats = [ [0, '< 1 sec'], [1, '1 sec'], [2, 'secs', 1], [60, '1 min'], [120, 'mins', 60], [3600, '1 hr'], [7200, 'hrs', 3600], [86400, '1 day'], [172800, 'days', 86400] ]; for (var index = 0; index < timeFormats.length; index++) { var format = timeFormats[index]; if (secs >= format[0]) { if ((timeFormats[index + 1] && secs < timeFormats[index + 1][0]) || index == timeFormats.length - 1) { if (2 == format.length) { return format[1]; } return Math.floor(secs / format[2]) + " " + format[1]; } } } } exports.formatTime = formatTime; /** * Pads a string to a certain length with another string. * * @param input The string to pad * @param padLength The desired length of the resulting string * @param padString The string to use as padding material * @param padType Where to pad the string */ function strPad(input, padLength, padString, padType) { if (padType === void 0) { padType = 'STR_PAD_RIGHT'; } var half = ''; var padToGo; var _strPadRepeater = function (s, len) { var collect = ''; while (collect.length < len) { collect += s; } collect = collect.substr(0, len); return collect; }; input += ''; padString = padString !== undefined ? padString : ' '; if (padType !== 'STR_PAD_LEFT' && padType !== 'STR_PAD_RIGHT' && padType !== 'STR_PAD_BOTH') { padType = 'STR_PAD_RIGHT'; } if ((padToGo = padLength - input.length) > 0) { if (padType === 'STR_PAD_LEFT') { input = _strPadRepeater(padString, padToGo) + input; } else if (padType === 'STR_PAD_RIGHT') { input = input + _strPadRepeater(padString, padToGo); } else if (padType === 'STR_PAD_BOTH') { half = _strPadRepeater(padString, Math.ceil(padToGo / 2)); input = half + input + half; input = input.substr(0, padLength); } } return input; } exports.strPad = strPad; /** * Replaces elements from passed arrays into the first array recursively. (non-destructive) * * @param arr The array to patch * @param args The arrays to merge into `arr` */ function arrayReplaceRecursive(arr) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } var i = 0; var p = ''; if (!args.length) { throw new Error('There should be at least 2 arguments passed to array_replace_recursive()'); } // Although docs state that the arguments are passed in by reference, // it seems they are not altered, but rather the copy that is returned // So we make a copy here, instead of acting on arr itself var retArr = []; for (p in arr) { if (typeof arr[p] === 'undefined') continue; retArr[+p] = arr[p]; } for (i = 0; i < args.length; i++) { for (p in args[i]) { if (retArr[+p] && typeof retArr[+p] === 'object') { retArr[+p] = arrayReplaceRecursive(retArr[+p], arguments[i][+p]); } else { retArr[+p] = arguments[i][+p]; } } } return retArr; } exports.arrayReplaceRecursive = arrayReplaceRecursive; /** * Creates an array filled with a value. * * @param startIndex The index to start filling at. Previous values will be `undefined`. * @param num The number elements to insert * @param value The element to fill the array with */ function arrayFill(startIndex, num, value) { var key; var tmpArr = []; if (!isNaN(startIndex) && !isNaN(num)) { for (key = 0; key < num; key++) { tmpArr[key + startIndex] = value; } } return tmpArr; } exports.arrayFill = arrayFill; /** * Cuts a string into an array of chunks of given length. * * @param string The string to cut * @param splitLength The length of the resulting chunks */ function chunkString(string, splitLength) { if (splitLength === void 0) { splitLength = 1; } if (string === null || splitLength < 1) { return false; } string += ''; var chunks = []; var pos = 0; var len = string.length; while (pos < len) { chunks.push(string.slice(pos, (pos += splitLength))); } return chunks; } exports.chunkString = chunkString; /** * Strips HTML tags from a string. * * @param input The string to sanitize * @param allowed A string containing a set of allowed tags. Example: `<a><strong><em>` */ function stripTags(input, allowed) { if (allowed === void 0) { allowed = ''; } allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi; var comments = /<!--[\s\S]*?-->/gi; return input.replace(comments, '').replace(tags, function ($0, $1) { return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : ''; }); } exports.stripTags = stripTags; /** * Trims the end of a string. * * @param str The string to trim * @param charlist The characters to trim. Everything that can go into a regex character class is allowed. */ function trimEnd(str, charlist) { if (charlist === void 0) { charlist = ' \\s\u00A0'; } charlist = String(charlist).replace(/([[\]().?/*{}+$^:])/g, '\\$1'); var re = new RegExp('[' + charlist.replace(/\\/g, '\\\\') + ']+$', 'g'); return (str + '').replace(re, ''); } exports.trimEnd = trimEnd; /** * Non-recursively replaces a set of values inside a string. * * @param str The string to perform replacements on * @param replacePairs An object that maps strings to their respective replacements */ function safeReplace(str, replacePairs) { 'use strict'; str = String(str); var key, re; for (key in replacePairs) { if (replacePairs.hasOwnProperty(key)) { re = new RegExp(key, 'g'); str = str.replace(re, replacePairs[key]); } } return str; } exports.safeReplace = safeReplace; /** * Wraps a string to a given number of characters. * * Note that, as opposed to PHP's wordwrap, this always cuts words which are too long for one line. * * @param str The string to wrap * @param width The maximum length of a line * @param breakSequence The character(s) to use as line breaks */ function wordwrap(str, width, breakSequence) { if (width === void 0) { width = 75; } if (breakSequence === void 0) { breakSequence = '\n'; } var j, l, s, r; str += ''; if (width < 1) { return str; } var splitted = str.split(/\r\n|\n|\r/); for (var i = 0; i < splitted.length; i++) { var rest = splitted[i]; var partials = []; while (rest.length > width) { var line = rest.slice(0, width); if (rest.slice(width).match(/^\s/)) { rest = rest.slice(line.length + 1); } else if (line.match(/\s/)) { line = line.slice(0, line.match(/\s(?=[^\s]*$)/).index); rest = rest.slice(line.length + 1); } else { rest = rest.slice(line.length); } partials.push(line); } if (rest.length) partials.push(rest); splitted[i] = partials.join(breakSequence); } return splitted.join(breakSequence); } exports.wordwrap = wordwrap; /** * Returns the current timestamp in seconds. */ function time() { return Math.floor(Date.now() / 1000); } exports.time = time;