OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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 file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library args.src.parser; | 5 library args.src.parser; |
6 | 6 |
7 import '../args.dart'; | 7 import '../args.dart'; |
8 | 8 |
9 final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$'); | 9 final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$'); |
10 final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$'); | 10 final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$'); |
11 final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$'); | 11 final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$'); |
12 | 12 |
13 /** | 13 /// The actual parsing class. Unlike [ArgParser] which is really more an "arg |
nweiz
2014/05/12 20:15:30
It would be nice to convert this to use single-sen
Bob Nystrom
2014/05/12 21:29:26
Done.
| |
14 * The actual parsing class. Unlike [ArgParser] which is really more an "arg | 14 /// grammar", this is the class that does the parsing and holds the mutable |
15 * grammar", this is the class that does the parsing and holds the mutable | 15 /// state required during a parse. |
16 * state required during a parse. | |
17 */ | |
18 class Parser { | 16 class Parser { |
19 /** | 17 /// If parser is parsing a command's options, this will be the name of the |
20 * If parser is parsing a command's options, this will be the name of the | 18 /// command. For top-level results, this returns `null`. |
21 * command. For top-level results, this returns `null`. | |
22 */ | |
23 final String commandName; | 19 final String commandName; |
24 | 20 |
25 /** | 21 /// The parser for the supercommand of this command parser, or `null` if this |
26 * The parser for the supercommand of this command parser, or `null` if this | 22 /// is the top-level parser. |
27 * is the top-level parser. | |
28 */ | |
29 final Parser parent; | 23 final Parser parent; |
30 | 24 |
31 /** The grammar being parsed. */ | 25 /// The grammar being parsed. |
32 final ArgParser grammar; | 26 final ArgParser grammar; |
33 | 27 |
34 /** The arguments being parsed. */ | 28 /// The arguments being parsed. |
35 final List<String> args; | 29 final List<String> args; |
36 | 30 |
37 /** The remaining non-option, non-command arguments. */ | 31 /// The remaining non-option, non-command arguments. |
38 final rest = <String>[]; | 32 final rest = <String>[]; |
39 | 33 |
40 /** The accumulated parsed options. */ | 34 /// The accumulated parsed options. |
41 final Map<String, dynamic> results = <String, dynamic>{}; | 35 final Map<String, dynamic> results = <String, dynamic>{}; |
42 | 36 |
43 Parser(this.commandName, this.grammar, this.args, this.parent, rest) { | 37 Parser(this.commandName, this.grammar, this.args, this.parent, rest) { |
44 if (rest != null) this.rest.addAll(rest); | 38 if (rest != null) this.rest.addAll(rest); |
45 } | 39 } |
46 | 40 |
47 | 41 |
48 /** The current argument being parsed. */ | 42 /// The current argument being parsed. |
49 String get current => args[0]; | 43 String get current => args[0]; |
50 | 44 |
51 /** Parses the arguments. This can only be called once. */ | 45 /// Parses the arguments. This can only be called once. |
52 ArgResults parse() { | 46 ArgResults parse() { |
53 var commandResults = null; | 47 var commandResults = null; |
54 | 48 |
55 // Initialize flags to their defaults. | 49 // Initialize flags to their defaults. |
56 grammar.options.forEach((name, option) { | 50 grammar.options.forEach((name, option) { |
57 if (option.allowMultiple) { | 51 if (option.allowMultiple) { |
58 results[name] = []; | 52 results[name] = []; |
59 } else { | 53 } else { |
60 results[name] = option.defaultValue; | 54 results[name] = option.defaultValue; |
61 } | 55 } |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
105 } | 99 } |
106 if (option.callback != null) option.callback(results[name]); | 100 if (option.callback != null) option.callback(results[name]); |
107 }); | 101 }); |
108 | 102 |
109 // Add in the leftover arguments we didn't parse to the innermost command. | 103 // Add in the leftover arguments we didn't parse to the innermost command. |
110 rest.addAll(args); | 104 rest.addAll(args); |
111 args.clear(); | 105 args.clear(); |
112 return new ArgResults(results, commandName, commandResults, rest); | 106 return new ArgResults(results, commandName, commandResults, rest); |
113 } | 107 } |
114 | 108 |
115 /** | 109 /// Pulls the value for [option] from the second argument in [args]. Validates |
116 * Pulls the value for [option] from the second argument in [args]. Validates | 110 /// that there is a valid value there. |
117 * that there is a valid value there. | |
118 */ | |
119 void readNextArgAsValue(Option option) { | 111 void readNextArgAsValue(Option option) { |
120 // Take the option argument from the next command line arg. | 112 // Take the option argument from the next command line arg. |
121 validate(args.length > 0, | 113 validate(args.length > 0, |
122 'Missing argument for "${option.name}".'); | 114 'Missing argument for "${option.name}".'); |
123 | 115 |
124 // Make sure it isn't an option itself. | 116 // Make sure it isn't an option itself. |
125 validate(!_ABBR_OPT.hasMatch(current) && !_LONG_OPT.hasMatch(current), | 117 validate(!_ABBR_OPT.hasMatch(current) && !_LONG_OPT.hasMatch(current), |
126 'Missing argument for "${option.name}".'); | 118 'Missing argument for "${option.name}".'); |
127 | 119 |
128 setOption(results, option, current); | 120 setOption(results, option, current); |
129 args.removeAt(0); | 121 args.removeAt(0); |
130 } | 122 } |
131 | 123 |
132 /** | 124 /// Tries to parse the current argument as a "solo" option, which is a single |
133 * Tries to parse the current argument as a "solo" option, which is a single | 125 /// hyphen followed by a single letter. We treat this differently than |
134 * hyphen followed by a single letter. We treat this differently than | 126 /// collapsed abbreviations (like "-abc") to handle the possible value that |
135 * collapsed abbreviations (like "-abc") to handle the possible value that | 127 /// may follow it. |
136 * may follow it. | |
137 */ | |
138 bool parseSoloOption() { | 128 bool parseSoloOption() { |
139 var soloOpt = _SOLO_OPT.firstMatch(current); | 129 var soloOpt = _SOLO_OPT.firstMatch(current); |
140 if (soloOpt == null) return false; | 130 if (soloOpt == null) return false; |
141 | 131 |
142 var option = grammar.findByAbbreviation(soloOpt[1]); | 132 var option = grammar.findByAbbreviation(soloOpt[1]); |
143 if (option == null) { | 133 if (option == null) { |
144 // Walk up to the parent command if possible. | 134 // Walk up to the parent command if possible. |
145 validate(parent != null, | 135 validate(parent != null, |
146 'Could not find an option or flag "-${soloOpt[1]}".'); | 136 'Could not find an option or flag "-${soloOpt[1]}".'); |
147 return parent.parseSoloOption(); | 137 return parent.parseSoloOption(); |
148 } | 138 } |
149 | 139 |
150 args.removeAt(0); | 140 args.removeAt(0); |
151 | 141 |
152 if (option.isFlag) { | 142 if (option.isFlag) { |
153 setOption(results, option, true); | 143 setOption(results, option, true); |
154 } else { | 144 } else { |
155 readNextArgAsValue(option); | 145 readNextArgAsValue(option); |
156 } | 146 } |
157 | 147 |
158 return true; | 148 return true; |
159 } | 149 } |
160 | 150 |
161 /** | 151 /// Tries to parse the current argument as a series of collapsed abbreviations |
162 * Tries to parse the current argument as a series of collapsed abbreviations | 152 /// (like "-abc") or a single abbreviation with the value directly attached |
163 * (like "-abc") or a single abbreviation with the value directly attached | 153 /// to it (like "-mrelease"). |
164 * to it (like "-mrelease"). | |
165 */ | |
166 bool parseAbbreviation(Parser innermostCommand) { | 154 bool parseAbbreviation(Parser innermostCommand) { |
167 var abbrOpt = _ABBR_OPT.firstMatch(current); | 155 var abbrOpt = _ABBR_OPT.firstMatch(current); |
168 if (abbrOpt == null) return false; | 156 if (abbrOpt == null) return false; |
169 | 157 |
170 // If the first character is the abbreviation for a non-flag option, then | 158 // If the first character is the abbreviation for a non-flag option, then |
171 // the rest is the value. | 159 // the rest is the value. |
172 var c = abbrOpt[1].substring(0, 1); | 160 var c = abbrOpt[1].substring(0, 1); |
173 var first = grammar.findByAbbreviation(c); | 161 var first = grammar.findByAbbreviation(c); |
174 if (first == null) { | 162 if (first == null) { |
175 // Walk up to the parent command if possible. | 163 // Walk up to the parent command if possible. |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
213 } | 201 } |
214 | 202 |
215 // In a list of short options, only the first can be a non-flag. If | 203 // In a list of short options, only the first can be a non-flag. If |
216 // we get here we've checked that already. | 204 // we get here we've checked that already. |
217 validate(option.isFlag, | 205 validate(option.isFlag, |
218 'Option "-$c" must be a flag to be in a collapsed "-".'); | 206 'Option "-$c" must be a flag to be in a collapsed "-".'); |
219 | 207 |
220 setOption(results, option, true); | 208 setOption(results, option, true); |
221 } | 209 } |
222 | 210 |
223 /** | 211 /// Tries to parse the current argument as a long-form named option, which |
224 * Tries to parse the current argument as a long-form named option, which | 212 /// may include a value like "--mode=release" or "--mode release". |
225 * may include a value like "--mode=release" or "--mode release". | |
226 */ | |
227 bool parseLongOption() { | 213 bool parseLongOption() { |
228 var longOpt = _LONG_OPT.firstMatch(current); | 214 var longOpt = _LONG_OPT.firstMatch(current); |
229 if (longOpt == null) return false; | 215 if (longOpt == null) return false; |
230 | 216 |
231 var name = longOpt[1]; | 217 var name = longOpt[1]; |
232 var option = grammar.options[name]; | 218 var option = grammar.options[name]; |
233 if (option != null) { | 219 if (option != null) { |
234 args.removeAt(0); | 220 args.removeAt(0); |
235 if (option.isFlag) { | 221 if (option.isFlag) { |
236 validate(longOpt[3] == null, | 222 validate(longOpt[3] == null, |
(...skipping 24 matching lines...) Expand all Loading... | |
261 setOption(results, option, false); | 247 setOption(results, option, false); |
262 } else { | 248 } else { |
263 // Walk up to the parent command if possible. | 249 // Walk up to the parent command if possible. |
264 validate(parent != null, 'Could not find an option named "$name".'); | 250 validate(parent != null, 'Could not find an option named "$name".'); |
265 return parent.parseLongOption(); | 251 return parent.parseLongOption(); |
266 } | 252 } |
267 | 253 |
268 return true; | 254 return true; |
269 } | 255 } |
270 | 256 |
271 /** | 257 /// Called during parsing to validate the arguments. Throws a |
272 * Called during parsing to validate the arguments. Throws a | 258 /// [FormatException] if [condition] is `false`. |
273 * [FormatException] if [condition] is `false`. | |
274 */ | |
275 void validate(bool condition, String message) { | 259 void validate(bool condition, String message) { |
276 if (!condition) throw new FormatException(message); | 260 if (!condition) throw new FormatException(message); |
277 } | 261 } |
278 | 262 |
279 /** Validates and stores [value] as the value for [option]. */ | 263 /// Validates and stores [value] as the value for [option]. |
280 void setOption(Map results, Option option, value) { | 264 void setOption(Map results, Option option, value) { |
281 // See if it's one of the allowed values. | 265 // See if it's one of the allowed values. |
282 if (option.allowed != null) { | 266 if (option.allowed != null) { |
283 validate(option.allowed.any((allow) => allow == value), | 267 validate(option.allowed.any((allow) => allow == value), |
284 '"$value" is not an allowed value for option "${option.name}".'); | 268 '"$value" is not an allowed value for option "${option.name}".'); |
285 } | 269 } |
286 | 270 |
287 if (option.allowMultiple) { | 271 if (option.allowMultiple) { |
288 results[option.name].add(value); | 272 results[option.name].add(value); |
289 } else { | 273 } else { |
290 results[option.name] = value; | 274 results[option.name] = value; |
291 } | 275 } |
292 } | 276 } |
293 } | 277 } |
OLD | NEW |