add cli_logging.dart
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4cd8cd..7d05c61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
## unreleased
- Use the new `Platform.resolvedExecutable` API to locate the SDK
+- add the `cli_logging.dart` library - some utilities to help cli tools
+ display output
## 0.0.1+3
diff --git a/example/main.dart b/example/main.dart
new file mode 100644
index 0000000..0cc6846
--- /dev/null
+++ b/example/main.dart
@@ -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();
+}
diff --git a/lib/cli_logging.dart b/lib/cli_logging.dart
new file mode 100644
index 0000000..72ec89a
--- /dev/null
+++ b/lib/cli_logging.dart
@@ -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]';
+ }
+}
diff --git a/lib/cli_util.dart b/lib/cli_util.dart
index 62191ec..2c8faa2 100644
--- a/lib/cli_util.dart
+++ b/lib/cli_util.dart
@@ -2,7 +2,7 @@
// 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.
-library cli_util;
+/// Utilities to return the Dart SDK location.
import 'dart:io';