]> git.tdb.fi Git - libs/core.git/blob - source/core/getopt.cpp
Add support for positional arguments in GetOpt
[libs/core.git] / source / core / getopt.cpp
1 #include <msp/strings/format.h>
2 #include "getopt.h"
3
4 using namespace std;
5
6 namespace Msp {
7
8 GetOpt::GetOpt():
9         help(false)
10 {
11         add_option("help", help, NO_ARG).set_help("Displays this help");
12 }
13
14 GetOpt::~GetOpt()
15 {
16         for(OptionList::iterator i=opts.begin(); i!=opts.end(); ++i)
17                 delete *i;
18         for(ArgumentList::iterator i=args.begin(); i!=args.end(); ++i)
19                 delete *i;
20 }
21
22 GetOpt::OptionImpl &GetOpt::add_option(char s, const string &l, const Store &t, ArgType a)
23 {
24         if(l.empty())
25                 throw invalid_argument("GetOpt::add_option");
26         if(t.is_list() && a!=REQUIRED_ARG)
27                 throw invalid_argument("GetOpt::add_option");
28
29         for(OptionList::iterator i=opts.begin(); i!=opts.end(); )
30         {
31                 if((s!=0 && (*i)->get_short()==s) || (*i)->get_long()==l)
32                 {
33                         delete *i;
34                         opts.erase(i++);
35                 }
36                 else
37                         ++i;
38         }
39
40         opts.push_back(new OptionImpl(s, l, t, a));
41         return *opts.back();
42 }
43
44 GetOpt::ArgumentImpl &GetOpt::add_argument(const string &n, const Store &t, ArgType a)
45 {
46         if(a==NO_ARG)
47                 throw invalid_argument("GetOpt::add_argument");
48
49         bool have_list = false;
50         bool have_optional = false;
51         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
52         {
53                 if((*i)->is_list_store())
54                         have_list = true;
55                 else if((*i)->get_type()==OPTIONAL_ARG)
56                         have_optional = true;
57         }
58
59         if(have_optional && (t.is_list() || a!=OPTIONAL_ARG))
60                 throw invalid_argument("GetOpt::add_argument");
61         if(have_list && (t.is_list() || a==OPTIONAL_ARG))
62                 throw invalid_argument("GetOpt::add_argument");
63
64         args.push_back(new ArgumentImpl(n, t, a));
65         return *args.back();
66 }
67
68 GetOpt::OptionImpl &GetOpt::get_option(char s)
69 {
70         for(OptionList::iterator i=opts.begin(); i!=opts.end(); ++i)
71                 if((*i)->get_short()==s)
72                         return **i;
73         throw usage_error(string("Unknown option -")+s);
74 }
75
76 GetOpt::OptionImpl &GetOpt::get_option(const string &l)
77 {
78         for(OptionList::iterator i=opts.begin(); i!=opts.end(); ++i)
79                 if((*i)->get_long()==l)
80                         return **i;
81         throw usage_error(string("Unknown option --")+l);
82 }
83
84 void GetOpt::operator()(unsigned argc, const char *const *argv)
85 {
86         try
87         {
88                 /* Arguments must first be collected into an array to handle the case
89                 where a variable-length argument list is followed by fixed arguments. */
90                 unsigned i = 1;
91                 for(; i<argc;)
92                 {
93                         if(argv[i][0]=='-')
94                         {
95                                 if(argv[i][1]=='-')
96                                 {
97                                         if(!argv[i][2])
98                                                 break;
99
100                                         i += process_long(argv+i);
101                                 }
102                                 else
103                                         i += process_short(argv+i);
104                         }
105                         else
106                                 args_raw.push_back(argv[i++]);
107                 }
108                 
109                 for(; i<argc; ++i)
110                         args_raw.push_back(argv[i]);
111
112                 i = 0;
113                 for(ArgumentList::const_iterator j=args.begin(); j!=args.end(); ++j)
114                 {
115                         if((*j)->is_list_store())
116                         {
117                                 unsigned end = args_raw.size();
118                                 for(ArgumentList::const_iterator k=j; ++k!=args.end(); )
119                                         --end;
120                                 if(i==end && (*j)->get_type()==REQUIRED_ARG)
121                                         throw usage_error((*j)->get_name()+" is required");
122                                 for(; i<end; ++i)
123                                         (*j)->process(args_raw[i]);
124                         }
125                         else
126                         {
127                                 if(i<args_raw.size())
128                                         (*j)->process(args_raw[i++]);
129                                 else if((*j)->get_type()==REQUIRED_ARG)
130                                         throw usage_error((*j)->get_name()+" is required");
131                         }
132                 }
133
134                 // XXX Enable this when get_args() is completely removed
135                 /*if(i<args_raw.size())
136                         throw usage_error("Extra positional arguments");*/
137         }
138         catch(const usage_error &e)
139         {
140                 throw usage_error(e.what(), "Usage: "+generate_usage(argv[0]));
141         }
142
143         if(help)
144                 throw usage_error(string("Help for ")+argv[0]+":", generate_help());
145 }
146
147 unsigned GetOpt::process_long(const char *const *argp)
148 {
149         // Skip the --
150         const char *arg = argp[0]+2;
151
152         // See if the argument contains an =
153         unsigned equals = 0;
154         for(; arg[equals] && arg[equals]!='='; ++equals) ;
155         
156         OptionImpl &opt = get_option(string(arg, equals));
157         
158         if(arg[equals])
159                 // Process the part after the = as option argument
160                 opt.process(arg+equals+1);
161         else if(opt.get_arg_type()==REQUIRED_ARG)
162         {
163                 if(!argp[1])
164                         throw usage_error("--"+string(arg)+" requires an argument");
165
166                 // Process the next argument as option argument
167                 opt.process(argp[1]);
168                 return 2;
169         }
170         else
171                 opt.process();
172         
173         return 1;
174 }
175
176 unsigned GetOpt::process_short(const char *const *argp)
177 {
178         // Skip the -
179         const char *arg = argp[0]+1;
180
181         // Loop through all characters in the argument
182         for(; *arg; ++arg)
183         {
184                 OptionImpl &opt = get_option(*arg);
185
186                 if(arg[1] && opt.get_arg_type()!=NO_ARG)
187                 {
188                         // Need an option argument and we have characters left - use them
189                         opt.process(arg+1);
190                         return 1;
191                 }
192                 else if(opt.get_arg_type()==REQUIRED_ARG)
193                 {
194                         if(!argp[1])
195                                 throw usage_error("-"+string(1, *arg)+" requires an argument");
196                         
197                         // Use the next argument as option argument
198                         opt.process(argp[1]);
199                         return 2;
200                 }
201                 else
202                         opt.process();
203         }
204
205         return 1;
206 }
207
208 string GetOpt::generate_usage(const string &argv0) const
209 {
210         string result = argv0;
211         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
212         {
213                 result += " [";
214                 if((*i)->get_short())
215                 {
216                         result += format("-%c", (*i)->get_short());
217                         if(!(*i)->get_long().empty())
218                                 result += '|';
219                         else if((*i)->get_arg_type()==OPTIONAL_ARG)
220                                 result += format("[%s]", (*i)->get_metavar());
221                         else if((*i)->get_arg_type()==REQUIRED_ARG)
222                                 result += format(" %s", (*i)->get_metavar());
223                 }
224                 if(!(*i)->get_long().empty())
225                 {
226                         result += format("--%s", (*i)->get_long());
227
228                         if((*i)->get_arg_type()==OPTIONAL_ARG)
229                                 result += format("[=%s]", (*i)->get_metavar());
230                         else if((*i)->get_arg_type()==REQUIRED_ARG)
231                                 result += format("=%s", (*i)->get_metavar());
232                 }
233                 result += ']';
234         }
235
236         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
237         {
238                 result += ' ';
239                 if((*i)->get_type()==OPTIONAL_ARG)
240                         result += '[';
241                 result += format("<%s>", (*i)->get_name());
242                 if((*i)->is_list_store())
243                         result += " ...";
244                 if((*i)->get_type()==OPTIONAL_ARG)
245                         result += ']';
246         }
247
248         return result;
249 }
250
251 string GetOpt::generate_help() const
252 {
253         bool any_short = false;
254         for(OptionList::const_iterator i=opts.begin(); (!any_short && i!=opts.end()); ++i)
255                 any_short = (*i)->get_short();
256
257         string::size_type maxw = 0;
258         list<string> switches;
259         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
260         {
261                 string swtch;
262                 if((*i)->get_short())
263                 {
264                         swtch += format("-%c", (*i)->get_short());
265                         if(!(*i)->get_long().empty())
266                                 swtch += ", ";
267                         else if((*i)->get_arg_type()==OPTIONAL_ARG)
268                                 swtch += format("[%s]", (*i)->get_metavar());
269                         else if((*i)->get_arg_type()==REQUIRED_ARG)
270                                 swtch += format(" %s", (*i)->get_metavar());
271                 }
272                 else if(any_short)
273                         swtch += "    ";
274                 if(!(*i)->get_long().empty())
275                 {
276                         swtch += format("--%s", (*i)->get_long());
277
278                         if((*i)->get_arg_type()==OPTIONAL_ARG)
279                                 swtch += format("[=%s]", (*i)->get_metavar());
280                         else if((*i)->get_arg_type()==REQUIRED_ARG)
281                                 swtch += format("=%s", (*i)->get_metavar());
282                 }
283                 switches.push_back(swtch);
284                 maxw = max(maxw, swtch.size());
285         }
286
287         list<string> pargs;
288         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
289         {
290                 string parg = format("<%s>", (*i)->get_name());
291                 pargs.push_back(parg);
292                 maxw = max(maxw, parg.size());
293         }
294
295         string result;
296         result += "Options:\n";
297         list<string>::const_iterator j = switches.begin();
298         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i, ++j)
299                 result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
300         if(!pargs.empty())
301         {
302                 result += "\nArguments:\n";
303                 j = pargs.begin();
304                 for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i, ++j)
305                         result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
306         }
307         
308         return result;
309 }
310
311
312 GetOpt::OptionImpl::OptionImpl(char s, const std::string &l, const Store &t, ArgType a):
313         shrt(s),
314         lng(l),
315         arg_type(a),
316         seen_count(0),
317         ext_seen_count(0),
318         metavar("ARG"),
319         store(t.clone())
320 { }
321
322 GetOpt::OptionImpl::~OptionImpl()
323 {
324         delete store;
325 }
326
327 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h)
328 {
329         help = h;
330         return *this;
331 }
332
333 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h, const string &m)
334 {
335         help = h;
336         metavar = m;
337         return *this;
338 }
339
340 GetOpt::OptionImpl &GetOpt::OptionImpl::bind_seen_count(unsigned &c)
341 {
342         ext_seen_count = &c;
343         return *this;
344 }
345
346 void GetOpt::OptionImpl::process()
347 {
348         if(arg_type==REQUIRED_ARG)
349                 throw usage_error("--"+lng+" requires an argument");
350
351         ++seen_count;
352         if(ext_seen_count)
353                 *ext_seen_count = seen_count;
354
355         try
356         {
357                 store->store();
358         }
359         catch(const exception &e)
360         {
361                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
362         }
363 }
364
365 void GetOpt::OptionImpl::process(const string &arg)
366 {
367         if(arg_type==NO_ARG)
368                 throw usage_error("--"+lng+" takes no argument");
369
370         ++seen_count;
371         if(ext_seen_count)
372                 *ext_seen_count = seen_count;
373
374         try
375         {
376                 store->store(arg);
377         }
378         catch(const exception &e)
379         {
380                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
381         }
382 }
383
384
385 GetOpt::ArgumentImpl::ArgumentImpl(const string &n, const Store &t, ArgType a):
386         name(n),
387         type(a),
388         store(t.clone())
389 { }
390
391 GetOpt::ArgumentImpl::~ArgumentImpl()
392 {
393         delete store;
394 }
395
396 GetOpt::ArgumentImpl &GetOpt::ArgumentImpl::set_help(const string &h)
397 {
398         help = h;
399         return *this;
400 }
401
402 void GetOpt::ArgumentImpl::process(const string &arg)
403 {
404         try
405         {
406                 store->store(arg);
407         }
408         catch(const exception &e)
409         {
410                 throw usage_error("Invalid "+name+" ("+e.what()+")");
411         }
412 }
413
414 } // namespace Msp