Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(118)

Side by Side Diff: args/lib/src/parser.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « args/lib/src/option.dart ('k') | args/lib/src/usage.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library args.src.parser;
6
7 import 'arg_parser.dart';
8 import 'arg_results.dart';
9 import 'option.dart';
10
11 final _SOLO_OPT = new RegExp(r'^-([a-zA-Z0-9])$');
12 final _ABBR_OPT = new RegExp(r'^-([a-zA-Z0-9]+)(.*)$');
13 final _LONG_OPT = new RegExp(r'^--([a-zA-Z\-_0-9]+)(=(.*))?$');
14
15 /// The actual argument parsing class.
16 ///
17 /// Unlike [ArgParser] which is really more an "arg grammar", this is the class
18 /// that does the parsing and holds the mutable state required during a parse.
19 class Parser {
20 /// If parser is parsing a command's options, this will be the name of the
21 /// command. For top-level results, this returns `null`.
22 final String commandName;
23
24 /// The parser for the supercommand of this command parser, or `null` if this
25 /// is the top-level parser.
26 final Parser parent;
27
28 /// The grammar being parsed.
29 final ArgParser grammar;
30
31 /// The arguments being parsed.
32 final List<String> args;
33
34 /// The remaining non-option, non-command arguments.
35 final rest = <String>[];
36
37 /// The accumulated parsed options.
38 final Map<String, dynamic> results = <String, dynamic>{};
39
40 Parser(this.commandName, this.grammar, this.args, this.parent, rest) {
41 if (rest != null) this.rest.addAll(rest);
42 }
43
44 /// The current argument being parsed.
45 String get current => args[0];
46
47 /// Parses the arguments. This can only be called once.
48 ArgResults parse() {
49 var arguments = args.toList();
50 var commandResults = null;
51
52 // Parse the args.
53 while (args.length > 0) {
54 if (current == '--') {
55 // Reached the argument terminator, so stop here.
56 args.removeAt(0);
57 break;
58 }
59
60 // Try to parse the current argument as a command. This happens before
61 // options so that commands can have option-like names.
62 var command = grammar.commands[current];
63 if (command != null) {
64 validate(rest.isEmpty, 'Cannot specify arguments before a command.');
65 var commandName = args.removeAt(0);
66 var commandParser = new Parser(commandName, command, args, this, rest);
67 commandResults = commandParser.parse();
68
69 // All remaining arguments were passed to command so clear them here.
70 rest.clear();
71 break;
72 }
73
74 // Try to parse the current argument as an option. Note that the order
75 // here matters.
76 if (parseSoloOption()) continue;
77 if (parseAbbreviation(this)) continue;
78 if (parseLongOption()) continue;
79
80 // This argument is neither option nor command, so stop parsing unless
81 // the [allowTrailingOptions] option is set.
82 if (!grammar.allowTrailingOptions) break;
83 rest.add(args.removeAt(0));
84 }
85
86 // Invoke the callbacks.
87 grammar.options.forEach((name, option) {
88 if (option.callback == null) return;
89 option.callback(option.getOrDefault(results[name]));
90 });
91
92 // Add in the leftover arguments we didn't parse to the innermost command.
93 rest.addAll(args);
94 args.clear();
95 return newArgResults(
96 grammar, results, commandName, commandResults, rest, arguments);
97 }
98
99 /// Pulls the value for [option] from the second argument in [args].
100 ///
101 /// Validates that there is a valid value there.
102 void readNextArgAsValue(Option option) {
103 // Take the option argument from the next command line arg.
104 validate(args.length > 0, 'Missing argument for "${option.name}".');
105
106 setOption(results, option, current);
107 args.removeAt(0);
108 }
109
110 /// Tries to parse the current argument as a "solo" option, which is a single
111 /// hyphen followed by a single letter.
112 ///
113 /// We treat this differently than collapsed abbreviations (like "-abc") to
114 /// handle the possible value that may follow it.
115 bool parseSoloOption() {
116 var soloOpt = _SOLO_OPT.firstMatch(current);
117 if (soloOpt == null) return false;
118
119 var option = grammar.findByAbbreviation(soloOpt[1]);
120 if (option == null) {
121 // Walk up to the parent command if possible.
122 validate(
123 parent != null, 'Could not find an option or flag "-${soloOpt[1]}".');
124 return parent.parseSoloOption();
125 }
126
127 args.removeAt(0);
128
129 if (option.isFlag) {
130 setFlag(results, option, true);
131 } else {
132 readNextArgAsValue(option);
133 }
134
135 return true;
136 }
137
138 /// Tries to parse the current argument as a series of collapsed abbreviations
139 /// (like "-abc") or a single abbreviation with the value directly attached
140 /// to it (like "-mrelease").
141 bool parseAbbreviation(Parser innermostCommand) {
142 var abbrOpt = _ABBR_OPT.firstMatch(current);
143 if (abbrOpt == null) return false;
144
145 // If the first character is the abbreviation for a non-flag option, then
146 // the rest is the value.
147 var c = abbrOpt[1].substring(0, 1);
148 var first = grammar.findByAbbreviation(c);
149 if (first == null) {
150 // Walk up to the parent command if possible.
151 validate(
152 parent != null, 'Could not find an option with short name "-$c".');
153 return parent.parseAbbreviation(innermostCommand);
154 } else if (!first.isFlag) {
155 // The first character is a non-flag option, so the rest must be the
156 // value.
157 var value = '${abbrOpt[1].substring(1)}${abbrOpt[2]}';
158 setOption(results, first, value);
159 } else {
160 // If we got some non-flag characters, then it must be a value, but
161 // if we got here, it's a flag, which is wrong.
162 validate(abbrOpt[2] == '',
163 'Option "-$c" is a flag and cannot handle value '
164 '"${abbrOpt[1].substring(1)}${abbrOpt[2]}".');
165
166 // Not an option, so all characters should be flags.
167 // We use "innermostCommand" here so that if a parent command parses the
168 // *first* letter, subcommands can still be found to parse the other
169 // letters.
170 for (var i = 0; i < abbrOpt[1].length; i++) {
171 var c = abbrOpt[1].substring(i, i + 1);
172 innermostCommand.parseShortFlag(c);
173 }
174 }
175
176 args.removeAt(0);
177 return true;
178 }
179
180 void parseShortFlag(String c) {
181 var option = grammar.findByAbbreviation(c);
182 if (option == null) {
183 // Walk up to the parent command if possible.
184 validate(
185 parent != null, 'Could not find an option with short name "-$c".');
186 parent.parseShortFlag(c);
187 return;
188 }
189
190 // In a list of short options, only the first can be a non-flag. If
191 // we get here we've checked that already.
192 validate(
193 option.isFlag, 'Option "-$c" must be a flag to be in a collapsed "-".');
194
195 setFlag(results, option, true);
196 }
197
198 /// Tries to parse the current argument as a long-form named option, which
199 /// may include a value like "--mode=release" or "--mode release".
200 bool parseLongOption() {
201 var longOpt = _LONG_OPT.firstMatch(current);
202 if (longOpt == null) return false;
203
204 var name = longOpt[1];
205 var option = grammar.options[name];
206 if (option != null) {
207 args.removeAt(0);
208 if (option.isFlag) {
209 validate(longOpt[3] == null,
210 'Flag option "$name" should not be given a value.');
211
212 setFlag(results, option, true);
213 } else if (longOpt[3] != null) {
214 // We have a value like --foo=bar.
215 setOption(results, option, longOpt[3]);
216 } else {
217 // Option like --foo, so look for the value as the next arg.
218 readNextArgAsValue(option);
219 }
220 } else if (name.startsWith('no-')) {
221 // See if it's a negated flag.
222 name = name.substring('no-'.length);
223 option = grammar.options[name];
224 if (option == null) {
225 // Walk up to the parent command if possible.
226 validate(parent != null, 'Could not find an option named "$name".');
227 return parent.parseLongOption();
228 }
229
230 args.removeAt(0);
231 validate(option.isFlag, 'Cannot negate non-flag option "$name".');
232 validate(option.negatable, 'Cannot negate option "$name".');
233
234 setFlag(results, option, false);
235 } else {
236 // Walk up to the parent command if possible.
237 validate(parent != null, 'Could not find an option named "$name".');
238 return parent.parseLongOption();
239 }
240
241 return true;
242 }
243
244 /// Called during parsing to validate the arguments.
245 ///
246 /// Throws a [FormatException] if [condition] is `false`.
247 void validate(bool condition, String message) {
248 if (!condition) throw new FormatException(message);
249 }
250
251 /// Validates and stores [value] as the value for [option], which must not be
252 /// a flag.
253 void setOption(Map results, Option option, String value) {
254 assert(!option.isFlag);
255
256 if (!option.isMultiple) {
257 _validateAllowed(option, value);
258 results[option.name] = value;
259 return;
260 }
261
262 var list = results.putIfAbsent(option.name, () => []);
263
264 if (option.splitCommas) {
265 for (var element in value.split(",")) {
266 _validateAllowed(option, element);
267 list.add(element);
268 }
269 } else {
270 _validateAllowed(option, value);
271 list.add(value);
272 }
273 }
274
275 /// Validates and stores [value] as the value for [option], which must be a
276 /// flag.
277 void setFlag(Map results, Option option, bool value) {
278 assert(option.isFlag);
279 results[option.name] = value;
280 }
281
282 /// Validates that [value] is allowed as a value of [option].
283 void _validateAllowed(Option option, String value) {
284 if (option.allowed == null) return;
285
286 validate(option.allowed.contains(value),
287 '"$value" is not an allowed value for option "${option.name}".');
288 }
289 }
OLDNEW
« no previous file with comments | « args/lib/src/option.dart ('k') | args/lib/src/usage.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698