From 8adaa25cbe92997b624ca4cf5e5b1cd8d4589fba Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Thu, 13 Oct 2022 17:23:31 +0200 Subject: [PATCH] feat(mason_logger): generic support for `chooseOne` and `chooseAny` (#539) --- .../mason_cli/test/commands/make_test.dart | 4 +- packages/mason_logger/example/main.dart | 22 ++++++ .../mason_logger/lib/src/mason_logger.dart | 37 +++++----- .../test/src/mason_logger_test.dart | 67 +++++++++++++++++++ 4 files changed, 112 insertions(+), 18 deletions(-) diff --git a/packages/mason_cli/test/commands/make_test.dart b/packages/mason_cli/test/commands/make_test.dart index 2e621557c..fbbb9e359 100644 --- a/packages/mason_cli/test/commands/make_test.dart +++ b/packages/mason_cli/test/commands/make_test.dart @@ -621,7 +621,7 @@ bricks: )..createSync(recursive: true); Directory.current = testDir.path; when( - () => logger.chooseOne( + () => logger.chooseOne( any(), choices: any(named: 'choices'), defaultValue: any(named: 'defaultValue'), @@ -680,7 +680,7 @@ bricks: )..createSync(recursive: true); Directory.current = testDir.path; when( - () => logger.chooseAny( + () => logger.chooseAny( any(), choices: any(named: 'choices'), defaultValues: any(named: 'defaultValues'), diff --git a/packages/mason_logger/example/main.dart b/packages/mason_logger/example/main.dart index 0cd4547eb..993d0a6dc 100644 --- a/packages/mason_logger/example/main.dart +++ b/packages/mason_logger/example/main.dart @@ -1,5 +1,12 @@ import 'package:mason_logger/mason_logger.dart'; +enum Shape { + square, + circle, + triangle, + diamond, +} + Future main() async { final logger = Logger(level: Level.verbose) ..info('info') @@ -52,4 +59,19 @@ Future main() async { uri: Uri.parse('https://github.com/felangel/mason'), ); logger.info('To learn more, visit the $repoLink.'); + + final shape = logger.chooseOne( + 'What is your favorite shape?', + choices: Shape.values, + display: (shape) => 'Is it a ${shape.name}?', + ); + logger.info('You chose $shape!'); + + final shapes = logger.chooseAny( + 'Or did you want to choose multiples?', + choices: Shape.values, + defaultValues: [shape], + display: (shape) => 'Is it a ${shape.name}?', + ); + logger.info('You chose the following shapes: $shapes!'); } diff --git a/packages/mason_logger/lib/src/mason_logger.dart b/packages/mason_logger/lib/src/mason_logger.dart index 2d3ddbaf7..70d1ccbef 100644 --- a/packages/mason_logger/lib/src/mason_logger.dart +++ b/packages/mason_logger/lib/src/mason_logger.dart @@ -144,12 +144,15 @@ class Logger { /// /// An optional [defaultValue] can be specified. /// The [defaultValue] must be one of the provided [choices]. - String chooseOne( + T chooseOne( String? message, { - required List choices, - String? defaultValue, + required List choices, + T? defaultValue, + String Function(T choice)? display, }) { - final hasDefault = defaultValue != null && defaultValue.isNotEmpty; + final _display = display ?? (value) => '$value'; + final hasDefault = + defaultValue != null && _display(defaultValue).isNotEmpty; var index = hasDefault ? choices.indexOf(defaultValue) : 0; void writeChoices() { @@ -166,11 +169,11 @@ class Logger { if (isCurrent) { _stdout ..write(green.wrap('❯')) - ..write(' $checkBox ${lightCyan.wrap(choice)}'); + ..write(' $checkBox ${lightCyan.wrap(_display(choice))}'); } else { _stdout ..write(' ') - ..write(' $checkBox $choice'); + ..write(' $checkBox ${_display(choice)}'); } if (choices.last != choice) { _stdout.write('\n'); @@ -185,8 +188,8 @@ class Logger { writeChoices(); final event = []; - var result = ''; - while (result.isEmpty) { + T? result; + while (result == null) { final byte = _stdin.readByteSync(); if (event.length == 3) event.clear(); event.add(byte); @@ -209,7 +212,7 @@ class Logger { // show cursor ..write('\x1b[?25h') ..write('$message ') - ..writeln(styleDim.wrap(lightCyan.wrap(choices[index]))); + ..writeln(styleDim.wrap(lightCyan.wrap(_display(choices[index])))); result = choices[index]; break; @@ -220,7 +223,7 @@ class Logger { writeChoices(); } - return result; + return result!; } /// Prompts user with [message] to choose zero or more values @@ -228,11 +231,13 @@ class Logger { /// /// An optional list of [defaultValues] can be specified. /// The [defaultValues] must be one of the provided [choices]. - List chooseAny( + List chooseAny( String? message, { - required List choices, - List? defaultValues, + required List choices, + List? defaultValues, + String Function(T choice)? display, }) { + final _display = display ?? (value) => '$value'; final hasDefaults = defaultValues != null && defaultValues.isNotEmpty; final selections = hasDefaults ? defaultValues.map((value) => choices.indexOf(value)).toSet() @@ -255,11 +260,11 @@ class Logger { if (isCurrent) { _stdout ..write(green.wrap('❯')) - ..write(' $checkBox ${lightCyan.wrap(choice)}'); + ..write(' $checkBox ${lightCyan.wrap(_display(choice))}'); } else { _stdout ..write(' ') - ..write(' $checkBox $choice'); + ..write(' $checkBox ${_display(choice)}'); } if (choices.last != choice) { _stdout.write('\n'); @@ -274,7 +279,7 @@ class Logger { writeChoices(); final event = []; - List? results; + List? results; while (results == null) { final byte = _stdin.readByteSync(); if (event.length == 3) event.clear(); diff --git a/packages/mason_logger/test/src/mason_logger_test.dart b/packages/mason_logger/test/src/mason_logger_test.dart index 6b5965c88..62b113036 100644 --- a/packages/mason_logger/test/src/mason_logger_test.dart +++ b/packages/mason_logger/test/src/mason_logger_test.dart @@ -904,6 +904,38 @@ void main() { stdin: () => stdin, ); }); + + test('converts list to a preferred display', () { + IOOverrides.runZoned( + () { + const message = 'test message'; + when(() => stdin.readByteSync()).thenReturn(10); + final actual = Logger().chooseAny>( + message, + choices: [ + {'key': 'a'}, + {'key': 'b'}, + {'key': 'c'}, + ], + display: (data) => 'Key: ${data['key']}', + ); + expect(actual, isEmpty); + verifyInOrder([ + () => stdout.write('\x1b7'), + () => stdout.write('\x1b[?25l'), + () => stdout.writeln(message), + () => stdout.write(green.wrap('❯')), + () => stdout.write(' ◯ ${lightCyan.wrap('Key: a')}'), + () => stdout.write(' '), + () => stdout.write(' ◯ Key: b'), + () => stdout.write(' '), + () => stdout.write(' ◯ Key: c'), + ]); + }, + stdout: () => stdout, + stdin: () => stdin, + ); + }); }); group('.chooseOne', () { @@ -1252,6 +1284,41 @@ void main() { stdin: () => stdin, ); }); + + test('converts list to a preferred display', () { + IOOverrides.runZoned( + () { + const message = 'test message'; + const expected = {'key': 'a'}; + when(() => stdin.readByteSync()).thenReturn(10); + final actual = Logger().chooseOne>( + message, + choices: [ + {'key': 'a'}, + {'key': 'b'}, + {'key': 'c'}, + ], + display: (data) => 'Key: ${data['key']}', + ); + expect(actual, equals(expected)); + verifyInOrder([ + () => stdout.write('\x1b7'), + () => stdout.write('\x1b[?25l'), + () => stdout.writeln(message), + () => stdout.write(green.wrap('❯')), + () => stdout.write( + ' ${lightCyan.wrap('◉')} ${lightCyan.wrap('Key: a')}', + ), + () => stdout.write(' '), + () => stdout.write(' ◯ Key: b'), + () => stdout.write(' '), + () => stdout.write(' ◯ Key: c'), + ]); + }, + stdout: () => stdout, + stdin: () => stdin, + ); + }); }); }); }