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

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

Issue 816693004: Add observatory_pub_packages snapshot to third_party (Closed) Base URL: http://dart.googlecode.com/svn/third_party/
Patch Set: Created 6 years 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 | Annotate | Revision Log
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(grammar, results, commandName, commandResults, rest,
96 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,
105 'Missing argument for "${option.name}".');
106
107 // Make sure it isn't an option itself.
108 validate(!_ABBR_OPT.hasMatch(current) && !_LONG_OPT.hasMatch(current),
109 'Missing argument for "${option.name}".');
110
111 setOption(results, option, current);
112 args.removeAt(0);
113 }
114
115 /// Tries to parse the current argument as a "solo" option, which is a single
116 /// hyphen followed by a single letter.
117 ///
118 /// We treat this differently than collapsed abbreviations (like "-abc") to
119 /// handle the possible value that may follow it.
120 bool parseSoloOption() {
121 var soloOpt = _SOLO_OPT.firstMatch(current);
122 if (soloOpt == null) return false;
123
124 var option = grammar.findByAbbreviation(soloOpt[1]);
125 if (option == null) {
126 // Walk up to the parent command if possible.
127 validate(parent != null,
128 'Could not find an option or flag "-${soloOpt[1]}".');
129 return parent.parseSoloOption();
130 }
131
132 args.removeAt(0);
133
134 if (option.isFlag) {
135 setOption(results, option, true);
136 } else {
137 readNextArgAsValue(option);
138 }
139
140 return true;
141 }
142
143 /// Tries to parse the current argument as a series of collapsed abbreviations
144 /// (like "-abc") or a single abbreviation with the value directly attached
145 /// to it (like "-mrelease").
146 bool parseAbbreviation(Parser innermostCommand) {
147 var abbrOpt = _ABBR_OPT.firstMatch(current);
148 if (abbrOpt == null) return false;
149
150 // If the first character is the abbreviation for a non-flag option, then
151 // the rest is the value.
152 var c = abbrOpt[1].substring(0, 1);
153 var first = grammar.findByAbbreviation(c);
154 if (first == null) {
155 // Walk up to the parent command if possible.
156 validate(parent != null,
157 'Could not find an option with short name "-$c".');
158 return parent.parseAbbreviation(innermostCommand);
159 } else if (!first.isFlag) {
160 // The first character is a non-flag option, so the rest must be the
161 // value.
162 var value = '${abbrOpt[1].substring(1)}${abbrOpt[2]}';
163 setOption(results, first, value);
164 } else {
165 // If we got some non-flag characters, then it must be a value, but
166 // if we got here, it's a flag, which is wrong.
167 validate(abbrOpt[2] == '',
168 'Option "-$c" is a flag and cannot handle value '
169 '"${abbrOpt[1].substring(1)}${abbrOpt[2]}".');
170
171 // Not an option, so all characters should be flags.
172 // We use "innermostCommand" here so that if a parent command parses the
173 // *first* letter, subcommands can still be found to parse the other
174 // letters.
175 for (var i = 0; i < abbrOpt[1].length; i++) {
176 var c = abbrOpt[1].substring(i, i + 1);
177 innermostCommand.parseShortFlag(c);
178 }
179 }
180
181 args.removeAt(0);
182 return true;
183 }
184
185 void parseShortFlag(String c) {
186 var option = grammar.findByAbbreviation(c);
187 if (option == null) {
188 // Walk up to the parent command if possible.
189 validate(parent != null,
190 'Could not find an option with short name "-$c".');
191 parent.parseShortFlag(c);
192 return;
193 }
194
195 // In a list of short options, only the first can be a non-flag. If
196 // we get here we've checked that already.
197 validate(option.isFlag,
198 'Option "-$c" must be a flag to be in a collapsed "-".');
199
200 setOption(results, option, true);
201 }
202
203 /// Tries to parse the current argument as a long-form named option, which
204 /// may include a value like "--mode=release" or "--mode release".
205 bool parseLongOption() {
206 var longOpt = _LONG_OPT.firstMatch(current);
207 if (longOpt == null) return false;
208
209 var name = longOpt[1];
210 var option = grammar.options[name];
211 if (option != null) {
212 args.removeAt(0);
213 if (option.isFlag) {
214 validate(longOpt[3] == null,
215 'Flag option "$name" should not be given a value.');
216
217 setOption(results, option, true);
218 } else if (longOpt[3] != null) {
219 // We have a value like --foo=bar.
220 setOption(results, option, longOpt[3]);
221 } else {
222 // Option like --foo, so look for the value as the next arg.
223 readNextArgAsValue(option);
224 }
225 } else if (name.startsWith('no-')) {
226 // See if it's a negated flag.
227 name = name.substring('no-'.length);
228 option = grammar.options[name];
229 if (option == null) {
230 // Walk up to the parent command if possible.
231 validate(parent != null, 'Could not find an option named "$name".');
232 return parent.parseLongOption();
233 }
234
235 args.removeAt(0);
236 validate(option.isFlag, 'Cannot negate non-flag option "$name".');
237 validate(option.negatable, 'Cannot negate option "$name".');
238
239 setOption(results, option, false);
240 } else {
241 // Walk up to the parent command if possible.
242 validate(parent != null, 'Could not find an option named "$name".');
243 return parent.parseLongOption();
244 }
245
246 return true;
247 }
248
249 /// Called during parsing to validate the arguments.
250 ///
251 /// Throws a [FormatException] if [condition] is `false`.
252 void validate(bool condition, String message) {
253 if (!condition) throw new FormatException(message);
254 }
255
256 /// Validates and stores [value] as the value for [option].
257 void setOption(Map results, Option option, value) {
258 // See if it's one of the allowed values.
259 if (option.allowed != null) {
260 validate(option.allowed.any((allow) => allow == value),
261 '"$value" is not an allowed value for option "${option.name}".');
262 }
263
264 if (option.isMultiple) {
265 var list = results.putIfAbsent(option.name, () => []);
266 list.add(value);
267 } else {
268 results[option.name] = value;
269 }
270 }
271 }
OLDNEW
« no previous file with comments | « observatory_pub_packages/args/src/option.dart ('k') | observatory_pub_packages/args/src/usage.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698