Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Add function to retrieve actual input values for command options by name #284

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Added source argument when throwing a `ArgParserException`.
* Fix inconsistent `FormatException` messages
* Add function to retrieve actual input values for command options by name.
* Require Dart 3.3

## 2.5.0
Expand Down
22 changes: 19 additions & 3 deletions 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 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 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