OLD | NEW |
| (Empty) |
1 // Copyright (c) 2016, 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 fasta.command_line; | |
6 | |
7 import 'fasta_codes.dart' show Message, templateFastaCLIArgumentRequired; | |
8 | |
9 import 'deprecated_problems.dart' show deprecated_inputError; | |
10 | |
11 import 'problems.dart' show unhandled; | |
12 | |
13 deprecated_argumentError(Message usage, String message) { | |
14 if (usage != null) print(usage.message); | |
15 deprecated_inputError(null, null, message); | |
16 } | |
17 | |
18 argumentError(Message usage, Message message) { | |
19 if (usage != null) print(usage.message); | |
20 deprecated_inputError(null, null, message.message); | |
21 } | |
22 | |
23 class ParsedArguments { | |
24 final Map<String, dynamic> options = <String, dynamic>{}; | |
25 final List<String> arguments = <String>[]; | |
26 | |
27 toString() => "ParsedArguments($options, $arguments)"; | |
28 } | |
29 | |
30 /// Abstract parser for command-line options. | |
31 class CommandLine { | |
32 final Map<String, dynamic> options; | |
33 | |
34 final List<String> arguments; | |
35 | |
36 final Message usage; | |
37 | |
38 CommandLine.parsed(ParsedArguments p, this.usage) | |
39 : this.options = p.options, | |
40 this.arguments = p.arguments { | |
41 validate(); | |
42 if (verbose) { | |
43 print(p); | |
44 } | |
45 } | |
46 | |
47 CommandLine(List<String> arguments, | |
48 {Map<String, dynamic> specification, Message usage}) | |
49 : this.parsed(parse(arguments, specification, usage), usage); | |
50 | |
51 bool get verbose { | |
52 return options.containsKey("-v") || options.containsKey("--verbose"); | |
53 } | |
54 | |
55 /// Override to validate arguments and options. | |
56 void validate() {} | |
57 | |
58 /// Parses a list of command-line [arguments] into options and arguments. | |
59 /// | |
60 /// An /option/ is something that, normally, starts with `-` or `--` (one or | |
61 /// two dashes). However, as a special case `/?` and `/h` are also recognized | |
62 /// as options for increased compatibility with Windows. An option can have a | |
63 /// value. | |
64 /// | |
65 /// An /argument/ is something that isn't an option, for example, a file name. | |
66 /// | |
67 /// The specification is a map of options to one of the type literals `Uri`, | |
68 /// `int`, `bool`, or `String`, or a comma (`","`) that represents option | |
69 /// values of type [Uri], [int], [bool], [String], or a comma-separated list | |
70 /// of [String], respectively. | |
71 /// | |
72 /// If [arguments] contains `"--"`, anything before is parsed as options, and | |
73 /// arguments; anything following is treated as arguments (even if starting | |
74 /// with, for example, a `-`). | |
75 /// | |
76 /// Anything that looks like an option is assumed to be a `bool` option set | |
77 /// to true, unless it's mentioned in [specification] in which case the | |
78 /// option requires a value, either on the form `--option value` or | |
79 /// `--option=value`. | |
80 /// | |
81 /// This method performs only a limited amount of validation, but if an error | |
82 /// occurs, it will print [usage] along with a specific error message. | |
83 static ParsedArguments parse(List<String> arguments, | |
84 Map<String, dynamic> specification, Message usage) { | |
85 specification ??= const <String, dynamic>{}; | |
86 ParsedArguments result = new ParsedArguments(); | |
87 int index = arguments.indexOf("--"); | |
88 Iterable<String> nonOptions = const <String>[]; | |
89 Iterator<String> iterator = arguments.iterator; | |
90 if (index != -1) { | |
91 nonOptions = arguments.skip(index + 1); | |
92 iterator = arguments.take(index).iterator; | |
93 } | |
94 while (iterator.moveNext()) { | |
95 String argument = iterator.current; | |
96 if (argument.startsWith("-")) { | |
97 var valueSpecification = specification[argument]; | |
98 String value; | |
99 if (valueSpecification != null) { | |
100 if (!iterator.moveNext()) { | |
101 return argumentError(usage, | |
102 templateFastaCLIArgumentRequired.withArguments(argument)); | |
103 } | |
104 value = iterator.current; | |
105 } else { | |
106 index = argument.indexOf("="); | |
107 if (index != -1) { | |
108 value = argument.substring(index + 1); | |
109 argument = argument.substring(0, index); | |
110 valueSpecification = specification[argument]; | |
111 } | |
112 } | |
113 if (valueSpecification == null) { | |
114 if (value != null) { | |
115 return deprecated_argumentError( | |
116 usage, "Argument '$argument' doesn't take a value: '$value'."); | |
117 } | |
118 result.options[argument] = true; | |
119 } else { | |
120 if (valueSpecification is! String && valueSpecification is! Type) { | |
121 return deprecated_argumentError( | |
122 usage, | |
123 "Unrecognized type of value " | |
124 "specification: ${valueSpecification.runtimeType}."); | |
125 } | |
126 switch ("$valueSpecification") { | |
127 case ",": | |
128 result.options | |
129 .putIfAbsent(argument, () => <String>[]) | |
130 .addAll(value.split(",")); | |
131 break; | |
132 | |
133 case "int": | |
134 case "bool": | |
135 case "String": | |
136 case "Uri": | |
137 if (result.options.containsKey(argument)) { | |
138 return deprecated_argumentError( | |
139 usage, | |
140 "Multiple values for '$argument': " | |
141 "'${result.options[argument]}' and '$value'."); | |
142 } | |
143 var parsedValue; | |
144 if (valueSpecification == int) { | |
145 parsedValue = int.parse(value, onError: (_) { | |
146 return deprecated_argumentError( | |
147 usage, "Value for '$argument', '$value', isn't an int."); | |
148 }); | |
149 } else if (valueSpecification == bool) { | |
150 if (value == "true" || value == "yes") { | |
151 parsedValue = true; | |
152 } else if (value == "false" || value == "no") { | |
153 parsedValue = false; | |
154 } else { | |
155 return deprecated_argumentError( | |
156 usage, | |
157 "Value for '$argument' is '$value', " | |
158 "but expected one of: 'true', 'false', 'yes', or 'no'."); | |
159 } | |
160 } else if (valueSpecification == Uri) { | |
161 parsedValue = Uri.base.resolve(value); | |
162 } else if (valueSpecification == String) { | |
163 parsedValue = value; | |
164 } else if (valueSpecification is String) { | |
165 return deprecated_argumentError( | |
166 usage, | |
167 "Unrecognized value specification: " | |
168 "'$valueSpecification', try using a type literal instead."); | |
169 } else { | |
170 // All possible cases should have been handled above. | |
171 return unhandled("${valueSpecification.runtimeType}", | |
172 "CommandLine.parse", -1, null); | |
173 } | |
174 result.options[argument] = parsedValue; | |
175 break; | |
176 | |
177 default: | |
178 return deprecated_argumentError(usage, | |
179 "Unrecognized value specification: '$valueSpecification'."); | |
180 } | |
181 } | |
182 } else if (argument == "/?" || argument == "/h") { | |
183 result.options[argument] = true; | |
184 } else { | |
185 result.arguments.add(argument); | |
186 } | |
187 } | |
188 result.arguments.addAll(nonOptions); | |
189 return result; | |
190 } | |
191 } | |
OLD | NEW |