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 /** |
14 * The actual parsing class. Unlike [ArgParser] which is really more an "arg | 14 * The actual parsing class. Unlike [ArgParser] which is really more an "arg |
15 * 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 |
16 * state required during a parse. | 16 * state required during a parse. |
| 17 * |
| 18 * If [continueParsing] is set, the parser will continue parsing even after it |
| 19 * finds an argument that is not an option. This allows you to specify options |
| 20 * after your command parameters. |
17 */ | 21 */ |
18 class Parser { | 22 class Parser { |
19 /** | 23 /** |
20 * If parser is parsing a command's options, this will be the name of the | 24 * If parser is parsing a command's options, this will be the name of the |
21 * command. For top-level results, this returns `null`. | 25 * command. For top-level results, this returns `null`. |
22 */ | 26 */ |
23 final String commandName; | 27 final String commandName; |
24 | 28 |
25 /** | 29 /** |
26 * The parser for the supercommand of this command parser, or `null` if this | 30 * The parser for the supercommand of this command parser, or `null` if this |
27 * is the top-level parser. | 31 * is the top-level parser. |
28 */ | 32 */ |
29 final Parser parent; | 33 final Parser parent; |
30 | 34 |
| 35 /** If `true`, parser will continue after it sees a non-option argument. */ |
| 36 final boolean continueParsing; |
| 37 |
31 /** The grammar being parsed. */ | 38 /** The grammar being parsed. */ |
32 final ArgParser grammar; | 39 final ArgParser grammar; |
33 | 40 |
34 /** The arguments being parsed. */ | 41 /** The arguments being parsed. */ |
35 final List<String> args; | 42 final List<String> args; |
36 | 43 |
| 44 /** |
| 45 * The remaining unparsed arguments. |
| 46 * |
| 47 * This will be passed onto commands so only the last command will use the |
| 48 * arguments. |
| 49 */ |
| 50 List<String> rest; |
| 51 |
37 /** The accumulated parsed options. */ | 52 /** The accumulated parsed options. */ |
38 final Map results = {}; | 53 final Map results = {}; |
39 | 54 |
40 Parser(this.commandName, this.grammar, this.args, [this.parent]); | 55 Parser(this.commandName, this.grammar, this.args, |
| 56 [this.continueParsing = false, this.parent, rest]) |
| 57 : this.rest = rest == null ? [] : rest; |
41 | 58 |
42 /** The current argument being parsed. */ | 59 /** The current argument being parsed. */ |
43 String get current => args[0]; | 60 String get current => args[0]; |
44 | 61 |
45 /** Parses the arguments. This can only be called once. */ | 62 /** |
| 63 * Parses the arguments. This can only be called once. |
| 64 */ |
46 ArgResults parse() { | 65 ArgResults parse() { |
47 var commandResults = null; | 66 var commandResults = null; |
48 | 67 |
49 // Initialize flags to their defaults. | 68 // Initialize flags to their defaults. |
50 grammar.options.forEach((name, option) { | 69 grammar.options.forEach((name, option) { |
51 if (option.allowMultiple) { | 70 if (option.allowMultiple) { |
52 results[name] = []; | 71 results[name] = []; |
53 } else { | 72 } else { |
54 results[name] = option.defaultValue; | 73 results[name] = option.defaultValue; |
55 } | 74 } |
56 }); | 75 }); |
57 | 76 |
58 // Parse the args. | 77 // Parse the args. |
59 while (args.length > 0) { | 78 while (args.length > 0) { |
60 if (current == '--') { | 79 if (current == '--') { |
61 // Reached the argument terminator, so stop here. | 80 // Reached the argument terminator, so stop here. |
62 args.removeAt(0); | 81 args.removeAt(0); |
63 break; | 82 break; |
64 } | 83 } |
65 | 84 |
66 // Try to parse the current argument as a command. This happens before | 85 // Try to parse the current argument as a command. This happens before |
67 // options so that commands can have option-like names. | 86 // options so that commands can have option-like names. |
68 var command = grammar.commands[current]; | 87 var command = grammar.commands[current]; |
69 if (command != null) { | 88 if (command != null) { |
70 var commandName = args.removeAt(0); | 89 var commandName = args.removeAt(0); |
71 var commandParser = new Parser(commandName, command, args, this); | 90 var commandParser = new Parser(commandName, command, args, |
| 91 continueParsing, this, rest.toList()); |
72 commandResults = commandParser.parse(); | 92 commandResults = commandParser.parse(); |
73 continue; | 93 |
| 94 // All remaining arguments were passed to command so clear them here. |
| 95 rest.clear(); |
| 96 |
| 97 break; |
74 } | 98 } |
75 | 99 |
76 // Try to parse the current argument as an option. Note that the order | 100 // Try to parse the current argument as an option. Note that the order |
77 // here matters. | 101 // here matters. |
78 if (parseSoloOption()) continue; | 102 if (parseSoloOption()) continue; |
79 if (parseAbbreviation(this)) continue; | 103 if (parseAbbreviation(this)) continue; |
80 if (parseLongOption()) continue; | 104 if (parseLongOption()) continue; |
81 | 105 |
82 // If we got here, the argument doesn't look like an option, so stop. | 106 if (!continueParsing) { |
83 break; | 107 // If we got here, the argument doesn't look like an option, so stop. |
| 108 break; |
| 109 } else { |
| 110 rest.add(args.removeAt(0)); |
| 111 } |
84 } | 112 } |
85 | 113 |
86 // Set unspecified multivalued arguments to their default value, | 114 // Set unspecified multivalued arguments to their default value, |
87 // if any, and invoke the callbacks. | 115 // if any, and invoke the callbacks. |
88 grammar.options.forEach((name, option) { | 116 grammar.options.forEach((name, option) { |
89 if (option.allowMultiple && | 117 if (option.allowMultiple && |
90 results[name].length == 0 && | 118 results[name].length == 0 && |
91 option.defaultValue != null) { | 119 option.defaultValue != null) { |
92 results[name].add(option.defaultValue); | 120 results[name].add(option.defaultValue); |
93 } | 121 } |
94 if (option.callback != null) option.callback(results[name]); | 122 if (option.callback != null) option.callback(results[name]); |
95 }); | 123 }); |
96 | 124 |
97 // Add in the leftover arguments we didn't parse to the innermost command. | 125 // Add in the leftover arguments we didn't parse to the innermost command. |
98 var rest = args.toList(); | 126 rest.addAll(args.toList()); |
99 args.clear(); | 127 args.clear(); |
100 return new ArgResults(results, commandName, commandResults, rest); | 128 return new ArgResults(results, commandName, commandResults, rest); |
101 } | 129 } |
102 | 130 |
103 /** | 131 /** |
104 * Pulls the value for [option] from the second argument in [args]. Validates | 132 * Pulls the value for [option] from the second argument in [args]. Validates |
105 * that there is a valid value there. | 133 * that there is a valid value there. |
106 */ | 134 */ |
107 void readNextArgAsValue(Option option) { | 135 void readNextArgAsValue(Option option) { |
108 // Take the option argument from the next command line arg. | 136 // Take the option argument from the next command line arg. |
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
272 '"$value" is not an allowed value for option "${option.name}".'); | 300 '"$value" is not an allowed value for option "${option.name}".'); |
273 } | 301 } |
274 | 302 |
275 if (option.allowMultiple) { | 303 if (option.allowMultiple) { |
276 results[option.name].add(value); | 304 results[option.name].add(value); |
277 } else { | 305 } else { |
278 results[option.name] = value; | 306 results[option.name] = value; |
279 } | 307 } |
280 } | 308 } |
281 } | 309 } |
OLD | NEW |