Skip to content

Commit

Permalink
fix(mason_logger): only animate progress on terminals (#582)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Nov 1, 2022
1 parent 83c16d1 commit 1797631
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 191 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/mason_logger.yaml
Expand Up @@ -36,6 +36,18 @@ jobs:
- name: Analyze
run: dart analyze --fatal-infos --fatal-warnings .

- name: Verify CI Behavior
run: |
dart test/ci.dart >> ci.txt
actual="ci.txt"
expected="test/fixtures/ci.txt"
if cmp -s "$actual" "$expected"; then
echo "PASSED"
else
echo "FAILED"
exit 1
fi
- name: Run Tests
run: |
dart pub global activate coverage 1.2.0
Expand Down
1 change: 1 addition & 0 deletions packages/mason_logger/lib/src/mason_logger.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'dart:io' as io;

import 'package:mason_logger/mason_logger.dart';
import 'package:mason_logger/src/stdio_overrides.dart';

part 'progress.dart';

Expand Down
25 changes: 19 additions & 6 deletions packages/mason_logger/lib/src/progress.dart
Expand Up @@ -51,6 +51,19 @@ class Progress {
_stopwatch
..reset()
..start();

final stdioType = StdioOverrides.current?.stdioType ?? io.stdioType;

// The animation is only shown when it would be meaningful.
// Do not animate if the stdio type is not a terminal.
if (stdioType(_stdout) != io.StdioType.terminal) {
final frames = _options.animation.frames;
final char = frames.isEmpty ? '' : frames.first;
final prefix = char.isEmpty ? char : '${lightGreen.wrap(char)} ';
_write('$prefix$_message...');
return;
}

_timer = Timer.periodic(const Duration(milliseconds: 80), _onTick);
}

Expand All @@ -62,7 +75,7 @@ class Progress {

final Stopwatch _stopwatch;

late final Timer _timer;
Timer? _timer;

String _message;

Expand All @@ -74,26 +87,26 @@ class Progress {
_write(
'''$_clearLine${lightGreen.wrap('✓')} ${update ?? _message} $_time\n''',
);
_timer.cancel();
_timer?.cancel();
}

/// End the progress and mark it as failed.
void fail([String? update]) {
_timer.cancel();
_timer?.cancel();
_write('$_clearLine${red.wrap('✗')} ${update ?? _message} $_time\n');
_stopwatch.stop();
}

/// Update the progress message.
void update(String update) {
_write(_clearLine);
if (_timer != null) _write(_clearLine);
_message = update;
_onTick(_timer);
}

/// Cancel the progress and remove the written line.
void cancel() {
_timer.cancel();
_timer?.cancel();
_write(_clearLine);
_stopwatch.stop();
}
Expand All @@ -103,7 +116,7 @@ class Progress {
'\r'; // bring cursor to the start of the current line
}

void _onTick(Timer _) {
void _onTick(Timer? _) {
_index++;
final frames = _options.animation.frames;
final char = frames.isEmpty ? '' : frames[_index % frames.length];
Expand Down
49 changes: 49 additions & 0 deletions packages/mason_logger/lib/src/stdio_overrides.dart
@@ -0,0 +1,49 @@
import 'dart:async';
import 'dart:io' as io;

const _asyncRunZoned = runZoned;

/// This class facilitates overriding [io.stdioType].
abstract class StdioOverrides {
static final _token = Object();

/// Returns the current [StdioOverrides] instance.
///
/// This will return `null` if the current [Zone] does not contain
/// any [StdioOverrides].
///
/// See also:
/// * [StdioOverrides.runZoned] to provide [StdioOverrides]
/// in a fresh [Zone].
///
static StdioOverrides? get current {
return Zone.current[_token] as StdioOverrides?;
}

/// Runs [body] in a fresh [Zone] using the provided overrides.
static R runZoned<R>(
R Function() body, {
io.StdioType Function(dynamic object) Function()? stdioType,
}) {
final overrides = _StdioOverridesScope(stdioType);
return _asyncRunZoned(
body,
zoneValues: {_token: overrides},
);
}

/// The [io.stdioType] that will be used for errors within the current [Zone].
io.StdioType Function(dynamic object) get stdioType => io.stdioType;
}

class _StdioOverridesScope extends StdioOverrides {
_StdioOverridesScope(this._stdioType);

final StdioOverrides? _previous = StdioOverrides.current;
final io.StdioType Function(dynamic object) Function()? _stdioType;

@override
io.StdioType Function(dynamic object) get stdioType {
return _stdioType?.call() ?? _previous?.stdioType ?? super.stdioType;
}
}
12 changes: 12 additions & 0 deletions packages/mason_logger/test/ci.dart
@@ -0,0 +1,12 @@
import 'package:mason_logger/mason_logger.dart';

Future<void> main() async {
final logger = Logger();
final progress = logger.progress('Calculating');
await Future<void>.delayed(const Duration(seconds: 1));
progress.update('This is taking longer than expected');
await Future<void>.delayed(const Duration(seconds: 1));
progress.update('Almost done');
await Future<void>.delayed(const Duration(seconds: 1));
progress.complete('Done');
}
1 change: 1 addition & 0 deletions packages/mason_logger/test/fixtures/ci.txt
@@ -0,0 +1 @@
⠋ Calculating...⠙ This is taking longer than expected... (1.0s)⠹ Almost done... (2.0s)✓ Done (3.0s)
Expand Down

0 comments on commit 1797631

Please sign in to comment.