ArgResults: Make easier for Dart2 users
This is a runtime failure in Dart2:
void readListOfNames(ArgResults results) {
// "List<dynamic> is not a List<String>"
List<String> names = results['names'];
}
... but patterns like this are really common.
I'd like to see a series of helper methods to make it easier to get common types and collections out of ArgResults, even if all they do behind the scenes is a combination of as and collection casts. Ideas:
Primitives
- [ ]
bool readBool(String name) - [ ]
double readDouble(String name) - [ ]
int readInt(String name) - [ ]
String readString(String name)
Collections
- [ ]
List<String> readStringList(String name)
Thoughts?
/cc @nex3 @natebosch
I think there's a general need for a good API to access heterogeneous untyped information. We have something like this for JSON-RPC 2 parameters, but it would be nice to generalize it enough that it could be used for parsed arguments, JSON/YAML objects, and so on. As the pressures to avoid dynamic values get stronger, this will be more and more relevant.
The args package already has a delineation between bool "flags" and other-type "options." What about even just an is or has method for accessing bools?
var argParser = new ArgParser()
..addFlag('help');
var argResults = argParser.parse(args);
if (argResults['help']) { ... } // Error: Conditions must have a static type of 'bool'.
if (argResults['help'] == true) { ... } // What I have to do today. :(
if (argResults.has('help')) { ... } // Would be nice.
if (argResults.isNot('help')) { ... } // Lends itself nicely to negatable.
I'm looking at disabling implicit downcasts in the flutter tool, and a large portion come from ArgResults.[] returning dynamic. I'm working around this with a typed wrapper which exposes getOption, getFlag and getMultiOption in https://github.com/flutter/flutter/pull/31679.
It would be great to have something similar in the args package.
You can do a lot with extensions these days.
extension ArgResultGetters on ArgResults {
bool? optionalFlag(String name) => this[name] as bool?;
bool flag(String name, {bool defaultValue: false}) => (this[name] as bool?) ?? defaultValue;
bool has(String name) => this[name] != null;
String stringOption(String name, {String defaultValue = ""}) => (this[name] as String?) ?? defaultValue;
int intOption(String name, {int defaultValue = -1}) {
var value = this[name];
if (value is! String) return defaultValue;
return int.tryParse(value) ?? defaultValue;
}
T option<T>(String name) => this[name] as T;
List<String> multiOption(String name) => this[name] as List<String>;
}