]> git.tdb.fi Git - libs/core.git/blob - source/core/getopt.h
Add support for positional arguments in GetOpt
[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 Positional arguments are also supported.  They are identified by an arbitrary
48 string, but the identifier is only used in help text and error messages.  Any
49 number of the final arguments may be optional.
50
51 To support applications that take an arbitrary amount of arguments, a single
52 positional argument list can be specified.  Fixed positional arguments are
53 allowed together with a list, but they can't be optional.  An application that
54 wants to do complex processing on the argument list can declare a list of
55 string arguments.
56
57 A built-in --help option is provided and will output a list of options,
58 arguments and their associated help texts.  An application may override this by
59 providing its own option with the same name.
60 */
61 class GetOpt
62 {
63 public:
64         enum ArgType
65         {
66                 NO_ARG,
67                 OPTIONAL_ARG,
68                 REQUIRED_ARG
69         };
70
71         class Option
72         {
73         protected:
74                 Option() { }
75         public:
76                 virtual ~Option() { }
77
78                 /// Sets help text for the option.
79                 virtual Option &set_help(const std::string &) = 0;
80
81                 /** Sets help text for the option, with a placeholder metavariable.  The
82                 metavariable is used to denote the argument in the option list. */
83                 virtual Option &set_help(const std::string &, const std::string &) = 0;
84
85                 virtual Option &bind_seen_count(unsigned &) = 0;
86
87                 /// Returns the number of times this option was seen on the command line.
88                 virtual unsigned get_seen_count() const = 0;
89         };
90
91         class Argument
92         {
93         protected:
94                 Argument() { }
95         public:
96                 virtual ~Argument() { }
97
98                 virtual Argument &set_help(const std::string &) = 0;
99         };
100
101 private:
102         class Store
103         {
104         protected:
105                 Store() { }
106         public:
107                 virtual ~Store() { }
108
109                 virtual Store *clone() const = 0;
110
111                 virtual bool is_list() const = 0;
112                 virtual void store() = 0;
113                 virtual void store(const std::string &) = 0;
114         };
115
116         class OptionImpl: public Option
117         {
118         protected:
119                 char shrt;
120                 std::string lng;
121                 ArgType arg_type;
122                 unsigned seen_count;
123                 unsigned *ext_seen_count;
124                 std::string help;
125                 std::string metavar;
126                 Store *store;
127
128         public:
129                 OptionImpl(char, const std::string &, const Store &, ArgType);
130                 virtual ~OptionImpl();
131
132                 virtual OptionImpl &set_help(const std::string &);
133                 virtual OptionImpl &set_help(const std::string &, const std::string &);
134                 virtual OptionImpl &bind_seen_count(unsigned &);
135                 char get_short() const { return shrt; }
136                 const std::string &get_long() const { return lng; }
137                 ArgType get_arg_type() const { return arg_type; }
138                 const std::string &get_help() const { return help; }
139                 const std::string &get_metavar() const { return metavar; }
140                 virtual unsigned get_seen_count() const { return seen_count; }
141                 void process();
142                 void process(const std::string &);
143         };
144
145         class ArgumentImpl: public Argument
146         {
147         private:
148                 std::string name;
149                 ArgType type;
150                 std::string help;
151                 Store *store;
152
153         public:
154                 ArgumentImpl(const std::string &, const Store &, ArgType);
155                 virtual ~ArgumentImpl();
156
157                 virtual ArgumentImpl &set_help(const std::string &);
158                 const std::string &get_name() const { return name; }
159                 ArgType get_type() const { return type; }
160                 const std::string &get_help() const { return help; }
161                 bool is_list_store() const { return store->is_list(); }
162                 void process(const std::string &);
163         };
164
165         template<typename T>
166         class SimpleStore: public Store
167         {
168         private:
169                 T &data;
170
171         public:
172                 SimpleStore(T &d): data(d) { }
173
174                 virtual SimpleStore *clone() const
175                 { return new SimpleStore(data); }
176
177                 virtual bool is_list() const { return false; }
178
179                 virtual void store() { }
180
181                 virtual void store(const std::string &a)
182                 { data = lexical_cast<T>(a); }
183         };
184
185         template<typename T>
186         class ListStore: public Store
187         {
188         private:
189                 T &data;
190
191         public:
192                 ListStore(T &d): data(d) { }
193
194                 virtual ListStore *clone() const
195                 { return new ListStore(data); }
196
197                 virtual bool is_list() const { return true; }
198
199                 virtual void store() { }
200
201                 virtual void store(const std::string &a)
202                 { data.push_back(lexical_cast<typename T::value_type>(a)); }
203         };
204
205         typedef std::list<OptionImpl *> OptionList;
206         typedef std::list<ArgumentImpl *> ArgumentList;
207
208         bool help;
209         OptionList opts;
210         ArgumentList args;
211         std::vector<std::string> args_raw;
212
213 public:
214         GetOpt();
215         ~GetOpt();
216
217         /** Returns any non-option arguments encountered during processing.
218         Deprecated. */
219         const std::vector<std::string> &get_args() const { return args_raw; }
220
221         /** Adds an option with both short and long forms.  Processing depends on
222         the type of the destination variable and whether an argument is taken or
223         not.  With an argument, the value is lexical_cast to appropriate type and
224         stored in the destination.  Without an argument, a bool will be set to true
225         and an unsigned will be incremented; any other type will be ignored. */
226         template<typename T>
227         Option &add_option(char s, const std::string &l, T &d, ArgType a = NO_ARG)
228         { return add_option(s, l, SimpleStore<T>(d), a); }
229
230         /** Adds an option with both short and long forms.  The option may be
231         specified multiple times, and the argument from each occurrence is stored in
232         the list.  The argument type must be REQUIRED_ARG. */
233         template<typename T>
234         Option &add_option(char s, const std::string &l, std::list<T> &d, ArgType a = REQUIRED_ARG)
235         { return add_option(s, l, ListStore<std::list<T> >(d), a); }
236
237         /** Adds an option with only a long form. */
238         template<typename T>
239         Option &add_option(const std::string &l, T &d, ArgType a)
240         { return add_option(0, l, d, a); }
241
242         /** Adds a positional argument.  The value will be lexical_cast to the
243         appropriate type and stored in the destination. */
244         template<typename T>
245         Argument &add_argument(const std::string &n, T &d, ArgType a = REQUIRED_ARG)
246         { return add_argument(n, SimpleStore<T>(d), a); }
247
248         /** Adds a positional argument list.  If the list is declared as required,
249         at least one element must be given; an optional list may be empty.  Only one
250         list may be added, and optional fixed arguments can't be used with it. */
251         template<typename T>
252         Argument &add_argument(const std::string &n, std::list<T> &d, ArgType a = REQUIRED_ARG)
253         { return add_argument(n, ListStore<std::list<T> >(d), a); }
254
255 private:
256         OptionImpl &add_option(char, const std::string &, const Store &, ArgType);
257         ArgumentImpl &add_argument(const std::string &, const Store &, ArgType);
258
259         OptionImpl &get_option(char);
260         OptionImpl &get_option(const std::string &);
261
262 public:
263         /** Processes argc/argv style command line arguments.  The contents of argv
264         will be unchanged; use get_args to access non-option arguments. */
265         void operator()(unsigned, const char *const *);
266
267 private:
268         /** Processes a long option.  Returns the number of arguments eaten. */
269         unsigned process_long(const char *const *);
270
271         /** Processes short options.  Returns the number of arguments eaten. */
272         unsigned process_short(const char *const *);
273
274 public:
275         /** Generates a single line that describes known options and arguments. */
276         std::string generate_usage(const std::string &) const;
277
278         /** Generates help for known options and arguments in tabular format, one
279         item per line.  The returned string will have a linefeed at the end. */
280         std::string generate_help() const;
281 };
282
283 template<> inline void GetOpt::SimpleStore<bool>::store()
284 { data = true; }
285
286 template<> inline void GetOpt::SimpleStore<unsigned>::store()
287 { ++data; }
288
289 template<> inline void GetOpt::SimpleStore<std::string>::store(const std::string &a)
290 { data = a; }
291
292 template<> inline void GetOpt::ListStore<std::list<std::string> >::store(const std::string &a)
293 { data.push_back(a); }
294
295 } // namespace Msp
296
297 #endif