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