Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'));
});
});
});
});
}