GRSISort "v4.0.0.5"
An extension of the ROOT analysis Framework
Loading...
Searching...
No Matches
ArgParser.h
Go to the documentation of this file.
1#ifndef ARGPARSER_H
2#define ARGPARSER_H
3
4#include <iostream>
5#include <fstream>
6#include <sstream>
7#include <stdexcept>
8#include <string>
9#include <utility>
10#include <vector>
11
12#include "Globals.h"
13
14struct ParseError : public std::runtime_error {
15 explicit ParseError(const char* msg) : std::runtime_error(msg) {}
16 explicit ParseError(const std::string& msg) : std::runtime_error(msg) {}
17};
18
19/** Base class used to parse an individual item.
20 Most methods are implemented in the templated ArgParseConfig<T>
21 */
23public:
24 explicit ArgParseItem(bool firstPass) : fFirstPass(firstPass) {}
25 ArgParseItem(const ArgParseItem&) = default;
26 ArgParseItem(ArgParseItem&&) noexcept = default;
27 ArgParseItem& operator=(const ArgParseItem&) = default;
28 ArgParseItem& operator=(ArgParseItem&&) noexcept = default;
29 virtual ~ArgParseItem() = default;
30 virtual bool matches(const std::string& flag) const = 0;
31 virtual void parse_item(const std::vector<std::string>& arguments) = 0;
32 virtual int num_arguments() const = 0;
33 virtual std::string printable(int description_column, int* chars_before_desc) const = 0;
34 virtual bool is_required() const = 0;
35 bool is_present() const { return fPresent; }
36 virtual std::string flag_name() const = 0;
37
38 void parse(const std::string& name, const std::vector<std::string>& arguments, bool firstPass,
39 bool ignore_num_arguments = false)
40 {
41 if(firstPass != fFirstPass) {
42 return;
43 }
44 if(!ignore_num_arguments) {
45 if((num_arguments() == -1 && arguments.empty()) ||
46 (num_arguments() != -1 && arguments.size() != static_cast<size_t>(num_arguments()))) {
47 std::ostringstream error;
48 if(num_arguments() == -1) {
49 error << R"(Flag ")" << name << R"(" expected at least one argument)";
50 } else {
51 error << R"(Flag ")" << name << R"(" expected )" << num_arguments() << " argument(s) and received "
52 << arguments.size();
53 }
54 throw ParseError(error.str());
55 }
56 }
57
58 fPresent = true;
59 parse_item(arguments);
60 }
61
62private:
63 bool fPresent{false};
65};
66
67template <typename T>
69public:
70 ArgParseConfig(const std::string& flag_list, bool firstPass) : ArgParseItem(firstPass)
71 {
72 std::istringstream str(flag_list);
73 while(!str.eof()) {
74 std::string temp;
75 str >> temp;
76 fRawFlags.push_back(temp);
77 if(temp.length() == 1) {
78 fFlags.push_back("-" + temp);
79 } else if(temp.length() > 1) {
80 fFlags.push_back("--" + temp);
81 }
82 }
83 }
84 ArgParseConfig(const ArgParseConfig&) = default;
85 ArgParseConfig(ArgParseConfig&&) noexcept = default;
86 ArgParseConfig& operator=(const ArgParseConfig&) = default;
87 ArgParseConfig& operator=(ArgParseConfig&&) noexcept = default;
88 ~ArgParseConfig() = default;
89
90 std::string flag_name() const override
91 {
92 std::string output;
93 for(const auto& flag : fFlags) {
94 if(flag.length() > output.length()) {
95 output = flag;
96 }
97 }
98 return output;
99 }
100
101 bool matches(const std::string& flag) const override
102 {
103 // This is the default option, and something not a flag was passed.
104 if(flag.at(0) != '-' && fFlags.empty()) {
105 return true;
106 }
107
108 return std::any_of(fFlags.begin(), fFlags.end(), [&flag](auto cfl) { return cfl == flag; });
109 }
110
111 virtual ArgParseConfig& description(const std::string& description)
112 {
114 return *this;
115 }
116
117 virtual ArgParseConfig& colour(const std::string& colour)
118 {
119 fColour = colour;
120 return *this;
121 }
122
124 {
125 fRequired = true;
126 return *this;
127 }
128
129 bool is_required() const override { return fRequired; }
130
131 virtual ArgParseConfig& default_value(T value) = 0;
132
133 std::string printable(int description_column, int* chars_before_desc) const override
134 {
135 std::ostringstream output;
136
137 output << " " << fColour;
138
139 bool has_singlechar_flag = false;
140 for(const auto& flag : fFlags) {
141 if(flag.length() == 2) {
142 output << flag << " ";
143 has_singlechar_flag = true;
144 }
145 }
146 for(const auto& flag : fFlags) {
147 if(flag.length() != 2) {
148 if(has_singlechar_flag) {
149 output << "[ ";
150 }
151 output << flag << " ";
152 if(has_singlechar_flag) {
153 output << "]";
154 }
155 }
156 }
157
158 if(num_arguments() != 0) {
159 output << " arg ";
160 }
161
162 auto chars = output.tellp();
163 chars -= fColour.length();
164 if(chars_before_desc != nullptr) {
165 *chars_before_desc = static_cast<int>(chars);
166 }
167
168 if(description_column != -1 && chars < description_column) {
169 for(unsigned int i = 0; i < description_column - chars; i++) {
170 output << " ";
171 }
172 }
173
174 output << fDescription << RESET_COLOR;
175
176 return output.str();
177 }
178
179private:
180 /// A description for display on the terminal.
181 std::string fDescription;
182
183 /// Colour string to be use for display
184 std::string fColour;
185
186 /// The literal flag that is searched for, including leading dashes.
187 std::vector<std::string> fFlags;
188
189 /// The flags without the leading dashes.
190 std::vector<std::string> fRawFlags;
191
192 /// Whether the flag must be supplied.
193 bool fRequired{false};
194};
195
196template <typename T>
198public:
199 ArgParseConfigT(std::string flag, T* output_location, bool firstPass)
200 : ArgParseConfig<T>(flag, firstPass), fOutput_location(output_location)
201 {
202 }
203
205 {
206 *fOutput_location = value;
207 return *this;
208 }
209
210 void parse_item(const std::vector<std::string>& arguments) override
211 {
212 std::istringstream str(arguments[0]);
213 str >> *fOutput_location;
214 }
215
216 int num_arguments() const override { return 1; }
217
218private:
220};
221
222template <>
223class ArgParseConfigT<bool> : public ArgParseConfig<bool> {
224public:
225 ArgParseConfigT(const std::string& flag, bool* output_location, bool firstPass)
226 : ArgParseConfig<bool>(flag, firstPass), fOutput_location(output_location)
227 {
228 *fOutput_location = fStored_default_value;
229 }
230
232 {
233 *fOutput_location = value;
234 fStored_default_value = value;
235 return *this;
236 }
237
239 {
240 fNum_arguments_expected = 1;
241 return *this;
242 }
243
244 void parse_item(const std::vector<std::string>& arguments) override
245 {
246 if(arguments.empty()) {
247 *fOutput_location = !fStored_default_value;
248 } else {
249 std::istringstream str(arguments[0]);
250 str >> std::boolalpha >> *fOutput_location;
251 }
252 }
253
254 int num_arguments() const override { return fNum_arguments_expected; }
255
256private:
258 bool fStored_default_value{false};
259 int fNum_arguments_expected{0};
260};
261
262template <typename T>
263class ArgParseConfigT<std::vector<T>> : public ArgParseConfig<std::vector<T>> {
264public:
265 ArgParseConfigT(std::string flag, std::vector<T>* output_location, bool firstPass)
266 : ArgParseConfig<std::vector<T>>(flag, firstPass), fOutput_location(output_location)
267 {
268 }
269
270 void parse_item(const std::vector<std::string>& arguments) override
271 {
272 for(const auto& arg : arguments) {
273 std::istringstream str(arg);
274 T val;
275 str >> val;
276 fOutput_location->push_back(val);
277 }
278 }
279
280 int num_arguments() const override { return fNum_arguments_expected; }
281
282 ArgParseConfig<std::vector<T>>& default_value(std::vector<T> value) override
283 {
284 *fOutput_location = value;
285 return *this;
286 }
287
288private:
289 std::vector<T>* fOutput_location;
290 int fNum_arguments_expected{-1};
291};
292
293//////////////////////////////////////////////////////////////////////////
294///
295/// \class ArgParser
296///
297/// This class is used to parse the command line arguments.
298///
299/// Example usage:
300/// ```
301/// ArgParser parser;
302/// int myOption = 0;
303/// parser.option("some-option s", &myOption, true).description("my cool option").default_value(42);
304/// parser.parse(argc, argv, true);
305/// ```
306/// This example reads the number after the flag "--some-option" or "-s"
307/// into the integer variable myOption. If no flag is provided, the default
308/// value of 42 is used.
309///
310/// The 3rd argument in ArgParser::option and ArgParser::parse that are set
311/// to true here are the "firstPass" arguments which simply means we want
312/// to read these options right away instead of reading something from an
313/// input file and only then parse the command line arguments.
314///
315//////////////////////////////////////////////////////////////////////////
316
318public:
319 ArgParser() = default;
320 ArgParser(const ArgParser&) = default;
321 ArgParser(ArgParser&&) noexcept = default;
322
323 ArgParser& operator=(const ArgParser&) = default;
324 ArgParser& operator=(ArgParser&&) noexcept = default;
325
327 {
328 for(auto* val : values) {
329 delete val;
330 }
331 }
332
333 void parse(int argc, char** argv, bool firstPass)
334 {
335 /// This version takes argc and argv, parses them, and sets only those
336 /// that have the matching firstPass flag set.
337 /// This allows us to parse command line arguments in two stages, one
338 /// to get the normal options and file names (from which the run info
339 /// and analysis options are read), and a second stage, where only the
340 /// run info and analysis option flags are parsed.
341 bool double_dash_encountered = false;
342
343 int iarg = 1;
344 while(iarg < argc) {
345 std::string arg = argv[iarg++];
346
347 if(arg.at(0) != '-' || double_dash_encountered) {
348 handle_default_option(argc, argv, iarg, firstPass);
349 } else if(arg.substr(0, 2) == "--") {
350 handle_long_flag(argc, argv, iarg, firstPass);
351 } else {
352 handle_short_flag(argc, argv, iarg, firstPass);
353 }
354 }
355
356 for(auto& val : values) {
357 if(val->is_required() && !val->is_present()) {
358 std::ostringstream error;
359 error << R"(Required argument ")" << val->flag_name() << R"(" is not present)";
360 throw ParseError(error.str());
361 }
362 }
363 }
364
365 void parse_file(std::string& filename)
366 {
367 std::ifstream infile(filename);
368
369 std::string line;
370 while(std::getline(infile, line)) {
371 size_t colon = line.find(':');
372 bool has_colon = (colon != std::string::npos);
373
374 std::string flag;
375 std::string remainder;
376 if(has_colon) {
377 flag = line.substr(0, colon);
378 std::istringstream(flag) >> flag; // Strip out whitespace
379 if(flag.length() == 1) {
380 flag.insert(0, 1, '-');
381 } else {
382 flag.insert(0, 2, '-');
383 }
384 remainder = line.substr(colon + 1, line.length());
385 } else {
386 flag = "";
387 remainder = line;
388 }
389
390 std::vector<std::string> args;
391 std::istringstream str(remainder);
392 std::string tmparg;
393 while(str >> tmparg) {
394 args.push_back(tmparg);
395 }
396
397 if(has_colon) {
398 ArgParseItem& item = get_item(flag);
399 item.parse(flag, args, true, true); // parse "firstPass" items only
400 item.parse(flag, args, false, true); // parse "!firstPass" items only
401 } else {
402 for(auto& arg : args) {
403 ArgParseItem& item = get_item(arg);
404 item.parse(arg, std::vector<std::string>{arg}, true); // parse "firstPass" items only
405 item.parse(arg, std::vector<std::string>{arg}, false); // parse "!firstPass" items only
406 }
407 }
408 }
409 }
410
411 template <typename T>
412 ArgParseConfigT<T>& option(const std::string flag, T* output_location, bool firstPass)
413 {
414 auto* output = new ArgParseConfigT<T>(flag, output_location, firstPass);
415 values.push_back(output);
416 return *output;
417 }
418
419 template <typename T>
420 ArgParseConfigT<std::vector<T>>& default_option(std::vector<T>* output_location, bool firstPass)
421 {
422 return option("", output_location, firstPass);
423 }
424
425 void print(std::ostream& out) const
426 {
427 out << "Options:\n";
428
429 int max_length = -1;
430 for(auto* item : values) {
431 int length = 0;
432 item->printable(-1, &length);
433 max_length = std::max(length, max_length);
434 }
435
436 for(auto it = values.begin(); it != values.end(); it++) {
437 ArgParseItem* item = *it;
438 out << item->printable(max_length, nullptr);
439 if(it != values.end() - 1) {
440 out << "\n";
441 }
442 }
443 }
444
445private:
446 void handle_long_flag(int argc, char** argv, int& iarg, bool firstPass)
447 {
448 std::string arg = argv[iarg - 1];
449 std::vector<std::string> flag_args;
450 std::string flag;
451 size_t equals_index = arg.find('=');
452 if(equals_index == std::string::npos) {
453 // flag followed by list of flag_args
454 flag = arg;
455 } else {
456 // = inside flag
457 flag = arg.substr(0, equals_index);
458 }
459
460 ArgParseItem& item = get_item(flag);
461
462 if(equals_index == std::string::npos) {
463 flag_args = argument_list(argc, argv, iarg, item.num_arguments());
464 } else {
465 flag_args.push_back(arg.substr(equals_index + 1));
466 }
467 item.parse(flag, flag_args, firstPass);
468 }
469
470 void handle_short_flag(int argc, char** argv, int& iarg, bool firstPass)
471 {
472 std::string arg = argv[iarg - 1];
473 std::string flag = arg.substr(0, 2);
474 ArgParseItem& item = get_item(flag);
475 if(item.num_arguments() == 0) {
476 // Each character is a boolean flag
477 for(unsigned int ichar = 1; ichar < arg.length(); ichar++) {
478 std::string tmpflag = "-" + arg.substr(ichar, 1);
479 std::vector<std::string> flag_args;
480 get_item(tmpflag).parse(tmpflag, flag_args, firstPass);
481 }
482 } else {
483 if(arg.length() == 2) {
484 // Next arguments passed to the program get passed to the flag.
485 std::vector<std::string> flag_args = argument_list(argc, argv, iarg, item.num_arguments());
486 item.parse(flag, flag_args, firstPass);
487 } else {
488 // Everything past the first character is argument to the flag.
489 std::vector<std::string> flag_args{arg.substr(2)};
490 item.parse(flag, flag_args, firstPass);
491 }
492 }
493 }
494
495 void handle_default_option(int /*argc*/, char** argv, int& iarg, bool firstPass)
496 {
497 std::string arg = argv[iarg - 1];
498 std::vector<std::string> flag_args{arg};
499
500 ArgParseItem& item = get_item(arg);
501 item.parse(arg, flag_args, firstPass);
502 }
503
504 //! Reads arguments into a list until finding one that begins with '-'
505 static std::vector<std::string> argument_list(int argc, char** argv, int& iarg, int max_args)
506 {
507 std::vector<std::string> output;
508 bool read_extra = false;
509 while(iarg < argc && (max_args == -1 || output.size() < static_cast<size_t>(max_args))) {
510 std::string next_arg = argv[iarg++];
511 if(!next_arg.empty() && next_arg.at(0) == '-') {
512 read_extra = true;
513 break;
514 }
515 output.push_back(next_arg);
516 }
517 if(read_extra) {
518 iarg--;
519 }
520 return output;
521 }
522
523 ArgParseItem& get_item(const std::string& flag)
524 {
525 for(auto* val : values) {
526 if(val->matches(flag)) {
527 return *val;
528 }
529 }
530
531 std::ostringstream error;
532 if(flag.at(0) == '-') {
533 error << R"(Unknown option: ")" << flag << R"(")";
534 } else {
535 error << R"(Was passed ")" << flag << R"(" as a non-option argument, when no non-option arguments are allowed)";
536 }
537 throw ParseError(error.str());
538 }
539
540 std::vector<ArgParseItem*> values;
541};
542
543std::ostream& operator<<(std::ostream& out, const ArgParser& val);
544
545#endif /* _ARGPARSER_H_ */
std::ostream & operator<<(std::ostream &out, const ArgParser &val)
Definition ArgParser.cxx:3
#define RESET_COLOR
Definition Globals.h:5
virtual ArgParseConfig & default_value(T value)=0
std::string fDescription
A description for display on the terminal.
Definition ArgParser.h:181
std::string fColour
Colour string to be use for display.
Definition ArgParser.h:184
ArgParseConfig(const ArgParseConfig &)=default
ArgParseConfig(ArgParseConfig &&) noexcept=default
bool matches(const std::string &flag) const override
Definition ArgParser.h:101
std::string flag_name() const override
Definition ArgParser.h:90
virtual ArgParseConfig & description(const std::string &description)
Definition ArgParser.h:111
virtual ArgParseConfig & required()
Definition ArgParser.h:123
std::string printable(int description_column, int *chars_before_desc) const override
Definition ArgParser.h:133
std::vector< std::string > fRawFlags
The flags without the leading dashes.
Definition ArgParser.h:190
bool fRequired
Whether the flag must be supplied.
Definition ArgParser.h:193
virtual ArgParseConfig & colour(const std::string &colour)
Definition ArgParser.h:117
std::vector< std::string > fFlags
The literal flag that is searched for, including leading dashes.
Definition ArgParser.h:187
ArgParseConfig(const std::string &flag_list, bool firstPass)
Definition ArgParser.h:70
bool is_required() const override
Definition ArgParser.h:129
int num_arguments() const override
Definition ArgParser.h:254
void parse_item(const std::vector< std::string > &arguments) override
Definition ArgParser.h:244
ArgParseConfig< bool > & takes_argument()
Definition ArgParser.h:238
ArgParseConfig< bool > & default_value(bool value) override
Definition ArgParser.h:231
ArgParseConfigT(const std::string &flag, bool *output_location, bool firstPass)
Definition ArgParser.h:225
void parse_item(const std::vector< std::string > &arguments) override
Definition ArgParser.h:210
ArgParseConfigT(std::string flag, T *output_location, bool firstPass)
Definition ArgParser.h:199
int num_arguments() const override
Definition ArgParser.h:216
ArgParseConfig< T > & default_value(T value) override
Definition ArgParser.h:204
ArgParseItem(const ArgParseItem &)=default
ArgParseItem(bool firstPass)
Definition ArgParser.h:24
virtual bool matches(const std::string &flag) const =0
virtual std::string flag_name() const =0
virtual void parse_item(const std::vector< std::string > &arguments)=0
bool fPresent
Definition ArgParser.h:63
ArgParseItem(ArgParseItem &&) noexcept=default
void parse(const std::string &name, const std::vector< std::string > &arguments, bool firstPass, bool ignore_num_arguments=false)
Definition ArgParser.h:38
virtual bool is_required() const =0
bool fFirstPass
Definition ArgParser.h:64
virtual std::string printable(int description_column, int *chars_before_desc) const =0
bool is_present() const
Definition ArgParser.h:35
virtual int num_arguments() const =0
void print(std::ostream &out) const
Definition ArgParser.h:425
static std::vector< std::string > argument_list(int argc, char **argv, int &iarg, int max_args)
Reads arguments into a list until finding one that begins with '-'.
Definition ArgParser.h:505
void handle_long_flag(int argc, char **argv, int &iarg, bool firstPass)
Definition ArgParser.h:446
std::vector< ArgParseItem * > values
Definition ArgParser.h:540
void parse(int argc, char **argv, bool firstPass)
Definition ArgParser.h:333
ArgParseConfigT< T > & option(const std::string flag, T *output_location, bool firstPass)
Definition ArgParser.h:412
void parse_file(std::string &filename)
Definition ArgParser.h:365
void handle_default_option(int, char **argv, int &iarg, bool firstPass)
Definition ArgParser.h:495
ArgParseItem & get_item(const std::string &flag)
Definition ArgParser.h:523
ArgParser(ArgParser &&) noexcept=default
ArgParseConfigT< std::vector< T > > & default_option(std::vector< T > *output_location, bool firstPass)
Definition ArgParser.h:420
ArgParser()=default
void handle_short_flag(int argc, char **argv, int &iarg, bool firstPass)
Definition ArgParser.h:470
ArgParser(const ArgParser &)=default
ParseError(const std::string &msg)
Definition ArgParser.h:16
ParseError(const char *msg)
Definition ArgParser.h:15