UNPKG

symfony-style-console

Version:

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

543 lines (542 loc) 18.1 kB
"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;