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