| import 'dart:io' as io; |
| import 'dart:math' as math; |
| |
| import 'package:logging/logging.dart'; |
| |
| /// The logging Level of a progress bar update. |
| /// |
| /// The number, 502, is not interesting; it is just unique, and one greater |
| /// than logging.dart's `_progressLevel`. That library performs different |
| /// actions when it sees logging messages with this Level. |
| const Level progressBarUpdate = Level('PROGRESS_BAR', 502); |
| |
| /// A facility for drawing a progress bar in the terminal. |
| /// |
| /// The bar is instantiated with the total number of "ticks" to be completed, |
| /// and progress is made by calling [tick]. The bar is drawn across one entire |
| /// line, like so: |
| /// |
| /// [---------- ] |
| /// |
| /// The hyphens represent completed progress, and the whitespace represents |
| /// remaining progress. |
| /// |
| /// If there is no terminal, the progress bar will not be drawn. |
| class ProgressBar { |
| final Logger _logger; |
| |
| /// Whether the progress bar should be drawn. |
| late bool _shouldDrawProgress; |
| |
| /// The width of the terminal, in terms of characters. |
| late int _width; |
| |
| /// The inner width of the terminal, in terms of characters. |
| /// |
| /// This represents the number of characters available for drawing progress. |
| late int _innerWidth; |
| |
| int totalTickCount; |
| |
| int _tickCount = 0; |
| |
| ProgressBar(this._logger, this.totalTickCount) { |
| if (!io.stdout.hasTerminal) { |
| _shouldDrawProgress = false; |
| } else { |
| _shouldDrawProgress = true; |
| _width = io.stdout.terminalColumns; |
| // Inclusion of the percent indicator assumes a terminal width of at least |
| // 12 (2 brackets + 1 space + 2 parenthesis characters + 3 digits + |
| // 1 period + 2 digits + 1 '%' character). |
| _innerWidth = io.stdout.terminalColumns - 12; |
| _logger.log(progressBarUpdate, '[${' ' * _innerWidth}]'); |
| } |
| } |
| |
| /// Clears the progress bar from the terminal, allowing other logging to be |
| /// printed. |
| void clear() { |
| if (!_shouldDrawProgress) { |
| return; |
| } |
| io.stdout.write('\r${' ' * _width}\r'); |
| } |
| |
| /// Draws the progress bar as complete, and print two newlines. |
| void complete() { |
| if (!_shouldDrawProgress) { |
| return; |
| } |
| _logger.log(progressBarUpdate, '\r[${'-' * _innerWidth}] (100.00%)\n\n'); |
| } |
| |
| /// Progresses the bar by one tick. |
| void tick() { |
| if (!_shouldDrawProgress) { |
| return; |
| } |
| _tickCount++; |
| final fractionComplete = |
| math.max(0, _tickCount * _innerWidth ~/ totalTickCount - 1); |
| // The inner space consists of hyphens, one spinner character, spaces, and a |
| // percentage (8 characters). |
| final hyphens = '-' * fractionComplete; |
| final trailingSpace = ' ' * (_innerWidth - fractionComplete - 1); |
| final spinner = _animationItems[_tickCount % 4]; |
| final pctComplete = (_tickCount * 100 / totalTickCount).toStringAsFixed(2); |
| _logger.log(progressBarUpdate, |
| '\r[$hyphens$spinner$trailingSpace] ($pctComplete%)'); |
| } |
| } |
| |
| const List<String> _animationItems = ['/', '-', r'\', '|']; |