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