Commit 943e8a70 authored by derek's avatar derek

Initial upload 0.1.0.

parents
# Files and directories created by pub
.packages
.pub/
build/
# If you're building an application, you may want to check-in your pubspec.lock
pubspec.lock
# Directory created by dartdoc
# If you don't generate documentation locally you can remove this line.
doc/api/
# OS generated files
.DS_Store
ehthumbs.db
Icon?
Thumbs.db
# IDEs
/.idea
# Others
/var
/test/var/*
!/test/var
!/test/var/.gitkeep
# Changelog
## 0.1.0
- Initial version, created by Derek Li
Copyright (c) 2017, Derek Li.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the <organization> nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# dl_console
### Description
A simple wrapper of dart 'args' package for building a command line console with easily plugged in commands.
### Limitations
* Anything that 'package:args/args.dart' does not support.
### Samples
See [mock](./test/mock)
\ No newline at end of file
analyzer:
strong-mode: true
# exclude:
# - path/to/excluded/files/**
# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
linter:
rules:
- cancel_subscriptions
- close_sinks
- hash_and_equals
- iterable_contains_unrelated_type
- list_remove_unrelated_type
- test_types_in_equals
- unrelated_type_equality_checks
- valid_regexps
// Copyright (c) 2017, Derek Li. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
export "src/annotations.dart";
export "src/command.dart";
export "src/console.dart";
export "src/console_config.dart";
export "src/console_config_builder.dart";
export "src/console_config_mirrors.dart";
export "src/errors.dart";
\ No newline at end of file
/// Annotation for a console command.
class Command {
final String _name;
const Command(String this._name);
String get name {
return _name;
}
}
\ No newline at end of file
import 'dart:async';
import 'package:args/args.dart';
/// Console printer.
class ConsoleOutput {
static const TYPE_INFO = 1;
static const TYPE_ERROR = 2;
static const TYPE_ALERT = 3;
const ConsoleOutput();
/// Print the message according to the type code.
_print(String message, {int type: ConsoleOutput.TYPE_INFO}) {
// @todo Color the message according to the type.
print(message);
}
/// Print out the message as normal information.
info(String message) {
_print(message);
}
/// Print the message as an error.
error(String message) {
_print(message, type: ConsoleOutput.TYPE_ERROR);
}
/// Print the message as an alert.
alert(String message) {
_print(message, type: ConsoleOutput.TYPE_ALERT);
}
}
/// Command interface
abstract class ConsoleCommand {
/// Configure the command arguments.
configure(ArgParser argParser);
/// Run the command with the passed arguments.
execute(ArgResults argResults, ConsoleOutput consoleOutput);
}
/// Async command interface
abstract class AsyncConsoleCommand extends ConsoleCommand {
@override
Future execute(ArgResults argResults, ConsoleOutput consoleOutput);
}
\ No newline at end of file
import 'dart:io';
import 'dart:async';
import 'package:args/args.dart';
import './command.dart';
import './console_config.dart' show ConsoleConfig;
import './errors.dart' show OptionMissedError;
/// A console must have a run method.
abstract class Console {
/// Create an async console.
factory Console.standard(ConsoleConfig config) => new StandardConsole(config);
/// Run the console.
run(List<String> arguments);
}
/// A index of all the command instances.
class StandardConsole implements Console {
/// The application configuration.
ConsoleConfig _config;
/// Argument parser.
ArgParser _consoleArgParser;
bool _isConfigured = false;
/// The console default constructor.
StandardConsole(ConsoleConfig this._config) : this._consoleArgParser = new ArgParser();
/// Internal method to configure all the commands.
_configureCommands() {
_consoleArgParser.addFlag('help', abbr: 'h',
help: 'Available commands: \n ${_config.commands.keys.join("\n")}');
_config.commands.forEach(_configureCommand);
}
/// Configure the given command.
_configureCommand(name, ConsoleCommand command) {
ArgParser commandParser = new ArgParser();
commandParser.addFlag('help', abbr: 'h', help: 'Show help message.');
_consoleArgParser.addCommand(name, commandParser);
command.configure(commandParser);
_isConfigured = true;
}
/// Internal method to run the command according to received arguments.
_runCommand(ArgResults argResults) async {
var commandResults = argResults.command;
if (commandResults is ArgResults) {
// If the command is not found in the config, print the error and exit.
// This if shouldn't be reached if everything is configured correctly since the commandResults
// will be null if the command is not found.
if (!_config.commands.containsKey(commandResults.name)) {
_config.consoleOutput.error('Invalid command.');
_printHelpAndExit();
}
// Print the sub command help message.
if (commandResults['help']) {
_printCommandHelpAndExit(commandResults.name);
}
// Execute the command.
try {
ConsoleCommand currentCommand = _config.commands[commandResults.name];
if (currentCommand is AsyncConsoleCommand) {
return await currentCommand.execute(commandResults, _config.consoleOutput);
} else {
return currentCommand.execute(commandResults, _config.consoleOutput);
}
} on OptionMissedError catch (exception) {
_config.consoleOutput.error(exception.toString());
_printCommandHelpAndExit(commandResults.name);
} catch (exception, traceStack) {
_config.consoleOutput.error(exception);
_config.consoleOutput.error(traceStack.toString());
_printCommandHelpAndExit(commandResults.name);
}
} else {
_config.consoleOutput.error('Invalid command.');
_printHelpAndExit();
}
}
/// Print the given command help message and exist.
_printCommandHelpAndExit(String commandName) {
_config.consoleOutput.info(_consoleArgParser.commands[commandName].usage);
exit(0);
}
/// Print the console help info.
_printHelp() {
_config.consoleOutput.info(_consoleArgParser.usage);
}
/// Print the console help and exist.
_printHelpAndExit() {
_printHelp();
exit(0);
}
/// Run the application.
Future run(List<String> arguments) async {
// Configure all the commands.
if (!_isConfigured) {
_configureCommands();
}
// Run the corresponding command according to the arguments passed.
try {
ArgResults argResults = _consoleArgParser.parse(arguments);
if (argResults['help']) {
_printHelpAndExit();
}
return await _runCommand(argResults);
} on OptionMissedError catch (exception) {
_config.consoleOutput.error(exception.toString());
_printHelp();
} on FormatException catch (exception) {
_config.consoleOutput.error(exception.toString());
_printHelp();
}
}
}
\ No newline at end of file
import './command.dart';
import './errors.dart' show InvalidConfigMapError;
/// Console configuration.
abstract class ConsoleConfig {
/// Create a console config by a map.
factory ConsoleConfig.fromMap(Map configMap) => new ConsoleMapConfig(configMap);
/// Get all the commands in a map.
Map<String, ConsoleCommand> get commands;
/// Get the console output.
ConsoleOutput get consoleOutput;
}
/// Console config based on a map.
class ConsoleMapConfig implements ConsoleConfig {
Map _configMap;
Map _resolvedMap;
ConsoleMapConfig(Map this._configMap);
/// Resolve the passed config map.
_resolve() {
_resolvedMap = {};
_resolvedMap['commands'] = _configMap.containsKey('commands')
? _configMap['commands']
: {};
if (!_configMap.containsKey('consoleOutput') || !(_configMap['consoleOutput'] is ConsoleOutput)) {
throw new InvalidConfigMapError('consoleOutput',
message: 'Console config is required and has to be a type of ConsoleOutput.');
}
_resolvedMap['consoleOutput'] = _configMap['consoleOutput'];
}
Map get resolvedMap {
if (_resolvedMap == null) {
_resolve();
}
return _resolvedMap;
}
Map<String, ConsoleCommand> get commands {
return resolvedMap['commands'];
}
ConsoleOutput get consoleOutput {
return resolvedMap['consoleOutput'];
}
}
import './command.dart';
import './console_config.dart';
/// The default console config builder (in a 'setter' way).
class ConsoleConfigBuilder {
Map<String, ConsoleCommand> _commands = {};
ConsoleOutput _consoleOutput;
addCommand(String name, ConsoleCommand command) {
_commands[name] = command;
}
setConsoleOutput(ConsoleOutput consoleOutput) {
_consoleOutput = consoleOutput;
}
/// Build the console config.
ConsoleMapConfig build() {
return new ConsoleConfig.fromMap({
'commands': _commands,
'consoleOutput': _consoleOutput});
}
}
\ No newline at end of file
import 'dart:mirrors';
import './annotations.dart' show Command;
import './command.dart';
import './console_config.dart';
/// Console config factory to create different kinds of console configurations.
/// @todo The best way to getting all the commands via metadata is always via static code. So then,
/// 1) No dart:mirrors is needed in run-time.
/// 2) No objects caching (serialization & deserialization) is needed.
class ConsoleConfigMetadataBuilder {
/// Create the immutable console config from metadata.
ConsoleMapConfig build() {
Map<String, ConsoleCommand> commands = {};
var mirrorSystem = currentMirrorSystem();
mirrorSystem.libraries.forEach((uri, LibraryMirror) {
// Go through every member in the library.
LibraryMirror.declarations.forEach((name, member) {
// Only pick classes.
if (member is ClassMirror) {
// Search for metadata 'Command';
var commandMetadataMirrors = member.metadata.where((instanceMirror) {
return instanceMirror.reflectee is Command;
});
if (commandMetadataMirrors.isNotEmpty) {
Command commandMetadata = commandMetadataMirrors.first.reflectee;
var commandInstance = member.newInstance(new Symbol(''), []).reflectee;
if (commandInstance is ConsoleCommand) {
commands[commandMetadata.name] = commandInstance;
}
}
}
});
});
return new ConsoleConfig.fromMap({
'commands': commands,
'consoleOutput': new ConsoleOutput()});
}
}
\ No newline at end of file
/// The error when a service is not defined (service id not found).
class InvalidConfigMapError extends Error {
String _field;
String _message;
InvalidConfigMapError(String this._field, {String message}) : this._message = message;
String toString() {
if (_message == null) {
return "Field [${_field}] is invalid.";
} else {
return "Field [${_field}] is invalid: ${_message}.";
}
}
}
/// The error when a required option is missed.
class OptionMissedError extends Error {
String _optionName;
OptionMissedError(String this._optionName);
String toString() {
return "Option [${_optionName}] is missed.";
}
}
\ No newline at end of file
name: dl_console
description: A simple wrapper of dart 'args' package for building a command line console with easily plugged in commands.
version: 0.1.0
#homepage: https://www.example.com
#author: derek <email@example.com>
environment:
sdk: '>=1.20.1 <2.0.0'
dependencies:
dev_dependencies:
test: ^0.12.0
// Copyright (c) 2017, derek. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
import 'integration/test_console.dart' as test_console;
main() async {
await test_console.run();
}
\ No newline at end of file
import 'package:test/test.dart';
import '../mock/bootstrap.dart' show Bootstrap;
import '../mock/console_app.dart' show ConsoleApp;
run() async {
test('test console app integrations.', () async {
Bootstrap bootstrap = new Bootstrap();
ConsoleApp app = bootstrap.init();
expect(await app.run(['say_hello']), equals('Hello _anomynous_.'));
expect(await app.run(['say_hello', '-n', 'derek']), equals('Hello derek.'));
expect(await app.run(['say_bye', '-n', 'derek']), equals('Bye derek.'));
});
}
\ No newline at end of file
import 'package:dl_console/src/command.dart' show ConsoleOutput;
import 'package:dl_console/src/console_config_builder.dart' show ConsoleConfigBuilder;
import './console_app.dart' show ConsoleApp;
// Here all the commands.
import './command/say_hello_command.dart' show SayHelloCommand;
import './command/say_bye_command.dart' show SayByeCommand;
class Bootstrap {
// Initialize the console app and return it.
ConsoleApp init() {
ConsoleConfigBuilder builder = new ConsoleConfigBuilder();
builder
..setConsoleOutput(new ConsoleOutput())
..addCommand('say_hello', new SayHelloCommand())
..addCommand('say_bye', new SayByeCommand());
return new ConsoleApp(builder.build());
}
}
\ No newline at end of file
import 'package:args/args.dart';
import 'package:dl_console/src/annotations.dart' show Command;
import 'package:dl_console/src/command.dart' show ConsoleCommand, ConsoleOutput;
/// Distribute files locally.
@Command('say_bye')
class SayByeCommand extends ConsoleCommand {
/// Configure the command.
configure(ArgParser argParser) {
argParser
..addOption('name', abbr: 'n', help: 'The name to say goodbye to.', defaultsTo: '_anomynous_');
}
/// Run the command.
execute(ArgResults argResults, ConsoleOutput consoleOutput) {
return 'Bye ${argResults['name']}.';
}
}
\ No newline at end of file
import 'package:args/args.dart';
import 'package:dl_console/src/annotations.dart' show Command;
import 'package:dl_console/src/command.dart' show ConsoleCommand, ConsoleOutput;
/// Distribute files locally.
@Command('say_hello')
class SayHelloCommand extends ConsoleCommand {
/// Configure the command.
configure(ArgParser argParser) {
argParser
..addOption('name', abbr: 'n', help: 'The name to say hello to.', defaultsTo: '_anomynous_');
}
/// Run the command.
execute(ArgResults argResults, ConsoleOutput consoleOutput) {
return 'Hello ${argResults['name']}.';
}
}
\ No newline at end of file
import 'dart:async';
import 'package:dl_console/src/console.dart';
import 'package:dl_console/src/console_config.dart' show ConsoleConfig;
/// The application root.
class ConsoleApp {
ConsoleConfig _consoleConfig;
Console _console;
ConsoleApp(ConsoleConfig this._consoleConfig);
/// Get the application console.
Console get console {
if (_console == null) {
_console = new Console.standard(_consoleConfig);
}
return _console;
}
/// Run the application.
Future run(List<String> arguments) async {
return await this.console.run(arguments);
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment