symfony-style-console
Version:
Use the style and utilities of the Symfony Console in Node.js
543 lines (542 loc) • 18.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var OutputInterface_1 = require("../Output/OutputInterface");
var Helper_1 = require("../Helper/Helper");
var ConsoleOutput_1 = require("../Output/ConsoleOutput");
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* Original PHP class
*
* @author Chris Jones <leeked@gmail.com>
*
* Original PHP class
*
* @author Florian Reuschel <florian@loilo.de>
*
* Port to TypeScript
*
*/
var ProgressBar = /** @class */ (function () {
/**
* Creates a new progress bar.
*
* @param OutputInterface output An OutputInterface instance
* @param int max Maximum steps (0 if unknown)
*/
function ProgressBar(output, max) {
if (max === void 0) { max = 0; }
/**
* The width of the progress bar.
*/
this.barWidth = 28;
/**
* The character that represents uncompleted progress.
*/
this.emptyBarChar = '-';
/**
* The character that represents the outer pointer of the completed progress.
*/
this.progressChar = '>';
/**
* The frequency of redrawing the bar (in steps).
*/
this.redrawFreq = 1;
/**
* The current step of the progress.
*/
this.step = 0;
/**
* The current progress in percent.
*/
this.percent = 0.0;
/**
* A Hash containing mapping format template placeholders to custom messages.
*/
this.messages = {};
/**
* If the progress should be rewritten to the same position.
*/
this.shouldOverwrite = true;
/**
* Indicator for the `overwrite` method if it's the first render cycle.
*/
this.firstRun = true;
if (output instanceof ConsoleOutput_1.default) {
output = output.getErrorOutput();
}
this.output = output;
this.setMaxSteps(max);
if (!this.output.isDecorated()) {
// disable overwrite when output does not support ANSI codes.
this.shouldOverwrite = false;
// set a reasonable redraw frequency so output isn't flooded
this.setRedrawFrequency(max / 10);
}
this.startTime = Helper_1.time();
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param name The format name
* @param format A format string
*/
ProgressBar.setFormatDefinition = function (name, format) {
if (!this.formats) {
this.formats = this.initFormats();
}
this.formats[name] = format;
};
/**
* Gets the format for a given name.
*
* @param name The format name
* @return A format string
*/
ProgressBar.getFormatDefinition = function (name) {
if (!this.formats) {
this.formats = this.initFormats();
}
return this.formats[name] || null;
};
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param name The placeholder name (including the delimiter char like %)
* @param callback A formatter callback
*/
ProgressBar.setPlaceholderFormatterDefinition = function (name, callable) {
if (!this.formatters) {
this.formatters = this.initPlaceholderFormatters();
}
this.formatters[name] = callable;
};
/**
* Gets the placeholder formatter for a given name.
*
* @param name The placeholder name (including the delimiter char like %)
* @return A formatter callback
*/
ProgressBar.getPlaceholderFormatterDefinition = function (name) {
if (!this.formatters) {
this.formatters = this.initPlaceholderFormatters();
}
return this.formatters[name] || null;
};
/**
* Gets the initially available format templates.
*/
ProgressBar.initFormats = function () {
return {
normal: ' %current%/%max% [%bar%] %percent:3s%%',
normal_nomax: ' %current% [%bar%]',
verbose: ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
verbose_nomax: ' %current% [%bar%] %elapsed:6s%',
very_verbose: ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
very_verbose_nomax: ' %current% [%bar%] %elapsed:6s%',
debug: ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
debug_nomax: ' %current% [%bar%] %elapsed:6s% %memory:6s%'
};
};
/**
* Gets the initially available format placeholder callbacks.
*/
ProgressBar.initPlaceholderFormatters = function () {
return {
bar: function (bar, output) {
var completeBars = Math.floor(bar.getMaxSteps() > 0
? bar.getProgressPercent() * bar.getBarWidth()
: bar.getProgress() % bar.getBarWidth());
var display = bar.getBarCharacter().repeat(completeBars);
if (completeBars < bar.getBarWidth()) {
var emptyBars = bar.getBarWidth() -
completeBars -
Helper_1.lengthWithoutDecoration(output.getFormatter(), bar.getProgressCharacter());
display +=
bar.getProgressCharacter() +
bar.getEmptyBarCharacter().repeat(emptyBars);
}
return display;
},
elapsed: function (bar) {
return Helper_1.formatTime(Helper_1.time() - bar.getStartTime());
},
remaining: function (bar) {
if (!bar.getMaxSteps()) {
throw new Error('Unable to display the remaining time if the maximum number of steps is not set.');
}
var remaining;
if (!bar.getProgress()) {
remaining = 0;
}
else {
remaining = Math.round(((Helper_1.time() - bar.getStartTime()) / bar.getProgress()) *
(bar.getMaxSteps() - bar.getProgress()));
}
return Helper_1.formatTime(remaining);
},
estimated: function (bar) {
if (!bar.getMaxSteps()) {
throw new Error('Unable to display the estimated time if the maximum number of steps is not set.');
}
var estimated;
if (!bar.getProgress()) {
estimated = 0;
}
else {
estimated = Math.round(((Helper_1.time() - bar.getStartTime()) / bar.getProgress()) *
bar.getMaxSteps());
}
return Helper_1.formatTime(estimated);
},
memory: function (bar) {
return Helper_1.formatMemory(process.memoryUsage().heapTotal);
},
current: function (bar) {
return Helper_1.strPad(String(bar.getProgress()), bar.getStepWidth(), ' ', 'STR_PAD_LEFT');
},
max: function (bar) {
return String(bar.getMaxSteps());
},
percent: function (bar) {
return String(Math.floor(bar.getProgressPercent() * 100));
}
};
};
/**
* Associates a text with a named placeholder.
*
* The text is displayed when the progress bar is rendered but only
* when the corresponding placeholder is part of the custom format line
* (by wrapping the name with %).
*
* @param message The text to associate with the placeholder
* @param name The name of the placeholder
*/
ProgressBar.prototype.setMessage = function (message, name) {
if (name === void 0) { name = 'message'; }
this.messages[name] = message;
};
/**
* Gets the message associated with a certain placeholder.
*
* @param name A format placeholder
*/
ProgressBar.prototype.getMessage = function (name) {
if (name === void 0) { name = 'message'; }
return this.messages[name];
};
/**
* Gets the progress bar start time.
*/
ProgressBar.prototype.getStartTime = function () {
return this.startTime;
};
/**
* Gets the progress bar maximal steps.
*/
ProgressBar.prototype.getMaxSteps = function () {
return this.max;
};
/**
* Gets the current step position.
*/
ProgressBar.prototype.getProgress = function () {
return this.step;
};
/**
* Gets the progress bar step width.
*/
ProgressBar.prototype.getStepWidth = function () {
return this.stepWidth;
};
/**
* Gets the current progress bar percent.
*/
ProgressBar.prototype.getProgressPercent = function () {
return this.percent;
};
/**
* Sets the progress bar width.
*/
ProgressBar.prototype.setBarWidth = function (size) {
this.barWidth = Math.max(1, size);
};
/**
* Gets the progress bar width in characters.
*/
ProgressBar.prototype.getBarWidth = function () {
return this.barWidth;
};
/**
* Sets the bar character.
*
* @param char A character
*/
ProgressBar.prototype.setBarCharacter = function (char) {
this.barChar = char;
};
/**
* Gets the bar character.
*/
ProgressBar.prototype.getBarCharacter = function () {
if (null == this.barChar) {
return this.max ? '=' : this.emptyBarChar;
}
return this.barChar;
};
/**
* Sets the empty bar character.
*
* @param char A character
*/
ProgressBar.prototype.setEmptyBarCharacter = function (char) {
this.emptyBarChar = char;
};
/**
* Gets the empty bar character.
*/
ProgressBar.prototype.getEmptyBarCharacter = function () {
return this.emptyBarChar;
};
/**
* Sets the progress bar character.
*
* @param char A character
*/
ProgressBar.prototype.setProgressCharacter = function (char) {
this.progressChar = char;
};
/**
* Gets the progress bar character.
*/
ProgressBar.prototype.getProgressCharacter = function () {
return this.progressChar;
};
/**
* Sets the progress bar format.
*
* @param format The format
*/
ProgressBar.prototype.setFormat = function (format) {
this.format = null;
this.internalFormat = format;
};
/**
* Sets the redraw frequency.
*
* @param freq The frequency in steps
*/
ProgressBar.prototype.setRedrawFrequency = function (freq) {
this.redrawFreq = Math.max(freq, 1);
};
/**
* Starts the progress output.
*
* @param max Number of steps to complete the bar (`0` if indeterminate), `null` to leave unchanged
*/
ProgressBar.prototype.start = function (max) {
if (max === void 0) { max = null; }
this.startTime = Helper_1.time();
this.step = 0;
this.percent = 0.0;
if (null != max) {
this.setMaxSteps(max);
}
this.display();
};
/**
* Advances the progress output X steps.
*
* @param step Number of steps to advance
*/
ProgressBar.prototype.advance = function (step) {
if (step === void 0) { step = 1; }
this.setProgress(this.step + step);
};
/**
* Sets whether to overwrite the progress bar, `false` for new line.
*
* @param overwrite Whether the progress bar should be overwritten
*/
ProgressBar.prototype.setOverwrite = function (overwrite) {
this.shouldOverwrite = overwrite;
};
/**
* Sets the current progress.
*
* @param step The current progress
*/
ProgressBar.prototype.setProgress = function (step) {
if (this.max && step > this.max) {
this.max = step;
}
else if (step < 0) {
step = 0;
}
var prevPeriod = Math.round(this.step / this.redrawFreq);
var currPeriod = Math.round(step / this.redrawFreq);
this.step = step;
this.percent = this.max ? this.step / this.max : 0;
if (prevPeriod !== currPeriod || this.max === step) {
this.display();
}
};
/**
* Finishes the progress output.
*/
ProgressBar.prototype.finish = function () {
if (!this.max) {
this.max = this.step;
}
if (this.step === this.max && !this.shouldOverwrite) {
// prevent double 100% output
return;
}
this.setProgress(this.max);
};
/**
* Outputs the current progress string.
*/
ProgressBar.prototype.display = function () {
if (OutputInterface_1.VERBOSITY_QUIET === this.output.getVerbosity()) {
return;
}
if (null == this.format) {
this.setRealFormat(this.internalFormat || this.determineBestFormat());
}
this.overwrite(this.buildLine());
};
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output while a progress bar is running.
* Call display() to show the progress bar again.
*/
ProgressBar.prototype.clear = function () {
if (!this.shouldOverwrite) {
return;
}
if (null == this.format) {
this.setRealFormat(this.internalFormat || this.determineBestFormat());
}
this.overwrite('');
};
/**
* Sets the progress bar format template.
*
* @param format The format template
*/
ProgressBar.prototype.setRealFormat = function (format) {
// try to use the _nomax variant if available
if (!this.max &&
null != ProgressBar.getFormatDefinition(format + '_nomax')) {
this.format = ProgressBar.getFormatDefinition(format + '_nomax');
}
else if (null != ProgressBar.getFormatDefinition(format)) {
this.format = ProgressBar.getFormatDefinition(format);
}
else {
this.format = format;
}
this.formatLineCount = Helper_1.countOccurences(this.format, '\n') || 0;
};
/**
* Sets the progress bar maximal steps.
*
* @param max The progress bar max steps
*/
ProgressBar.prototype.setMaxSteps = function (max) {
this.max = Math.max(0, max);
this.stepWidth = this.max ? String(this.max).length : 4;
};
/**
* Overwrites a previous message to the output.
*
* @param message The message
*/
ProgressBar.prototype.overwrite = function (message) {
if (this.shouldOverwrite) {
if (!this.firstRun) {
// Move the cursor to the beginning of the line
this.output.write('\x0D');
// Erase the line
this.output.write('\x1B[2K');
// Erase previous lines
if (this.formatLineCount > 0) {
this.output.write('\x1B[1A\x1B[2K'.repeat(this.formatLineCount));
}
}
}
else if (this.step > 0) {
this.output.writeln('');
}
this.firstRun = false;
this.output.write(message);
};
/**
* Determines the fitting format template for the currently set verbosity.
*/
ProgressBar.prototype.determineBestFormat = function () {
switch (this.output.getVerbosity()) {
// VERBOSITY_QUIET: display is disabled anyway
case OutputInterface_1.VERBOSITY_VERBOSE:
return this.max ? 'verbose' : 'verbose_nomax';
case OutputInterface_1.VERBOSITY_VERY_VERBOSE:
return this.max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface_1.VERBOSITY_DEBUG:
return this.max ? 'debug' : 'debug_nomax';
default:
return this.max ? 'normal' : 'normal_nomax';
}
};
/**
* Renders the current state of the progress bar.
*
* @return The output of the progress bar's current state
*/
ProgressBar.prototype.buildLine = function () {
var _this = this;
var regex = /%([a-z\-_]+)(||\:([^%]+))?%/gi;
var callback = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var str = args.pop();
var offset = args.pop();
var matches = args;
var formatter = ProgressBar.getPlaceholderFormatterDefinition(matches[1]);
var text;
if (formatter) {
text = formatter(_this, _this.output);
}
else if (_this.messages[matches[1]]) {
text = _this.messages[matches[1]];
}
else {
return matches[0];
}
if (matches[3]) {
text = Helper_1.sprintf("%" + matches[3], text);
}
return text;
};
var line = this.format.replace(regex, callback);
var lineLength = Helper_1.lengthWithoutDecoration(this.output.getFormatter(), line);
var terminalWidth = process.stdout.columns || 40;
if (lineLength <= terminalWidth) {
return line;
}
this.setBarWidth(this.barWidth - lineLength + terminalWidth);
return this.format.replace(regex, callback);
};
return ProgressBar;
}());
exports.default = ProgressBar;