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