Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

root/trunk/utils/ArgParser.d

Revision 280, 10.9 kB (checked in by pragma, 18 years ago)

Dirty commit. please see r278 for something stable.

Line 
1 /+
2     Copyright (c) 2005-2007 Eric Anderton, Lars Ivar Igesund
3
4     Permission is hereby granted, free of charge, to any person
5     obtaining a copy of this software and associated documentation
6     files (the "Software"), to deal in the Software without
7     restriction, including without limitation the rights to use,
8     copy, modify, merge, publish, distribute, sublicense, and/or
9     sell copies of the Software, and to permit persons to whom the
10     Software is furnished to do so, subject to the following
11     conditions:
12
13     The above copyright notice and this permission notice shall be
14     included in all copies or substantial portions of the Software.
15
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23     OTHER DEALINGS IN THE SOFTWARE.
24 +/
25 /**
26     This module holds a utility class that can parse your command
27     line arguments.
28
29     A sample program would instantiate the ArgParser class, bind
30     some delegates (can be anonymous), then call parse with the
31     arguments as a parameter.
32
33     Note that the delegates must return the correct number of
34     consumed characters to ensure that the ArgParser operates correctly.
35     Simple arguments that don't use the value parameter of the callback,
36     should return 0 consumed characters. This behaviour will ensure that
37     ArgParser will correctly handle all arguments, even when there are
38     no space between them.
39
40     Authors: Eric Anderton, Lars Ivar Igesund
41     License: BSD Derivative (see source for details)
42     Copyright: 2005-2006 Eric Anderton, Lars Ivar Igesund
43 */
44 module utils.ArgParser;
45
46 /**
47     An alias to a delegate taking a char[] as a parameter and returning
48     an uint. The value parameter will hold any chars immediately
49     following the argument. The returned value tell how many chars of
50     value was used by the callback.
51 */
52 alias uint delegate (char[] value) ArgParserCallback;
53
54 /**
55     An alias to a delegate taking a char[] as a parameter and returning
56     an uint. The value parameter will hold any chars immediately
57     following the argument. The returned value tell how many chars of
58     value was used by the callback.
59     
60     The ordinal argument represents which default argument this is for
61     the given stream of arguments.  The first default argument will
62     be ordinal=0 with each successive call to this callback having
63     ordinal values of 1, 2, 3 and so forth.
64 */
65 alias uint delegate (char[] value,uint ordinal) DefaultArgParserCallback;
66
67 /**
68     An alias to a delegate taking no parameters and returning
69     nothing.
70 */
71 alias void delegate () ArgParserSimpleCallback;
72
73 /**
74     A utility class to parse and handle your command line arguments.
75 */
76 class ArgParser{
77     /**
78         A helper struct containing a callback and an id to, corresponding to
79         the argId passed to one of the bind methods.
80     */
81     protected struct PrefixCallback {
82         char[] id;
83         ArgParserCallback cb;
84     }   
85
86     protected PrefixCallback[][char[]] bindings;
87     protected DefaultArgParserCallback[char[]] defaultBindings;
88     protected uint[char[]] prefixOrdinals;
89     protected char[][] prefixSearchOrder;
90     protected DefaultArgParserCallback defaultbinding;
91     private uint defaultOrdinal = 0;
92
93     protected void addBinding(PrefixCallback pcb, char[] argPrefix){
94         if (!(argPrefix in bindings)) {
95             prefixSearchOrder ~= argPrefix;
96         }
97         bindings[argPrefix] ~= pcb;
98     }
99
100     /**
101         Binds a delegate callback to argument with a prefix and
102         a argId.
103         
104         Params:
105             argPrefix = the prefix of the argument, e.g. a dash '-'.
106             argId = the name of the argument, what follows the prefix
107             cb = the delegate that should be called when this argument is found
108     */
109     public void bind(char[] argPrefix, char[] argId, ArgParserCallback cb){
110         PrefixCallback pcb;
111         pcb.id = argId;
112         pcb.cb = cb;
113         addBinding(pcb, argPrefix);
114     }
115
116     /**
117         The constructor, creates an empty ArgParser instance.
118     */
119     public this(){
120         defaultbinding = null;
121     }
122      
123     /**
124         The constructor, creates an ArgParser instance with a defined default callback.
125     */   
126     public this(DefaultArgParserCallback callback){
127         defaultbinding = callback;
128     }   
129
130     protected class SimpleCallbackAdapter{
131         ArgParserSimpleCallback callback;
132         public this(ArgParserSimpleCallback callback){
133             this.callback = callback;
134         }
135        
136         public uint adapterCallback(char[] value){
137             callback();
138             return value.length;
139         }
140     }
141
142     /**
143         Binds a delegate callback to argument with a prefix and
144         a argId.
145         
146         Params:
147             argPrefix = the prefix of the argument, e.g. a dash '-'.
148             argId = the name of the argument, what follows the prefix
149             cb = the delegate that should be called when this argument is found
150     */
151     public void bind(char[] argPrefix, char[] argId, ArgParserSimpleCallback cb){
152         SimpleCallbackAdapter adapter = new SimpleCallbackAdapter(cb);
153         PrefixCallback pcb;
154         pcb.id = argId;
155         pcb.cb = &adapter.adapterCallback;
156         addBinding(pcb, argPrefix);
157     }
158    
159     /**
160         Binds a delegate callback to all arguments with prefix argPrefix, but that
161         do not conform to an argument bound in a call to bind().
162
163         Params:
164             argPrefix = the prefix for the callback
165             callback = the default callback
166     */
167     public void bindDefault(char[] argPrefix, DefaultArgParserCallback callback){
168         defaultBindings[argPrefix] = callback;
169         prefixOrdinals[argPrefix] = 0;
170         if (!(argPrefix in bindings)) {
171             prefixSearchOrder ~= argPrefix;
172         }
173     }
174
175     /**
176         Binds a delegate callback to all arguments not conforming to an
177         argument bound in a call to bind(). These arguments will be passed to the
178         delegate without having any matching prefixes removed.
179
180         Params:
181             callback = the default callback
182     */
183     public void bindDefault(DefaultArgParserCallback callback){
184         defaultbinding = callback;
185     }
186
187     /**
188         Parses the arguments provided by the parameter. The bound
189         callbacks are called as arguments are recognized.
190
191         Params:
192             arguments = the command line arguments from the application
193     */
194     public void parse(char[][] arguments){
195         if (bindings.length == 0) return;
196
197         foreach (char[] arg; arguments) {
198             char[] argData = arg;
199             while (argData.length > 0) {
200                 bool found = false;
201                 char[] argOrig = argData;
202                 foreach (char[] prefix; prefixSearchOrder) {
203                     if(argData.length < prefix.length) continue;
204                     if(argData[0..prefix.length] != prefix) {
205                         continue;
206                     }
207                     else {
208                         argData = argData[prefix.length..$];
209                     }
210                     if (prefix in bindings) {
211                         foreach (PrefixCallback cb; bindings[prefix]) {
212                             if (argData.length < cb.id.length) continue;
213                             uint cbil = cb.id.length;
214                             if (cb.id == argData[0..cbil]) {
215                                 found = true;
216                                 argData = argData[cbil..$];
217                                 uint consumed = cb.cb(argData);
218                                 argData = argData[consumed..$];
219                                 break;
220                             }
221                         }
222                     }
223                     if (found) {
224                         break;
225                     }
226                     else if (prefix in defaultBindings){
227                         uint consumed = defaultBindings[prefix](argData,prefixOrdinals[prefix]);
228                         argData = argData[consumed..$];
229                         prefixOrdinals[prefix]++;
230                         found = true;
231                         break;
232                     }
233                     argData = argOrig;
234                 }
235                 if (!found) {
236                     if (!(defaultbinding is null)) {
237                         uint consumed = defaultbinding(argData,defaultOrdinal);
238                         argData = argData[consumed..$];
239                         defaultOrdinal++;
240                     }
241                     else {
242                         throw new Exception("Illegal argument "
243                                   ~ argData);
244                     }
245                 }
246             }
247         }
248     }
249 }
250
251 unittest {
252
253     //import tango.text.String;
254
255     ArgParser parser = new ArgParser();
256     bool h = h2 = b = bb = false;
257     bool boolean = false;
258     int n;
259
260     parser.bind("--", "h2", delegate void(){
261         h2 = true;
262     });
263
264     parser.bind("-", "h", delegate void(){
265         h = true;
266     });
267
268     parser.bind("-", "bb", delegate void(){
269         bb = true;
270     });
271
272     parser.bind("-", "b", delegate void(){
273         b = true;
274     });
275
276     parser.bind("-", "n", delegate uint(char[] value){
277         assert(value.length > 2);
278         assert(value[1] == '=');
279         n = toInt(value[2..5]);
280         return 5;
281     });
282
283     parser.bind("-", "bool", delegate uint(char[] value){
284         assert(value.length == 9);
285         assert(value[4] == '=');
286         if (value[5..9] == "true") {
287             boolean = true;
288         }
289         else {
290             assert(false);
291         }
292         return 9;
293     });
294
295     parser.bindDefault(delegate uint(char[] value, uint ordinal){
296         assert (ordinal < 2);
297         ordinalCount = ordinal;
298         if (ordinal == 0) {
299             assert(value == "ordinalTest1");
300         }
301         else if (ordinal == 1) {
302             assert(value == "ordinalTest2");
303         }
304         return value.length;
305     });
306
307     parser.bindDefault("-", delegate uint(char[] value, uint ordinal){
308         assert(ordinal < 2);
309         dashOrdinalCount = ordinal;
310         if (ordinal == 0) {
311             assert(value == "dashTest1");
312         }
313         else if (ordinal == 1) {
314             assert(value == "dashTest1");
315         }
316         return value.length;
317     });
318
319     parser.bindDefault("@", delegate uint(char[] value, uint ordinal){
320         assert (value == "atTest");
321         return value.length;
322     });
323
324    
325
326 }
Note: See TracBrowser for help on using the browser.