Prefer more cache-efficient containers
[libs/core.git] / source / core / getopt.cpp
1 #include <msp/strings/format.h>
2 #include "algorithm.h"
3 #include "getopt.h"
4
5 using namespace std;
6
7 namespace Msp {
8
9 GetOpt::GetOpt():
10         help(false)
11 {
12         add_option("help", help, NO_ARG).set_help("Displays this help");
13 }
14
15 GetOpt::~GetOpt()
16 {
17         for(OptionImpl *i: opts)
18                 delete i;
19         for(ArgumentImpl *i: args)
20                 delete i;
21 }
22
23 GetOpt::OptionImpl &GetOpt::add_option(char s, const string &l, const Store &t, ArgType a)
24 {
25         if(l.empty())
26                 throw invalid_argument("GetOpt::add_option");
27         if(t.is_list() && a!=REQUIRED_ARG)
28                 throw invalid_argument("GetOpt::add_option");
29
30         for(auto i=opts.begin(); i!=opts.end(); )
31         {
32                 if((s!=0 && (*i)->get_short()==s) || (*i)->get_long()==l)
33                 {
34                         delete *i;
35                         opts.erase(i++);
36                 }
37                 else
38                         ++i;
39         }
40
41         opts.push_back(new OptionImpl(s, l, t, a));
42         return *opts.back();
43 }
44
45 GetOpt::ArgumentImpl &GetOpt::add_argument(const string &n, const Store &t, ArgType y)
46 {
47         if(y==NO_ARG)
48                 throw invalid_argument("GetOpt::add_argument");
49
50         bool have_list = false;
51         bool have_optional = false;
52         for(const ArgumentImpl *a: args)
53         {
54                 if(a->is_list_store())
55                         have_list = true;
56                 else if(a->get_type()==OPTIONAL_ARG)
57                         have_optional = true;
58         }
59
60         if(have_optional && (t.is_list() || y!=OPTIONAL_ARG))
61                 throw invalid_argument("GetOpt::add_argument");
62         if(have_list && (t.is_list() || y==OPTIONAL_ARG))
63                 throw invalid_argument("GetOpt::add_argument");
64
65         args.push_back(new ArgumentImpl(n, t, y));
66         return *args.back();
67 }
68
69 GetOpt::OptionImpl &GetOpt::get_option(char s)
70 {
71         auto i = find_if(opts, [s](const OptionImpl *o){ return o->get_short()==s; });
72         if(i!=opts.end())
73                 return **i;
74         throw usage_error(string("Unknown option -")+s);
75 }
76
77 GetOpt::OptionImpl &GetOpt::get_option(const string &l)
78 {
79         auto i = find_if(opts, [&l](const OptionImpl *o){ return o->get_long()==l; });
80         if(i!=opts.end())
81                 return **i;
82         throw usage_error(string("Unknown option --")+l);
83 }
84
85 void GetOpt::operator()(unsigned argc, const char *const *argv)
86 {
87         try
88         {
89                 /* Arguments must first be collected into an array to handle the case
90                 where a variable-length argument list is followed by fixed arguments. */
91                 unsigned i = 1;
92                 for(; i<argc;)
93                 {
94                         if(argv[i][0]=='-')
95                         {
96                                 if(argv[i][1]=='-')
97                                 {
98                                         if(!argv[i][2])
99                                                 break;
100
101                                         i += process_long(argv+i);
102                                 }
103                                 else
104                                         i += process_short(argv+i);
105                         }
106                         else
107                                 args_raw.push_back(argv[i++]);
108                 }
109
110                 for(; i<argc; ++i)
111                         args_raw.push_back(argv[i]);
112
113                 i = 0;
114                 for(auto j=args.begin(); j!=args.end(); ++j)
115                 {
116                         if((*j)->is_list_store())
117                         {
118                                 unsigned end = args_raw.size();
119                                 for(auto k=j; ++k!=args.end(); )
120                                         --end;
121                                 if(i==end && (*j)->get_type()==REQUIRED_ARG)
122                                         throw usage_error((*j)->get_name()+" is required");
123                                 for(; i<end; ++i)
124                                         (*j)->process(args_raw[i]);
125                         }
126                         else
127                         {
128                                 if(i<args_raw.size())
129                                         (*j)->process(args_raw[i++]);
130                                 else if((*j)->get_type()==REQUIRED_ARG)
131                                         throw usage_error((*j)->get_name()+" is required");
132                         }
133                 }
134
135                 // XXX Enable this when get_args() is completely removed
136                 /*if(i<args_raw.size())
137                         throw usage_error("Extra positional arguments");*/
138         }
139         catch(const usage_error &e)
140         {
141                 if(!help)
142                         throw usage_error(e.what(), "Usage: "+generate_usage(argv[0]));
143         }
144
145         if(help)
146                 throw usage_error(string("Help for ")+argv[0]+":", "\nUsage:\n  "+generate_usage(argv[0], true)+"\n\n"+generate_help());
147 }
148
149 unsigned GetOpt::process_long(const char *const *argp)
150 {
151         // Skip the --
152         const char *arg = argp[0]+2;
153
154         // See if the argument contains an =
155         unsigned equals = 0;
156         for(; arg[equals] && arg[equals]!='='; ++equals) ;
157
158         OptionImpl &opt = get_option(string(arg, equals));
159
160         if(arg[equals])
161                 // Process the part after the = as option argument
162                 opt.process(arg+equals+1);
163         else if(opt.get_arg_type()==REQUIRED_ARG)
164         {
165                 if(!argp[1])
166                         throw usage_error("--"+string(arg)+" requires an argument");
167
168                 // Process the next argument as option argument
169                 opt.process(argp[1]);
170                 return 2;
171         }
172         else
173                 opt.process();
174
175         return 1;
176 }
177
178 unsigned GetOpt::process_short(const char *const *argp)
179 {
180         // Skip the -
181         const char *arg = argp[0]+1;
182
183         // Loop through all characters in the argument
184         for(; *arg; ++arg)
185         {
186                 OptionImpl &opt = get_option(*arg);
187
188                 if(arg[1] && opt.get_arg_type()!=NO_ARG)
189                 {
190                         // Need an option argument and we have characters left - use them
191                         opt.process(arg+1);
192                         return 1;
193                 }
194                 else if(opt.get_arg_type()==REQUIRED_ARG)
195                 {
196                         if(!argp[1])
197                                 throw usage_error("-"+string(1, *arg)+" requires an argument");
198
199                         // Use the next argument as option argument
200                         opt.process(argp[1]);
201                         return 2;
202                 }
203                 else
204                         opt.process();
205         }
206
207         return 1;
208 }
209
210 string GetOpt::generate_usage(const string &argv0, bool compact) const
211 {
212         string result = argv0;
213         if(compact)
214                 result += " [options]";
215         else
216         {
217                 for(const OptionImpl *o: opts)
218                 {
219                         result += " [";
220                         if(o->get_short())
221                         {
222                                 result += format("-%c", o->get_short());
223                                 if(!o->get_long().empty())
224                                         result += '|';
225                                 else if(o->get_arg_type()==OPTIONAL_ARG)
226                                         result += format("[%s]", o->get_metavar());
227                                 else if(o->get_arg_type()==REQUIRED_ARG)
228                                         result += format(" %s", o->get_metavar());
229                         }
230                         if(!o->get_long().empty())
231                         {
232                                 result += format("--%s", o->get_long());
233
234                                 if(o->get_arg_type()==OPTIONAL_ARG)
235                                         result += format("[=%s]", o->get_metavar());
236                                 else if(o->get_arg_type()==REQUIRED_ARG)
237                                         result += format("=%s", o->get_metavar());
238                         }
239                         result += ']';
240                 }
241         }
242
243         for(const ArgumentImpl *a: args)
244         {
245                 result += ' ';
246                 if(a->get_type()==OPTIONAL_ARG)
247                         result += '[';
248                 result += format("<%s>", a->get_name());
249                 if(a->is_list_store())
250                         result += " ...";
251                 if(a->get_type()==OPTIONAL_ARG)
252                         result += ']';
253         }
254
255         return result;
256 }
257
258 string GetOpt::generate_help() const
259 {
260         bool any_short = any_of(opts.begin(), opts.end(), [](const OptionImpl *o){ return o->get_short(); });
261
262         string::size_type maxw = 0;
263         vector<string> switches;
264         for(const OptionImpl *o: opts)
265         {
266                 string swtch;
267                 if(o->get_short())
268                 {
269                         swtch += format("-%c", o->get_short());
270                         if(!o->get_long().empty())
271                                 swtch += ", ";
272                         else if(o->get_arg_type()==OPTIONAL_ARG)
273                                 swtch += format("[%s]", o->get_metavar());
274                         else if(o->get_arg_type()==REQUIRED_ARG)
275                                 swtch += format(" %s", o->get_metavar());
276                 }
277                 else if(any_short)
278                         swtch += "    ";
279                 if(!o->get_long().empty())
280                 {
281                         swtch += format("--%s", o->get_long());
282
283                         if(o->get_arg_type()==OPTIONAL_ARG)
284                                 swtch += format("[=%s]", o->get_metavar());
285                         else if(o->get_arg_type()==REQUIRED_ARG)
286                                 swtch += format("=%s", o->get_metavar());
287                 }
288                 switches.push_back(swtch);
289                 maxw = max(maxw, swtch.size());
290         }
291
292         vector<string> pargs;
293         for(const ArgumentImpl *a: args)
294         {
295                 string parg = format("<%s>", a->get_name());
296                 pargs.push_back(parg);
297                 maxw = max(maxw, parg.size());
298         }
299
300         string result;
301         result += "Options:\n";
302         auto j = switches.begin();
303         for(auto 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(auto 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 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