]> git.tdb.fi Git - libs/core.git/blob - source/core/getopt.cpp
Add move semantics to Variant
[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 {
11         add_option("help", help, NO_ARG).set_help("Displays this help");
12 }
13
14 GetOpt::~GetOpt()
15 {
16         for(OptionImpl *i: opts)
17                 delete i;
18         for(ArgumentImpl *i: args)
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(auto i=opts.begin(); i!=opts.end(); )
30         {
31                 if((s!=0 && (*i)->get_short()==s) || (*i)->get_long()==l)
32                 {
33                         delete *i;
34                         i = 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 y)
45 {
46         if(y==NO_ARG)
47                 throw invalid_argument("GetOpt::add_argument");
48
49         bool have_list = false;
50         bool have_optional = false;
51         for(const ArgumentImpl *a: args)
52         {
53                 if(a->is_list_store())
54                         have_list = true;
55                 else if(a->get_type()==OPTIONAL_ARG)
56                         have_optional = true;
57         }
58
59         if(have_optional && (t.is_list() || y!=OPTIONAL_ARG))
60                 throw invalid_argument("GetOpt::add_argument");
61         if(have_list && (t.is_list() || y==OPTIONAL_ARG))
62                 throw invalid_argument("GetOpt::add_argument");
63
64         args.push_back(new ArgumentImpl(n, t, y));
65         return *args.back();
66 }
67
68 GetOpt::OptionImpl &GetOpt::get_option(char s)
69 {
70         auto i = find_if(opts, [s](const OptionImpl *o){ return o->get_short()==s; });
71         if(i!=opts.end())
72                 return **i;
73         throw usage_error(string("Unknown option -")+s);
74 }
75
76 GetOpt::OptionImpl &GetOpt::get_option(const string &l)
77 {
78         auto i = find_if(opts, [&l](const OptionImpl *o){ return o->get_long()==l; });
79         if(i!=opts.end())
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(auto j=args.begin(); j!=args.end(); ++j)
114                 {
115                         if((*j)->is_list_store())
116                         {
117                                 unsigned end = args_raw.size();
118                                 for(auto 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(const OptionImpl *o: opts)
217                 {
218                         result += " [";
219                         if(o->get_short())
220                         {
221                                 result += format("-%c", o->get_short());
222                                 if(!o->get_long().empty())
223                                         result += '|';
224                                 else if(o->get_arg_type()==OPTIONAL_ARG)
225                                         result += format("[%s]", o->get_metavar());
226                                 else if(o->get_arg_type()==REQUIRED_ARG)
227                                         result += format(" %s", o->get_metavar());
228                         }
229                         if(!o->get_long().empty())
230                         {
231                                 result += format("--%s", o->get_long());
232
233                                 if(o->get_arg_type()==OPTIONAL_ARG)
234                                         result += format("[=%s]", o->get_metavar());
235                                 else if(o->get_arg_type()==REQUIRED_ARG)
236                                         result += format("=%s", o->get_metavar());
237                         }
238                         result += ']';
239                 }
240         }
241
242         for(const ArgumentImpl *a: args)
243         {
244                 result += ' ';
245                 if(a->get_type()==OPTIONAL_ARG)
246                         result += '[';
247                 result += format("<%s>", a->get_name());
248                 if(a->is_list_store())
249                         result += " ...";
250                 if(a->get_type()==OPTIONAL_ARG)
251                         result += ']';
252         }
253
254         return result;
255 }
256
257 string GetOpt::generate_help() const
258 {
259         bool any_short = any_of(opts.begin(), opts.end(), [](const OptionImpl *o){ return o->get_short(); });
260
261         string::size_type maxw = 0;
262         vector<string> switches;
263         for(const OptionImpl *o: opts)
264         {
265                 string swtch;
266                 if(o->get_short())
267                 {
268                         swtch += format("-%c", o->get_short());
269                         if(!o->get_long().empty())
270                                 swtch += ", ";
271                         else if(o->get_arg_type()==OPTIONAL_ARG)
272                                 swtch += format("[%s]", o->get_metavar());
273                         else if(o->get_arg_type()==REQUIRED_ARG)
274                                 swtch += format(" %s", o->get_metavar());
275                 }
276                 else if(any_short)
277                         swtch += "    ";
278                 if(!o->get_long().empty())
279                 {
280                         swtch += format("--%s", o->get_long());
281
282                         if(o->get_arg_type()==OPTIONAL_ARG)
283                                 swtch += format("[=%s]", o->get_metavar());
284                         else if(o->get_arg_type()==REQUIRED_ARG)
285                                 swtch += format("=%s", o->get_metavar());
286                 }
287                 switches.push_back(swtch);
288                 maxw = max(maxw, swtch.size());
289         }
290
291         vector<string> pargs;
292         for(const ArgumentImpl *a: args)
293         {
294                 string parg = format("<%s>", a->get_name());
295                 pargs.push_back(parg);
296                 maxw = max(maxw, parg.size());
297         }
298
299         string result;
300         result += "Options:\n";
301         auto j = switches.begin();
302         for(auto i=opts.begin(); i!=opts.end(); ++i, ++j)
303                 result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
304         if(!pargs.empty())
305         {
306                 result += "\nArguments:\n";
307                 j = pargs.begin();
308                 for(auto i=args.begin(); i!=args.end(); ++i, ++j)
309                         result += format("  %s%s%s\n", *j, string(maxw+2-j->size(), ' '), (*i)->get_help());
310         }
311
312         return result;
313 }
314
315
316 GetOpt::OptionImpl::OptionImpl(char s, const string &l, const Store &t, ArgType a):
317         shrt(s),
318         lng(l),
319         arg_type(a),
320         store(t.clone())
321 { }
322
323 GetOpt::OptionImpl::~OptionImpl()
324 {
325         delete store;
326 }
327
328 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h)
329 {
330         help = h;
331         return *this;
332 }
333
334 GetOpt::OptionImpl &GetOpt::OptionImpl::set_help(const string &h, const string &m)
335 {
336         help = h;
337         metavar = m;
338         return *this;
339 }
340
341 GetOpt::OptionImpl &GetOpt::OptionImpl::bind_seen_count(unsigned &c)
342 {
343         ext_seen_count = &c;
344         return *this;
345 }
346
347 void GetOpt::OptionImpl::process()
348 {
349         if(arg_type==REQUIRED_ARG)
350                 throw usage_error("--"+lng+" requires an argument");
351
352         ++seen_count;
353         if(ext_seen_count)
354                 *ext_seen_count = seen_count;
355
356         try
357         {
358                 store->store();
359         }
360         catch(const exception &e)
361         {
362                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
363         }
364 }
365
366 void GetOpt::OptionImpl::process(const string &arg)
367 {
368         if(arg_type==NO_ARG)
369                 throw usage_error("--"+lng+" takes no argument");
370
371         ++seen_count;
372         if(ext_seen_count)
373                 *ext_seen_count = seen_count;
374
375         try
376         {
377                 store->store(arg);
378         }
379         catch(const exception &e)
380         {
381                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
382         }
383 }
384
385
386 GetOpt::ArgumentImpl::ArgumentImpl(const string &n, const Store &t, ArgType a):
387         name(n),
388         type(a),
389         store(t.clone())
390 { }
391
392 GetOpt::ArgumentImpl::~ArgumentImpl()
393 {
394         delete store;
395 }
396
397 GetOpt::ArgumentImpl &GetOpt::ArgumentImpl::set_help(const string &h)
398 {
399         help = h;
400         return *this;
401 }
402
403 void GetOpt::ArgumentImpl::process(const string &arg)
404 {
405         try
406         {
407                 store->store(arg);
408         }
409         catch(const exception &e)
410         {
411                 throw usage_error("Invalid "+name+" ("+e.what()+")");
412         }
413 }
414
415 } // namespace Msp