Skip to content

Commit 8f966f6

Browse files
authored
Add some progress bars for time-intensive tasks (#3579)
1 parent d4ab3ee commit 8f966f6

8 files changed

+324
-97
lines changed

lib/src/dartdoc.dart

+3-7
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,8 @@ class Dartdoc {
213213

214214
var warnings = packageGraph.packageWarningCounter.warningCount;
215215
var errors = packageGraph.packageWarningCounter.errorCount;
216-
if (warnings == 0 && errors == 0) {
217-
logInfo('no issues found');
218-
} else {
219-
logWarning("Found $warnings ${pluralize('warning', warnings)} "
220-
"and $errors ${pluralize('error', errors)}.");
221-
}
216+
logWarning("Found $warnings ${pluralize('warning', warnings)} "
217+
"and $errors ${pluralize('error', errors)}.");
222218

223219
var seconds = stopwatch.elapsedMilliseconds / 1000.0;
224220
libs = packageGraph.localPublicLibraries.length;
@@ -288,7 +284,7 @@ class Dartdoc {
288284
exitCode = e is DartdocFailure ? 1 : 255;
289285
},
290286
zoneSpecification: ZoneSpecification(
291-
print: (_, __, ___, String line) => logPrint(line),
287+
print: (_, __, ___, String line) => logInfo(line),
292288
),
293289
);
294290
}

lib/src/logging.dart

+152-49
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:convert';
6-
import 'dart:io' show stderr, stdout;
6+
import 'dart:io' as io;
77

88
import 'package:analyzer/file_system/file_system.dart';
9-
import 'package:cli_util/cli_logging.dart' show Ansi;
109
import 'package:dartdoc/src/dartdoc_options.dart';
1110
import 'package:dartdoc/src/package_meta.dart';
11+
import 'package:dartdoc/src/progress_bar.dart';
1212
import 'package:logging/logging.dart';
1313

1414
final _logger = Logger('dartdoc');
1515

1616
/// A custom [Level] for tracking file writes and verification.
1717
///
1818
/// Has a value of `501` – one more than [Level.FINE].
19-
const Level progressLevel = Level('PROGRESS', 501);
19+
const Level _progressLevel = Level('PROGRESS', 501);
2020

2121
/// A custom [Level] for errant print statements.
2222
///
@@ -36,13 +36,37 @@ void logDebug(String message) {
3636
}
3737

3838
void logProgress(String message) {
39-
_logger.log(progressLevel, message);
39+
_logger.log(_progressLevel, message);
4040
}
4141

4242
void logPrint(String message) {
4343
_logger.log(printLevel, message);
4444
}
4545

