132
|
1 /*******************************************************************************
|
|
2
|
|
3 copyright: Copyright (c) 2005-2006 Lars Ivar Igesund,
|
|
4 Eric Anderton. All rights reserved
|
|
5
|
|
6 license: BSD style: $(LICENSE)
|
|
7
|
|
8 version: Initial release: December 2005
|
|
9
|
|
10 author: Lars Ivar Igesund, Eric Anderton
|
|
11
|
|
12 *******************************************************************************/
|
|
13
|
|
14 module tango.util.ArgParser;
|
|
15
|
|
16 private import tango.core.Exception;
|
|
17
|
|
18 /**
|
|
19 An alias to a delegate taking a char[] as a parameter. The value
|
|
20 parameter will hold any chars immediately
|
|
21 following the argument.
|
|
22 */
|
|
23 alias void delegate (char[] value) ArgParserCallback;
|
|
24
|
|
25 /**
|
|
26 An alias to a delegate taking a char[] as a parameter. The value
|
|
27 parameter will hold any chars immediately
|
|
28 following the argument.
|
|
29
|
|
30 The ordinal argument represents which default argument this is for
|
|
31 the given stream of arguments. The first default argument will
|
|
32 be ordinal=0 with each successive call to this callback having
|
|
33 ordinal values of 1, 2, 3 and so forth. This can be reset to zero
|
|
34 in new calls to parse.
|
|
35 */
|
|
36 alias void delegate (char[] value,uint ordinal) DefaultArgParserCallback;
|
|
37
|
|
38 /**
|
|
39 An alias to a delegate taking no parameters
|
|
40 */
|
|
41 alias void delegate () ArgParserSimpleCallback;
|
|
42
|
|
43
|
|
44 /**
|
|
45 A struct that represents a "{Prefix}{Identifier}" string.
|
|
46 */
|
|
47 struct Argument {
|
|
48 char[] prefix;
|
|
49 char[] identifier;
|
|
50
|
|
51 /**
|
|
52 Creates a new Argument instance with given prefix and identifier.
|
|
53 */
|
|
54 static Argument opCall ( char[] prefix, char[] identifier ) {
|
|
55 Argument result;
|
|
56
|
|
57 result.prefix = prefix;
|
|
58 result.identifier = identifier;
|
|
59
|
|
60 return result;
|
|
61 }
|
|
62 }
|
|
63
|
|
64 /**
|
|
65 Alias for for the lazy people.
|
|
66 */
|
|
67 alias Argument Arg;
|
|
68
|
|
69
|
|
70 /**
|
|
71 A utility class to parse and handle your command line arguments.
|
|
72 */
|
|
73 class ArgParser{
|
|
74
|
|
75 /**
|
|
76 A helper struct containing a callback and an id, corresponding to
|
|
77 the argId passed to one of the bind methods.
|
|
78 */
|
|
79 protected struct PrefixCallback {
|
|
80 char[] id;
|
|
81 ArgParserCallback cb;
|
|
82 }
|
|
83
|
|
84 protected PrefixCallback[][char[]] bindings;
|
|
85 protected DefaultArgParserCallback[char[]] defaultBindings;
|
|
86 protected uint[char[]] prefixOrdinals;
|
|
87 protected char[][] prefixSearchOrder;
|
|
88 protected DefaultArgParserCallback defaultbinding;
|
|
89 private uint defaultOrdinal = 0;
|
|
90
|
|
91 protected void addBinding(PrefixCallback pcb, char[] argPrefix){
|
|
92 if (!(argPrefix in bindings)) {
|
|
93 prefixSearchOrder ~= argPrefix;
|
|
94 }
|
|
95 bindings[argPrefix] ~= pcb;
|
|
96 }
|
|
97
|
|
98 /**
|
|
99 Binds a delegate callback to argument with a prefix and
|
|
100 a argId.
|
|
101
|
|
102 Params:
|
|
103 argPrefix = the prefix of the argument, e.g. a dash '-'.
|
|
104 argId = the name of the argument, what follows the prefix
|
|
105 cb = the delegate that should be called when this argument is found
|
|
106 */
|
|
107 public void bind(char[] argPrefix, char[] argId, ArgParserCallback cb){
|
|
108 PrefixCallback pcb;
|
|
109 pcb.id = argId;
|
|
110 pcb.cb = cb;
|
|
111 addBinding(pcb, argPrefix);
|
|
112 }
|
|
113
|
|
114 /**
|
|
115 The constructor, creates an empty ArgParser instance.
|
|
116 */
|
|
117 public this(){
|
|
118 defaultbinding = null;
|
|
119 }
|
|
120
|
|
121 /**
|
|
122 The constructor, creates an ArgParser instance with a defined default callback.
|
|
123 */
|
|
124 public this(DefaultArgParserCallback callback){
|
|
125 defaultbinding = callback;
|
|
126 }
|
|
127
|
|
128 protected class SimpleCallbackAdapter{
|
|
129 ArgParserSimpleCallback callback;
|
|
130 public this(ArgParserSimpleCallback callback){
|
|
131 this.callback = callback;
|
|
132 }
|
|
133
|
|
134 public void adapterCallback(char[] value){
|
|
135 callback();
|
|
136 }
|
|
137 }
|
|
138
|
|
139 /**
|
|
140 Binds a delegate callback to argument with a prefix and
|
|
141 a argId.
|
|
142
|
|
143 Params:
|
|
144 argPrefix = the prefix of the argument, e.g. a dash '-'.
|
|
145 argId = the name of the argument, what follows the prefix
|
|
146 cb = the delegate that should be called when this argument is found
|
|
147 */
|
|
148 public void bind(char[] argPrefix, char[] argId, ArgParserSimpleCallback cb){
|
|
149 SimpleCallbackAdapter adapter = new SimpleCallbackAdapter(cb);
|
|
150 PrefixCallback pcb;
|
|
151 pcb.id = argId;
|
|
152 pcb.cb = &adapter.adapterCallback;
|
|
153 addBinding(pcb, argPrefix);
|
|
154 }
|
|
155
|
|
156 /**
|
|
157 Binds a delegate callback to all arguments with prefix argPrefix, but that
|
|
158 do not conform to an argument bound in a call to bind().
|
|
159
|
|
160 Params:
|
|
161 argPrefix = the prefix for the callback
|
|
162 callback = the default callback
|
|
163 */
|
|
164 public void bindDefault(char[] argPrefix, DefaultArgParserCallback callback){
|
|
165 defaultBindings[argPrefix] = callback;
|
|
166 prefixOrdinals[argPrefix] = 0;
|
|
167 if (!(argPrefix in bindings)) {
|
|
168 prefixSearchOrder ~= argPrefix;
|
|
169 }
|
|
170 }
|
|
171
|
|
172 /**
|
|
173 Binds a delegate callback to all arguments not conforming to an
|
|
174 argument bound in a call to bind(). These arguments will be passed to the
|
|
175 delegate without having any matching prefixes removed.
|
|
176
|
|
177 Params:
|
|
178 callback = the default callback
|
|
179 */
|
|
180 public void bindDefault(DefaultArgParserCallback callback){
|
|
181 defaultbinding = callback;
|
|
182 }
|
|
183
|
|
184 /**
|
|
185 Binds a delegate callback to an argument.
|
|
186
|
|
187 Params:
|
|
188 argument = argument to respond to
|
|
189 callback = the delegate that should be called when the argument is found
|
|
190 */
|
|
191 public void bind (Argument argument, ArgParserCallback callback) {
|
|
192 bind(argument.prefix, argument.identifier, callback);
|
|
193 }
|
|
194
|
|
195 /**
|
|
196 Binds a delegate callback to any number of arguments.
|
|
197
|
|
198 Params:
|
|
199 arguments = an array of Argument struct instances
|
|
200 callback = the delegate that should be called when one of the arguments is found
|
|
201 */
|
|
202 public void bind ( Argument[] arguments, void delegate(char[]) callback ) {
|
|
203 foreach (argument; arguments) { bind(argument, callback); }
|
|
204 }
|
|
205
|
|
206 /**
|
|
207 Binds a delegate callback to an identifier with Posix-like prefixes. This means,
|
|
208 it binds for both prefixes "-" and "--", as well as identifier with, and
|
|
209 without a delimiting "=" between identifier and value.
|
|
210
|
|
211 Params:
|
|
212 identifier = argument identifier
|
|
213 callback = the delegate that should be called when one of the arguments is found
|
|
214 */
|
|
215 public void bindPosix ( char[] identifier, ArgParserCallback callback ) {
|
|
216 bind([ Argument("-", identifier ~ "="), Argument("-", identifier),
|
|
217 Argument("--", identifier ~ "="), Argument("--", identifier) ], callback);
|
|
218 }
|
|
219
|
|
220 /**
|
|
221 Binds a delegate callback to any number of identifiers with Posix-like prefixes.
|
|
222 See bindPosix(identifier, callback).
|
|
223
|
|
224 Params:
|
|
225 arguments = an array of argument identifiers
|
|
226 callback = the delegate that should be called when one of the arguments is found
|
|
227 */
|
|
228 public void bindPosix ( char[][] identifiers, ArgParserCallback callback ) {
|
|
229 foreach (identifier; identifiers) { bindPosix(identifier, callback); }
|
|
230 }
|
|
231
|
|
232 /**
|
|
233 Parses the arguments provided by the parameter. The bound callbacks are called as
|
|
234 arguments are recognized. If two arguments have the same prefix, and start with
|
|
235 the same characters (e.g.: --open, --opened), the longest matching bound callback
|
|
236 is called.
|
|
237
|
|
238 Params:
|
|
239 arguments = the command line arguments from the application
|
|
240 resetOrdinals = if true, all ordinal counts will be set to zero
|
|
241 */
|
|
242 public void parse(char[][] arguments, bool resetOrdinals = false){
|
|
243 if (bindings.length == 0) return;
|
|
244
|
|
245 if (resetOrdinals) {
|
|
246 defaultOrdinal = 0;
|
|
247 foreach (key; prefixOrdinals.keys) {
|
|
248 prefixOrdinals[key] = 0;
|
|
249 }
|
|
250 }
|
|
251
|
|
252 foreach (char[] arg; arguments) {
|
|
253 char[] argData = arg;
|
|
254 char[] argOrig = argData;
|
|
255 bool found = false;
|
|
256
|
|
257 foreach (char[] prefix; prefixSearchOrder) {
|
|
258 if(argData.length < prefix.length) continue;
|
|
259
|
|
260 if(argData[0..prefix.length] != prefix) continue;
|
|
261 else argData = argData[prefix.length..$];
|
|
262
|
|
263 if (prefix in bindings) {
|
|
264 PrefixCallback[] candidates;
|
|
265
|
|
266 foreach (PrefixCallback cb; bindings[prefix]) {
|
|
267 if (argData.length < cb.id.length) continue;
|
|
268
|
|
269 uint cbil = cb.id.length;
|
|
270
|
|
271 if (cb.id == argData[0..cbil]) {
|
|
272 found = true;
|
|
273 candidates ~= cb;
|
|
274 }
|
|
275 }
|
|
276
|
|
277 if (found) {
|
|
278 // Find the longest matching callback identifier from the candidates.
|
|
279 uint indexLongestMatch = 0;
|
|
280
|
|
281 if (candidates.length > 1) {
|
|
282 foreach (i, candidate; candidates) {
|
|
283 if (candidate.id.length > candidates[indexLongestMatch].id.length) {
|
|
284 indexLongestMatch = i;
|
|
285 }
|
|
286 }
|
|
287 }
|
|
288
|
|
289 // Call the best matching callback.
|
|
290 with(candidates[indexLongestMatch]) { cb(argData[id.length..$]); }
|
|
291 }
|
|
292 }
|
|
293 if (found) {
|
|
294 break;
|
|
295 }
|
|
296 else if (prefix in defaultBindings){
|
|
297 defaultBindings[prefix](argData,prefixOrdinals[prefix]);
|
|
298 prefixOrdinals[prefix]++;
|
|
299 found = true;
|
|
300 break;
|
|
301 }
|
|
302 argData = argOrig;
|
|
303 }
|
|
304 if (!found) {
|
|
305 if (defaultbinding !is null) {
|
|
306 defaultbinding(argData,defaultOrdinal);
|
|
307 defaultOrdinal++;
|
|
308 }
|
|
309 else {
|
|
310 throw new IllegalArgumentException("Illegal argument "~ argData);
|
|
311 }
|
|
312 }
|
|
313 }
|
|
314 }
|
|
315 }
|
|
316
|
|
317 debug (UnitTest) {
|
|
318 import Integer = tango.text.convert.Integer;
|
|
319
|
|
320 //void main() {}
|
|
321
|
|
322 unittest {
|
|
323
|
|
324 ArgParser parser = new ArgParser();
|
|
325 bool h = false;
|
|
326 bool h2 = false;
|
|
327 bool b = false;
|
|
328 bool bb = false;
|
|
329 bool boolean = false;
|
|
330 int n = -1;
|
|
331 int dashOrdinalCount = -1;
|
|
332 int ordinalCount = -1;
|
|
333
|
|
334 parser.bind("--", "h2", delegate void(){
|
|
335 h2 = true;
|
|
336 });
|
|
337
|
|
338 parser.bind("-", "h", delegate void(){
|
|
339 h = true;
|
|
340 });
|
|
341
|
|
342 parser.bind("-", "bb", delegate void(){
|
|
343 bb = true;
|
|
344 });
|
|
345
|
|
346 parser.bind("-", "bool", delegate void(char[] value){
|
|
347 assert(value.length == 5);
|
|
348 assert(value[0] == '=');
|
|
349 if (value[1..5] == "true") {
|
|
350 boolean = true;
|
|
351 }
|
|
352 else {
|
|
353 assert(false);
|
|
354 }
|
|
355 });
|
|
356
|
|
357 parser.bind("-", "b", delegate void(){
|
|
358 b = true;
|
|
359 });
|
|
360
|
|
361 parser.bind("-", "n", delegate void(char[] value){
|
|
362 assert(value[0] == '=');
|
|
363 n = cast(int) Integer.parse(value[1..5]);
|
|
364 assert(n == 4349);
|
|
365 });
|
|
366
|
|
367 parser.bindDefault(delegate void(char[] value, uint ordinal){
|
|
368 ordinalCount = ordinal;
|
|
369 if (ordinal == 0) {
|
|
370 assert(value == "ordinalTest1");
|
|
371 }
|
|
372 else if (ordinal == 1) {
|
|
373 assert(value == "ordinalTest2");
|
|
374 }
|
|
375 });
|
|
376
|
|
377 parser.bindDefault("-", delegate void(char[] value, uint ordinal){
|
|
378 dashOrdinalCount = ordinal;
|
|
379 if (ordinal == 0) {
|
|
380 assert(value == "dashTest1");
|
|
381 }
|
|
382 else if (ordinal == 1) {
|
|
383 assert(value == "dashTest2");
|
|
384 }
|
|
385 });
|
|
386
|
|
387 parser.bindDefault("@", delegate void(char[] value, uint ordinal){
|
|
388 assert (value == "atTest");
|
|
389 });
|
|
390
|
|
391 static char[][] test1 = ["--h2", "-h", "-bb", "-b", "-n=4349", "-bool=true", "ordinalTest1", "ordinalTest2", "-dashTest1", "-dashTest2", "@atTest"];
|
|
392
|
|
393 parser.parse(test1);
|
|
394 assert(h2);
|
|
395 assert(h);
|
|
396 assert(b);
|
|
397 assert(bb);
|
|
398 assert(n == 4349);
|
|
399 assert(ordinalCount == 1);
|
|
400 assert(dashOrdinalCount == 1);
|
|
401
|
|
402 h = h2 = b = bb = false;
|
|
403 boolean = false;
|
|
404 n = ordinalCount = dashOrdinalCount = -1;
|
|
405
|
|
406 static char[][] test2 = ["-n=4349", "ordinalTest1", "@atTest", "--h2", "-b", "-bb", "-h", "-dashTest1", "-dashTest2", "ordinalTest2", "-bool=true"];
|
|
407
|
|
408 parser.parse(test2, true);
|
|
409 assert(h2 && h && b && bb && boolean && (n ==4349));
|
|
410 assert(ordinalCount == 1);
|
|
411 assert(dashOrdinalCount == 1);
|
|
412
|
|
413 h = h2 = b = bb = false;
|
|
414 boolean = false;
|
|
415 n = ordinalCount = dashOrdinalCount = -1;
|
|
416
|
|
417 static char[][] test3 = ["-n=4349", "ordinalTest1", "@atTest", "--h2", "-b", "-bb", "-h", "-dashTest1", "-dashTest2", "ordinalTest2", "-bool=true"];
|
|
418
|
|
419 parser.parse(test3, true);
|
|
420 assert(h2 && h && b && bb && boolean && (n ==4349));
|
|
421 assert((ordinalCount == 1) && (dashOrdinalCount == 1));
|
|
422
|
|
423 ordinalCount = dashOrdinalCount = -1;
|
|
424
|
|
425 static char[][] test4 = ["ordinalTest1", "ordinalTest2", "ordinalTest3", "ordinalTest4"];
|
|
426 static char[][] test5 = ["-dashTest1", "-dashTest2", "-dashTest3"];
|
|
427
|
|
428 parser.parse(test4, true);
|
|
429 assert(ordinalCount == 3);
|
|
430
|
|
431 parser.parse(test5, true);
|
|
432 assert(dashOrdinalCount == 2);
|
|
433 }
|
|
434 }
|