| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE.md file. | |
| 4 | |
| 5 library fletchc.verbs.options; | |
| 6 | |
| 7 import '../diagnostic.dart' show | |
| 8 DiagnosticKind, | |
| 9 throwFatalError; | |
| 10 | |
| 11 typedef R ArgumentParser<R>(String argument); | |
| 12 | |
| 13 enum OptionKind { | |
| 14 help, | |
| 15 verbose, | |
| 16 version, | |
| 17 testDebugger, | |
| 18 define, | |
| 19 analyzeOnly, | |
| 20 fatalIncrementalFailures, | |
| 21 terminateDebugger, | |
| 22 | |
| 23 /// Not an option | |
| 24 none, | |
| 25 } | |
| 26 | |
| 27 const List<Option> supportedOptions = const <Option>[ | |
| 28 const Option(OptionKind.help, const ['h', '?'], const ['help', 'usage']), | |
| 29 const Option(OptionKind.verbose, 'v', 'verbose'), | |
| 30 const Option(OptionKind.version, null, 'version'), | |
| 31 const Option( | |
| 32 OptionKind.testDebugger, null, 'test-debugger', requiresArgument: true, | |
| 33 parseArgument: parseCommaSeparatedList), | |
| 34 const Option(OptionKind.analyzeOnly, null, 'analyze-only'), | |
| 35 const Option( | |
| 36 OptionKind.fatalIncrementalFailures, null, 'fatal-incremental-failures'), | |
| 37 const Option( | |
| 38 OptionKind.terminateDebugger, null, 'terminate-debugger'), | |
| 39 ]; | |
| 40 | |
| 41 final Map<String, Option> shortOptions = computeShortOptions(); | |
| 42 | |
| 43 final Map<String, Option> longOptions = computeLongOptions(); | |
| 44 | |
| 45 const String StringOrList = | |
| 46 "Type of annotation object is either String or List<String>"; | |
| 47 | |
| 48 class Option { | |
| 49 final OptionKind kind; | |
| 50 | |
| 51 @StringOrList final shortName; | |
| 52 | |
| 53 @StringOrList final longName; | |
| 54 | |
| 55 final bool requiresArgument; | |
| 56 | |
| 57 final ArgumentParser<dynamic> parseArgument; | |
| 58 | |
| 59 final DiagnosticKind missingArgumentDiagnostic; | |
| 60 | |
| 61 const Option( | |
| 62 this.kind, | |
| 63 this.shortName, | |
| 64 this.longName, | |
| 65 {this.requiresArgument: false, | |
| 66 this.parseArgument, | |
| 67 this.missingArgumentDiagnostic: DiagnosticKind.missingRequiredArgument}); | |
| 68 | |
| 69 String toString() { | |
| 70 return "Option($kind, $shortName, $longName, " | |
| 71 "requiresArgument: $requiresArgument, " | |
| 72 "missingArgumentDiagnostic: $missingArgumentDiagnostic)"; | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 List<String> parseCommaSeparatedList(String argument) { | |
| 77 argument = argument.trim(); | |
| 78 if (argument.isEmpty) return <String>[]; | |
| 79 return argument.split(',').map((String e) => e.trim()).toList(); | |
| 80 } | |
| 81 | |
| 82 Map<String, Option> computeShortOptions() { | |
| 83 Map<String, Option> result = <String, Option>{}; | |
| 84 for (Option option in supportedOptions) { | |
| 85 var shortName = option.shortName; | |
| 86 if (shortName == null) { | |
| 87 continue; | |
| 88 } else if (shortName is String) { | |
| 89 result[shortName] = option; | |
| 90 } else { | |
| 91 List<String> shortNames = shortName; | |
| 92 for (String name in shortNames) { | |
| 93 result[name] = option; | |
| 94 } | |
| 95 } | |
| 96 } | |
| 97 return result; | |
| 98 } | |
| 99 | |
| 100 Map<String, Option> computeLongOptions() { | |
| 101 Map<String, Option> result = <String, Option>{}; | |
| 102 for (Option option in supportedOptions) { | |
| 103 var longName = option.longName; | |
| 104 if (longName == null) { | |
| 105 continue; | |
| 106 } else if (longName is String) { | |
| 107 result[longName] = option; | |
| 108 } else { | |
| 109 List<String> longNames = longName; | |
| 110 for (String name in longNames) { | |
| 111 result[name] = option; | |
| 112 } | |
| 113 } | |
| 114 } | |
| 115 return result; | |
| 116 } | |
| 117 | |
| 118 class Options { | |
| 119 final bool help; | |
| 120 final bool verbose; | |
| 121 final bool version; | |
| 122 final List<String> testDebuggerCommands; | |
| 123 final Map<String, String> defines; | |
| 124 final List<String> nonOptionArguments; | |
| 125 final bool analyzeOnly; | |
| 126 final bool fatalIncrementalFailures; | |
| 127 final bool terminateDebugger; | |
| 128 | |
| 129 Options( | |
| 130 this.help, | |
| 131 this.verbose, | |
| 132 this.version, | |
| 133 this.testDebuggerCommands, | |
| 134 this.defines, | |
| 135 this.nonOptionArguments, | |
| 136 this.analyzeOnly, | |
| 137 this.fatalIncrementalFailures, | |
| 138 this.terminateDebugger); | |
| 139 | |
| 140 /// Parse [options] which is a list of command-line arguments, such as those | |
| 141 /// passed to `main`. | |
| 142 static Options parse(Iterable<String> options) { | |
| 143 bool help = false; | |
| 144 bool verbose = false; | |
| 145 bool version = false; | |
| 146 List<String> testDebuggerCommands; | |
| 147 Map<String, String> defines = <String, String>{}; | |
| 148 List<String> nonOptionArguments = <String>[]; | |
| 149 bool analyzeOnly = false; | |
| 150 bool fatalIncrementalFailures = false; | |
| 151 bool terminateDebugger = false; | |
| 152 | |
| 153 Iterator<String> iterator = options.iterator; | |
| 154 | |
| 155 while (iterator.moveNext()) { | |
| 156 String optionString = iterator.current; | |
| 157 OptionKind kind; | |
| 158 var parsedArgument; | |
| 159 if (optionString.startsWith("-D")) { | |
| 160 // Define. | |
| 161 kind = OptionKind.define; | |
| 162 parsedArgument = optionString.split('='); | |
| 163 if (parsedArgument.length > 2) { | |
| 164 throwFatalError(DiagnosticKind.illegalDefine, | |
| 165 userInput: optionString, | |
| 166 additionalUserInput: | |
| 167 parsedArgument.sublist(1).join('=')); | |
| 168 } else if (parsedArgument.length == 1) { | |
| 169 // If the user does not provide a value, we use `null`. | |
| 170 parsedArgument.add(null); | |
| 171 } | |
| 172 } else if (optionString.startsWith("-")) { | |
| 173 String name; | |
| 174 Option option; | |
| 175 String argument; | |
| 176 if (optionString.startsWith("--")) { | |
| 177 // Long option. | |
| 178 int equalIndex = optionString.indexOf("=", 2); | |
| 179 if (equalIndex != -1) { | |
| 180 argument = optionString.substring(equalIndex + 1); | |
| 181 name = optionString.substring(2, equalIndex); | |
| 182 } else { | |
| 183 name = optionString.substring(2); | |
| 184 } | |
| 185 option = longOptions[name]; | |
| 186 } else { | |
| 187 // Short option. | |
| 188 name = optionString.substring(1); | |
| 189 option = shortOptions[name]; | |
| 190 } | |
| 191 | |
| 192 if (option == null) { | |
| 193 throwFatalError( | |
| 194 DiagnosticKind.unknownOption, userInput: optionString); | |
| 195 } else if (option.requiresArgument) { | |
| 196 if (argument == null && iterator.moveNext()) { | |
| 197 argument = iterator.current; | |
| 198 if (argument == "=") { | |
| 199 argument = null; | |
| 200 if (iterator.moveNext()) { | |
| 201 argument = iterator.current; | |
| 202 } | |
| 203 } | |
| 204 } | |
| 205 if (argument == null) { | |
| 206 // TODO(ahe): Improve error recovery, don't throw. | |
| 207 throwFatalError(option.missingArgumentDiagnostic, userInput: name); | |
| 208 } | |
| 209 parsedArgument = option.parseArgument == null | |
| 210 ? argument : option.parseArgument(argument); | |
| 211 } else if (argument != null) { | |
| 212 assert(!option.requiresArgument); | |
| 213 // TODO(ahe): Pass what should be removed as additionalUserInput, for | |
| 214 // example, if saying `--help=fisk`, [userInput] should be `help`, | |
| 215 // and [additionalUserInput] should be `=fisk`. | |
| 216 throwFatalError(DiagnosticKind.unexpectedArgument, userInput: name); | |
| 217 } | |
| 218 kind = option.kind; | |
| 219 } else { | |
| 220 nonOptionArguments.add(optionString); | |
| 221 kind = OptionKind.none; | |
| 222 } | |
| 223 | |
| 224 switch (kind) { | |
| 225 case OptionKind.help: | |
| 226 help = true; | |
| 227 break; | |
| 228 | |
| 229 case OptionKind.verbose: | |
| 230 verbose = true; | |
| 231 break; | |
| 232 | |
| 233 case OptionKind.version: | |
| 234 version = true; | |
| 235 break; | |
| 236 | |
| 237 case OptionKind.testDebugger: | |
| 238 testDebuggerCommands.addAll(parsedArgument); | |
| 239 break; | |
| 240 | |
| 241 case OptionKind.define: | |
| 242 defines[parsedArgument[0]] = parsedArgument[1]; | |
| 243 break; | |
| 244 | |
| 245 case OptionKind.analyzeOnly: | |
| 246 analyzeOnly = true; | |
| 247 break; | |
| 248 | |
| 249 case OptionKind.fatalIncrementalFailures: | |
| 250 fatalIncrementalFailures = true; | |
| 251 break; | |
| 252 | |
| 253 case OptionKind.terminateDebugger: | |
| 254 terminateDebugger = true; | |
| 255 break; | |
| 256 | |
| 257 case OptionKind.none: | |
| 258 break; | |
| 259 } | |
| 260 } | |
| 261 | |
| 262 return new Options( | |
| 263 help, verbose, version, testDebuggerCommands, defines, | |
| 264 nonOptionArguments, analyzeOnly, fatalIncrementalFailures, | |
| 265 terminateDebugger); | |
| 266 } | |
| 267 } | |
| OLD | NEW |