| // Copyright (c) 2021, 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. |
| |
| import '../messages/codes.dart'; |
| import 'resolve_input_uri.dart'; |
| |
| class CommandLineProblem { |
| final Message message; |
| |
| CommandLineProblem(this.message); |
| |
| CommandLineProblem.deprecated(String message) |
| : this(templateUnspecified.withArguments(message)); |
| |
| String toString() => message.problemMessage; |
| } |
| |
| class ParsedOptions { |
| final Map<String, dynamic> options = <String, dynamic>{}; |
| final List<String> arguments = <String>[]; |
| final Map<String, String> defines = <String, String>{}; |
| |
| String toString() => "ParsedArguments($options, $arguments)"; |
| |
| /// Returns arguments stored as line separated text. |
| static List<String> readOptionsFile(String optionsFile) { |
| return optionsFile |
| .split('\n') |
| .map((String line) => line.trim()) |
| .where((line) => line.isNotEmpty) |
| .toList(); |
| } |
| |
| /// Parses a list of command-line [arguments] into options and arguments. |
| /// |
| /// An /option/ is something that, normally, starts with `-` or `--` (one or |
| /// two dashes). However, as a special case `/?` and `/h` are also recognized |
| /// as options for increased compatibility with Windows. An option can have a |
| /// value. |
| /// |
| /// An /argument/ is something that isn't an option, for example, a file name. |
| /// |
| /// The specification is a map of options to one of the following values: |
| /// * the type literal `Uri`, representing an option value of type [Uri], |
| /// * the type literal `int`, representing an option value of type [int], |
| /// * the bool literal `false`, representing a boolean option that is turned |
| /// off by default, |
| /// * the bool literal `true, representing a boolean option that is turned on |
| /// by default, |
| /// * or the string literal `","`, representing a comma-separated list of |
| /// values. |
| /// |
| /// If [arguments] contains `"--"`, anything before is parsed as options, and |
| /// arguments; anything following is treated as arguments (even if starting |
| /// with, for example, a `-`). |
| /// |
| /// If an option isn't found in [specification], an error is thrown. |
| /// |
| /// Boolean options do not require an option value, but an optional value can |
| /// be provided using the forms `--option=value` where `value` can be `true` |
| /// or `yes` to turn on the option, or `false` or `no` to turn it off. If no |
| /// option value is specified, a boolean option is turned on. |
| /// |
| /// All other options require an option value, either on the form `--option |
| /// value` or `--option=value`. |
| static ParsedOptions parse(List<String> arguments, List<Option>? options) { |
| options ??= []; |
| Map<String, ValueSpecification>? specification = {}; |
| void addSpec(String flag, ValueSpecification spec) { |
| if (specification.containsKey(flag)) { |
| throw new CommandLineProblem.deprecated("Duplicate option '${flag}'."); |
| } |
| specification[flag] = spec; |
| } |
| |
| for (Option option in options) { |
| addSpec(option.flag, option.spec); |
| for (String alias in option.aliases) { |
| addSpec(alias, new AliasValue(option.flag)); |
| } |
| } |
| ParsedOptions result = new ParsedOptions(); |
| int index = arguments.indexOf("--"); |
| Iterable<String> nonOptions = const <String>[]; |
| Iterator<String> iterator = arguments.iterator; |
| if (index != -1) { |
| nonOptions = arguments.skip(index + 1); |
| iterator = arguments.take(index).iterator; |
| } |
| while (iterator.moveNext()) { |
| String argument = iterator.current; |
| if (argument.startsWith("-") || argument == "/?" || argument == "/h") { |
| String? value; |
| if (argument.startsWith("-D")) { |
| value = argument.substring("-D".length); |
| argument = "-D"; |
| } else { |
| index = argument.indexOf("="); |
| if (index != -1) { |
| value = argument.substring(index + 1); |
| argument = argument.substring(0, index); |
| } |
| } |
| ValueSpecification? valueSpecification = specification[argument]; |
| if (valueSpecification == null) { |
| throw new CommandLineProblem.deprecated( |
| "Unknown option '$argument'."); |
| } |
| String canonicalArgument = argument; |
| if (valueSpecification.alias != null) { |
| canonicalArgument = valueSpecification.alias as String; |
| valueSpecification = specification[valueSpecification.alias]; |
| } |
| if (valueSpecification == null) { |
| throw new CommandLineProblem.deprecated( |
| "Unknown option alias '$canonicalArgument'."); |
| } |
| final bool requiresValue = valueSpecification.requiresValue; |
| if (requiresValue && value == null) { |
| if (!iterator.moveNext()) { |
| throw new CommandLineProblem( |
| templateFastaCLIArgumentRequired.withArguments(argument)); |
| } |
| value = iterator.current; |
| } |
| valueSpecification.processValue( |
| result, canonicalArgument, argument, value); |
| } else { |
| result.arguments.add(argument); |
| } |
| } |
| specification.forEach((String key, ValueSpecification value) { |
| if (value.defaultValue != null) { |
| result.options[key] ??= value.defaultValue; |
| } |
| }); |
| result.arguments.addAll(nonOptions); |
| return result; |
| } |
| } |
| |
| abstract class ValueSpecification<T> { |
| const ValueSpecification(); |
| |
| String? get alias => null; |
| |
| T? get defaultValue => null; |
| |
| bool get requiresValue => true; |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value); |
| } |
| |
| class AliasValue<T> extends ValueSpecification<T> { |
| final String alias; |
| |
| const AliasValue(this.alias); |
| |
| bool get requiresValue => |
| throw new UnsupportedError("AliasValue.requiresValue"); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| throw new UnsupportedError("AliasValue.processValue"); |
| } |
| } |
| |
| class UriValue extends ValueSpecification<Uri?> { |
| const UriValue(); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| if (result.options.containsKey(canonicalArgument)) { |
| throw new CommandLineProblem.deprecated( |
| "Multiple values for '$argument': " |
| "'${result.options[canonicalArgument]}' and '$value'."); |
| } |
| // TODO(ahe): resolve Uris lazily, so that schemes provided by |
| // other flags can be used for parsed command-line arguments too. |
| result.options[canonicalArgument] = resolveInputUri(value!); |
| } |
| } |
| |
| class StringValue extends ValueSpecification<String?> { |
| final String? defaultValue; |
| |
| const StringValue({this.defaultValue}); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| if (result.options.containsKey(canonicalArgument)) { |
| throw new CommandLineProblem.deprecated( |
| "Multiple values for '$argument': " |
| "'${result.options[canonicalArgument]}' and '$value'."); |
| } |
| result.options[canonicalArgument] = value!; |
| } |
| } |
| |
| class BoolValue extends ValueSpecification<bool?> { |
| final bool? defaultValue; |
| |
| const BoolValue(this.defaultValue); |
| |
| bool get requiresValue => false; |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| if (result.options.containsKey(canonicalArgument)) { |
| throw new CommandLineProblem.deprecated( |
| "Multiple values for '$argument': " |
| "'${result.options[canonicalArgument]}' and '$value'."); |
| } |
| bool parsedValue; |
| if (value == null || value == "true" || value == "yes") { |
| parsedValue = true; |
| } else if (value == "false" || value == "no") { |
| parsedValue = false; |
| } else { |
| throw new CommandLineProblem.deprecated( |
| "Value for '$argument' is '$value', " |
| "but expected one of: 'true', 'false', 'yes', or 'no'."); |
| } |
| result.options[canonicalArgument] = parsedValue; |
| } |
| } |
| |
| class IntValue extends ValueSpecification<int?> { |
| final int? defaultValue; |
| final int? noArgValue; |
| |
| const IntValue({this.defaultValue, this.noArgValue}); |
| |
| bool get requiresValue => noArgValue == null; |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| if (result.options.containsKey(canonicalArgument)) { |
| throw new CommandLineProblem.deprecated( |
| "Multiple values for '$argument': " |
| "'${result.options[canonicalArgument]}' and '$value'."); |
| } |
| int? parsedValue = noArgValue; |
| if (value != null) { |
| parsedValue = int.tryParse(value); |
| } |
| if (parsedValue == null) { |
| throw new CommandLineProblem.deprecated( |
| "Value for '$argument', '$value', isn't an int."); |
| } |
| result.options[canonicalArgument] = parsedValue; |
| } |
| } |
| |
| class DefineValue extends ValueSpecification<Map<String, String>> { |
| const DefineValue(); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| int index = value!.indexOf('='); |
| String name; |
| String expression; |
| if (index != -1) { |
| name = value.substring(0, index); |
| expression = value.substring(index + 1); |
| } else { |
| name = value; |
| expression = value; |
| } |
| result.defines[name] = expression; |
| } |
| } |
| |
| class StringListValue extends ValueSpecification<List<String>?> { |
| const StringListValue(); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| List<String> values = result.options[canonicalArgument] ??= <String>[]; |
| values.addAll(value!.split(",")); |
| } |
| } |
| |
| class UriListValue extends ValueSpecification<List<Uri>?> { |
| const UriListValue(); |
| |
| void processValue(ParsedOptions result, String canonicalArgument, |
| String argument, String? value) { |
| List<Uri> values = result.options[canonicalArgument] ??= <Uri>[]; |
| values.addAll(value!.split(",").map(resolveInputUri)); |
| } |
| } |
| |
| class Option<T> { |
| final String flag; |
| |
| final ValueSpecification<T?> spec; |
| |
| final bool isDefines; |
| |
| final List<String> aliases; |
| |
| const Option(this.flag, this.spec, |
| {this.isDefines: false, this.aliases: const []}); |
| |
| T read(ParsedOptions parsedOptions) => |
| (isDefines ? parsedOptions.defines : parsedOptions.options[flag]) as T; |
| } |