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