Skip to content

Commit

Permalink
feat: create VeryGoodCommandRunner (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Jan 31, 2021
1 parent d349389 commit 5a936d6
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 7 deletions.
14 changes: 10 additions & 4 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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/very_good_coverage@v1.1.1
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ doc/api/

# Temporary Files
.tmp/

# Files generated during tests
.test_coverage.dart
coverage/
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Developed with 💙 by [Very Good Ventures](very_good_ventures_link) 🦄

[![ci][ci_badge]][ci_link]
[![coverage][coverage_badge]][ci_link]
[![License: MIT][license_badge]][license_link]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]

Expand All @@ -14,14 +15,34 @@ A Very Good Command Line Interface for Dart.

## Commands

### `very_good create`
### `$ very_good create`

Create a new very good flutter application in seconds.

![Very Good CLI][very_good_cli]

### `$ very_good --help`

See the complete list of commands and usage information.

```sh
🦄 A Very Good Commandline Interface

Usage: very_good <command> [arguments]

Global options:
-h, --help Print this usage information.
--version Print the current version.

Available commands:
help Display help information for very_good.

Run "very_good help <command>" for more information about a command.
```

[ci_badge]: https://github.com/VeryGoodOpenSource/very_good_cli/workflows/ci/badge.svg
[ci_link]: https://github.com/VeryGoodOpenSource/very_good_cli/actions
[coverage_badge]: coverage_badge.svg
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[logo]: docs/assets/vgv_logo.png
Expand Down
18 changes: 17 additions & 1 deletion bin/very_good.dart
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));
}
20 changes: 20 additions & 0 deletions coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
53 changes: 53 additions & 0 deletions lib/src/command_runner.dart
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);
}
}
9 changes: 8 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ version: 0.0.1-dev.0
homepage: https://github.com/VeryGoodOpenSource/very_good_cli

environment:
sdk: ">=2.8.1 <3.0.0"
sdk: ">=2.10.0 <3.0.0"

dependencies:
args: ^1.6.0
io: ^0.3.4
mason: ^0.0.1-dev.22

dev_dependencies:
coverage: ^0.13.4
build_runner: ^1.10.0
build_verify: ^1.1.1
build_version: ^2.0.1
mockito: ^4.0.0
test: ^1.14.3
very_good_analysis: ^1.0.4

Expand Down
128 changes: 128 additions & 0 deletions test/src/command_runner_test.dart
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'));
});
});
});
});
}

0 comments on commit 5a936d6

Please sign in to comment.