subsystems/cli_parser.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#ifndef STLPLUS_CLI_PARSER
#define STLPLUS_CLI_PARSER
////////////////////////////////////////////////////////////////////////////////
 
//   Author:    Andy Rushton
//   Copyright: (c) Southampton University 1999-2004
//              (c) Andy Rushton           2004 onwards
//   License:   BSD License, see ../docs/license.html
 
//   A subsystem for managing command-line parsing, including using INI files to
//   control the default options.
 
////////////////////////////////////////////////////////////////////////////////
#include "subsystems_fixes.hpp"
#include "message_handler.hpp"
#include "ini_manager.hpp"
#include "smart_ptr.hpp"
#include <string>
#include <stdexcept>
 
namespace stlplus
{
 
  ////////////////////////////////////////////////////////////////////////////////
  // Internals
 
  class cli_parser_data;
 
  ////////////////////////////////////////////////////////////////////////////////
  // declarations
 
  // enum to define the basic behaviour of an argument
  //   - a switch is an option with no value but which can be switched on or off e.g. -help and -nohelp
  //   - a value is an option followed by a value e.g. -output results.txt
  //       (a default value can be removed by using the option as a negated switch e.g. -nooutput)
  //   - command-line values (i.e. any strings not preceded by '-') are treated
  //     internally as an option with no name and must be values
  enum cli_kind_t {cli_switch_kind, cli_value_kind};
 
  // the mode controls the behaviour if an option appears more than once in either the command-line or the ini files
  //   - a single mode option overrides all previous values so will only be found once in the parsed result
  //   - a multiple mode option can be repeated to define multiple values, but overrides values from ini files
  //   - a cumulative mode option is a multiple mode option which keeps ini file values as well
  enum cli_mode_t {cli_single_mode, cli_multiple_mode, cli_cumulative_mode};
 
  // There are two structures used for defining command-line parameters
  //  (1) a C struct which is used in a C array - this is used for declaring
  //      command-line parameters in a static declaration
  //  (2) a C++ class which is used in an STL vector - this is used for building
  //      command-line parameters within code
 
  // The C struct for definitions
  struct cli_definition_t
  {
    // the name of the option, e.g. "help"
    const char* m_name;
 
    // the kind of the option, e.g. cli_switch_kind
    cli_kind_t m_kind;
 
    // the mode e.g. cli_single_mode
    cli_mode_t m_mode;
 
    // the mnemonic for the message giving usage information for this option
    const char* m_message;
 
    // built-in default value - null if not present
    const char* m_default;
  };
 
  // The C array of the C struct. The array must be terminated by END_CLI_DEFINITIONS.
  typedef cli_definition_t cli_definitions_t [];
#define END_CLI_DEFINITIONS {0,stlplus::cli_switch_kind,stlplus::cli_single_mode,"",0}
 
  // The C++ class for definitions
  class cli_definition
  {
  public:
    // constructor that allows a definition to be created in one line
    cli_definition(const std::string& name, cli_kind_t kind, cli_mode_t mode,
                   const std::string& message, const std::string& default_value = std::string()) :
      m_name(name), m_kind(kind), m_mode(mode), m_message(message), m_default(default_value) {}
 
    // the name of the option, e.g. "help"
    const std::string& name(void) const;
 
    // the kind of the option, e.g. switch_kind
    cli_kind_t kind(void) const;
 
    // the mode e.g. single_mode
    cli_mode_t mode(void) const;
 
    // the mnemonic for the message giving usage
    const std::string& message(void) const;
 
    // built-in default value - empty string if not present
    const std::string& default_value(void) const;
 
  private:
    std::string m_name;
    cli_kind_t m_kind;
    cli_mode_t m_mode;
    std::string m_message;
    std::string m_default;
  };
 
  // The C++ vector of the C++ class
  typedef std::vector<cli_definition> cli_definitions;
 
  //////////////////////////////////////////////////////////////////////////////
  // exceptions that can be thrown by the CLI parser
  // they are all derivatives of std::logic_error because all errors are predictable by code inspection
  // a correct program will never throw an exception
 
  // thrown if a command-line argument is accessed with the wrong mode - i.e. attempt to get the value of a switch
  class cli_mode_error : public std::invalid_argument
  {
  public:
    cli_mode_error(const std::string& arg) : std::invalid_argument(arg) {}
    ~cli_mode_error(void) throw() {}
  };
 
  // similar to std::out_of_range thrown for using an index out of range
  class cli_index_error : public std::out_of_range
  {
  public:
    cli_index_error(const std::string& arg) : std::out_of_range(arg) {}
    ~cli_index_error(void) throw() {}
  };
 
  // similar to std::invalid_argument - thrown for passing an illegal argument to a method
  class cli_argument_error : public std::invalid_argument
  {
  public:
    cli_argument_error(const std::string& arg) : std::invalid_argument(arg) {}
    ~cli_argument_error(void) throw() {}
  };
 
  ////////////////////////////////////////////////////////////////////////////////
 
  class cli_parser
  {
  public:
    // Type definitions map the global type names onto convenient scoped names
 
    typedef cli_kind_t kind_t;
    typedef cli_mode_t mode_t;
    typedef cli_definition_t definition_t;
    typedef cli_definitions_t definitions_t;
    typedef cli_definition definition;
    typedef cli_definitions definitions;
 
    ////////////////////////////////////////////////////////////////////////////////
    // Methods
 
    // various constructors
 
    // you have a choice of either creating an uninitialised CLI parser and then
    // calling separate functions to set it up or of calling one of the
    // composite constructors. However, you must set up the error handler in the
    // constructor.
 
