Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(mason_logger): only animate progress on terminals #582

Merged
merged 5 commits into from Nov 1, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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