Skip to content

Commit

Permalink
Fix #91
Browse files Browse the repository at this point in the history
  • Loading branch information
seaneagan committed Jun 10, 2015
1 parent 7026a58 commit e65792c
Show file tree
Hide file tree
Showing 12 changed files with 106 additions and 55 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
@@ -1,3 +1,11 @@
## Unreleased

- Add `name` parameter to `Option` and `Flag` ( #102 )

## 0.6.1

- Allow dynamic help content

## 0.6.0

- Deprecated `declare` in favor of `new Script`.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Expand Up @@ -27,12 +27,12 @@ main(arguments) => new Script(greet).execute(arguments);
greet(
@Rest(help: 'Name(s) to greet.')
List<String> who, {
@Option(help: 'Alternate word to greet with e.g. "Hi".')
String salutation : 'Hello',
@Option(help: 'How many !\'s to append.')
int enthusiasm : 0,
@Flag(abbr: 'l', help: 'Put names on separate lines.')
bool lineMode : false
bool lineMode : false,
@Option(name: 'greeting', help: 'Alternate word to greet with e.g. "Hi".')
String salutation : 'Hello'
}) {
print(salutation +
Expand All @@ -46,7 +46,7 @@ We can call this script as follows:
```shell
$ greet.dart Bob
Hello Bob
$ greet.dart --salutation Hi --enthusiasm 3 -l Alice Bob
$ greet.dart --enthusiasm 3 -l --greeting Hi Alice Bob
Hi
Alice,
Bob!!!
Expand Down
6 changes: 3 additions & 3 deletions example/greet.dart
Expand Up @@ -10,12 +10,12 @@ main(arguments) => new Script(greet).execute(arguments);
greet(
@Rest(valueHelp: 'who', help: 'Name(s) to greet.')
List<String> who, {
@Option(valueHelp: 'greeting', help: 'Alternate <greeting> to greet with e.g. "Hi".')
String salutation : 'Hello',
@Option(help: 'How many !\'s to append.')
int enthusiasm : 0,
@Flag(abbr: 'l', help: 'Put names on separate lines.')
bool lineMode : false
bool lineMode : false,
@Option(name: 'greeting', help: 'Alternate word to greet with e.g. "Hi".')
String salutation : 'Hello'
}) {

print(salutation +
Expand Down
12 changes: 8 additions & 4 deletions lib/src/annotations.dart
Expand Up @@ -19,8 +19,10 @@ class Option extends HelpAnnotation {
/// value into a form accepted by the [Script]. It should throw to indicate
/// that the argument is invalid.
final Function parser;
/// A function which validates and/or transforms the raw command-line String
/// A short label or description of the option's value.
final String valueHelp;
/// The non-abbreviated name used to identify the option on the command-line.
final String name;

const Option({
help,
Expand All @@ -30,7 +32,8 @@ class Option extends HelpAnnotation {
this.allowMultiple,
this.hide,
this.defaultsTo,
this.valueHelp})
this.valueHelp,
this.name})
: this.parser = parser,
super(help: help);
}
Expand All @@ -51,9 +54,10 @@ class Flag extends Option {
defaultsTo,
bool hide,
bool negatable,
String metaName})
String valueHelp,
String name})
: this.negatable = negatable == null ? false : negatable,
super(help: help, abbr: abbr, defaultsTo: defaultsTo, hide: hide, valueHelp: metaName);
super(help: help, abbr: abbr, defaultsTo: defaultsTo, hide: hide, valueHelp: valueHelp, name: name);
}

/// An annotation which gives example arguments that can be passed to a
Expand Down
3 changes: 2 additions & 1 deletion lib/src/plugins/completion/completion.dart
Expand Up @@ -134,7 +134,8 @@ class CompletionOptionAdapter extends CompletionAdapter {
CompletionOptionAdapter() : super._();

updateUsage(Usage usage) {
usage.addOption(_COMPLETION, new Option(
usage.addOption(new Option(
name: _COMPLETION,
allowed: _installationNamesHelp,
help: 'Tab completion for this command.'));
}
Expand Down
3 changes: 2 additions & 1 deletion lib/src/plugins/help/help.dart
Expand Up @@ -28,7 +28,8 @@ class Help extends Plugin {
usage.commands.values.forEach(updateUsage);

if(!usage.options.containsKey(_HELP)) {
usage.addOption(_HELP, new Flag(
usage.addOption(new Flag(
name: _HELP,
abbr: 'h',
help: 'Print this usage information.',
negatable: false));
Expand Down
14 changes: 9 additions & 5 deletions lib/src/script_impl.dart
Expand Up @@ -64,6 +64,8 @@ abstract class DeclarationScript extends ScriptImpl {

MethodMirror get _method;

final Map<Usage, Map<String, String>> usageOptionParameterMap = {};

List<Plugin> get plugins {
if(_plugins == null) {
_plugins = [];
Expand All @@ -90,7 +92,7 @@ abstract class DeclarationScript extends ScriptImpl {

Usage get usage {
if(_usage == null) {
_usage = getUsageFromFunction(_method);
_usage = getUsageFromFunction(_method, this);
plugins.forEach((plugin) => plugin.updateUsage(_usage));
}
return _usage;
Expand All @@ -99,7 +101,7 @@ abstract class DeclarationScript extends ScriptImpl {

_handleResults(CommandInvocation commandInvocation, bool isWindows) {

var topInvocation = convertCommandInvocationToInvocation(commandInvocation, _method);
var topInvocation = convertCommandInvocationToInvocation(commandInvocation, _method, usageOptionParameterMap[usage]);

var topResult = _getTopCommandResult(topInvocation);

Expand Down Expand Up @@ -131,10 +133,11 @@ abstract class DeclarationScript extends ScriptImpl {
var classMirror = result.type;
var methods = classMirror.instanceMembers;
var commandMethod = methods[commandSymbol];
var invocation = convertCommandInvocationToInvocation(commandInvocation, commandMethod, memberName: commandSymbol);
var subResult = result.delegate(invocation);
Usage subUsage;
if (commandInvocation.subCommand != null) subUsage = usage.commands[commandInvocation.subCommand.name];
subUsage = usage.commands[commandInvocation.name];
var optionParameterMap = subUsage != null ? usageOptionParameterMap[subUsage] : {};
var invocation = convertCommandInvocationToInvocation(commandInvocation, commandMethod, optionParameterMap, memberName: commandSymbol);
var subResult = result.delegate(invocation);
return _handleSubCommands(reflect(subResult), commandInvocation.subCommand, subUsage, isWindows);
}

Expand Down Expand Up @@ -172,3 +175,4 @@ class ClassScript extends DeclarationScript {
invocation.positionalArguments,
invocation.namedArguments);
}

9 changes: 6 additions & 3 deletions lib/src/usage.dart
Expand Up @@ -77,9 +77,12 @@ class Usage {
}
return _optionsView;
}
addOption(String name, Option option) {
addOptionToParser(parser, name, option);
_options[name] = option;
addOption(Option option) {
if (option.name == null) {
throw new ArgumentError('option.name cannot be null');
}
addOptionToParser(parser, option);
_options[option.name] = option;
}

List<String> _commandPath;
Expand Down
34 changes: 21 additions & 13 deletions lib/src/util.dart
Expand Up @@ -10,6 +10,7 @@ import 'package:collection/iterable_zip.dart';
import 'package:mockable_filesystem/filesystem.dart' as filesystem;

import '../unscripted.dart';
import 'script_impl.dart';
import 'string_codecs.dart';
import 'usage.dart';
import 'invocation_maker.dart';
Expand Down Expand Up @@ -79,9 +80,10 @@ getDefaultPositionalName(Symbol symbol) {
// return MirrorSystem.getName(symbol).toUpperCase();
}

Usage getUsageFromFunction(MethodMirror methodMirror, {Usage usage}) {
Usage getUsageFromFunction(MethodMirror methodMirror, DeclarationScript script, {Usage usage}) {

if(usage == null) usage = new Usage();
script.usageOptionParameterMap[usage] = {};

usage.rest = getRestFromMethod(methodMirror);

Expand Down Expand Up @@ -160,23 +162,26 @@ Usage getUsageFromFunction(MethodMirror methodMirror, {Usage usage}) {
defaultValue = parameter.defaultValue.reflectee;
}

var optionName = dashesToCamelCase.decode(option.name != null
? option.name : parameterName);

// Update option with any configuration detected in the parameter.
// TODO: This is not very maintainable.
// Use reflection instead to copy values over?
option = option is Flag ?
new Flag(help: option.help, abbr: option.abbr, hide: option.hide,
defaultsTo: defaultValue, negatable: option.negatable) :
defaultsTo: defaultValue, negatable: option.negatable,
name: optionName) :
new Option(help: option.help, abbr: option.abbr,
defaultsTo: defaultValue, allowed: option.allowed,
allowMultiple: allowMultiple, hide: option.hide,
valueHelp: option.valueHelp, parser: parser);

var optionName = dashesToCamelCase.decode(parameterName);
valueHelp: option.valueHelp, parser: parser, name: optionName);

usage.addOption(optionName, option);
script.usageOptionParameterMap[usage][optionName] = parameterName;
usage.addOption(option);
});

_addSubCommandsForClass(usage, methodMirror.returnType);
_addSubCommandsForClass(usage, script, methodMirror.returnType);

return usage;
}
Expand All @@ -190,7 +195,7 @@ getParserFromType(TypeMirror typeMirror) {
return null;
}

_addSubCommandsForClass(Usage usage, TypeMirror typeMirror) {
_addSubCommandsForClass(Usage usage, DeclarationScript script, TypeMirror typeMirror) {
if(typeMirror is ClassMirror) {

var methods = typeMirror.instanceMembers.values;
Expand All @@ -214,6 +219,7 @@ _addSubCommandsForClass(Usage usage, TypeMirror typeMirror) {
.decode(MirrorSystem.getName(methodMirror.simpleName));
getUsageFromFunction(
methodMirror,
script,
usage: usage.addCommand(commandName, subCommand));
});
}
Expand Down Expand Up @@ -241,7 +247,7 @@ getFirstMetadataMatch(DeclarationMirror declaration, bool match(metadata)) {
.firstWhere(match, orElse: () => null);
}

void addOptionToParser(ArgParser parser, String name, Option option) {
void addOptionToParser(ArgParser parser, Option option) {

var suffix;

Expand Down Expand Up @@ -282,7 +288,7 @@ void addOptionToParser(ArgParser parser, String name, Option option) {

var parserMethod = 'add$suffix';

reflect(parser).invoke(new Symbol(parserMethod), [name], namedParameters);
reflect(parser).invoke(new Symbol(parserMethod), [option.name], namedParameters);
}

// Returns a List whose elements are the required argument count, and whether
Expand Down Expand Up @@ -329,18 +335,20 @@ MethodMirror getUnnamedConstructor(ClassMirror classMirror) {
constructor.constructorName == const Symbol(''), orElse: () => null);
}

convertCommandInvocationToInvocation(CommandInvocation commandInvocation, MethodMirror method, {Symbol memberName: #call}) {
convertCommandInvocationToInvocation(CommandInvocation commandInvocation, MethodMirror method, Map<String, String> optionParameterMap, {Symbol memberName: #call}) {

var positionals = commandInvocation.positionals;

var named = {};

commandInvocation.options.forEach((option, value) {
var paramSymbol = new Symbol(dashesToCamelCase.encode(option));
var paramSymbol = new Symbol(optionParameterMap[option]);
var paramExists = method.parameters.any((param) =>
param.simpleName == paramSymbol);
if(paramExists) {
if (paramExists) {
named[paramSymbol] = value;
} else {
// print('Param "${optionParameterMap[option]}" does not exist for option "$option"');
}
});

Expand Down
1 change: 0 additions & 1 deletion test/foo.txt

This file was deleted.

30 changes: 15 additions & 15 deletions test/plugins/completion/usage_completion_test.dart
Expand Up @@ -57,8 +57,8 @@ main() {

test('should suggest all long options for -- or empty', () {
var usage = new Usage()
..addOption('aaa', new Option())
..addOption('bbb', new Option());
..addOption(new Option(name: 'aaa'))
..addOption(new Option(name: 'bbb'));
addPlugins(usage);

testAllowed(usage, '', [
Expand All @@ -78,8 +78,8 @@ main() {

test('should suggest long options with same prefix', () {
var usage = new Usage()
..addOption('aaa', new Option())
..addOption('bbb', new Option());
..addOption(new Option(name: 'aaa'))
..addOption(new Option(name: 'bbb'));
addPlugins(usage);

testAllowed(usage, '--a', ['--aaa']);
Expand All @@ -89,7 +89,7 @@ main() {

test('should complete - to --', () {
var usage = new Usage()
..addOption('opt', new Option(abbr: 'o'));
..addOption(new Option(name: 'opt', abbr: 'o'));
addPlugins(usage);

testAllowed(usage, '-', [
Expand All @@ -101,8 +101,8 @@ main() {

test('should complete short option to long option', () {
var usage = new Usage()
..addOption('opt', new Option(abbr: 'o'))
..addOption('flag', new Flag(abbr: 'f'));
..addOption(new Option(name: 'opt', abbr: 'o'))
..addOption(new Flag(name: 'flag', abbr: 'f'));
addPlugins(usage);

testAllowed(usage, '-o', [['--opt']]);
Expand All @@ -114,10 +114,10 @@ main() {

test('should suggest allowed', () {
var usage = new Usage()
..addOption('aaa', new Option(abbr: 'a', allowed: ['x', 'y', 'z']))
..addOption('bbb', new Option(abbr: 'b', allowed: {'x': '', 'y': '', 'z': ''}))
..addOption('ccc', new Option(abbr: 'c'))
..addOption('flag', new Flag(abbr: 'f'));
..addOption(new Option(name: 'aaa', abbr: 'a', allowed: ['x', 'y', 'z']))
..addOption(new Option(name: 'bbb', abbr: 'b', allowed: {'x': '', 'y': '', 'z': ''}))
..addOption(new Option(name: 'ccc', abbr: 'c'))
..addOption(new Flag(name: 'flag', abbr: 'f'));
addPlugins(usage);

testAllowed(usage, '--aaa ', ['x', 'y', 'z']);
Expand All @@ -133,7 +133,7 @@ main() {

test('should suggest synchronously returned completions', () {
var usage = new Usage()
..addOption('aaa', new Option(abbr: 'a', allowed: (partial) => ['x', 'y', 'z']));
..addOption(new Option(name: 'aaa', abbr: 'a', allowed: (partial) => ['x', 'y', 'z']));
addPlugins(usage);

testAllowed(usage, '--aaa ', ['x', 'y', 'z']);
Expand All @@ -144,7 +144,7 @@ main() {

test('should suggest asynchronously returned completions', () {
var usage = new Usage()
..addOption('aaa', new Option(abbr: 'a', allowed: (partial) => new Future.value(['x', 'y', 'z'])));
..addOption(new Option(name: 'aaa', abbr: 'a', allowed: (partial) => new Future.value(['x', 'y', 'z'])));
addPlugins(usage);

testAllowed(usage, '--aaa ', ['x', 'y', 'z']);
Expand All @@ -158,7 +158,7 @@ main() {

test('should suggest synchronously returned completions', () {
var usage = new Usage()
..addOption('aaa', new Option(abbr: 'a', allowed: () => ['x', 'y', 'z']));
..addOption(new Option(name: 'aaa', abbr: 'a', allowed: () => ['x', 'y', 'z']));
addPlugins(usage);

testAllowed(usage, '--aaa ', ['x', 'y', 'z']);
Expand All @@ -169,7 +169,7 @@ main() {

test('should suggest asynchronously returned completions', () {
var usage = new Usage()
..addOption('aaa', new Option(abbr: 'a', allowed: () => new Future.value(['x', 'y', 'z'])));
..addOption(new Option(name: 'aaa', abbr: 'a', allowed: () => new Future.value(['x', 'y', 'z'])));
addPlugins(usage);

testAllowed(usage, '--aaa ', ['x', 'y', 'z']);
Expand Down

0 comments on commit e65792c

Please sign in to comment.