]> git.tdb.fi Git - libs/core.git/blob - source/core/getopt.cpp
Don't output the list of options in usage with full help
[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]+":", "\nUsage:\n  "+generate_usage(argv[0], true)+"\n\n"+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, bool compact) const
209 {
210         string result = argv0;
211         if(compact)
212                 result += " [options]";
213         else
214         {
215                 for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
216                 {
217                         result += " [";
218                         if((*i)->get_short())
219                         {
220                                 result += format("-%c", (*i)->get_short());
221                                 if(!(*i)->get_long().empty())
222                                         result += '|';
223                                 else if((*i)->get_arg_type()==OPTIONAL_ARG)
224                                         result += format("[%s]", (*i)->get_metavar());
225                                 else if((*i)->get_arg_type()==REQUIRED_ARG)
226                                         result += format(" %s", (*i)->get_metavar());
227                         }
228                         if(!(*i)->get_long().empty())
229                         {
230                                 result += format("--%s", (*i)->get_long());
231
232                                 if((*i)->get_arg_type()==OPTIONAL_ARG)
233                                         result += format("[=%s]", (*i)->get_metavar());
234                                 else if((*i)->get_arg_type()==REQUIRED_ARG)
235                                         result += format("=%s", (*i)->get_metavar());
236                         }
237                         result += ']';
238                 }
239         }
240
241         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
242         {
243                 result += ' ';
244                 if((*i)->get_type()==OPTIONAL_ARG)
245                         result += '[';
246                 result += format("<%s>", (*i)->get_name());
247                 if((*i)->is_list_store())
248                         result += " ...";
249                 if((*i)->get_type()==OPTIONAL_ARG)
250                         result += ']';
251         }
252
253         return result;
254 }
255
256 string GetOpt::generate_help() const
257 {
258         bool any_short = false;
259         for(OptionList::const_iterator i=opts.begin(); (!any_short && i!=opts.end()); ++i)
260                 any_short = (*i)->get_short();
261
262         string::size_type maxw = 0;
263         list<string> switches;
264         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i)
265         {
266                 string swtch;
267                 if((*i)->get_short())
268                 {
269                         swtch += format("-%c", (*i)->get_short());
270                         if(!(*i)->get_long().empty())
271                                 swtch += ", ";
272                         else if((*i)->get_arg_type()==OPTIONAL_ARG)
273                                 swtch += format("[%s]", (*i)->get_metavar());
274                         else if((*i)->get_arg_type()==REQUIRED_ARG)
275                                 swtch += format(" %s", (*i)->get_metavar());
276                 }
277                 else if(any_short)
278                         swtch += "    ";
279                 if(!(*i)->get_long().empty())
280                 {
281                         swtch += format("--%s", (*i)->get_long());
282
283                         if((*i)->get_arg_type()==OPTIONAL_ARG)
284                                 swtch += format("[=%s]", (*i)->get_metavar());
285                         else if((*i)->get_arg_type()==REQUIRED_ARG)
286                                 swtch += format("=%s", (*i)->get_metavar());
287                 }
288                 switches.push_back(swtch);
289                 maxw = max(maxw, swtch.size());
290         }
291
292         list<string> pargs;
293         for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i)
294         {
295                 string parg = format("<%s>", (*i)->get_name());
296                 pargs.push_back(parg);
297                 maxw = max(maxw, parg.size());
298         }
299
300         string result;
301         result += "Options:\n";
302         list<string>::const_iterator j = switches.begin();
303         for(OptionList::const_iterator i=opts.begin(); i!=opts.end(); ++i, ++j)
304                 result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
305         if(!pargs.empty())
306         {
307                 result += "\nArguments:\n";
308                 j = pargs.begin();
309                 for(ArgumentList::const_iterator i=args.begin(); i!=args.end(); ++i, ++j)
310                         result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
311         }
312         
313         return result;
314 }
315
316
317 GetOpt::OptionImpl::OptionImpl(char s, const std::string &l, const Store &t, ArgType a):
318         shrt(s),
319         lng(l),
320         arg_type(a),
321         seen_count(0),
322         ext_seen_count(0),
323         metavar("ARG"),
324         store(t.clone())
325 { }
326
327 GetOpt::OptionImpl::~OptionImpl()
328 {
329         delete store;
330 }
331
332 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h)
333 {
334         help = h;
335         return *this;
336 }
337
338 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h, const string &m)
339 {
340         help = h;
341         metavar = m;
342         return *this;
343 }
344
345 GetOpt::OptionImpl &GetOpt::OptionImpl::bind_seen_count(unsigned &c)
346 {
347         ext_seen_count = &c;
348         return *this;
349 }
350
351 void GetOpt::OptionImpl::process()
352 {
353         if(arg_type==REQUIRED_ARG)
354                 throw usage_error("--"+lng+" requires an argument");
355
356         ++seen_count;
357         if(ext_seen_count)
358                 *ext_seen_count = seen_count;
359
360         try
361         {
362                 store->store();
363         }
364         catch(const exception &e)
365         {
366                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
367         }
368 }
369
370 void GetOpt::OptionImpl::process(const string &arg)
371 {
372         if(arg_type==NO_ARG)
373                 throw usage_error("--"+lng+" takes no argument");
374
375         ++seen_count;
376         if(ext_seen_count)
377                 *ext_seen_count = seen_count;
378
379         try
380         {
381                 store->store(arg);
382         }
383         catch(const exception &e)
384         {
385                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
386         }
387 }
388
389
390 GetOpt::ArgumentImpl::ArgumentImpl(const string &n, const Store &t, ArgType a):
391         name(n),
392         type(a),
393         store(t.clone())
394 { }
395
396 GetOpt::ArgumentImpl::~ArgumentImpl()
397 {
398         delete store;
399 }
400
401 GetOpt::ArgumentImpl &GetOpt::ArgumentImpl::set_help(const string &h)
402 {
403         help = h;
404         return *this;
405 }
406
407 void GetOpt::ArgumentImpl::process(const string &arg)
408 {
409         try
410         {
411                 store->store(arg);
412         }
413         catch(const exception &e)
414         {
415                 throw usage_error("Invalid "+name+" ("+e.what()+")");
416         }
417 }
418
419 } // namespace Msp