blob: 6da0ec53b5e51083281da5ee574eba75ff014067 [file] [log] [blame]
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'\', '|'];