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

Commit e62b70e

Browse files
authored
Merge pull request #15 from dart-lang/cli_logging
add cli_logging.dart
2 parents d8f691a + a47b1ed commit e62b70e

File tree

5 files changed

+327
-2
lines changed

5 files changed

+327
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## unreleased
44

55
- Use the new `Platform.resolvedExecutable` API to locate the SDK
6+
- add the `cli_logging.dart` library - some utilities to help cli tools
7+
display output
68

79
## 0.0.1+3
810

README.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interact with the Dart SDK (such as the [analyzer][analyzer]).
88

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

11-
## Usage
11+
## Locating the Dart SDK
1212

1313
```dart
1414
import 'dart:io';
@@ -26,6 +26,38 @@ main(args) {
2626
}
2727
```
2828

29+
## Displaying output and progress
30+
31+
`package:cli_util` can also be used to help CLI tools display output and progress.
32+
It has a logging mechanism which can help differentiate between regular tool
33+
output and error messages, and can facilitate having a more verbose (`-v`) mode for
34+
output.
35+
36+
In addition, it can display an indeterminate progress spinner for longer running
37+
tasks, and optionally display the elapsed time when finished:
38+
39+
```dart
40+
import 'package:cli_util/cli_logging.dart';
41+
42+
main(List<String> args) async {
43+
bool verbose = args.contains('-v');
44+
Logger logger = verbose ? new Logger.verbose() : new Logger.standard();
45+
46+
logger.stdout('Hello world!');
47+
logger.trace('message 1');
48+
await new Future.delayed(new Duration(milliseconds: 200));
49+
logger.trace('message 2');
50+
logger.trace('message 3');
51+
52+
Progress progress = logger.progress('doing some work');
53+
await new Future.delayed(new Duration(seconds: 2));
54+
progress.finish(showTiming: true);
55+
56+
logger.stdout('All ${logger.ansi.emphasized('done')}.');
57+
logger.flush();
58+
}
59+
```
60+
2961
## Features and bugs
3062

3163
Please file feature requests and bugs at the [issue tracker][tracker].

example/main.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:cli_util/cli_logging.dart';
8+
9+
main(List<String> args) async {
10+
bool verbose = args.contains('-v');
11+
Logger logger = verbose ? new Logger.verbose() : new Logger.standard();
12+
13+
logger.stdout('Hello world!');
14+
logger.trace('message 1');
15+
await new Future.delayed(new Duration(milliseconds: 200));
16+
logger.trace('message 2');
17+
logger.trace('message 3');
18+
19+
Progress progress = logger.progress('doing some work');
20+
await new Future.delayed(new Duration(seconds: 2));
21+
progress.finish(showTiming: true);
22+
23+
logger.stdout('All ${logger.ansi.emphasized('done')}.');
24+
logger.flush();
25+
}

