This repository has been archived by the owner on Nov 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from dart-lang/cli_logging
add cli_logging.dart
- Loading branch information
Showing
5 changed files
with
327 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// 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:async'; | ||
|
||
import 'package:cli_util/cli_logging.dart'; | ||
|
||
main(List<String> args) async { | ||
bool verbose = args.contains('-v'); | ||
Logger logger = verbose ? new Logger.verbose() : new Logger.standard(); | ||
|
||
logger.stdout('Hello world!'); | ||
logger.trace('message 1'); | ||
await new Future.delayed(new Duration(milliseconds: 200)); | ||
logger.trace('message 2'); | ||
logger.trace('message 3'); | ||
|
||
Progress progress = logger.progress('doing some work'); | ||
await new Future.delayed(new Duration(seconds: 2)); | ||
progress.finish(showTiming: true); | ||
|
||
logger.stdout('All ${logger.ansi.emphasized('done')}.'); | ||
logger.flush(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
// 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. | ||
|
||
/// This library contains functionality to help command-line utilities to easily | ||
/// create aesthetic output. | ||
import 'dart:async'; | ||
import 'dart:io' as io; | ||
|
||
/// create aesthetic output. | ||
/// A small utility class to make it easier to work with common ANSI escape | ||
/// sequences. | ||
class Ansi { | ||
/// Return whether the current stdout terminal supports ANSI escape sequences. | ||
static bool get terminalSupportsAnsi { | ||
return io.stdout.supportsAnsiEscapes && | ||
io.stdioType(io.stdout) == io.StdioType.TERMINAL; | ||
} | ||
|
||
final bool useAnsi; | ||
|
||
Ansi(this.useAnsi); | ||
|
||
String get cyan => _code('\u001b[36m'); | ||
String get green => _code('\u001b[32m'); | ||
String get magenta => _code('\u001b[35m'); | ||
String get red => _code('\u001b[31m'); | ||
String get yellow => _code('\u001b[33m'); | ||
String get blue => _code('\u001b[34m'); | ||
String get gray => _code('\u001b[1;30m'); | ||
String get noColor => _code('\u001b[39m'); | ||
|
||
String get none => _code('\u001b[0m'); | ||
|
||
String get bold => _code('\u001b[1m'); | ||
|
||
String get backspace => '\b'; | ||
|
||
String get bullet => io.stdout.supportsAnsiEscapes ? '•' : '-'; | ||
|
||
/// Display [message] in an emphasized format. | ||
String emphasized(String message) => '$bold$message$none'; | ||
|
||
/// Display [message] in an subtle (gray) format. | ||
String subtle(String message) => '$gray$message$none'; | ||
|
||
/// Display [message] in an error (red) format. | ||
String error(String message) => '$red$message$none'; | ||
|
||
String _code(String ansiCode) => useAnsi ? ansiCode : ''; | ||
} | ||
|
||
/// An abstract representation of a [Logger] - used to pretty print errors, | ||
/// standard status messages, trace level output, and indeterminate progress. | ||
abstract class Logger { | ||
/// Create a normal [Logger]; this logger will not display trace level output. | ||
factory Logger.standard({Ansi ansi}) => new _StandardLogger(ansi: ansi); | ||
|
||
/// Create a [Logger] that will display trace level output. | ||
factory Logger.verbose({Ansi ansi}) => new _VerboseLogger(ansi: ansi); | ||
|
||
Ansi get ansi; | ||
|
||
/// Print an error message. | ||
void stderr(String message); | ||
|
||
/// Print a standard status message. | ||
void stdout(String message); | ||
|
||
/// Print trace output. | ||
void trace(String message); | ||
|
||
/// Start an indeterminate progress display. | ||
Progress progress(String message); | ||
void _progressFinished(Progress progress); | ||
|
||
/// Flush any un-written output. | ||
void flush(); | ||
} | ||
|
||
/// A handle to an indeterminate progress display. | ||
abstract class Progress { | ||
final String message; | ||
final Stopwatch _stopwatch; | ||
|
||
Progress._(this.message) : _stopwatch = new Stopwatch()..start(); | ||
|
||
Duration get elapsed => _stopwatch.elapsed; | ||
|
||
/// Finish the indeterminate progress display. | ||
void finish({String message, bool showTiming}); | ||
|
||
/// Cancel the indeterminate progress display. | ||
void cancel(); | ||
} | ||
|
||
class _StandardLogger implements Logger { | ||
Ansi ansi; | ||
|
||
_StandardLogger({this.ansi}) { | ||
ansi ??= new Ansi(Ansi.terminalSupportsAnsi); | ||
} | ||
|
||
Progress _currentProgress; | ||
|
||
void stderr(String message) { | ||
io.stderr.writeln(message); | ||
_currentProgress?.cancel(); | ||
_currentProgress = null; | ||
} | ||
|
||
void stdout(String message) { | ||
print(message); | ||
_currentProgress?.cancel(); | ||
_currentProgress = null; | ||
} | ||
|
||
void trace(String message) {} | ||
|
||
Progress progress(String message) { | ||
_currentProgress?.cancel(); | ||
_currentProgress = null; | ||
|
||
Progress progress = ansi.useAnsi | ||
? new _AnsiProgress(this, ansi, message) | ||
: new _SimpleProgress(this, message); | ||
_currentProgress = progress; | ||
return progress; | ||
} | ||
|
||
void _progressFinished(Progress progress) { | ||
if (_currentProgress == progress) { | ||
_currentProgress = null; | ||
} | ||
} | ||
|
||
void flush() {} | ||
} | ||
|
||
class _SimpleProgress extends Progress { | ||
final Logger logger; | ||
|
||
_SimpleProgress(this.logger, String message) : super._(message) { | ||
logger.stdout('$message...'); | ||
} | ||
|
||
@override | ||
void cancel() { | ||
logger._progressFinished(this); | ||
} | ||
|
||
@override | ||
void finish({String message, bool showTiming}) { | ||
logger._progressFinished(this); | ||
} | ||
} | ||
|
||
class _AnsiProgress extends Progress { | ||
static const List<String> kAnimationItems = const ['/', '-', '\\', '|']; | ||
|
||
final Logger logger; | ||
final Ansi ansi; | ||
|
||
int _index = 0; | ||
Timer _timer; | ||
|
||
_AnsiProgress(this.logger, this.ansi, String message) : super._(message) { | ||
io.stdout.write('${message}... '.padRight(40)); | ||
|
||
_timer = new Timer.periodic(new Duration(milliseconds: 80), (t) { | ||
_index++; | ||
_updateDisplay(); | ||
}); | ||
|
||
_updateDisplay(); | ||
} | ||
|
||
@override | ||
void cancel() { | ||
if (_timer.isActive) { | ||
_timer.cancel(); | ||
_updateDisplay(cancelled: true); | ||
logger._progressFinished(this); | ||
} | ||
} | ||
|
||
@override | ||
void finish({String message, bool showTiming: false}) { | ||
if (_timer.isActive) { | ||
_timer.cancel(); | ||
_updateDisplay(isFinal: true, message: message, showTiming: showTiming); | ||
logger._progressFinished(this); | ||
} | ||
} | ||
|
||
void _updateDisplay( | ||
{bool isFinal: false, | ||
bool cancelled: false, | ||
String message, | ||
bool showTiming: false}) { | ||
String char = kAnimationItems[_index % kAnimationItems.length]; | ||
if (isFinal || cancelled) { | ||
char = ' '; | ||
} | ||
io.stdout.write('${ansi.backspace}${char}'); | ||
if (isFinal || cancelled) { | ||
if (message != null) { | ||
io.stdout.write(message); | ||
} else if (showTiming) { | ||
String time = (elapsed.inMilliseconds / 1000.0).toStringAsFixed(1); | ||
io.stdout.write('${time}s'); | ||
} | ||
io.stdout.writeln(); | ||
} | ||
} | ||
} | ||
|
||
class _VerboseLogger implements Logger { | ||
Ansi ansi; | ||
Stopwatch _timer; | ||
|
||
String _previousErr; | ||
String _previousMsg; | ||
|
||
_VerboseLogger({this.ansi}) { | ||
ansi ??= new Ansi(Ansi.terminalSupportsAnsi); | ||
_timer = new Stopwatch()..start(); | ||
} | ||
|
||
void stderr(String message) { | ||
flush(); | ||
_previousErr = '${ansi.red}$message${ansi.none}'; | ||
} | ||
|
||
void stdout(String message) { | ||
flush(); | ||
_previousMsg = message; | ||
} | ||
|
||
void trace(String message) { | ||
flush(); | ||
_previousMsg = '${ansi.gray}$message${ansi.none}'; | ||
} | ||
|
||
Progress progress(String message) => new _SimpleProgress(this, message); | ||
|
||
void _progressFinished(Progress progress) {} | ||
|
||
void flush() { | ||
if (_previousErr != null) { | ||
io.stderr.writeln('${_createTag()} $_previousErr'); | ||
_previousErr = null; | ||
} else if (_previousMsg != null) { | ||
io.stdout.writeln('${_createTag()} $_previousMsg'); | ||
_previousMsg = null; | ||
} | ||
} | ||
|
||
String _createTag() { | ||
int millis = _timer.elapsedMilliseconds; | ||
_timer.reset(); | ||
return '[${millis.toString().padLeft(4)} ms]'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters