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 |