    // set up the parser with its error handler
    // defer everything else
    cli_parser(message_handler& errors);
 
    // constructors using the C definitions_t structure
 
    // set up the parser with the error handler and define all the command-line options
    // defer default values and parameter parsing
    // exceptions: cli_mode_error
    cli_parser(cli_definitions_t, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options and their default from the ini files
    // defer parameter parsing
    // exceptions: cli_mode_error
    cli_parser(cli_definitions_t, const ini_manager& defaults, const std::string& ini_section, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options no ini files used for default values, so only built-in defaults
    // supported then parse the command line
    // exceptions: cli_mode_error,message_handler_id_error,message_handler_format_error
    cli_parser(char* argv[], cli_definitions_t, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options and their default from the ini files then parse the command line
    // exceptions: cli_mode_error,message_handler_id_error,message_handler_format_error
    cli_parser(char* argv[], cli_definitions_t, const ini_manager& defaults, const std::string& ini_section, message_handler& errors);
 
    // constructors using the C++ definitions structure
 
    // set up the parser with the error handler and define all the command-line
    // options from a C array of structs
    // defer default values and parameter parsing
    // exceptions: cli_mode_error
    cli_parser(cli_definitions, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options and their default from the ini files
    // defer parameter parsing
    // exceptions: cli_mode_error
    cli_parser(cli_definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options no ini files used for default values, so only built-in defaults
    // supported then parse the command line
    // exceptions: cli_mode_error,message_handler_id_error,message_handler_format_error
    cli_parser(char* argv[], cli_definitions, message_handler& errors);
    // set up the parser with the error handler and define all the command-line
    // options and their default from the ini files then parse the command line
    // exceptions: cli_mode_error,message_handler_id_error,message_handler_format_error
    cli_parser(char* argv[], cli_definitions, const ini_manager& defaults, const std::string& ini_section, message_handler& errors);
 
    ~cli_parser(void);
 
    // the separate functions for initialising the parser in steps. These are
    // declared in the order of use. Firts, add definitions of command-line
    // arguments. Then optionally load default values from ini files, then
    // finally parse the command line.
 
    // add a set of C definitions. The definitions will be given ID codes from 0
    // to the number of elements - 1 in the array
    // exceptions: cli_mode_error
    void add_definitions(cli_definitions_t);
    // add a single C definition, returning the ID code for it
    // exceptions: cli_mode_error,cli_argument_error
    unsigned add_definition(const definition_t&);
    // add a set of C++ definitions. The definitions will be given ID codes from
    // 0 to the number of elements - 1 in the array
    // exceptions: cli_mode_error
    void add_definitions(cli_definitions);
    // add a single C++ definition, returning the ID code for it
    // exceptions: cli_mode_error
    unsigned add_definition(const definition&);
 
    // All definitions have an optional built-in default value which is stored
    // in the definition types above. However, these can optionally be
    // overridden by a value from an ini file. If you want this functionality,
    // call this function. If you don't want ini file handling, simply don't
    // call it. The values will be searched for only in the named section of the
    // ini file (sections are labelled by e.g. [vassemble]), so in this case you
    // would specify the section name as "vassemble" (exclude the brackets).
    void set_defaults(const ini_manager& defaults, const std::string& ini_section);
 
    // the final stage of initialisation is to read the command-line and extract
    // the values from it. If parse errors are found, this will report the
    // errors using the error handler and return false.
    // exceptions: cli_argument_error,message_handler_id_error,message_handler_format_error
    bool parse(char* argv[]);
 
    // test for whether the CLI parser is still valid (no errors have happened)
    // after the initialisation phase
    bool valid(void);
 
    // iteration functions avoiding the use of iterators. Just loop through the
    // arguments from 0 to size()-1 and use the index of the loop to interrogate
    // the command-line for the value at that position.
 
    // the number of values to read, indexed 0 to size()-1
    unsigned size(void) const;
 
    // the argument name
    // exceptions: cli_index_error
    std::string name(unsigned i) const;
    // the argument ID, that is, the offset into the original definitions
    // exceptions: cli_index_error
    unsigned id(unsigned i) const;
 
    // the kind (switch or value) and short-cut tests for the different kinds
    // exceptions: cli_index_error
    cli_kind_t kind(unsigned i) const;
    // exceptions: cli_index_error
    bool switch_kind(unsigned i) const;
    // exceptions: cli_index_error
    bool value_kind(unsigned i) const;
 
    // the mode (single, multiple, cumulative) and short-cut tests for the
    // different modes - you rarely need to know this since it mainly controls
    // the parsing
    // exceptions: cli_index_error
    cli_mode_t mode(unsigned i) const;
    // exceptions: cli_index_error
    bool single_mode(unsigned i) const;
    // exceptions: cli_index_error
    bool multiple_mode(unsigned i) const;
    // exceptions: cli_index_error
    bool cumulative_mode(unsigned i) const;
 
    // get the switch's value, but only if the value is of switch kind
    // exceptions: cli_mode_error,cli_index_error
    bool switch_value(unsigned i) const;
 
    // get the option's value, but only if it is of value kind
    // exceptions: cli_mode_error,cli_index_error
    std::string string_value(unsigned i) const;
 
    // print the usage report - typically in response to the -help switch being on
    // exceptions: std::runtime_error
    void usage(void) const;
 
  private:
    friend class cli_parser_data;
    smart_ptr_nocopy<cli_parser_data> m_data;
  };
 
} // end namespace stlplus
 
#endif