46+
/// Creates a new deterministic progress bar, and displays it (with zero
47+
/// progress).
48+
void progressBarStart(int totalTickCount) {
49+
_DartdocLogger.instance.progressBarStart(totalTickCount);
50+
}
51+
52+
/// Increments the progress of the current progress bar.
53+
void progressBarTick() {
54+
_DartdocLogger.instance.progressBarTick();
55+
}
56+
57+
/// Updates the total length of the current progress bar.
58+
void progressBarUpdateTickCount(int totalTickCount) {
59+
_DartdocLogger.instance.progressBarUpdateTickCount(totalTickCount);
60+
}
61+
62+
/// Completes the current progress bar.
63+
///
64+
/// It is important to call this after progress is complete, in case rounding
65+
/// errors leave the displayed progress bar at something less than 100%.
66+
void progressBarComplete() {
67+
_DartdocLogger.instance.progressBarComplete();
68+
}
69+
4670
abstract class Jsonable {
4771
/// The `String` to print when in human-readable mode
4872
String get text;
@@ -54,47 +78,61 @@ abstract class Jsonable {
5478
String toString() => text;
5579
}
5680

57-
void startLogging(LoggingContext config) {
58-
// By default, get all log output at `progressLevel` or greater.
59-
// This allows us to capture progress events and print `...`.
60-
// Change this to `Level.FINE` for debug logging.
61-
Logger.root.level = progressLevel;
62-
if (config.json) {
63-
Logger.root.onRecord.listen((record) {
64-
if (record.level == progressLevel) {
65-
return;
66-
}
81+
class _DartdocLogger {
82+
/// By default, we use a quiet logger.
83+
///
84+
/// This field can be re-set, with [startLogging].
85+
static _DartdocLogger instance =
86+
_DartdocLogger._(isJson: false, isQuiet: true, showProgress: false);
6787

68-
var output = <String, dynamic>{'level': record.level.name};
88+
final bool _showProgressBar;
6989

70-
if (record.object is Jsonable) {
71-
output['data'] = record.object;
72-
} else {
73-
output['message'] = record.message;
74-
}
90+
ProgressBar? _progressBar;
7591

76-
print(json.encode(output));
77-
});
78-
} else {
92+
_DartdocLogger._({
93+
required bool isJson,
94+
required bool isQuiet,
95+
required bool showProgress,
96+
}) : _showProgressBar = showProgress && !isJson && !isQuiet {
97+
// By default, get all log output at `progressLevel` or greater.
98+
// This allows us to capture progress events and print `...`.
99+
// Change this to `Level.FINE` for debug logging.
100+
Logger.root.level = _progressLevel;
101+
if (isJson) {
102+
Logger.root.onRecord.listen(_onJsonRecord);
103+
return;
104+
}
105+
106+
_initialize(isQuiet: isQuiet, showProgress: showProgress);
107+
}
108+
109+
/// Initializes this as a non-JSON logger.
110+
///
111+
/// This method mostly sets up callback behavior for each logged message.
112+
void _initialize({required bool isQuiet, required bool showProgress}) {
79113
final stopwatch = Stopwatch()..start();
80114

81115
// Used to track if we're printing `...` to show progress.
82116
// Allows unified new-line tracking
83117
var writingProgress = false;
84-
var ansi = Ansi(Ansi.terminalSupportsAnsi);
85118
var spinnerIndex = 0;
86119
const spinner = ['-', r'\', '|', '/'];
87120

88121
Logger.root.onRecord.listen((record) {
89-
if (record.level == progressLevel) {
90-
if (!config.quiet &&
91-
config.showProgress &&
122+
if (record.level == progressBarUpdate) {
123+
io.stdout.write(record.message);
124+
return;
125+
}
126+
127+
if (record.level == _progressLevel) {
128+
if (!isQuiet &&
129+
showProgress &&
92130
stopwatch.elapsed.inMilliseconds > 125) {
93131
if (writingProgress = false) {
94-
stdout.write(' ');
132+
io.stdout.write(' ');
95133
}
96134
writingProgress = true;
97-
stdout.write('${ansi.backspace}${spinner[spinnerIndex]}');
135+
io.stdout.write('$_backspace${spinner[spinnerIndex]}');
98136
spinnerIndex = (spinnerIndex + 1) % spinner.length;
99137
stopwatch.reset();
100138
}
@@ -103,26 +141,79 @@ void startLogging(LoggingContext config) {
103141

104142
stopwatch.reset();
105143
if (writingProgress) {
106-
stdout.write('${ansi.backspace} ${ansi.backspace}');
144+
io.stdout.write('$_backspace $_backspace');
107145
}
108146
var message = record.message;
109147
assert(message.isNotEmpty);
110148

111149
if (record.level < Level.WARNING) {
112-
if (!config.quiet) {
150+
if (!isQuiet) {
113151
print(message);
114152
}
115153
} else {
116154
if (writingProgress) {
117155
// Some console implementations, like IntelliJ, apparently need
118156
// the backspace to occur for stderr as well.
119-
stderr.write('${ansi.backspace} ${ansi.backspace}');
157+
io.stderr.write('$_backspace $_backspace');
120158
}
121-
stderr.writeln(message);
159+
io.stderr.writeln(message);
122160
}
123161
writingProgress = false;
124162
});
125163
}
164+
165+
void progressBarStart(int totalTickCount) {
166+
if (!_showProgressBar) {
167+
return;
168+
}
169+
_progressBar = ProgressBar(_logger, totalTickCount);
170+
}
171+
172+
void progressBarTick() {
173+
if (!_showProgressBar) {
174+
return;
175+
}
176+
_progressBar?.tick();
177+
}
178+
179+
void progressBarUpdateTickCount(int totalTickCount) {
180+
if (!_showProgressBar) {
181+
return;
182+
}
183+
_progressBar?.totalTickCount = totalTickCount;
184+
}
185+
186+
void progressBarComplete() {
187+
if (!_showProgressBar) {
188+
return;
189+
}
190+
_progressBar?.complete();
191+
_progressBar = null;
192+
}
193+
194+
void _onJsonRecord(LogRecord record) {
195+
if (record.level == _progressLevel) {
196+
return;
197+
}
198+
199+
var output = <String, dynamic>{'level': record.level.name};
200+
201+
if (record.object is Jsonable) {
202+
output['data'] = record.object;
203+
} else {
204+
output['message'] = record.message;
205+
}
206+
207+
print(json.encode(output));
208+
}
209+
}
210+
211+
void startLogging(LoggingContext config) {
212+
_DartdocLogger.instance = _DartdocLogger._(
213+
isJson: config.json,
214+
isQuiet: config.quiet,
215+
showProgress: config.showProgress,
216+
);
126217
}
127218

128219
mixin LoggingContext on DartdocOptionContextBase {
@@ -137,22 +228,34 @@ List<DartdocOption<Object>> createLoggingOptions(
137228
PackageMetaProvider packageMetaProvider) {
138229
var resourceProvider = packageMetaProvider.resourceProvider;
139230
return [
140-
DartdocOptionArgOnly<bool>('json', false, resourceProvider,
141-
help: 'Prints out progress JSON maps. One entry per line.',
142-
negatable: true),
143231
DartdocOptionArgOnly<bool>(
144-
'showProgress', Ansi.terminalSupportsAnsi, resourceProvider,
145-
help: 'Display progress indications to console stdout.',
146-
negatable: true),
147-
DartdocOptionArgSynth<bool>('quiet',
148-
(DartdocSyntheticOption<Object> option, Folder dir) {
149-
if (option.parent['generateDocs'].valueAt(dir) == false) {
150-
return true;
151-
}
152-
return false;
153-
}, resourceProvider,
154-
abbr: 'q',
155-
negatable: true,
156-
help: 'Only show warnings and errors; silence all other output.'),
232+
'json',
233+
false,
234+
resourceProvider,
235+
help: 'Prints out progress JSON maps. One entry per line.',
236+
negatable: true,
237+
),
238+
DartdocOptionArgOnly<bool>(
239+
'showProgress',
240+
_terminalSupportsAnsi,
241+
resourceProvider,
242+
help: 'Display progress indications to console stdout.',
243+
negatable: true,
244+
),
245+
DartdocOptionArgSynth<bool>(
246+
'quiet',
247+
(DartdocSyntheticOption<Object> option, Folder dir) =>
248+
option.parent['generateDocs'].valueAt(dir) == false,
249+
resourceProvider,
250+
abbr: 'q',
251+
negatable: true,
252+
help: 'Only show warnings and errors; silence all other output.',
253+
),
157254
];
158255
}
256+
257+
const String _backspace = '\b';
258+
259+
bool get _terminalSupportsAnsi =>
260+
io.stdout.supportsAnsiEscapes &&
261+
io.stdioType(io.stdout) == io.StdioType.terminal;

lib/src/model/package_builder.dart

+13-2
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,15 @@ class PubPackageBuilder implements PackageBuilder {
227227
// find all documentable files in that package, for the universal reference
228228
// scope. This variable tracks which packages we've seen so far.
229229
var knownPackages = <PackageMeta>{};
230+
if (!addingSpecials) {
231+
progressBarStart(files.length);
232+
}
230233
do {
231234
filesInLastPass = filesInCurrentPass;
232235
var newFiles = <String>{};
236+
if (!addingSpecials) {
237+
progressBarUpdateTickCount(files.length);
238+
}
233239
// Be careful here, not to accidentally stack up multiple
234240
// [DartDocResolvedLibrary]s, as those eat our heap.
235241
var libraryFiles = files.difference(_knownParts);
@@ -239,7 +245,9 @@ class PubPackageBuilder implements PackageBuilder {
239245
continue;
240246
}
241247
processedFiles.add(file);
242-
logProgress(file);
248+
if (!addingSpecials) {
249+
progressBarTick();
250+
}
243251
var resolvedLibrary = await _resolveLibrary(file);
244252
if (resolvedLibrary == null) {
245253
_knownParts.add(file);
@@ -283,6 +291,9 @@ class PubPackageBuilder implements PackageBuilder {
283291
knownPackages.addAll(packages);
284292
}
285293
} while (!filesInLastPass.containsAll(filesInCurrentPass));
294+
if (!addingSpecials) {
295+
progressBarComplete();
296+
}
286297
}
287298

288299
/// Whether [libraryElement] should be included in the libraries-to-document.
@@ -439,7 +450,7 @@ class PubPackageBuilder implements PackageBuilder {
439450
var files = await _getFilesToDocument();
440451
var specialFiles = specialLibraryFiles(findSpecialsSdk);
441452

442-
logDebug('${DateTime.now()}: Discovering Dart libraries...');
453+
logInfo('Discovering libraries...');
443454
var foundLibraries = <LibraryElement>{};
444455
await _discoverLibraries(
445456
uninitializedPackageGraph.addLibraryToGraph,

0 commit comments

Comments
 (0)