-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: create VeryGoodCommandRunner (#4)
- Loading branch information
Showing
8 changed files
with
262 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,18 +6,24 @@ jobs: | |
build: | ||
runs-on: ubuntu-latest | ||
container: | ||
image: google/dart:2.9.3 | ||
image: google/dart:2.10.0 | ||
steps: | ||
- uses: actions/checkout@v2 | ||
|
||
- name: Install Dependencies | ||
run: pub get | ||
|
||
- name: Format | ||
run: dartfmt --dry-run --set-exit-if-changed . | ||
run: dart format --set-exit-if-changed . | ||
|
||
- name: Analyze | ||
run: dartanalyzer --fatal-infos --fatal-warnings . | ||
run: dart analyze --fatal-infos --fatal-warnings . | ||
|
||
- name: Ensure Build | ||
- name: Verify Build | ||
run: pub run test --run-skipped -t pull-request-only | ||
|
||
- name: Run Tests | ||
run: dart test -x pull-request-only --coverage=coverage && pub run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.packages --report-on=lib | ||
|
||
- name: Check Code Coverage | ||
uses: VeryGoodOpenSource/[email protected] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,7 @@ doc/api/ | |
|
||
# Temporary Files | ||
.tmp/ | ||
|
||
# Files generated during tests | ||
.test_coverage.dart | ||
coverage/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,17 @@ | ||
void main() {} | ||
import 'dart:io'; | ||
import 'package:very_good_cli/src/command_runner.dart'; | ||
|
||
void main(List<String> args) async { | ||
await _flushThenExit(await VeryGoodCommandRunner().run(args)); | ||
} | ||
|
||
/// Flushes the stdout and stderr streams, then exits the program with the given | ||
/// status code. | ||
/// | ||
/// This returns a Future that will never complete, since the program will have | ||
/// exited already. This is useful to prevent Future chains from proceeding | ||
/// after you've decided to exit. | ||
Future _flushThenExit(int status) { | ||
return Future.wait<void>([stdout.close(), stderr.close()]) | ||
.then<void>((_) => exit(status)); | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import 'package:args/args.dart'; | ||
import 'package:args/command_runner.dart'; | ||
import 'package:io/io.dart'; | ||
import 'package:mason/mason.dart'; | ||
|
||
import 'version.dart'; | ||
|
||
/// {@template very_good_command_runner} | ||
/// A [CommandRunner] for the Very Good CLI. | ||
/// {@endtemplate} | ||
class VeryGoodCommandRunner extends CommandRunner<int> { | ||
/// {@macro very_good_command_runner} | ||
VeryGoodCommandRunner({Logger logger}) | ||
: _logger = logger ?? Logger(), | ||
super('very_good', '🦄 A Very Good Commandline Interface') { | ||
argParser.addFlag( | ||
'version', | ||
negatable: false, | ||
help: 'Print the current version.', | ||
); | ||
} | ||
|
||
final Logger _logger; | ||
|
||
@override | ||
Future<int> run(Iterable<String> args) async { | ||
try { | ||
final _argResults = parse(args); | ||
return await runCommand(_argResults) ?? ExitCode.success.code; | ||
} on FormatException catch (e) { | ||
_logger | ||
..err(e.message) | ||
..info('') | ||
..info(usage); | ||
return ExitCode.usage.code; | ||
} on UsageException catch (e) { | ||
_logger | ||
..err(e.message) | ||
..info('') | ||
..info(usage); | ||
return ExitCode.usage.code; | ||
} | ||
} | ||
|
||
@override | ||
Future<int> runCommand(ArgResults topLevelResults) async { | ||
if (topLevelResults['version'] == true) { | ||
_logger.info('very_good version: $packageVersion'); | ||
return ExitCode.success.code; | ||
} | ||
return super.runCommand(topLevelResults); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// ignore_for_file: no_adjacent_strings_in_list | ||
import 'dart:async'; | ||
|
||
import 'package:args/command_runner.dart'; | ||
import 'package:io/io.dart'; | ||
import 'package:mason/mason.dart'; | ||
import 'package:mockito/mockito.dart'; | ||
import 'package:test/test.dart'; | ||
import 'package:very_good_cli/src/command_runner.dart'; | ||
import 'package:very_good_cli/src/version.dart'; | ||
|
||
class MockLogger extends Mock implements Logger {} | ||
|
||
void main() { | ||
group('VeryGoodCommandRunner', () { | ||
List<String> printLogs; | ||
Logger logger; | ||
VeryGoodCommandRunner commandRunner; | ||
|
||
void Function() overridePrint(void Function() fn) { | ||
return () { | ||
final spec = ZoneSpecification(print: (_, __, ___, String msg) { | ||
printLogs.add(msg); | ||
}); | ||
return Zone.current.fork(specification: spec).run<void>(fn); | ||
}; | ||
} | ||
|
||
setUp(() { | ||
printLogs = []; | ||
logger = MockLogger(); | ||
commandRunner = VeryGoodCommandRunner(logger: logger); | ||
}); | ||
|
||
test('can be instantiated without an explicit logger instance', () { | ||
final commandRunner = VeryGoodCommandRunner(); | ||
expect(commandRunner, isNotNull); | ||
}); | ||
|
||
group('run', () { | ||
test('handles FormatException', () async { | ||
const exception = FormatException('oops!'); | ||
var isFirstInvocation = true; | ||
when(logger.info(any)).thenAnswer((_) { | ||
if (isFirstInvocation) { | ||
isFirstInvocation = false; | ||
throw exception; | ||
} | ||
}); | ||
final result = await commandRunner.run(['--version']); | ||
expect(result, equals(ExitCode.usage.code)); | ||
verify(logger.err(exception.message)).called(1); | ||
verify(logger.info(commandRunner.usage)).called(1); | ||
}); | ||
|
||
test('handles UsageException', () async { | ||
final exception = UsageException('oops!', commandRunner.usage); | ||
var isFirstInvocation = true; | ||
when(logger.info(any)).thenAnswer((_) { | ||
if (isFirstInvocation) { | ||
isFirstInvocation = false; | ||
throw exception; | ||
} | ||
}); | ||
final result = await commandRunner.run(['--version']); | ||
expect(result, equals(ExitCode.usage.code)); | ||
verify(logger.err(exception.message)).called(1); | ||
verify(logger.info(commandRunner.usage)).called(1); | ||
}); | ||
|
||
test('handles no command', overridePrint(() async { | ||
const expectedPrintLogs = [ | ||
'🦄 A Very Good Commandline Interface\n' | ||
'\n' | ||
'Usage: very_good <command> [arguments]\n' | ||
'\n' | ||
'Global options:\n' | ||
'-h, --help Print this usage information.\n' | ||
' --version Print the current version.\n' | ||
'\n' | ||
'Available commands:\n' | ||
' help Display help information for very_good.\n' | ||
'\n' | ||
'''Run "very_good help <command>" for more information about a command.''' | ||
]; | ||
final result = await commandRunner.run([]); | ||
expect(printLogs, equals(expectedPrintLogs)); | ||
expect(result, equals(ExitCode.success.code)); | ||
})); | ||
|
||
group('--help', () { | ||
test('outputs usage', overridePrint(() async { | ||
const expectedPrintLogs = [ | ||
'🦄 A Very Good Commandline Interface\n' | ||
'\n' | ||
'Usage: very_good <command> [arguments]\n' | ||
'\n' | ||
'Global options:\n' | ||
'-h, --help Print this usage information.\n' | ||
' --version Print the current version.\n' | ||
'\n' | ||
'Available commands:\n' | ||
' help Display help information for very_good.\n' | ||
'\n' | ||
'''Run "very_good help <command>" for more information about a command.''' | ||
]; | ||
final result = await commandRunner.run(['--help']); | ||
expect(printLogs, equals(expectedPrintLogs)); | ||
expect(result, equals(ExitCode.success.code)); | ||
|
||
printLogs.clear(); | ||
|
||
final resultAbbr = await commandRunner.run(['-h']); | ||
expect(printLogs, equals(expectedPrintLogs)); | ||
expect(resultAbbr, equals(ExitCode.success.code)); | ||
})); | ||
}); | ||
|
||
group('--version', () { | ||
test('outputs current version', () async { | ||
final result = await commandRunner.run(['--version']); | ||
expect(result, equals(ExitCode.success.code)); | ||
verify(logger.info('very_good version: $packageVersion')); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} |