Skip to content
This repository was archived by the owner on Nov 1, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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