dbc0f9cef0284e94d4f5ec66b890e94700515e4d
[libs/core.git] / source / core / getopt.h
1 #ifndef MSP_CORE_GETOPT_H_
2 #define MSP_CORE_GETOPT_H_
3
4 #include <list>
5 #include <stdexcept>
6 #include <string>
7 #include <vector>
8 #include <msp/strings/lexicalcast.h>
9
10 namespace Msp {
11
12 class usage_error: public std::runtime_error
13 {
14 private:
15         std::string help_;
16
17 public:
18         usage_error(const std::string &w, const std::string &h = std::string()): std::runtime_error(w), help_(h) { }
19         ~usage_error() throw() { }
20
21         const char *help() const throw() { return help_.c_str(); }
22 };
23
24
25 /**
26 Command line option processor.  Both short and long options are supported, with
27 optional and required arguments.  Automatic help text generation is also
28 available.
29
30 Short options begin with a single dash and are identified by a single letter.
31 Multiple short options may be grouped if they take no arguments; for example,
32 the string "-abc" could be interpreted as having the options 'a', 'b' and 'c'.
33 If the option takes an argument and there are unused characters in the argv
34 element, then those characters are interpreted as the argument.  Otherwise the
35 next element is taken as the argument.  An optional argument must be given in
36 the same element.
37
38 Long options begin with a double dash and are identified by an arbitrary
39 string.  An argument can be specified either in the same argv element,
40 separated by an equals sign, or in the next element.  As with short options,
41 an optional argument must be in the same element.
42
43 A single option may have both alternative forms, but must always have at least
44 a long form.  This is to encourage self-documenting options; it's much easier
45 to remember words than letters.
46
47 A built-in --help option is provided and will output a list of options and
48 their associated help texts.  An application may override this by providing
49 its own option with the same name.
50 */
51 class GetOpt
52 {
53 public:
54         enum ArgType
55         {
56                 NO_ARG,
57                 OPTIONAL_ARG,
58                 REQUIRED_ARG
59         };
60
61         class Option
62         {
63         protected:
64                 Option() { }
65         public:
66                 virtual ~Option() { }
67
68                 /// Sets help text for the option.
69                 virtual Option &set_help(const std::string &) = 0;
70
71                 /** Sets help text for the option, with a placeholder metavariable.  The
72                 metavariable is used to denote the argument in the option list. */
73                 virtual Option &set_help(const std::string &, const std::string &) = 0;
74
75                 /// Returns the number of times this option was seen on the command line.
76                 virtual unsigned get_seen_count() const = 0;
77         };
78
79 private:
80         class OptBase: public Option
81         {
82         protected:
83                 char shrt;
84                 std::string lng;
85                 ArgType arg_type;
86                 unsigned seen_count;
87                 std::string help;
88                 std::string metavar;
89
90                 OptBase(char, const std::string &, ArgType);
91         public:
92                 virtual ~OptBase() { }
93
94                 virtual OptBase &set_help(const std::string &);
95                 virtual OptBase &set_help(const std::string &, const std::string &);
96                 char get_short() const { return shrt; }
97                 const std::string &get_long() const { return lng; }
98                 ArgType get_arg_type() const { return arg_type; }
99                 const std::string &get_help() const { return help; }
100                 const std::string &get_metavar() const { return metavar; }
101                 virtual unsigned get_seen_count() const { return seen_count; }
102                 void process();
103                 void process(const std::string &);
104         protected:
105                 virtual void store() = 0;
106                 virtual void store(const std::string &) = 0;
107         };
108
109         template<typename T>
110         class SimpleOption: public OptBase
111         {
112         private:
113                 T &data;
114
115         public:
116                 SimpleOption(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d) { }
117
118                 virtual void store() { }
119
120                 virtual void store(const std::string &a)
121                 {
122                         try
123                         {
124                                 data = lexical_cast<T>(a);
125                         }
126                         catch(const lexical_error &e)
127                         {
128                                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
129                         }
130                 }
131         };
132
133         template<typename T>
134         class ListOption: public OptBase
135         {
136         private:
137                 T &data;
138
139         public:
140                 ListOption(char s, const std::string &l, T &d, ArgType a): OptBase(s, l, a), data(d)
141                 { if(arg_type!=REQUIRED_ARG) throw std::invalid_argument("ListOption arg_type!=REQUIRED"); }
142
143                 virtual void store() { }
144
145                 virtual void store(const std::string &a)
146                 {
147                         try
148                         {
149                                 data.push_back(lexical_cast<typename T::value_type>(a));
150                         }
151                         catch(const lexical_error &e)
152                         {
153                                 throw usage_error("Invalid argument for --"+lng+" ("+e.what()+")");
154                         }
155                 }
156         };
157
158         bool help;
159         std::list<OptBase *> opts;
160         std::vector<std::string> args;
161
162 public:
163         GetOpt();
164         ~GetOpt();
165
166         /// Returns any non-option arguments encountered during processing.
167         const std::vector<std::string> &get_args() const { return args; }
168
169         /** Adds an option with both short and long forms.  Processing depends on
170         the type of the destination variable and whether an argument is taken or
171         not.  With an argument, the value is lexical_cast to appropriate type and
172         stored in the destination.  Without an argument, a bool will be set to true
173         and an unsigned will be incremented; any other type will be ignored. */
174         template<typename T>
175         Option &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG)
176         { return add_option(new SimpleOption<T>(s, l, d, a)); }
177
178         /** Adds an option with both short and long forms.  The option may be
179         specified multiple times, and the argument from each occurrence is stored in
180         the list.  The argument type must be REQUIRED_ARG. */
181         template<typename T>
182         Option &add_option(char s, const std::string &l, std::list<T> &d, ArgType a = REQUIRED_ARG)
183         { return add_option(new ListOption<std::list<T> >(s, l, d, a)); }
184
185         /** Adds an option with only a long form. */
186         template<typename T>
187         Option &add_option(const std::string &l, T &d, ArgType a)
188         { return add_option(0, l, d, a); }
189
190 private:
191         OptBase &add_option(OptBase *);
192
193         OptBase &get_option(char);
194         OptBase &get_option(const std::string &);
195
196 public:
197         /** Processes argc/argv style command line arguments.  The contents of argv
198         will be unchanged; use get_args to access non-option arguments. */
199         void operator()(unsigned, const char *const *);
200
201 private:
202         /** Processes a long option.  Returns the number of arguments eaten. */
203         unsigned process_long(const char *const *);
204
205         /** Processes short options.  Returns the number of arguments eaten. */
206         unsigned process_short(const char *const *);
207
208 public:
209         /** Generates a single line that describes known options. */
210         std::string generate_usage(const std::string &) const;
211
212         /** Generates help for known options in tabular format, one option per
213         line.  The returned string will have a linefeed at the end. */
214         std::string generate_help() const;
215 };
216
217 template<> inline void GetOpt::SimpleOption<bool>::store()
218 { data = true; }
219
220 template<> inline void GetOpt::SimpleOption<unsigned>::store()
221 { ++data; }
222
223 template<> inline void GetOpt::SimpleOption<std::string>::store(const std::string &a)
224 { data = a; }
225
226 template<> inline void GetOpt::ListOption<std::list<std::string> >::store(const std::string &a)
227 { data.push_back(a); }
228
229 } // namespace Msp
230
231 #endif