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]+)(.*)$'); |
(...skipping 10 matching lines...) Expand all Loading... | |
21 * command. For top-level results, this returns `null`. | 21 * command. For top-level results, this returns `null`. |
22 */ | 22 */ |
23 final String commandName; | 23 final String commandName; |
24 | 24 |
25 /** | 25 /** |
26 * 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 |
27 * is the top-level parser. | 27 * is the top-level parser. |
28 */ | 28 */ |
29 final Parser parent; | 29 final Parser parent; |
30 | 30 |
31 /** If `true`, parsing will continue after a non-option argument. */ | |
32 final bool allowTrailingOptions; | |
33 | |
31 /** The grammar being parsed. */ | 34 /** The grammar being parsed. */ |
32 final ArgParser grammar; | 35 final ArgParser grammar; |
33 | 36 |
34 /** The arguments being parsed. */ | 37 /** The arguments being parsed. */ |
35 final List<String> args; | 38 final List<String> args; |
36 | 39 |
40 /** The remaining non-option, non-command arguments. */ | |
41 List<String> rest = <String>[]; | |
Bob Nystrom
2013/06/24 20:49:29
Make this final and lose the type annotation.
Andrei Mouravski
2013/06/24 20:54:29
Done.
| |
42 | |
37 /** The accumulated parsed options. */ | 43 /** The accumulated parsed options. */ |
38 final Map results = {}; | 44 final Map results = {}; |
39 | 45 |
40 Parser(this.commandName, this.grammar, this.args, [this.parent]); | 46 Parser(this.commandName, this.grammar, this.args, this.parent, rest, |
47 {this.allowTrailingOptions: false}) { | |
48 if (rest != null) this.rest.addAll(rest); | |
49 } | |
50 | |
41 | 51 |
42 /** The current argument being parsed. */ | 52 /** The current argument being parsed. */ |
43 String get current => args[0]; | 53 String get current => args[0]; |
44 | 54 |
45 /** Parses the arguments. This can only be called once. */ | 55 /** Parses the arguments. This can only be called once. */ |
46 ArgResults parse() { | 56 ArgResults parse() { |
47 var commandResults = null; | 57 var commandResults = null; |
48 | 58 |
49 // Initialize flags to their defaults. | 59 // Initialize flags to their defaults. |
50 grammar.options.forEach((name, option) { | 60 grammar.options.forEach((name, option) { |
51 if (option.allowMultiple) { | 61 if (option.allowMultiple) { |
52 results[name] = []; | 62 results[name] = []; |
53 } else { | 63 } else { |
54 results[name] = option.defaultValue; | 64 results[name] = option.defaultValue; |
55 } | 65 } |
56 }); | 66 }); |
57 | 67 |
58 // Parse the args. | 68 // Parse the args. |
59 while (args.length > 0) { | 69 while (args.length > 0) { |
60 if (current == '--') { | 70 if (current == '--') { |
61 // Reached the argument terminator, so stop here. | 71 // Reached the argument terminator, so stop here. |
62 args.removeAt(0); | 72 args.removeAt(0); |
63 break; | 73 break; |
64 } | 74 } |
65 | 75 |
66 // Try to parse the current argument as a command. This happens before | 76 // Try to parse the current argument as a command. This happens before |
67 // options so that commands can have option-like names. | 77 // options so that commands can have option-like names. |
68 var command = grammar.commands[current]; | 78 var command = grammar.commands[current]; |
69 if (command != null) { | 79 if (command != null) { |
80 validate(rest.isEmpty, 'Cannot specify arguments before a command.'); | |
70 var commandName = args.removeAt(0); | 81 var commandName = args.removeAt(0); |
71 var commandParser = new Parser(commandName, command, args, this); | 82 var commandParser = new Parser(commandName, command, args, this, rest, |
83 allowTrailingOptions: allowTrailingOptions); | |
72 commandResults = commandParser.parse(); | 84 commandResults = commandParser.parse(); |
73 continue; | 85 |
86 // All remaining arguments were passed to command so clear them here. | |
87 rest.clear(); | |
88 break; | |
74 } | 89 } |
75 | 90 |
76 // Try to parse the current argument as an option. Note that the order | 91 // Try to parse the current argument as an option. Note that the order |
77 // here matters. | 92 // here matters. |
78 if (parseSoloOption()) continue; | 93 if (isCurrentArgAnOption) { |
Bob Nystrom
2013/06/24 20:49:29
This isn't needed any more.
Andrei Mouravski
2013/06/24 20:54:29
Done.
| |
79 if (parseAbbreviation(this)) continue; | 94 if (parseSoloOption()) continue; |
80 if (parseLongOption()) continue; | 95 if (parseAbbreviation(this)) continue; |
96 if (parseLongOption()) continue; | |
97 } | |
81 | 98 |
82 // If we got here, the argument doesn't look like an option, so stop. | 99 // This argument is neither option nor command, so stop parsing unless |
83 break; | 100 // the [allowTrailingOptions] option is set. |
101 if (!allowTrailingOptions) break; | |
102 rest.add(args.removeAt(0)); | |
84 } | 103 } |
85 | 104 |
86 // Set unspecified multivalued arguments to their default value, | 105 // Set unspecified multivalued arguments to their default value, |
87 // if any, and invoke the callbacks. | 106 // if any, and invoke the callbacks. |
88 grammar.options.forEach((name, option) { | 107 grammar.options.forEach((name, option) { |
89 if (option.allowMultiple && | 108 if (option.allowMultiple && |
90 results[name].length == 0 && | 109 results[name].length == 0 && |
91 option.defaultValue != null) { | 110 option.defaultValue != null) { |
92 results[name].add(option.defaultValue); | 111 results[name].add(option.defaultValue); |
93 } | 112 } |
94 if (option.callback != null) option.callback(results[name]); | 113 if (option.callback != null) option.callback(results[name]); |
95 }); | 114 }); |
96 | 115 |
97 // Add in the leftover arguments we didn't parse to the innermost command. | 116 // Add in the leftover arguments we didn't parse to the innermost command. |
98 var rest = args.toList(); | 117 rest.addAll(args); |
99 args.clear(); | 118 args.clear(); |
100 return new ArgResults(results, commandName, commandResults, rest); | 119 return new ArgResults(results, commandName, commandResults, rest); |
101 } | 120 } |
102 | 121 |
103 /** | 122 /** |
104 * Pulls the value for [option] from the second argument in [args]. Validates | 123 * Pulls the value for [option] from the second argument in [args]. Validates |
105 * that there is a valid value there. | 124 * that there is a valid value there. |
106 */ | 125 */ |
107 void readNextArgAsValue(Option option) { | 126 void readNextArgAsValue(Option option) { |
108 // Take the option argument from the next command line arg. | 127 // Take the option argument from the next command line arg. |
109 validate(args.length > 0, | 128 validate(args.length > 0, |
110 'Missing argument for "${option.name}".'); | 129 'Missing argument for "${option.name}".'); |
111 | 130 |
112 // Make sure it isn't an option itself. | 131 // Make sure it isn't an option itself. |
113 validate(!_ABBR_OPT.hasMatch(current) && !_LONG_OPT.hasMatch(current), | 132 validate(!_ABBR_OPT.hasMatch(current) && !_LONG_OPT.hasMatch(current), |
114 'Missing argument for "${option.name}".'); | 133 'Missing argument for "${option.name}".'); |
115 | 134 |
116 setOption(results, option, current); | 135 setOption(results, option, current); |
117 args.removeAt(0); | 136 args.removeAt(0); |
118 } | 137 } |
119 | 138 |
139 /** Returns `true` if the current argument looks like an option. */ | |
140 bool get isCurrentArgAnOption => [_SOLO_OPT, _ABBR_OPT, _LONG_OPT].any( | |
141 (re) => re.firstMatch(current) != null); | |
Bob Nystrom
2013/06/24 20:49:29
You can ditch this.
Andrei Mouravski
2013/06/24 20:54:29
Done.
| |
142 | |
120 /** | 143 /** |
121 * Tries to parse the current argument as a "solo" option, which is a single | 144 * Tries to parse the current argument as a "solo" option, which is a single |
122 * hyphen followed by a single letter. We treat this differently than | 145 * hyphen followed by a single letter. We treat this differently than |
123 * collapsed abbreviations (like "-abc") to handle the possible value that | 146 * collapsed abbreviations (like "-abc") to handle the possible value that |
124 * may follow it. | 147 * may follow it. |
125 */ | 148 */ |
126 bool parseSoloOption() { | 149 bool parseSoloOption() { |
127 var soloOpt = _SOLO_OPT.firstMatch(current); | 150 var soloOpt = _SOLO_OPT.firstMatch(current); |
128 if (soloOpt == null) return false; | 151 if (soloOpt == null) return false; |
129 | 152 |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
272 '"$value" is not an allowed value for option "${option.name}".'); | 295 '"$value" is not an allowed value for option "${option.name}".'); |
273 } | 296 } |
274 | 297 |
275 if (option.allowMultiple) { | 298 if (option.allowMultiple) { |
276 results[option.name].add(value); | 299 results[option.name].add(value); |
277 } else { | 300 } else { |
278 results[option.name] = value; | 301 results[option.name] = value; |
279 } | 302 } |
280 } | 303 } |
281 } | 304 } |
OLD | NEW |