/
progress.dart
141 lines (117 loc) · 3.5 KB
/
progress.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
part of 'mason_logger.dart';
/// {@template progress_options}
/// An object containing configuration for a [Progress] instance.
/// {@endtemplate}
class ProgressOptions {
/// {@macro progress_options}
const ProgressOptions({this.animation = const ProgressAnimation()});
/// The progress animation configuration.
final ProgressAnimation animation;
}
/// {@template progress_animation}
/// An object which contains configuration for the animation
/// of a [Progress] instance.
/// {@endtemplate}
class ProgressAnimation {
/// {@macro progress_animation}
const ProgressAnimation({this.frames = _defaultFrames});
static const _defaultFrames = [
'⠋',
'⠙',
'⠹',
'⠸',
'⠼',
'⠴',
'⠦',
'⠧',
'⠇',
'⠏'
];
/// The list of animation frames.
final List<String> frames;
}
/// {@template progress}
/// A class that can be used to display progress information to the user.
/// {@endtemplate}
class Progress {
/// {@macro progress}
Progress._(
this._message,
this._stdout,
this._level, {
ProgressOptions options = const ProgressOptions(),
}) : _stopwatch = Stopwatch(),
_options = options {
_stopwatch
..reset()
..start();
// The animation is only shown when it would be meaningful.
// Do not animate if the stdio type is not a terminal.
if (io.stdioType(_stdout) != io.StdioType.terminal) {
final frames = _options.animation.frames;
final char = frames.isEmpty ? '' : frames.first;
final prefix = char.isEmpty ? char : '${lightGreen.wrap(char)} ';
_write('$prefix$_message...');
return;
}
_timer = Timer.periodic(const Duration(milliseconds: 80), _onTick);
}
final ProgressOptions _options;
final io.Stdout _stdout;
final Level _level;
final Stopwatch _stopwatch;
Timer? _timer;
String _message;
int _index = 0;
/// End the progress and mark it as completed.
void complete([String? update]) {
_stopwatch.stop();
_write(
'''$_clearLine${lightGreen.wrap('✓')} ${update ?? _message} $_time\n''',
);
_timer?.cancel();
}
/// End the progress and mark it as failed.
void fail([String? update]) {
_timer?.cancel();
_write('$_clearLine${red.wrap('✗')} ${update ?? _message} $_time\n');
_stopwatch.stop();
}
/// Update the progress message.
void update(String update) {
_write(_clearLine);
_message = update;
_onTick(_timer);
}
/// Cancel the progress and remove the written line.
void cancel() {
_timer?.cancel();
_write(_clearLine);
_stopwatch.stop();
}
String get _clearLine {
return '\u001b[2K' // clear current line
'\r'; // bring cursor to the start of the current line
}
void _onTick(Timer? timer) {
if (timer == null) return;
_index++;
final frames = _options.animation.frames;
final char = frames.isEmpty ? '' : frames[_index % frames.length];
final prefix = char.isEmpty ? char : '${lightGreen.wrap(char)} ';
_write('$_clearLine$prefix$_message... $_time');
}
void _write(String object) {
if (_level.index > Level.info.index) return;
_stdout.write(object);
}
String get _time {
final elapsedTime = _stopwatch.elapsed.inMilliseconds;
final displayInMilliseconds = elapsedTime < 100;
final time = displayInMilliseconds ? elapsedTime : elapsedTime / 1000;
final formattedTime = displayInMilliseconds
? '${time.toString()}ms'
: '${time.toStringAsFixed(1)}s';
return '${darkGray.wrap('($formattedTime)')}';
}
}