symfony-style-console
Version:
Use the style and utilities of the Symfony Console in Node.js
553 lines (552 loc) • 19.1 kB
JavaScript
;
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;