Skip to content
This repository has been archived by the owner on Nov 1, 2024. It is now read-only.

Commit

Permalink
Merge pull request #15 from dart-lang/cli_logging
Browse files Browse the repository at this point in the history
add cli_logging.dart
  • Loading branch information
devoncarew authored May 17, 2017
2 parents d8f691a + a47b1ed commit e62b70e
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interact with the Dart SDK (such as the [analyzer][analyzer]).

[![Build Status](https://travis-ci.org/dart-lang/cli_util.svg)](https://travis-ci.org/dart-lang/cli_util)

## Usage
## Locating the Dart SDK

```dart
import 'dart:io';
Expand All @@ -26,6 +26,38 @@ main(args) {
}
```

## Displaying output and progress

`package:cli_util` can also be used to help CLI tools display output and progress.
It has a logging mechanism which can help differentiate between regular tool
output and error messages, and can facilitate having a more verbose (`-v`) mode for
output.

In addition, it can display an indeterminate progress spinner for longer running
tasks, and optionally display the elapsed time when finished:

```dart
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();
}
```

## Features and bugs

Please file feature requests and bugs at the [issue tracker][tracker].
Expand Down
25 changes: 25 additions & 0 deletions example/main.dart
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();
}
266 changes: 266 additions & 0 deletions lib/cli_logging.dart
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]';
}
}
2 changes: 1 addition & 1 deletion lib/cli_util.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down

0 comments on commit e62b70e

Please sign in to comment.