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