Skip to content
Open
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
5 changes: 5 additions & 0 deletions pkgs/args/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 2.9.0-wip

* Adds `flagCount(name)` to `ArgResults` which returns the number of occurrences
of a flag. Allows, for example, `-vv` to represent "double verbose".

## 2.8.0

* Allow designating a top-level command or a subcommand as a default one by
Expand Down
65 changes: 52 additions & 13 deletions pkgs/args/lib/src/arg_results.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,18 @@ class ArgResults {
/// > flags, [option] for options, and [multiOption] for multi-options.
dynamic operator [](String name) {
if (!_parser.options.containsKey(name)) {
throw ArgumentError('Could not find an option named "--$name".');
throw ArgumentError.value(
name, 'name', 'Could not find an option named "--$name".');
}

final option = _parser.options[name]!;
if (option.mandatory && !_parsed.containsKey(name)) {
throw ArgumentError('Option $name is mandatory.');
throw ArgumentError.value(name, 'name', 'Option $name is mandatory.');
}

return option.valueOrDefault(_parsed[name]);
var parsedValue = _parsed[name];
if (option.isFlag && parsedValue is int) parsedValue = parsedValue > 0;
var result = option.valueOrDefault(parsedValue);
return result;
}

/// Returns the parsed or default command-line flag named [name].
Expand All @@ -83,12 +86,45 @@ class ArgResults {
bool flag(String name) {
final option = _parser.options[name];
if (option == null) {
throw ArgumentError('Could not find a flag named "--$name".');
throw ArgumentError.value(
name, 'name', 'Could not find a flag named "--$name".');
}
if (!option.isFlag) {
throw ArgumentError.value(name, 'name', '"$name" is not a flag.');
}
var parsedValue = _parsed[name];
if (parsedValue is int) {
parsedValue = parsedValue > 0;
}
return option.valueOrDefault(parsedValue) as bool;
}

/// The number of times the flag named [name] occurred.
///
/// If a flag occurred more than once in the arguments,
/// this is the total number of counts.
Comment on lines +104 to +105
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit]

Suggested change
/// If a flag occurred more than once in the arguments,
/// this is the total number of counts.
/// If a flag occurred more than once in the arguments,
/// this is the total number of occurrences.

///
/// If the negated flag occurred, it resets the count to zero,
/// ignoring all prior occurrences. Later occurrences may still
/// increase the count again.
///
/// If the default is to be enabled, the default count is `1` if there were
/// no occurrences of the flag.
Comment on lines +111 to +112
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth describing specifically the behavior of passing the flag once when it is also the default?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably best to do so. Just to be safe.

///
/// The [name] must be a valid flag name in the parser.
///
/// The result is never negative.
int flagCount(String name) {
final option = _parser.options[name];
if (option == null) {
throw ArgumentError.value(
name, 'name', 'Could not find a flag named "--$name".');
}
if (!option.isFlag) {
throw ArgumentError('"$name" is not a flag.');
throw ArgumentError.value(name, 'name', '"$name" is not a flag.');
}
return option.valueOrDefault(_parsed[name]) as bool;
return (_parsed[name] as int?) ??
(option.valueOrDefault(null) as bool ? 1 : 0);
}

/// Returns the parsed or default command-line option named [name].
Expand All @@ -97,13 +133,14 @@ class ArgResults {
String? option(String name) {
final option = _parser.options[name];
if (option == null) {
throw ArgumentError('Could not find an option named "--$name".');
throw ArgumentError.value(
name, 'name', 'Could not find an option named "--$name".');
}
if (!option.isSingle) {
throw ArgumentError('"$name" is a multi-option.');
throw ArgumentError.value(name, 'name', '"$name" is a multi-option.');
}
if (option.mandatory && !_parsed.containsKey(name)) {
throw ArgumentError('Option $name is mandatory.');
throw ArgumentError.value(name, 'name', 'Option $name is mandatory.');
}
return option.valueOrDefault(_parsed[name]) as String?;
}
Expand All @@ -114,10 +151,11 @@ class ArgResults {
List<String> multiOption(String name) {
var option = _parser.options[name];
if (option == null) {
throw ArgumentError('Could not find an option named "--$name".');
throw ArgumentError.value(
name, 'name', 'Could not find an option named "--$name".');
}
if (!option.isMultiple) {
throw ArgumentError('"$name" is not a multi-option.');
throw ArgumentError.value(name, 'name', '"$name" is not a multi-option.');
}
return option.valueOrDefault(_parsed[name]) as List<String>;
}
Expand Down Expand Up @@ -146,7 +184,8 @@ class ArgResults {
/// [name] must be a valid option name in the parser.
bool wasParsed(String name) {
if (!_parser.options.containsKey(name)) {
throw ArgumentError('Could not find an option named "--$name".');
throw ArgumentError.value(
name, 'name', 'Could not find an option named "--$name".');
}

return _parsed.containsKey(name);
Expand Down
11 changes: 8 additions & 3 deletions pkgs/args/lib/src/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ class Parser {
// Check if mandatory and invoke existing callbacks.
_grammar.options.forEach((name, option) {
var parsedOption = _results[name];
if (option.isFlag && parsedOption is int) parsedOption = parsedOption > 0;

var callback = option.callback;
if (callback == null) return;
Expand Down Expand Up @@ -374,11 +375,15 @@ class Parser {
}
}

/// Validates and stores [value] as the value for [option], which must be a
/// flag.
/// Validates and increases or resets the count for [option].
///
/// If [value] is `false`, resets the option's value to zero.
/// If `true`, increases the count of occurrences of the [option],
/// which must be a flag.
void _setFlag(Map results, Option option, bool value) {
assert(option.isFlag);
results[option.name] = value;
results[option.name] =
value ? ((results[option.name] as int?) ?? 0) + 1 : 0;
}

/// Validates that [value] is allowed as a value of [option].
Expand Down
2 changes: 1 addition & 1 deletion pkgs/args/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: args
version: 2.8.0
version: 2.9.0-wip
description: >-
Library for defining parsers for parsing raw command-line arguments into a set
of options and values using GNU and POSIX style options.
Expand Down
44 changes: 44 additions & 0 deletions pkgs/args/test/parse_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,50 @@ void main() {
var results = parser.parse(['--a']);
throwsIllegalArg(() => results.multiOption('a'));
});

test('flagCount', () {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] I think it would be better to test these different flag behaviors in different test cases.

var parser = ArgParser();
parser.addFlag('flag', abbr: 'f', defaultsTo: false, negatable: false);
parser.addFlag('negatable-flag', abbr: 'n', defaultsTo: false);

// Flags not occurring for testing default value.
parser.addFlag('default-true', abbr: 'd', defaultsTo: true);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Can you add a test case with a flag that defaults to true and is also passed? Also one that defaults to true and is negated with a --no flag.

parser.addFlag('default-false', defaultsTo: false);

var results = parser.parse([
'--flag',
'--negatable-flag',
'-f',
'-n',
'-fn',
'--no-negatable-flag', // Resets `-n` count
'-nf',
'-f',
'-n',
'--flag',
'--negatable-flag',
]);

// Counts all occurrences.
expect(results.flagCount('flag'), 6);
expect(results.flag('flag'), true);

// Reset at `--no-negatable-flag`, only counts those after.
expect(results.flagCount('negatable-flag'), 3);
expect(results.flag('negatable-flag'), true);

expect(results.flagCount('default-true'), 1);
expect(results.flag('default-true'), true);

expect(results.flagCount('default-false'), 0);
expect(results.flag('default-false'), false);

// Reset works correctly as last occurrence,
// and doesn't fall back on default value.
results = parser.parse(['-d', '--no-default-true']);
expect(results.flagCount('default-true'), 0);
expect(results.flag('default-true'), false);
});
});

group('flag()', () {
Expand Down
Loading