| Index: mojo/public/dart/third_party/args/lib/src/parser.dart
|
| diff --git a/mojo/public/dart/third_party/args/lib/src/parser.dart b/mojo/public/dart/third_party/args/lib/src/parser.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a10692d4ed84e328122ecefce672089a48dede34
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/args/lib/src/parser.dart
|
| @@ -0,0 +1,289 @@
|
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library args.src.parser;
|
| +
|
| +import 'arg_parser.dart';
|
| +import 'arg_results.dart';
|
| +import 'option.dart';
|
| +
|
| +final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$');
|
| +final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$');
|
| +final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$');
|
| +
|
| +/// The actual argument parsing class.
|
| +///
|
| +/// Unlike [ArgParser] which is really more an "arg grammar", this is the class
|
| +/// that does the parsing and holds the mutable state required during a parse.
|
| +class Parser {
|
| + /// If parser is parsing a command's options, this will be the name of the
|
| + /// command. For top-level results, this returns `null`.
|
| + final String commandName;
|
| +
|
| + /// The parser for the supercommand of this command parser, or `null` if this
|
| + /// is the top-level parser.
|
| + final Parser parent;
|
| +
|
| + /// The grammar being parsed.
|
| + final ArgParser grammar;
|
| +
|
| + /// The arguments being parsed.
|
| + final List<String> args;
|
| +
|
| + /// The remaining non-option, non-command arguments.
|
| + final rest = <String>[];
|
| +
|
| + /// The accumulated parsed options.
|
| + final Map<String, dynamic> results = <String, dynamic>{};
|
| +
|
| + Parser(this.commandName, this.grammar, this.args, this.parent, rest) {
|
| + if (rest != null) this.rest.addAll(rest);
|
| + }
|
| +
|
| + /// The current argument being parsed.
|
| + String get current => args[0];
|
| +
|
| + /// Parses the arguments. This can only be called once.
|
| + ArgResults parse() {
|
| + var arguments = args.toList();
|
| + var commandResults = null;
|
| +
|
| + // Parse the args.
|
| + while (args.length > 0) {
|
| + if (current == '--') {
|
| + // Reached the argument terminator, so stop here.
|
| + args.removeAt(0);
|
| + break;
|
| + }
|
| +
|
| + // Try to parse the current argument as a command. This happens before
|
| + // options so that commands can have option-like names.
|
| + var command = grammar.commands[current];
|
| + if (command != null) {
|
| + validate(rest.isEmpty, 'Cannot specify arguments before a command.');
|
| + var commandName = args.removeAt(0);
|
| + var commandParser = new Parser(commandName, command, args, this, rest);
|
| + commandResults = commandParser.parse();
|
| +
|
| + // All remaining arguments were passed to command so clear them here.
|
| + rest.clear();
|
| + break;
|
| + }
|
| +
|
| + // Try to parse the current argument as an option. Note that the order
|
| + // here matters.
|
| + if (parseSoloOption()) continue;
|
| + if (parseAbbreviation(this)) continue;
|
| + if (parseLongOption()) continue;
|
| +
|
| + // This argument is neither option nor command, so stop parsing unless
|
| + // the [allowTrailingOptions] option is set.
|
| + if (!grammar.allowTrailingOptions) break;
|
| + rest.add(args.removeAt(0));
|
| + }
|
| +
|
| + // Invoke the callbacks.
|
| + grammar.options.forEach((name, option) {
|
| + if (option.callback == null) return;
|
| + option.callback(option.getOrDefault(results[name]));
|
| + });
|
| +
|
| + // 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);
|
| + }
|
| +
|
| + /// Pulls the value for [option] from the second argument in [args].
|
| + ///
|
| + /// Validates that there is a valid value there.
|
| + void readNextArgAsValue(Option option) {
|
| + // Take the option argument from the next command line arg.
|
| + validate(args.length > 0, 'Missing argument for "${option.name}".');
|
| +
|
| + setOption(results, option, current);
|
| + args.removeAt(0);
|
| + }
|
| +
|
| + /// Tries to parse the current argument as a "solo" option, which is a single
|
| + /// hyphen followed by a single letter.
|
| + ///
|
| + /// We treat this differently than collapsed abbreviations (like "-abc") to
|
| + /// handle the possible value that may follow it.
|
| + bool parseSoloOption() {
|
| + var soloOpt = _SOLO_OPT.firstMatch(current);
|
| + if (soloOpt == null) return false;
|
| +
|
| + var option = grammar.findByAbbreviation(soloOpt[1]);
|
| + if (option == null) {
|
| + // Walk up to the parent command if possible.
|
| + validate(
|
| + parent != null, 'Could not find an option or flag "-${soloOpt[1]}".');
|
| + return parent.parseSoloOption();
|
| + }
|
| +
|
| + args.removeAt(0);
|
| +
|
| + if (option.isFlag) {
|
| + setFlag(results, option, true);
|
| + } else {
|
| + readNextArgAsValue(option);
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /// Tries to parse the current argument as a series of collapsed abbreviations
|
| + /// (like "-abc") or a single abbreviation with the value directly attached
|
| + /// to it (like "-mrelease").
|
| + bool parseAbbreviation(Parser innermostCommand) {
|
| + var abbrOpt = _ABBR_OPT.firstMatch(current);
|
| + if (abbrOpt == null) return false;
|
| +
|
| + // If the first character is the abbreviation for a non-flag option, then
|
| + // the rest is the value.
|
| + var c = abbrOpt[1].substring(0, 1);
|
| + var first = grammar.findByAbbreviation(c);
|
| + if (first == null) {
|
| + // Walk up to the parent command if possible.
|
| + validate(
|
| + parent != null, 'Could not find an option with short name "-$c".');
|
| + return parent.parseAbbreviation(innermostCommand);
|
| + } else if (!first.isFlag) {
|
| + // The first character is a non-flag option, so the rest must be the
|
| + // value.
|
| + var value = '${abbrOpt[1].substring(1)}${abbrOpt[2]}';
|
| + setOption(results, first, value);
|
| + } 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.
|
| + validate(abbrOpt[2] == '',
|
| + 'Option "-$c" is a flag and cannot handle value '
|
| + '"${abbrOpt[1].substring(1)}${abbrOpt[2]}".');
|
| +
|
| + // Not an option, so all characters should be flags.
|
| + // We use "innermostCommand" here so that if a parent command parses the
|
| + // *first* letter, subcommands can still be found to parse the other
|
| + // letters.
|
| + for (var i = 0; i < abbrOpt[1].length; i++) {
|
| + var c = abbrOpt[1].substring(i, i + 1);
|
| + innermostCommand.parseShortFlag(c);
|
| + }
|
| + }
|
| +
|
| + args.removeAt(0);
|
| + return true;
|
| + }
|
| +
|
| + void parseShortFlag(String c) {
|
| + var option = grammar.findByAbbreviation(c);
|
| + if (option == null) {
|
| + // Walk up to the parent command if possible.
|
| + validate(
|
| + parent != null, 'Could not find an option with short name "-$c".');
|
| + parent.parseShortFlag(c);
|
| + return;
|
| + }
|
| +
|
| + // In a list of short options, only the first can be a non-flag. If
|
| + // we get here we've checked that already.
|
| + validate(
|
| + option.isFlag, 'Option "-$c" must be a flag to be in a collapsed "-".');
|
| +
|
| + setFlag(results, option, true);
|
| + }
|
| +
|
| + /// Tries to parse the current argument as a long-form named option, which
|
| + /// may include a value like "--mode=release" or "--mode release".
|
| + bool parseLongOption() {
|
| + var longOpt = _LONG_OPT.firstMatch(current);
|
| + if (longOpt == null) return false;
|
| +
|
| + var name = longOpt[1];
|
| + var option = grammar.options[name];
|
| + if (option != null) {
|
| + args.removeAt(0);
|
| + if (option.isFlag) {
|
| + validate(longOpt[3] == null,
|
| + 'Flag option "$name" should not be given a value.');
|
| +
|
| + setFlag(results, option, true);
|
| + } else if (longOpt[3] != null) {
|
| + // We have a value like --foo=bar.
|
| + setOption(results, option, longOpt[3]);
|
| + } else {
|
| + // Option like --foo, so look for the value as the next arg.
|
| + readNextArgAsValue(option);
|
| + }
|
| + } else if (name.startsWith('no-')) {
|
| + // See if it's a negated flag.
|
| + name = name.substring('no-'.length);
|
| + option = grammar.options[name];
|
| + if (option == null) {
|
| + // Walk up to the parent command if possible.
|
| + validate(parent != null, 'Could not find an option named "$name".');
|
| + return parent.parseLongOption();
|
| + }
|
| +
|
| + args.removeAt(0);
|
| + validate(option.isFlag, 'Cannot negate non-flag option "$name".');
|
| + validate(option.negatable, 'Cannot negate option "$name".');
|
| +
|
| + setFlag(results, option, false);
|
| + } else {
|
| + // Walk up to the parent command if possible.
|
| + validate(parent != null, 'Could not find an option named "$name".');
|
| + return parent.parseLongOption();
|
| + }
|
| +
|
| + return true;
|
| + }
|
| +
|
| + /// Called during parsing to validate the arguments.
|
| + ///
|
| + /// Throws a [FormatException] if [condition] is `false`.
|
| + void validate(bool condition, String message) {
|
| + if (!condition) throw new FormatException(message);
|
| + }
|
| +
|
| + /// Validates and stores [value] as the value for [option], which must not be
|
| + /// a flag.
|
| + void setOption(Map results, Option option, String value) {
|
| + assert(!option.isFlag);
|
| +
|
| + if (!option.isMultiple) {
|
| + _validateAllowed(option, value);
|
| + results[option.name] = value;
|
| + return;
|
| + }
|
| +
|
| + var list = results.putIfAbsent(option.name, () => []);
|
| +
|
| + if (option.splitCommas) {
|
| + for (var element in value.split(",")) {
|
| + _validateAllowed(option, element);
|
| + list.add(element);
|
| + }
|
| + } else {
|
| + _validateAllowed(option, value);
|
| + list.add(value);
|
| + }
|
| + }
|
| +
|
| + /// Validates and stores [value] as the value for [option], which must be a
|
| + /// flag.
|
| + void setFlag(Map results, Option option, bool value) {
|
| + assert(option.isFlag);
|
| + results[option.name] = value;
|
| + }
|
| +
|
| + /// Validates that [value] is allowed as a value of [option].
|
| + void _validateAllowed(Option option, String value) {
|
| + if (option.allowed == null) return;
|
| +
|
| + validate(option.allowed.contains(value),
|
| + '"$value" is not an allowed value for option "${option.name}".');
|
| + }
|
| +}
|
|
|