├── LICENSE
├── README.md
├── quick_arg_parser.hpp
├── quick_arg_parser_test.cpp
└── quick_arg_parser_test_manual.cpp
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Dugy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # Quick Arg Parser
3 | Tired of unwieldy tools like _getopt_ or _argp_? Quick Arg Parser is a single header C++ library for parsing command line arguments and options with minimal amount of code. All you have to do is to instantiate a class inheriting from the `MainArguments` type and access its members.
4 |
5 | ```C++
6 | #include "quick_arg_parser.hpp"
7 |
8 | struct Args : MainArguments {
9 | std::string folder = argument(0) = ".";
10 | int efficiency = option("efficiency", 'e', "The intended efficiency") = 5;
11 | bool verbose = option('v');
12 | std::vector ports = option("port", p);
13 | };
14 |
15 | int main(int argc, char** argv) {
16 | Args args{{argc, argv}};
17 | if (args.verbose) {
18 | std::cout << "Folder " << args.folder << std::endl;
19 | std::cout << "Efficiency " << args.efficiency << std::endl;
20 | }
21 | // ...
22 | ```
23 |
24 | And it can deal with the following call:
25 | ```
26 | ./a.out . --efficiency 9 -v -p 4242,6824
27 | ```
28 |
29 | A longer example of usage is [here](https://github.com/Dugy/quick_arg_parser/blob/main/quick_arg_parser_test_manual.cpp).
30 |
31 | ## More detailed information
32 | The library requires C++11. I have tested it on GCC and Clang. C++11 does not allow aggregate-initialising parent classes, so the child class of `MainArguments` will have to inherit its constructor `using MainArguments::MainArguments`, allowing to create instances without double braces.
33 |
34 | It should work on Windows, but the command-line arguments will be Unix-like (unless explicitly made different, see [below](https://github.com/Dugy/quick_arg_parser#legacy-options)).
35 |
36 | It can parse:
37 | * integer types
38 | * floating point types
39 | * `std::string`
40 | * `std::filesystem::path` (if C++17 is available)
41 | * `std::vector` containing types that it can parse, expecting them to set multiple times (options only) or comma-separated
42 | * `std::unordered_map` indexed by `std::string` and containing types it can parse, expecting to be set as `-pjob=3,work=5 -ptask=7`
43 | * `std::shared_ptr` to types it can parse
44 | * `std::unique_ptr` to types it can parse
45 | * `Optional` (a clone of `std::optional` that can be implicitly converted to it if C++17 is available) of types it can parse
46 | * custom types if a parser for them is added (see [below](https://github.com/Dugy/quick_arg_parser#custom-types))
47 |
48 | A class called `Optional` has to be used instead of `std::optional` (its usage is similar to `std::optional` and can be implicitly converted to it). If the option is missing, it will be empty; it won't compile with default arguments (except `nullptr` and `std::nullopt`).
49 |
50 | Options are declared as follows:
51 | ```C++
52 | TypeName varName = option("long_name", 'l', "Help entry") = "default value";
53 | ```
54 | The long name of the option or the short name of the option can be omitted. No option is mandatory, if the option is not listed, it's zero, an empty string, an empty vector or a null pointer. The value when it's missing can be set by assigning into the `option` call. The help entry will be printed when calling the program with the `--help` option. It can be omitted.
55 |
56 | Boolean options are true when the option is listed and false by default. Groups of boolean arguments can be written together, for example you can write `-qrc` instead of `-q -r -c` in the options. Other options expect a value to follow them.
57 |
58 | Mandatory arguments are declared as follows:
59 | ```C++
60 | TypeName varName = argument(0);
61 | ```
62 | The number is the index of the argument, indexed from zero. The program name is not argument zero.
63 |
64 | Optional arguments are declared as follows:
65 | ```C++
66 | TypeName varName = argument(0) = "default value";
67 | ```
68 |
69 | Anything behind a ` -- ` separator will be considered an argument, no matter how closely it resembles an option.
70 |
71 | If you expect an unlimited number of arguments, you can access them all through `MainArguments`' public variable named `arguments`. The first one in the vector is the first argument after the program name.
72 |
73 | To implement a behaviour where the first argument is actually a command, like with `git`, the arguments have to be parsed separately for each command. Quick Arg Parser does not facilitate this, but it can be used with it by dealing with the first argument through a usual `if`/`else if` group, then constructing `MainArguments` instantiations with `{argc - 1, argv + 1}`.
74 |
75 | ### Automatic help entry
76 | Calling the program with `--help` or `-?` will print the expected number of arguments and all options, also listing their help entries if set.
77 |
78 | The description of the program and arguments can be altered by defining a method with signature `static std::string help(const std::string&)`, which gets the program name as argument and is expected to output the first part of help. To replace the help for options, you need to define a method `static std::string options()`.
79 |
80 | By default, the program exits after printing help. This behaviour can be changed by defining a method with signature `void onHelp()` and it will be called instead.
81 |
82 | ## Automatic version entry
83 | If the class has an `inline static` string member called `version` or a method with signature `static std::string version()`, it will react to options `--version` or `-V` by printing the string and exiting. The automatic exit can be overriden by defining a `void onVersion()` method, which will be called instead.
84 |
85 | ## Validation
86 | You can add a lambda (or a class with overloaded function call operator) that takes the value and returns either a bool indicating if the value is valid or throws an exception if the value is invalid.
87 |
88 | ```C++
89 | int port = option("port", 'p').validator([] (int port) { return port > 1023; });
90 | ```
91 |
92 | ## C++17
93 | If C++17 is available, then the `Optional` type can be converted into `std::optional`. Because of a technical limitation, `std::optional` cannot be used as an argument type. Also, arguments can be deserialised into `std::filesystem::path`.
94 |
95 | ## Legacy options
96 | Sometimes, it's necessary to support options like `-something` or `/something`. This can be done using:
97 | ```C++
98 | int speed = nonstandardOption("-efficiency", 'e');
99 | ```
100 | If this is done, the long option will not be expected to be exactly as listed in the first argument, not preceded by a double dash. If it starts with a single dash, it will not be considered an aggregate of short options.
101 |
102 | For compatibility with atypical command line interfaces, setting an argument `-p` to `1024` can be done not only as `-p 1024`, but also as `-p=1024` or `-p1024`. Also, if it's a long argument named `--port`, it can be written as `--port=1024`. A vector type argument can be alternatively written as multiple settings of the same option, for example `-p 1024 -p1025`.
103 |
104 | ## Custom types
105 | To support your custom class (called `MyType` here), define this somewhere before the definition of the parsing class:
106 | ```C++
107 | namespace QuickArgParserInternals {
108 | template <>
109 | struct ArgConverter {
110 | static MyType makeDefault() {
111 | return {}; // Do something else if it doesn't have a default constructor
112 | }
113 | static MyType deserialise(const std::string& from) {
114 | return MyType::fromString(from); // assuming this is how it's deserialised
115 | }
116 | constexpr static bool canDo = true;
117 | };
118 | } // namespace
119 | ```
120 |
121 | ## Gotchas
122 | This isn't exactly the way C++ was expected to be used, so there might be a few traps for those who use it differently than intended. The class inheriting from `MainArguments` can have other members, but its constructor can be dangerous. Using the constructor to initialise members set through `option` or `argument` will cause the assignment to override the parsing behaviour for those members. The constructor also should not have side effects, because it will be called more than once, not always with the parsed values. Neither of this matters if you use it as showcased.
123 |
124 | Because of consistency, using `Optional` as an argument type does not make that argument optional, you need to set `nullptr` or `std::nullopt` (C++17) as default argument to make it optional.
125 |
--------------------------------------------------------------------------------
/quick_arg_parser.hpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #if __cplusplus > 201402L
9 | #include
10 | #include
11 | #endif
12 |
13 | namespace QuickArgParserInternals {
14 |
15 | struct ArgumentError : std::runtime_error {
16 | using std::runtime_error::runtime_error;
17 | };
18 |
19 | template
20 | struct ArgConverter {
21 | constexpr static bool canDo = false;
22 | };
23 |
24 | template
25 | struct ArgConverter::value>::type> {
26 | static T makeDefault() {
27 | return 0;
28 | }
29 | static T deserialise(const std::string& from) {
30 | return std::stoi(from);
31 | }
32 | constexpr static bool canDo = true;
33 | };
34 |
35 | template
36 | struct ArgConverter::value>::type> {
37 | static T makeDefault() {
38 | return 0;
39 | }
40 | static T deserialise(const std::string& from) {
41 | return std::stof(from);
42 | }
43 | constexpr static bool canDo = true;
44 | };
45 |
46 | template <>
47 | struct ArgConverter {
48 | static std::string makeDefault() {
49 | return "";
50 | }
51 | static std::string deserialise(const std::string& from) {
52 | return from;
53 | }
54 | constexpr static bool canDo = true;
55 | };
56 |
57 | template
58 | struct ArgConverter, void> {
59 | static std::shared_ptr makeDefault() {
60 | return nullptr;
61 | }
62 | static std::shared_ptr deserialise(const std::string& from) {
63 | return std::make_shared(ArgConverter::deserialise(from));
64 | }
65 | constexpr static bool canDo = true;
66 | };
67 |
68 | template
69 | struct ArgConverter, void> {
70 | static std::unique_ptr makeDefault() {
71 | return nullptr;
72 | }
73 | static std::unique_ptr deserialise(const std::string& from) {
74 | return std::unique_ptr(new T(ArgConverter::deserialise(from)));
75 | }
76 | constexpr static bool canDo = true;
77 | };
78 |
79 | template
80 | struct ArgConverter, typename std::enable_if::canDo>::type> {
81 | static std::vector makeDefault() {
82 | return {};
83 | }
84 | static std::vector deserialise(const std::vector& from) {
85 | std::vector made;
86 | for (const std::string& part : from) {
87 | int lastPosition = 0;
88 | for (int i = 0; i < int(part.size()) + 1; i++) {
89 | if (part[i] == ',' || i == int(part.size())) {
90 | made.push_back(ArgConverter::deserialise(
91 | std::string(part.begin() + lastPosition, part.begin() + i)));
92 | lastPosition = i + 1;
93 | }
94 | }
95 | }
96 | return made;
97 | }
98 | constexpr static bool canDo = true;
99 | };
100 |
101 | template
102 | struct ArgConverter, typename std::enable_if::canDo>::type> {
103 | static std::unordered_map makeDefault() {
104 | return {};
105 | }
106 | static std::unordered_map deserialise(const std::vector& from) {
107 | std::unordered_map made;
108 | for (const std::string& part : from) {
109 | int lastPosition = 0;
110 | for (int i = 0; i < int(part.size()) + 1; i++) {
111 | if (part[i] == ',' || i == int(part.size())) {
112 | std::string section = std::string(part.begin() + lastPosition, part.begin() + i);
113 | auto separator = section.find('=');
114 | if (separator == std::string::npos)
115 | throw ArgumentError("Argument is expected to be a comma separated list of name-value pairs separated by '='");
116 | made[section.substr(0, separator)] = ArgConverter::deserialise(section.substr(separator + 1));
117 | lastPosition = i + 1;
118 | }
119 | }
120 | }
121 | return made;
122 | }
123 | constexpr static bool canDo = true;
124 | };
125 |
126 | template
127 | class Optional {
128 | alignas(T) std::array _contents;
129 | bool _exists = false;
130 | void clear() {
131 | if (_exists)
132 | operator*().~T();
133 | }
134 | public:
135 | Optional() = default;
136 | Optional(std::nullptr_t) {}
137 | #if __cplusplus > 201402L
138 | Optional(std::nullopt_t) {}
139 | #endif
140 | Optional(const Optional& other) : _exists(other._exists) {
141 | if (_exists)
142 | new (operator->()) T(*other);
143 | }
144 | Optional(Optional&& other) : _exists(other._exists) {
145 | if (_exists)
146 | new (operator->()) T(*other);
147 | }
148 | T& operator=(const T& other) {
149 | clear();
150 | if (_exists) {
151 | operator*() = other;
152 | } else
153 | new (_contents.data()) T(other);
154 | _exists = true;
155 | return operator*();
156 | }
157 | T& operator=(T&& other) {
158 | clear();
159 | if (_exists)
160 | operator*() = other;
161 | else
162 | new (_contents.data()) T(other);
163 | _exists = true;
164 | return operator*();
165 | }
166 | void operator=(std::nullptr_t) {
167 | clear();
168 | _exists = false;
169 | }
170 | #if __cplusplus > 201402L
171 | void operator=(std::nullopt_t) {
172 | operator=(nullptr);
173 | }
174 | #endif
175 | T& operator*() {
176 | return *reinterpret_cast(_contents.data());
177 | }
178 | const T& operator*() const {
179 | return *reinterpret_cast(_contents.data());
180 | }
181 | T* operator->() {
182 | return reinterpret_cast(_contents.data());
183 | }
184 | const T* operator->() const {
185 | return reinterpret_cast(_contents.data());
186 | }
187 | operator bool() const {
188 | return _exists;
189 | }
190 | #if __cplusplus > 201402L
191 | operator std::optional() {
192 | if (_exists)
193 | return std::optional(operator*());
194 | else
195 | return std::nullopt;
196 | }
197 | #endif
198 | ~Optional() {
199 | clear();
200 | }
201 | };
202 |
203 | template
204 | struct ArgConverter, void> {
205 | static Optional makeDefault() {
206 | return nullptr;
207 | }
208 | static Optional deserialise(const std::string& from) {
209 | Optional made;
210 | made = ArgConverter::deserialise(from);
211 | return made;
212 | }
213 | constexpr static bool canDo = true;
214 | };
215 |
216 | #if __cplusplus > 201402L
217 | template <>
218 | struct ArgConverter {
219 | static std::filesystem::path makeDefault() {
220 | return {};
221 | }
222 | static std::filesystem::path deserialise(const std::string& from) {
223 | return std::filesystem::path(from);
224 | }
225 | constexpr static bool canDo = true;
226 | };
227 | #endif
228 |
229 |
230 | template
231 | struct Demultiplexer {
232 | static T deserialise(const std::vector& multiplexed) {
233 | return ArgConverter::deserialise(multiplexed);
234 | }
235 | };
236 |
237 | template
238 | struct Demultiplexer::deserialise(std::declval()))>::value>::type> {
239 | static T deserialise(const std::vector& multiplexed) {
240 | if (multiplexed.size() > 1)
241 | throw ArgumentError("Argument was not expected to appear more than once (" + multiplexed[1] + " is excessive)");
242 | return ArgConverter::deserialise(multiplexed[0]);
243 | }
244 | };
245 |
246 | template
247 | struct HelpProvider{
248 | template
249 | static std::string get(const F& ifAbsent, const std::string& programName) {
250 | return ifAbsent(programName);
251 | }
252 | };
253 |
254 | template
255 | struct HelpProvider()))>::value>::type> {
256 | template
257 | static std::string get(const F&, const std::string& programName) {
258 | return T::help(programName);
259 | }
260 | };
261 |
262 | template
263 | struct OnHelpCallback {
264 | template
265 | static void on(T*, const F& ifAbsent) {
266 | ifAbsent();
267 | }
268 | };
269 |
270 | template
271 | struct OnHelpCallback().onHelp())>::value>::type> {
272 | template
273 | static void on(T* instance, const F&) {
274 | instance->onHelp();
275 | }
276 | };
277 |
278 | template
279 | struct HasHelpOptionsProvider : std::false_type {};
280 |
281 | template
282 | struct HasHelpOptionsProvider::value>::type>
284 | : std::true_type {};
285 |
286 |
287 | template
288 | struct VersionPrinter {
289 | static bool print() {
290 | return false;
291 | }
292 | };
293 |
294 | template
295 | struct VersionPrinter::value>::type> {
296 | static bool print() {
297 | std::cout << T::version << std::endl;
298 | return true;
299 | }
300 | };
301 |
302 | template
303 | struct VersionPrinter::value>::type> {
304 | static bool print() {
305 | std::cout << T::version() << std::endl;
306 | return true;
307 | }
308 | };
309 |
310 | template
311 | struct OnVersionCallback {
312 | template
313 | static void on(T*, const F& ifAbsent) {
314 | ifAbsent();
315 | }
316 | };
317 |
318 | template
319 | struct OnVersionCallback().onVersion())>::value>::type> {
320 | template
321 | static void on(T* instance, const F&) {
322 | instance->onVersion();
323 | }
324 | };
325 |
326 | #if _MSC_VER && !__INTEL_COMPILER
327 | // MSVC likes converting const char* literals to initialiser lists and causing ambiguous calls with it
328 | template
329 | struct IsInitializerList : std::false_type {};
330 |
331 | template
332 | struct IsInitializerList> : std::true_type {};
333 |
334 | template
335 | struct StringFilterOk : std::false_type {};
336 |
337 | template
338 | struct StringFilterOk::value && !IsInitializerList::value) || std::is_arithmetic::value
339 | || std::is_floating_point::value || std::is_enum::value>::type> : std::true_type {};
340 | #endif
341 |
342 | struct DummyValidator{};
343 |
344 | template
345 | struct ValidatorUser {
346 | template
347 | static bool useValidator(const Validator& validator, const Value& value) {
348 | return true;
349 | }
350 | };
351 |
352 | template
353 | struct ValidatorUser::value>::type> {
354 | template ()(std::declval()))>::value>::type* = nullptr>
355 | static bool useValidator(const Validator& validator, const Value& value) {
356 | return validator(value);
357 | }
358 | template ()(std::declval()))>::value>::type* = nullptr>
359 | static bool useValidator(const Validator& validator, const Value& value) {
360 | validator(value);
361 | return true;
362 | }
363 | };
364 |
365 | } // namespace
366 |
367 | template
368 | class MainArguments {
369 | std::string _programName;
370 | std::vector _argv;
371 |
372 | enum InitialisationStep {
373 | UNINITIALISED,
374 | INITIALISING,
375 | INITIALISED
376 | };
377 | struct Singleton {
378 | std::stringstream helpPreface;
379 | std::stringstream help;
380 | std::vector> nullarySwitches;
381 | std::vector> unarySwitches;
382 | std::vector confusingSwitches; // nonstandard switches starting with a single dash
383 | int argumentCountMin = 0;
384 | int argumentCountMax = 0;
385 | InitialisationStep initialisationState = UNINITIALISED;
386 | };
387 | static Singleton& singleton() {
388 | static Singleton instance;
389 | return instance;
390 | }
391 |
392 | using DummyValidator = QuickArgParserInternals::DummyValidator;
393 | public:
394 | template using Optional = QuickArgParserInternals::Optional;
395 | MainArguments() = default;
396 | MainArguments(int argc, char** argv) : _programName(argv[0]), _argv(argv + 1, argv + argc) {
397 | using namespace QuickArgParserInternals;
398 | if (singleton().initialisationState == UNINITIALISED) {
399 | // When first created, create temporarily another instance to explore what are the members
400 | singleton().initialisationState = INITIALISING;
401 |
402 | Child investigator;
403 | // This will fill the static variables
404 | singleton().helpPreface << QuickArgParserInternals::HelpProvider::get([] (const std::string& programName) {
405 | return programName + " takes between " + std::to_string(singleton().argumentCountMin) + " and " +
406 | std::to_string(singleton().argumentCountMax) + " arguments, plus these options:";
407 | }, _programName);
408 |
409 | singleton().initialisationState = INITIALISED;
410 | }
411 | if (singleton().initialisationState == INITIALISED) {
412 | bool switchesEnabled = true;
413 | auto isListedAsChar = [] (const char arg, const std::vector>& switches) {
414 | for (const auto& it : switches) {
415 | if (it.second == arg)
416 | return true;
417 | }
418 | return false;
419 | };
420 | auto isListedAsString = [] (const std::string& arg, const std::vector>& switches, bool unary, bool& skipsNext) {
421 | for (const auto& it : switches) {
422 | for (int i = 0; i < int(it.first.size()); i++) {
423 | if (arg[i] != it.first[i])
424 | goto noMatch;
425 | }
426 | if (arg.size() == it.first.size()) {
427 | skipsNext = true;
428 | return true;
429 | }
430 | if (unary && arg[it.first.size()] == '=') {
431 | skipsNext = false;
432 | return true;
433 | }
434 | noMatch:;
435 | }
436 | return false;
437 | };
438 | auto printHelp = [this] () {
439 | std::cout << singleton().helpPreface.str() << std::endl;
440 | std::cout << singleton().help.str() << std::endl;
441 |
442 | QuickArgParserInternals::OnHelpCallback::on(static_cast(this), [] { std::exit(0); });
443 | };
444 | auto printVersion = [this] () {
445 | if (!QuickArgParserInternals::VersionPrinter::print())
446 | return false; // Returns false if the version is not known, leading to no action if found
447 |
448 | QuickArgParserInternals::OnVersionCallback::on(static_cast(this), [] { std::exit(0); });
449 | return true;
450 | };
451 |
452 | // Collect program arguments (as opposed to switches) and validate everything
453 | for (int i = 0; i < int(_argv.size()); i++) {
454 | if (switchesEnabled) {
455 | if (_argv[i] == "--help") {
456 | printHelp();
457 | goto nextArg;
458 | }
459 | if (_argv[i] == "--version") {
460 | if (printVersion())
461 | goto nextArg;
462 | }
463 | if (_argv[i] == "--") {
464 | switchesEnabled = false;
465 | goto nextArg;
466 | }
467 | bool skipsNext = false;
468 | if (isListedAsString(_argv[i], singleton().unarySwitches, true, skipsNext)) {
469 | if (skipsNext)
470 | i++; // The next argument is part of the switch
471 | goto nextArg;
472 | } else if (isListedAsString(_argv[i], singleton().nullarySwitches, false, skipsNext)) {
473 | goto nextArg;
474 | }
475 |
476 |
477 | if (_argv[i][0] == '-') {
478 | if (_argv[i][1] == '-')
479 | throw ArgumentError("Unknown switch " + _argv[i]);
480 |
481 | // Starts with -
482 | if (_argv[i].size() == 2) {
483 | // Is an argument of type -x
484 | if (_argv[i][1] == '?') {
485 | printHelp();
486 | goto nextArg;
487 | }
488 | if (_argv[i][1] == 'V') {
489 | if (printVersion())
490 | goto nextArg;
491 | }
492 | }
493 |
494 | // Some validations that all massed single letter switches
495 | for (int j = 1; j < int(_argv[i].size()); j++) {
496 | if (isListedAsChar(_argv[i][j], singleton().unarySwitches)) {
497 | if (j == int(_argv[i].size()) - 1) {
498 | i++; // The next argument is part of the switch
499 | }
500 | goto nextArg;
501 | }
502 | if (!isListedAsChar(_argv[i][j], singleton().nullarySwitches)) {
503 | throw ArgumentError(std::string("Unknown switch ") + _argv[i][j]);
504 | }
505 | }
506 | goto nextArg;
507 | }
508 | }
509 |
510 | // Is not a switch, continue was not used
511 | arguments.push_back(_argv[i]);
512 |
513 | nextArg:;
514 | }
515 |
516 | if (int(arguments.size()) < singleton().argumentCountMin)
517 | throw ArgumentError("Expected at least " + std::to_string(singleton().argumentCountMin)
518 | + " arguments, got " + std::to_string(arguments.size()));
519 | if (int(arguments.size()) > singleton().argumentCountMax)
520 | throw ArgumentError("Expected at most " + std::to_string(singleton().argumentCountMax)
521 | + " arguments, got " + std::to_string(arguments.size()));
522 | }
523 | }
524 | std::vector arguments;
525 |
526 | private:
527 |
528 | std::vector findOption(const std::string& argument, char shortcut) const {
529 | // This returns hogwash if the option is bool, but in that case, we only care that the vector is not empty
530 | std::vector collected;
531 | auto matches = [&] (const std::string& matched, int argument) {
532 | for (int i = 0; i < int(matched.size()); i++) {
533 | if (matched[i] != _argv[argument][i])
534 | return false;
535 | }
536 | return matched.size() == _argv[argument].size() || _argv[argument][matched.size()] == '=';
537 | };
538 |
539 | for (int i = 0; i < int(_argv.size()); i++) {
540 | // Look for shortcut, end of string means no shortcut
541 | if (shortcut != '\0') {
542 | // Skip this if it is a strange switch starting with a single dash
543 | for (const auto& it : singleton().confusingSwitches)
544 | if (matches(it, i))
545 | goto skipThisOne;
546 |
547 | if (_argv[i][0] == '-' && _argv[i][1] != '-') {
548 | for (int j = 1; _argv[i][j] != '\0'; j++) {
549 | if (_argv[i][j] == shortcut) {
550 | if (_argv[i][j + 1] == '\0') // Last letter, argument follows
551 | collected.push_back(_argv[std::min(i + 1, _argv.size() - 1)]);
552 | else if (_argv[i][j + 1] == '=') // Argument value not sperated
553 | collected.push_back(_argv[i].substr(j + 2));
554 | else
555 | collected.push_back(_argv[i].substr(j + 1));
556 | }
557 |
558 | for (auto& it : singleton().unarySwitches)
559 | if (it.second == _argv[i][j])
560 | goto skipThisOne; // It is a switch followed by arguments
561 | }
562 | }
563 | if (_argv[i] == "--") {
564 | break;
565 | }
566 |
567 | skipThisOne:;
568 | }
569 |
570 | // Look for full argument name, empty means no full argument name
571 | if (!argument.empty()) {
572 | if (matches(argument, i)) {
573 | if (_argv[i].size() > argument.size() && _argv[i][argument.size()] == '=')
574 | collected.push_back(_argv[i].substr(argument.size() + 1));
575 | else
576 | collected.push_back(_argv[std::min(i + 1, _argv.size() - 1)]);
577 | }
578 | }
579 | }
580 |
581 | return collected;
582 | }
583 |
584 | protected:
585 | template
586 | class GrabberBase {
587 | protected:
588 | const std::string name;
589 | const MainArguments* parent;
590 | const char shortcut;
591 | const std::string help;
592 | Validator validator;
593 | GrabberBase(const MainArguments* parent, const std::string& name, char shortcut, const std::string& help, const Validator& validator)
594 | : name(name), parent(parent), shortcut(shortcut), help(help), validator(validator) {}
595 |
596 | void addHelpEntry() const {
597 | if (QuickArgParserInternals::HasHelpOptionsProvider::value)
598 | return;
599 |
600 | if (shortcut != '\0')
601 | parent->singleton().help << '-' << shortcut;
602 | parent->singleton().help << '\t';
603 | if (!name.empty())
604 | parent->singleton().help << name;
605 | parent->singleton().help << "\t " << help << std::endl;
606 | }
607 | public:
608 | operator bool() const {
609 | if (parent->singleton().initialisationState == INITIALISING) {
610 | parent->singleton().nullarySwitches.push_back(std::make_pair(name, shortcut));
611 | addHelpEntry();
612 | return false;
613 | }
614 | return !parent->findOption(name, shortcut).empty();
615 | }
616 |
617 | operator std::vector() const {
618 | if (parent->singleton().initialisationState == INITIALISING) {
619 | parent->singleton().nullarySwitches.push_back(std::make_pair(name, shortcut));
620 | addHelpEntry();
621 | return std::vector();
622 | }
623 | return std::vector(parent->findOption(name, shortcut).size(), true);
624 | }
625 |
626 | #if _MSC_VER && !__INTEL_COMPILER
627 | template ::value>::type* = nullptr>
628 | #else
629 | template
630 | #endif
631 | T getOption(T defaultValue) const {
632 | if (parent->singleton().initialisationState == INITIALISING) {
633 | parent->singleton().unarySwitches.push_back(std::make_pair(name, shortcut));
634 | addHelpEntry();
635 | return defaultValue;
636 | }
637 |
638 | auto validate = [&] (const T& value) {
639 | if (!QuickArgParserInternals::ValidatorUser::useValidator(validator, value)) {
640 | throw QuickArgParserInternals::ArgumentError("Invalid value of argument " + name);
641 | }
642 | };
643 | const auto found = parent->findOption(name, shortcut);
644 |
645 | if (!found.empty()) {
646 | auto obtained = QuickArgParserInternals::Demultiplexer::deserialise(found);
647 | validate(obtained);
648 | return obtained;
649 | }
650 | validate(defaultValue);
651 | return defaultValue;
652 | }
653 | };
654 |
655 | template
656 | class GrabberDefaulted : public GrabberBase {
657 | Default defaultValue;
658 | using Base = GrabberBase;
659 | public:
660 | GrabberDefaulted(const MainArguments* parent, const std::string& name, char shortcut,
661 | const std::string& help, Validator validator, Default defaultValue)
662 | : Base(parent, name, shortcut, help, validator), defaultValue(defaultValue) {}
663 | #if _MSC_VER && !__INTEL_COMPILER
664 | template ::value
665 | && !std::is_same::value>::type* = nullptr>
666 | #else
667 | template ::value>::type* = nullptr>
668 | #endif
669 | operator T() const {
670 | static_assert(QuickArgParserInternals::ArgConverter::canDo, "Cannot deserialise into this type");
671 | return Base::template getOption(defaultValue);
672 | }
673 | };
674 |
675 | template
676 | class Grabber : public GrabberBase {
677 | friend class MainArguments;
678 | using Base = GrabberBase;
679 | public:
680 | using Base::GrabberBase;
681 | template
682 | GrabberDefaulted operator=(Default defaultValue) {
683 | return {Base::parent, Base::name, Base::shortcut, Base::help, Base::validator, defaultValue};
684 | }
685 |
686 | #if _MSC_VER && !__INTEL_COMPILER
687 | template ::value>::type* = nullptr>
688 | #else
689 | template
690 | #endif
691 | operator T() const {
692 | static_assert(QuickArgParserInternals::ArgConverter::canDo, "Cannot deserialise into this type");
693 | return Base::template getOption(QuickArgParserInternals::ArgConverter::makeDefault());
694 | }
695 |
696 | template
697 | Grabber validator(const NewValidator& newValidator) {
698 | return {Base::parent, Base::name, Base::shortcut,
699 | Base::help, newValidator};
700 | }
701 | };
702 |
703 | Grabber option(const std::string& name, char shortcut = '\0', const std::string& help = "") {
704 | return Grabber(this, "--" + name, shortcut, help, DummyValidator{});
705 | }
706 | Grabber option(char shortcut = '\0', const std::string& help = "") {
707 | return Grabber(this, "", shortcut, help, DummyValidator{});
708 | }
709 | Grabber nonstandardOption(const std::string& name, char shortcut = '\0', const std::string& help = "") {
710 | if (singleton().initialisationState == INITIALISING && name[0] == '-' && name[1] != '-')
711 | singleton().confusingSwitches.push_back(name);
712 |
713 | return Grabber(this, name, shortcut, help, DummyValidator{});
714 | }
715 |
716 | template
717 | class ArgGrabberBase {
718 | protected:
719 | const MainArguments* parent;
720 | const int index;
721 | Validator validator;
722 | template
723 | void validate(const Value& value) const {
724 | if (!QuickArgParserInternals::ValidatorUser::useValidator(validator, value)) {
725 | throw QuickArgParserInternals::ArgumentError("Invalid value of argument " + std::to_string(index));
726 | }
727 | }
728 | public:
729 | ArgGrabberBase(const MainArguments* parent, int index, const Validator& validator) : parent(parent), index(index), validator(validator) {}
730 | };
731 |
732 | template
733 | class ArgGrabberDefaulted : public ArgGrabberBase {
734 | Default defaultValue;
735 | using Base = ArgGrabberBase;
736 | public:
737 | ArgGrabberDefaulted(const MainArguments* parent, int index, const Validator& validator, Default defaultValue) :
738 | Base(parent, index, validator), defaultValue(defaultValue) {}
739 |
740 | #if _MSC_VER && !__INTEL_COMPILER
741 | template ::value>::type* = nullptr>
742 | #else
743 | template
744 | #endif
745 | operator T() const {
746 | static_assert(QuickArgParserInternals::ArgConverter::canDo, "Cannot deserialise into this type");
747 | if (Base::parent->singleton().initialisationState == INITIALISING) {
748 | Base::parent->singleton().argumentCountMax =
749 | std::max(Base::parent->singleton().argumentCountMax, Base::index + 1);
750 | return QuickArgParserInternals::ArgConverter::makeDefault();
751 | }
752 | if (Base::index >= int(Base::parent->arguments.size())) {
753 | Base::validate(defaultValue);
754 | return defaultValue;
755 | }
756 | auto obtained = QuickArgParserInternals::ArgConverter::deserialise(
757 | Base::parent->arguments[Base::index]);
758 | Base::validate(obtained);
759 | return obtained;
760 | }
761 | };
762 |
763 | template
764 | struct ArgGrabber : public ArgGrabberBase {
765 | using Base = ArgGrabberBase;
766 | using Base::ArgGrabberBase;
767 | template
768 | ArgGrabberDefaulted operator=(Default defaultValue) const {
769 | return ArgGrabberDefaulted{Base::parent, Base::index, Base::validator, defaultValue};
770 | }
771 |
772 | #if _MSC_VER && !__INTEL_COMPILER
773 | template ::value>::type* = nullptr>
774 | #else
775 | template
776 | #endif
777 | operator T() const {
778 | static_assert(QuickArgParserInternals::ArgConverter::canDo, "Cannot deserialise into this type");
779 | if (Base::parent->singleton().initialisationState == INITIALISING) {
780 | Base::parent->singleton().argumentCountMin =
781 | std::max(Base::parent->singleton().argumentCountMin, Base::index + 1);
782 | Base::parent->singleton().argumentCountMax =
783 | std::max(Base::parent->singleton().argumentCountMax, Base::index + 1);
784 | return QuickArgParserInternals::ArgConverter::makeDefault();
785 | }
786 | auto obtained = QuickArgParserInternals::ArgConverter::deserialise(Base::parent->arguments[Base::index]);
787 | Base::validate(obtained);
788 | return obtained;
789 | }
790 |
791 | template
792 | ArgGrabber validator(const NewValidator& newValidator) {
793 | return ArgGrabber{Base::parent, Base::index, newValidator};
794 | }
795 | };
796 |
797 | ArgGrabber argument(int index) {
798 | return ArgGrabber{this, index, DummyValidator{}};
799 | }
800 | };
801 |
--------------------------------------------------------------------------------
/quick_arg_parser_test.cpp:
--------------------------------------------------------------------------------
1 | //usr/bin/g++ --std=c++11 -Wall $0 -o ${o=`mktemp`} && exec $o $*
2 | #include "quick_arg_parser.hpp"
3 |
4 | struct Input : MainArguments {
5 | using MainArguments::MainArguments; // Not necessary in C++17
6 | bool verbose = option("verbose", 'V');
7 | int port = option("port", 'p');
8 | int secondaryPort = option("port2", 'P') = 999;
9 | int parts = argument(0) = 1;
10 | Optional logPort = option("logPort", 'l');
11 | };
12 |
13 | struct Input2 : MainArguments {
14 | using MainArguments::MainArguments;
15 | std::vector ports = option("ports", 'p');
16 | std::shared_ptr downloads = option("downloads", 'd', "The number of downloads");
17 | std::unique_ptr uploads = option("uploads", 'u');
18 | std::string file = argument(0);
19 | std::string logFile = argument(1) = "log.log";
20 | std::string debugLogFile = argument(2) = "debug.log";
21 | Optional logAddress = option("logAddress", 'l');
22 | int legacyOption = nonstandardOption("-line").validator([] (int a) { return a < 10; }) = 0;
23 | std::string legacyOption2 = nonstandardOption("/tool") = "none";
24 |
25 | static std::string help(const std::string& programName) {
26 | return "Usage\n" + programName + " FILE LOG DEBUGLOG";
27 | }
28 | void onHelp() {
29 | std::cout << "Help called" << std::endl;
30 | }
31 |
32 | static std::string version() {
33 | return "3.3.7";
34 | }
35 | void onVersion() {}
36 | };
37 |
38 | struct Input3 : MainArguments {
39 | using MainArguments::MainArguments;
40 | std::vector ports = option("ports", 'p');
41 | bool enableHorns = option('h');
42 | std::string file = argument(0);
43 | bool enableHooves = option('H');
44 | bool loud = option("LOUD");
45 | std::string target = argument(1) = "a.out";
46 |
47 | void onHelp() {}
48 | static std::string options() {
49 | return "Don't use the options, they suck\n";
50 | }
51 | static std::string version;
52 | void onVersion() {}
53 | };
54 | std::string Input3::version = "1.0"; // Not necessary in C++17
55 |
56 | struct Input4 : MainArguments {
57 | using MainArguments::MainArguments;
58 | std::vector outputConnectors = option("connectors", 'c');
59 | std::string genre = option("genre", 'g') = "metal";
60 | float masterVolume = option("master_volume", 'v') = 100;
61 | std::unordered_map speakerVolumes = option("speaker_volumes", 's');
62 | bool muteNeighbours = option("mute_neighbours", 'm');
63 | bool jamPhones = option("jam_phones", 'j');
64 | std::string path = argument(0) = ".";
65 | };
66 |
67 | struct Input5 : MainArguments {
68 | using MainArguments::MainArguments; // Not necessary in C++17
69 | std::vector verbose = option("verbose", 'V');
70 | bool extra = option("extra", 'e');
71 | int port = option("port", 'p');
72 | int secondaryPort = option("port2", 'P') = 999;
73 | int parts = argument(0) = 1;
74 | Optional logPort = option("logPort", 'l');
75 | };
76 |
77 | template
78 | T constructFromString(std::string args) {
79 | std::vector segments;
80 | segments.push_back(&args[0]);
81 | for (int i = 0; i < int(args.size()); i++) {
82 | if (args[i] == ' ') {
83 | segments.push_back(&args[i + 1]);
84 | args[i] = '\0';
85 | }
86 | }
87 | return T{int(segments.size()), &segments[0]};
88 | }
89 |
90 | int errors = 0;
91 |
92 | template
93 | void verify(T1 first, T2 second) {
94 | if (first != second) {
95 | std::cout << first << " and " << second << " were supposed to be equal" << std::endl;
96 | errors++;
97 | }
98 | };
99 |
100 | int main() {
101 |
102 | std::cout << "First input" << std::endl;
103 | Input t1 = constructFromString("super_program -V --port 666 -- 3");
104 | verify(t1.verbose, true);
105 | verify(t1.port, 666);
106 | verify(t1.secondaryPort, 999);
107 | verify(t1.parts, 3);
108 | verify(bool(t1.logPort), false);
109 |
110 | std::cout << "Second input" << std::endl;
111 | Input2 t2 = constructFromString("mega_program -p 23,80,442 -u 3 -p 778 --help --version -line 2 --logAddress 127.0.0.1 -- -lame_file_name log");
112 | verify(int(t2.ports.size()), 4);
113 | verify(t2.file, "-lame_file_name");
114 | verify(bool(t2.downloads), false);
115 | verify(bool(t2.uploads), true);
116 | verify(*t2.uploads, 3);
117 | verify(t2.logFile, "log");
118 | verify(t2.debugLogFile, "debug.log");
119 | verify(bool(t2.logAddress), true);
120 | if (bool(t2.logAddress))
121 | verify(*t2.logAddress, "127.0.0.1");
122 | verify(t2.legacyOption, 2);
123 | verify(t2.legacyOption2, "none");
124 |
125 | std::cout << "Third input" << std::endl;
126 | Input3 t3 = constructFromString("supreme_program file -hH -? -V --LOUD target");
127 | verify(int(t3.ports.size()), 0);
128 | verify(t3.file, "file");
129 | verify(t3.enableHooves, true);
130 | verify(t3.enableHorns, true);
131 | verify(t3.target, "target");
132 | verify(t3.loud, true);
133 |
134 | std::cout << "Fourth input" << std::endl;
135 | Input4 t4 = constructFromString("ultimate_program -v110 -jc=5 --connectors=8 -mc 10 -sleft=110 -sright=105,bottom=115 -gpunk ~/Music");
136 | verify(int(t4.outputConnectors.size()), 3);
137 | if (int(t4.outputConnectors.size()) == 3) {
138 | verify(t4.outputConnectors[0], 5);
139 | verify(t4.outputConnectors[1], 8);
140 | verify(t4.outputConnectors[2], 10);
141 | }
142 | verify(t4.genre, "punk");
143 | verify(t4.masterVolume, 110);
144 | verify(t4.muteNeighbours, true);
145 | verify(t4.jamPhones, true);
146 | verify(t4.path, "~/Music");
147 |
148 | std::cout << "Fifth input" << std::endl;
149 | Input5 t5 = constructFromString("super_program -VV -VeVV --port 666 -- 3");
150 | verify(int(t5.verbose.size()), 5);
151 | verify(t5.extra, true);
152 | verify(t5.port, 666);
153 | verify(t5.secondaryPort, 999);
154 | verify(t5.parts, 3);
155 | verify(bool(t5.logPort), false);
156 |
157 | std::cout << "Errors: " << errors << std::endl;
158 | }
159 |
--------------------------------------------------------------------------------
/quick_arg_parser_test_manual.cpp:
--------------------------------------------------------------------------------
1 | //usr/bin/g++ --std=c++17 -Wall $0 -o ${o=`mktemp`} && exec $o $*
2 | #include "quick_arg_parser.hpp"
3 |
4 | struct Input : MainArguments {
5 | bool verbose = option("verbose", 'v');
6 | bool shorten = option("shorten", 's');
7 | int port = option("port", 'p').validator([] (int port) { return port > 1023; });
8 | float timeout = option("timeout", 't', "Timeout in seconds");
9 | Optional debugLog = option("debug_log", 'd');
10 | std::unordered_map priorities = option("priorities", 'P');
11 |
12 | std::filesystem::path file = argument(0);
13 | std::filesystem::path secondaryFile = argument(1) = "aux.out";
14 | int rotation = argument(2).validator([] (int rotation) { return rotation > 0; }) = 2;
15 |
16 | inline static std::string version = "1.0";
17 | static std::string help(const std::string& programName) {
18 | return "Usage:\n" + programName + " FILE (SECONDARY_FILE)";
19 | }
20 | };
21 |
22 | int main(int argc, char** argv) {
23 | Input in{{argc, argv}};
24 |
25 | std::cout << "Arguments interpreted:" << std::endl;
26 | std::cout << "Verbose: " << in.verbose << std::endl;
27 | std::cout << "Shorten: " << in.shorten << std::endl;
28 | std::cout << "Port: " << in.port << std::endl;
29 | std::cout << "Timeout: " << in.timeout << std::endl;
30 | std::optional debugLog = in.debugLog;
31 | if (debugLog)
32 | std::cout << "DebugLog " << *debugLog << std::endl;
33 | std::cout << "File: " << in.file << std::endl;
34 | std::cout << "Secondary file: " << in.secondaryFile << std::endl;
35 | std::cout << "Rotation: " << in.rotation << std::endl;
36 |
37 | for (auto& it : in.priorities) {
38 | std::cout << "Priorities[" << it.first << "]=" << it.second << std::endl;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------