]> git.tdb.fi Git - libs/core.git/blob - source/core/getopt.cpp
Suppress usage errors if help is requested
[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                 if(!help)
141                         throw usage_error(e.what(), "Usage: "+generate_usage(argv[0]));
142         }
143
144         if(help)
145                 throw usage_error(string("Help for ")+argv[0]+":", "\nUsage:\n  "+generate_usage(argv[0], true)+"\n\n"+generate_help());
146 }
147
148 unsigned GetOpt::process_long(const char *const *argp)
149 {
150         // Skip the --
151         const char *arg = argp[0]+2;
152
153         // See if the argument contains an =
154         unsigned equals = 0;
155         for(; arg[equals] && arg[equals]!='='; ++equals) ;
156         
157         OptionImpl &opt = get_option(string(arg, equals));
158         
159         if(arg[equals])
160                 // Process the part after the = as option argument
161                 opt.process(arg+equals+1);
162         else if(opt.get_arg_type()==REQUIRED_ARG)
163         {
164                 if(!argp[1])
165                         throw usage_error("--"+string(arg)+" requires an argument");
166
167                 // Process the next argument as option argument
168                 opt.process(argp[1]);
169                 return 2;
170         }
171         else
172                 opt.process();
173         
174         return 1;
175 }
176
177 unsigned GetOpt::process_short(const char *const *argp)
178 {
179         // Skip the -
180         const char *arg = argp[0]+1;
181
182         // Loop through all characters in the argument
183         for(; *arg; ++arg)
184         {
185                 OptionImpl &opt = get_option(*arg);
186
187                 if(arg[1] && opt.get_arg_type()!=NO_ARG)
188                 {
189                         // Need an option argument and we have characters left - use them
190                         opt.process(arg+1);
191                         return 1;
192                 }
193                 else if(opt.get_arg_type()==REQUIRED_ARG)
194                 {
195                         if(!argp[1])
196                                 throw usage_error("-"+string(1, *arg)+" requires an argument");
197                         
198                         // Use the next argument as option argument
199                         opt.process(argp[1]);
200                         return 2;
201                 }
202                 else
203                         opt.process();
204         }
205
206         return 1;
207 }
208
209 string GetOpt::generate_usage(const string &argv0, bool compact) const
210 {
211         string result = argv0;
212         if(compact)
213                 result += " [options]";
214         else
215         {
216                 for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
217                 {
218                         result += " [";
219                         if((*i)->get_short())
220                         {
221                                 result += format("-%c", (*i)->get_short());
222                                 if(!(*i)->get_long().empty())
223                                         result += '|';
224                                 else if((*i)->get_arg_type()==OPTIONAL_ARG)
225                                         result += format("[%s]", (*i)->get_metavar());
226                                 else if((*i)->get_arg_type()==REQUIRED_ARG)
227                                         result += format(" %s", (*i)->get_metavar());
228                         }
229                         if(!(*i)->get_long().empty())
230                         {
231                                 result += format("--%s", (*i)->get_long());
232
233                                 if((*i)->get_arg_type()==OPTIONAL_ARG)
234                                         result += format("[=%s]", (*i)->get_metavar());
235                                 else if((*i)->get_arg_type()==REQUIRED_ARG)
236                                         result += format("=%s", (*i)->get_metavar());
237                         }
238                         result += ']';
239                 }
240         }
241
242         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
243         {
244                 result += ' ';
245                 if((*i)->get_type()==OPTIONAL_ARG)
246                         result += '[';
247                 result += format("<%s>", (*i)->get_name());
248                 if((*i)->is_list_store())
249                         result += " ...";
250                 if((*i)->get_type()==OPTIONAL_ARG)
251                         result += ']';
252         }
253
254         return result;
255 }
256
257 string GetOpt::generate_help() const
258 {
259         bool any_short = false;
260         for(OptionList::const_iterator i=opts.begin(); (!any_short && i!=opts.end()); ++i)
261                 any_short = (*i)->get_short();
262
263         string::size_type maxw = 0;
264         list<string> switches;
265         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
266         {
267                 string swtch;
268                 if((*i)->get_short())
269                 {
270                         swtch += format("-%c", (*i)->get_short());
271                         if(!(*i)->get_long().empty())
272                                 swtch += ", ";
273                         else if((*i)->get_arg_type()==OPTIONAL_ARG)
274                                 swtch += format("[%s]", (*i)->get_metavar());
275                         else if((*i)->get_arg_type()==REQUIRED_ARG)
276                                 swtch += format(" %s", (*i)->get_metavar());
277                 }
278                 else if(any_short)
279                         swtch += "    ";
280                 if(!(*i)->get_long().empty())
281                 {
282                         swtch += format("--%s", (*i)->get_long());
283
284                         if((*i)->get_arg_type()==OPTIONAL_ARG)
285                                 swtch += format("[=%s]", (*i)->get_metavar());
286                         else if((*i)->get_arg_type()==REQUIRED_ARG)
287                                 swtch += format("=%s", (*i)->get_metavar());
288                 }
289                 switches.push_back(swtch);
290                 maxw = max(maxw, swtch.size());
291         }
292
293         list<string> pargs;
294         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
295         {
296                 string parg = format("<%s>", (*i)->get_name());
297                 pargs.push_back(parg);
298                 maxw = max(maxw, parg.size());
299         }
300
301         string result;
302         result += "Options:\n";
303         list<string>::const_iterator j = switches.begin();
304         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i, ++j)
305                 result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
306         if(!pargs.empty())
307         {
308                 result += "\nArguments:\n";
309                 j = pargs.begin();
310                 for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i, ++j)
311                         result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
312         }
313         
314         return result;
315 }
316
317
318 GetOpt::OptionImpl::OptionImpl(char s, const std::string &l, const Store &t, ArgType a):
319         shrt(s),
320         lng(l),
321         arg_type(a),
322         seen_count(0),
323         ext_seen_count(0),
324         metavar("ARG"),
325         store(t.clone())
326 { }
327
328 GetOpt::OptionImpl::~OptionImpl()
329 {
330         delete store;
331 }
332
333 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h)
334 {
335         help = h;
336         return *this;
337 }
338
339 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h, const string &m)
340 {
341         help = h;
342         metavar = m;
343         return *this;
344 }
345
346 GetOpt::OptionImpl &GetOpt::OptionImpl::bind_seen_count(unsigned &c)
347 {
348         ext_seen_count = &c;
349         return *this;
350 }
351
352 void GetOpt::OptionImpl::process()
353 {
354         if(arg_type==REQUIRED_ARG)
355                 throw usage_error("--"+lng+" requires an argument");
356
357         ++seen_count;
358         if(ext_seen_count)
359                 *ext_seen_count = seen_count;
360
361         try
362         {
363                 store->store();
364         }
365         catch(const exception &e)
366         {
367                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
368         }
369 }
370
371 void GetOpt::OptionImpl::process(const string &arg)
372 {
373         if(arg_type==NO_ARG)
374                 throw usage_error("--"+lng+" takes no argument");
375
376         ++seen_count;
377         if(ext_seen_count)
378                 *ext_seen_count = seen_count;
379
380         try
381         {
382                 store->store(arg);
383         }
384         catch(const exception &e)
385         {
386                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
387         }
388 }
389
390
391 GetOpt::ArgumentImpl::ArgumentImpl(const string &n, const Store &t, ArgType a):
392         name(n),
393         type(a),
394         store(t.clone())
395 { }
396
397 GetOpt::ArgumentImpl::~ArgumentImpl()
398 {
399         delete store;
400 }
401
402 GetOpt::ArgumentImpl &GetOpt::ArgumentImpl::set_help(const string &h)
403 {
404         help = h;
405         return *this;
406 }
407
408 void GetOpt::ArgumentImpl::process(const string &arg)
409 {
410         try
411         {
412                 store->store(arg);
413         }
414         catch(const exception &e)
415         {
416                 throw usage_error("Invalid "+name+" ("+e.what()+")");
417         }
418 }
419
420 } // namespace Msp