comparison tango/tango/util/ArgParser.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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 }