Chromium Code Reviews| 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 |