Skip to content

Commit

Permalink
Add function to retrieve actual input values for command options by name
Browse files Browse the repository at this point in the history
Signed-off-by: ふぁ <[email protected]>
  • Loading branch information
fa0311 committed Oct 16, 2024
1 parent ade36c4 commit f375e0c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 16 deletions.
1 change: 1 addition & 0 deletions pkgs/args/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 2.6.1-wip

* Fix the reporitory URL in `pubspec.yaml`.
* Add function to retrieve actual input values for command options by name.

## 2.6.0

Expand Down
22 changes: 19 additions & 3 deletions pkgs/args/lib/src/arg_results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ import 'arg_parser.dart';
ArgResults newArgResults(
ArgParser parser,
Map<String, dynamic> parsed,
Map<String, String> actual,
String? name,
ArgResults? command,
List<String> rest,
List<String> arguments) {
return ArgResults._(parser, parsed, name, command, rest, arguments);
return ArgResults._(parser, parsed, actual, name, command, rest, arguments);
}

/// The results of parsing a series of command line arguments using
Expand All @@ -32,6 +33,9 @@ class ArgResults {
/// The option values that were parsed from arguments.
final Map<String, dynamic> _parsed;

/// The actual values that were parsed from arguments.
final Map<String, String> _actual;

/// The name of the command for which these options are parsed, or `null` if
/// these are the top-level results.
final String? name;
Expand All @@ -52,8 +56,8 @@ class ArgResults {
/// The original arguments that were parsed.
final List<String> arguments;

ArgResults._(this._parser, this._parsed, this.name, this.command,
List<String> rest, List<String> arguments)
ArgResults._(this._parser, this._parsed, this._actual, this.name,
this.command, List<String> rest, List<String> arguments)
: rest = UnmodifiableListView(rest),
arguments = UnmodifiableListView(arguments);

Expand Down Expand Up @@ -134,6 +138,18 @@ class ArgResults {
return result;
}

/// Returns the actual value of the command-line option named [name].
/// Returns `null` if the option was not provided.
///
/// [name] must be a valid option name in the parser.
String? actual(String name) {
if (_actual.containsKey(name)) {
return _actual[name];
} else {
return null;
}
}

/// Returns `true` if the option with [name] was parsed from an actual
/// argument.
///
Expand Down
33 changes: 20 additions & 13 deletions pkgs/args/lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class Parser {
/// The accumulated parsed options.
final Map<String, dynamic> _results = <String, dynamic>{};

/// The actual inputs that were parsed.
final Map<String, String> _actualStores = <String, String>{};

Parser(this._commandName, this._grammar, this._args,
[this._parent, List<String>? rest])
: _rest = [...?rest];
Expand All @@ -45,8 +48,8 @@ class Parser {
ArgResults parse() {
var arguments = _args.toList();
if (_grammar.allowsAnything) {
return newArgResults(
_grammar, const {}, _commandName, null, arguments, arguments);
return newArgResults(_grammar, const {}, const {}, _commandName, null,
arguments, arguments);
}

ArgResults? commandResults;
Expand Down Expand Up @@ -116,8 +119,8 @@ class Parser {
// Add in the leftover arguments we didn't parse to the innermost command.
_rest.addAll(_args);
_args.clear();
return newArgResults(
_grammar, _results, _commandName, commandResults, _rest, arguments);
return newArgResults(_grammar, _results, _actualStores, _commandName,
commandResults, _rest, arguments);
}

/// Pulls the value for [option] from the second argument in [_args].
Expand All @@ -127,7 +130,7 @@ class Parser {
// Take the option argument from the next command line arg.
_validate(_args.isNotEmpty, 'Missing argument for "$arg".', arg);

_setOption(_results, option, _current, arg);
_setOption(_results, option, _current, _actualStores, arg);
_args.removeFirst();
}

Expand Down Expand Up @@ -158,7 +161,7 @@ class Parser {
_args.removeFirst();

if (option.isFlag) {
_setFlag(_results, option, true);
_setFlag(_results, option, true, _actualStores, '-$opt');
} else {
_readNextArgAsValue(option, '-$opt');
}
Expand Down Expand Up @@ -207,7 +210,7 @@ class Parser {
// The first character is a non-flag option, so the rest must be the
// value.
var value = '${lettersAndDigits.substring(1)}$rest';
_setOption(_results, first, value, '-$c');
_setOption(_results, first, value, _actualStores, '-$c');
} else {
// If we got some non-flag characters, then it must be a value, but
// if we got here, it's a flag, which is wrong.
Expand Down Expand Up @@ -246,7 +249,7 @@ class Parser {
_validate(option.isFlag,
'Option "-$c" must be a flag to be in a collapsed "-".', '-$c');

_setFlag(_results, option, true);
_setFlag(_results, option, true, _actualStores, '-$c');
}

/// Tries to parse the current argument as a long-form named option, which
Expand Down Expand Up @@ -279,10 +282,10 @@ class Parser {
_validate(value == null,
'Flag option "--$name" should not be given a value.', '--$name');

_setFlag(_results, option, true);
_setFlag(_results, option, true, _actualStores, '--$name');
} else if (value != null) {
// We have a value like --foo=bar.
_setOption(_results, option, value, '--$name');
_setOption(_results, option, value, _actualStores, '--$name');
} else {
// Option like --foo, so look for the value as the next arg.
_readNextArgAsValue(option, '--$name');
Expand All @@ -304,7 +307,7 @@ class Parser {
_validate(
option.negatable!, 'Cannot negate option "--$name".', '--$name');

_setFlag(_results, option, false);
_setFlag(_results, option, false, _actualStores, '--$name');
} else {
// Walk up to the parent command if possible.
_validate(_parent != null, 'Could not find an option named "--$name".',
Expand All @@ -327,8 +330,10 @@ class Parser {

/// Validates and stores [value] as the value for [option], which must not be
/// a flag.
void _setOption(Map results, Option option, String value, String arg) {
void _setOption(Map results, Option option, String value,
Map<String, String> actualStores, String arg) {
assert(!option.isFlag);
actualStores[option.name] = arg;

if (!option.isMultiple) {
_validateAllowed(option, value, arg);
Expand All @@ -351,9 +356,11 @@ class Parser {

/// Validates and stores [value] as the value for [option], which must be a
/// flag.
void _setFlag(Map results, Option option, bool value) {
void _setFlag(Map results, Option option, bool value,
Map<String, String> actualStores, String source) {
assert(option.isFlag);
results[option.name] = value;
actualStores[option.name] = source;
}

/// Validates that [value] is allowed as a value of [option].
Expand Down
46 changes: 46 additions & 0 deletions pkgs/args/test/parse_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,52 @@ void main() {
});
});

group('actual()', () {
test('returns value if present', () {
var parser = ArgParser();
parser.addFlag('verbose');
parser.addOption('mode');
parser.addMultiOption('define');

var args = parser.parse(['--verbose', '--mode=release', '--define=1']);
expect(args.actual('verbose'), '--verbose');
expect(args.actual('mode'), '--mode');
expect(args.actual('define'), '--define');
});

test('returns null if missing', () {
var parser = ArgParser();
parser.addFlag('a', defaultsTo: true);
parser.addOption('b', defaultsTo: 'c');
parser.addMultiOption('d', defaultsTo: ['e']);

var args = parser.parse([]);
expect(args.actual('a'), isNull);
expect(args.actual('b'), isNull);
expect(args.actual('d'), isNull);
});

test('can match by alias', () {
var parser = ArgParser()..addFlag('verbose', abbr: 'v');
var results = parser.parse(['-v']);
expect(results.actual('verbose'), '-v');
});

test('can be negated by alias', () {
var parser = ArgParser()
..addFlag('a', aliases: ['b'], defaultsTo: true, negatable: true);
var results = parser.parse(['--no-b']);
expect(results.actual('a'), '--no-b');
});

// abbr test
test('can match by abbreviation', () {
var parser = ArgParser()..addFlag('a', abbr: 'b');
var results = parser.parse(['-b']);
expect(results.actual('a'), '-b');
});
});

group('remaining args', () {
test('stops parsing args when a non-option-like arg is encountered', () {
var parser = ArgParser();
Expand Down

0 comments on commit f375e0c

Please sign in to comment.