blob: c2dbb7a3418136073d6a2ff5c6c7e98c804f7194 [file] [log] [blame] [edit]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
import 'dart:io' as io;
import 'package:analyzer/file_system/file_system.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/progress_bar.dart';
import 'package:logging/logging.dart';
final _logger = Logger('dartdoc');
/// A custom [Level] for tracking file writes and verification.
///
/// Has a value of `501` – one more than [Level.FINE].
const Level _progressLevel = Level('PROGRESS', 501);
/// A custom [Level] for errant print statements.
///
/// Has a value of `1201` – one more than [Level.SHOUT].
const Level printLevel = Level('PRINT', 1201);
void logWarning(String message) {
_logger.log(Level.WARNING, message);
}
void logInfo(String message) {
_logger.log(Level.INFO, message);
}
void logDebug(String message) {
_logger.log(Level.FINE, message);
}
void logProgress(String message) {
_logger.log(_progressLevel, message);
}
/// Creates a new deterministic progress bar, and displays it (with zero
/// progress).
void progressBarStart(int totalTickCount) {
_DartdocLogger.instance.progressBarStart(totalTickCount);
}
/// Increments the progress of the current progress bar.
void progressBarTick() {
_DartdocLogger.instance.progressBarTick();
}
/// Updates the total length of the current progress bar.
void progressBarUpdateTickCount(int totalTickCount) {
_DartdocLogger.instance.progressBarUpdateTickCount(totalTickCount);
}
/// Completes the current progress bar.
///
/// It is important to call this after progress is complete, in case rounding
/// errors leave the displayed progress bar at something less than 100%.
void progressBarComplete() {
_DartdocLogger.instance.progressBarComplete();
}
abstract class Jsonable {
/// The `String` to print when in human-readable mode
String get text;
/// The JSON content to print when in JSON-output mode.
Object toJson();
@override
String toString() => text;
}
class _DartdocLogger {
/// By default, we use a quiet logger.
///
/// This field can be re-set, with [startLogging].
static _DartdocLogger instance =
_DartdocLogger._(isJson: false, isQuiet: true, showProgress: false);
final StringSink _outSink;
final StringSink _errSink;
final bool _showProgressBar;
ProgressBar? _progressBar;
_DartdocLogger._({
required bool isJson,
required bool isQuiet,
required bool showProgress,
StringSink? outSink,
StringSink? errSink,
}) : _outSink = outSink ?? io.stdout,
_errSink = errSink ?? io.stderr,
_showProgressBar = showProgress && !isJson && !isQuiet {
// By default, get all log output at `progressLevel` or greater.
// This allows us to capture progress events and print `...`.
// Change this to `Level.FINE` for debug logging.
Logger.root.level = _progressLevel;
if (isJson) {
Logger.root.onRecord.listen(_onJsonRecord);
return;
}
_initialize(isQuiet: isQuiet, showProgress: showProgress);
}
/// Initializes this as a non-JSON logger.
///
/// This method mostly sets up callback behavior for each logged message.
void _initialize({required bool isQuiet, required bool showProgress}) {
final stopwatch = Stopwatch()..start();
// Used to track if we're printing `...` to show progress.
// Allows unified new-line tracking
var writingProgress = false;
var spinnerIndex = 0;
const spinner = ['-', r'\', '|', '/'];
Logger.root.onRecord.listen((record) {
if (record.level == progressBarUpdate) {
_outSink.write(record.message);
return;
}
if (record.level == _progressLevel) {
if (!isQuiet &&
showProgress &&
stopwatch.elapsed.inMilliseconds > 125) {
if (writingProgress = false) {
_outSink.write(' ');
}
writingProgress = true;
_outSink.write('$_backspace${spinner[spinnerIndex]}');
spinnerIndex = (spinnerIndex + 1) % spinner.length;
stopwatch.reset();
}
return;
}
stopwatch.reset();
if (writingProgress) {
_outSink.write('$_backspace $_backspace');
}
var message = record.message;
assert(message.isNotEmpty);
if (record.level < Level.WARNING) {
if (!isQuiet) {
_outSink.writeln(message);
}
} else {
if (writingProgress) {
// Some console implementations, like IntelliJ, apparently need
// the backspace to occur for stderr as well.
_errSink.write('$_backspace $_backspace');
}
_errSink.writeln(message);
}
writingProgress = false;
});
}
void progressBarStart(int totalTickCount) {
if (!_showProgressBar) {
return;
}
_progressBar = ProgressBar(_logger, totalTickCount);
}
void progressBarTick() {
if (!_showProgressBar) {
return;
}
_progressBar?.tick();
}
void progressBarUpdateTickCount(int totalTickCount) {
if (!_showProgressBar) {
return;
}
_progressBar?.totalTickCount = totalTickCount;
}
void progressBarComplete() {
if (!_showProgressBar) {
return;
}
_progressBar?.complete();
_progressBar = null;
}
void _onJsonRecord(LogRecord record) {
if (record.level == _progressLevel) {
return;
}
var output = <String, dynamic>{'level': record.level.name};
if (record.object is Jsonable) {
output['data'] = record.object;
} else {
output['message'] = record.message;
}
_outSink.writeln(json.encode(output));
}
}
void startLogging({
required bool isJson,
required bool isQuiet,
required bool showProgress,
StringSink? outSink,
StringSink? errSink,
}) {
_DartdocLogger.instance = _DartdocLogger._(
isJson: isJson,
isQuiet: isQuiet,
showProgress: showProgress,
outSink: outSink ?? io.stdout,
errSink: errSink ?? io.stderr,
);
}
mixin LoggingContext on DartdocOptionContextBase {
bool get json => optionSet['json'].valueAt(context);
bool get showProgress => optionSet['showProgress'].valueAt(context);
bool get quiet => optionSet['quiet'].valueAt(context);
}
List<DartdocOption<Object>> createLoggingOptions(
PackageMetaProvider packageMetaProvider) {
var resourceProvider = packageMetaProvider.resourceProvider;
return [
DartdocOptionArgOnly<bool>(
'json',
false,
resourceProvider,
help: 'Prints out progress JSON maps. One entry per line.',
negatable: true,
),
DartdocOptionArgOnly<bool>(
'showProgress',
_terminalSupportsAnsi,
resourceProvider,
help: 'Display progress indications to console stdout.',
negatable: true,
),
DartdocOptionArgSynth<bool>(
'quiet',
(DartdocSyntheticOption<Object> option, Folder dir) =>
option.parent['generateDocs'].valueAt(dir) == false,
resourceProvider,
abbr: 'q',
negatable: true,
help: 'Only show warnings and errors; silence all other output.',
),
];
}
const String _backspace = '\b';
bool get _terminalSupportsAnsi =>
io.stdout.supportsAnsiEscapes &&
io.stdioType(io.stdout) == io.StdioType.terminal;