lib/cli_logging.dart

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// This library contains functionality to help command-line utilities to easily
6+
/// create aesthetic output.
7+
8+
import 'dart:async';
9+
import 'dart:io' as io;
10+
11+
/// create aesthetic output.
12+
13+
/// A small utility class to make it easier to work with common ANSI escape
14+
/// sequences.
15+
class Ansi {
16+
/// Return whether the current stdout terminal supports ANSI escape sequences.
17+
static bool get terminalSupportsAnsi {
18+
return io.stdout.supportsAnsiEscapes &&
19+
io.stdioType(io.stdout) == io.StdioType.TERMINAL;
20+
}
21+
22+
final bool useAnsi;
23+
24+
Ansi(this.useAnsi);
25+
26+
String get cyan => _code('\u001b[36m');
27+
String get green => _code('\u001b[32m');
28+
String get magenta => _code('\u001b[35m');
29+
String get red => _code('\u001b[31m');
30+
String get yellow => _code('\u001b[33m');
31+
String get blue => _code('\u001b[34m');
32+
String get gray => _code('\u001b[1;30m');
33+
String get noColor => _code('\u001b[39m');
34+
35+
String get none => _code('\u001b[0m');
36+
37+
String get bold => _code('\u001b[1m');
38+
39+
String get backspace => '\b';
40+
41+
String get bullet => io.stdout.supportsAnsiEscapes ? '•' : '-';
42+
43+
/// Display [message] in an emphasized format.
44+
String emphasized(String message) => '$bold$message$none';
45+
46+
/// Display [message] in an subtle (gray) format.
47+
String subtle(String message) => '$gray$message$none';
48+
49+
/// Display [message] in an error (red) format.
50+
String error(String message) => '$red$message$none';
51+
52+
String _code(String ansiCode) => useAnsi ? ansiCode : '';
53+
}
54+
55+
/// An abstract representation of a [Logger] - used to pretty print errors,
56+
/// standard status messages, trace level output, and indeterminate progress.
57+
abstract class Logger {
58+
/// Create a normal [Logger]; this logger will not display trace level output.
59+
factory Logger.standard({Ansi ansi}) => new _StandardLogger(ansi: ansi);
60+
61+
/// Create a [Logger] that will display trace level output.
62+
factory Logger.verbose({Ansi ansi}) => new _VerboseLogger(ansi: ansi);
63+
64+
Ansi get ansi;
65+
66+
/// Print an error message.
67+
void stderr(String message);
68+
69+
/// Print a standard status message.
70+
void stdout(String message);
71+
72+
/// Print trace output.
73+
void trace(String message);
74+
75+
/// Start an indeterminate progress display.
76+
Progress progress(String message);
77+
void _progressFinished(Progress progress);
78+
79+
/// Flush any un-written output.
80+
void flush();
81+
}
82+
83+
/// A handle to an indeterminate progress display.
84+
abstract class Progress {
85+
final String message;
86+
final Stopwatch _stopwatch;
87+
88+
Progress._(this.message) : _stopwatch = new Stopwatch()..start();
89+
90+
Duration get elapsed => _stopwatch.elapsed;
91+
92+
/// Finish the indeterminate progress display.
93+
void finish({String message, bool showTiming});
94+
95+
/// Cancel the indeterminate progress display.
96+
void cancel();
97+
}
98+
99+
class _StandardLogger implements Logger {
100+
Ansi ansi;
101+
102+
_StandardLogger({this.ansi}) {
103+
ansi ??= new Ansi(Ansi.terminalSupportsAnsi);
104+
}
105+
106+
Progress _currentProgress;
107+
108+
void stderr(String message) {
109+
io.stderr.writeln(message);
110+
_currentProgress?.cancel();
111+
_currentProgress = null;
112+
}
113+
114+
void stdout(String message) {
115+
print(message);
116+
_currentProgress?.cancel();
117+
_currentProgress = null;
118+
}
119+
120+
void trace(String message) {}
121+
122+
Progress progress(String message) {
123+
_currentProgress?.cancel();
124+
_currentProgress = null;
125+
126+
Progress progress = ansi.useAnsi
127+
? new _AnsiProgress(this, ansi, message)
128+
: new _SimpleProgress(this, message);
129+
_currentProgress = progress;
130+
return progress;
131+
}
132+
133+
void _progressFinished(Progress progress) {
134+
if (_currentProgress == progress) {
135+
_currentProgress = null;
136+
}
137+
}
138+
139+
void flush() {}
140+
}
141+
142+
class _SimpleProgress extends Progress {
143+
final Logger logger;
144+
145+
_SimpleProgress(this.logger, String message) : super._(message) {
146+
logger.stdout('$message...');
147+
}
148+
149+
@override
150+
void cancel() {
151+
logger._progressFinished(this);
152+
}
153+
154+
@override
155+
void finish({String message, bool showTiming}) {
156+
logger._progressFinished(this);
157+
}
158+
}
159+
160+
class _AnsiProgress extends Progress {
161+
static const List<String> kAnimationItems = const ['/', '-', '\\', '|'];
162+
163+
final Logger logger;
164+
final Ansi ansi;
165+
166+
int _index = 0;
167+
Timer _timer;
168+
169+
_AnsiProgress(this.logger, this.ansi, String message) : super._(message) {
170+
io.stdout.write('${message}... '.padRight(40));
171+
172+
_timer = new Timer.periodic(new Duration(milliseconds: 80), (t) {
173+
_index++;
174+
_updateDisplay();
175+
});
176+
177+
_updateDisplay();
178+
}
179+
180+
@override
181+
void cancel() {
182+
if (_timer.isActive) {
183+
_timer.cancel();
184+
_updateDisplay(cancelled: true);
185+
logger._progressFinished(this);
186+
}
187+
}
188+
189+
@override
190+
void finish({String message, bool showTiming: false}) {
191+
if (_timer.isActive) {
192+
_timer.cancel();
193+
_updateDisplay(isFinal: true, message: message, showTiming: showTiming);
194+
logger._progressFinished(this);
195+
}
196+
}
197+
198+
void _updateDisplay(
199+
{bool isFinal: false,
200+
bool cancelled: false,
201+
String message,
202+
bool showTiming: false}) {
203+
String char = kAnimationItems[_index % kAnimationItems.length];
204+
if (isFinal || cancelled) {
205+
char = ' ';
206+
}
207+
io.stdout.write('${ansi.backspace}${char}');
208+
if (isFinal || cancelled) {
209+
if (message != null) {
210+
io.stdout.write(message);
211+
} else if (showTiming) {
212+
String time = (elapsed.inMilliseconds / 1000.0).toStringAsFixed(1);
213+
io.stdout.write('${time}s');
214+
}
215+
io.stdout.writeln();
216+
}
217+
}
218+
}
219+
220+
class _VerboseLogger implements Logger {
221+
Ansi ansi;
222+
Stopwatch _timer;
223+
224+
String _previousErr;
225+
String _previousMsg;
226+
227+
_VerboseLogger({this.ansi}) {
228+
ansi ??= new Ansi(Ansi.terminalSupportsAnsi);
229+
_timer = new Stopwatch()..start();
230+
}
231+
232+
void stderr(String message) {
233+
flush();
234+
_previousErr = '${ansi.red}$message${ansi.none}';
235+
}
236+
237+
void stdout(String message) {
238+
flush();
239+
_previousMsg = message;
240+
}
241+
242+
void trace(String message) {
243+
flush();
244+
_previousMsg = '${ansi.gray}$message${ansi.none}';
245+
}
246+
247+
Progress progress(String message) => new _SimpleProgress(this, message);
248+
249+
void _progressFinished(Progress progress) {}
250+
251+
void flush() {
252+
if (_previousErr != null) {
253+
io.stderr.writeln('${_createTag()} $_previousErr');
254+
_previousErr = null;
255+
} else if (_previousMsg != null) {
256+
io.stdout.writeln('${_createTag()} $_previousMsg');
257+
_previousMsg = null;
258+
}
259+
}
260+
261+
String _createTag() {
262+
int millis = _timer.elapsedMilliseconds;
263+
_timer.reset();
264+
return '[${millis.toString().padLeft(4)} ms]';
265+
}
266+
}

lib/cli_util.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
library cli_util;
5+
/// Utilities to return the Dart SDK location.
66
77
import 'dart:io';
88

0 commit comments

Comments
 (0)