Mercurial > projects > ldc
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 } |