Commit 494a65f8120145d5e923c8c62668d9e4d1f414d3

Authored by Henry Fredrick Schreiner
1 parent c7dadfc5

Dropping Make syntax, moving to pointers from combiners, structured errors.

This mostly is cleanup, with fewer alternative methods and more standard syntax, avoiding the use of the namespace all the time. Validators are simpler and are added through `->check()`.

Defaults are automatic, and can be specified with a final arg to the options.

Expected arguments and required arguments are now accessed through a pointer to option.

Option now can be checked as a bool to see if the argument was passed.

Errors have better organisation.
CMakeLists.txt
... ... @@ -20,14 +20,15 @@ add_compile_options(-pedantic -Wall -Wextra)
20 20 add_library(CLI INTERFACE)
21 21 target_include_directories(CLI INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
22 22  
  23 +file(GLOB CLI_headers "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/*")
  24 +
23 25 # Single file test
24 26 option(CLI_SINGLE_FILE "Generate a single header file (and test)" ${CUR_PROJ})
25 27 if(CLI_SINGLE_FILE)
26 28 file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include")
27 29 add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
28 30 COMMAND python "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp"
29   - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp"
30   - IMPLICIT_DEPENDS CXX "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp"
  31 + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI_headers}
31 32 )
32 33 add_custom_target(generate_cli_single_file
33 34 DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp")
... ...
examples/try.cpp
... ... @@ -12,7 +12,7 @@ int main (int argc, char** argv) {
12 12 app.add_flag("-c,--count", count, "Counter");
13 13  
14 14 double value = 3.14;
15   - app.add_option("-d,--double", value, "Some Value", CLI::Default);
  15 + app.add_option("-d,--double", value, "Some Value", false);
16 16  
17 17 try {
18 18 app.run(argc, argv);
... ...
include/CLI/App.hpp
... ... @@ -20,9 +20,7 @@
20 20 #include "CLI/TypeTools.hpp"
21 21 #include "CLI/StringTools.hpp"
22 22 #include "CLI/Split.hpp"
23   -#include "CLI/Combiner.hpp"
24 23 #include "CLI/Option.hpp"
25   -#include "CLI/Value.hpp"
26 24  
27 25 namespace CLI {
28 26  
... ... @@ -92,8 +90,6 @@ public:
92 90 }
93 91  
94 92  
95   - //------------ ADD STYLE ---------//
96   -
97 93 /// Add an option, will automatically understand the type for common types.
98 94 /** To use, create a variable with the expected type, and pass it in after the name.
99 95 * After start is called, you can use count to see if the value was passed, and
... ... @@ -111,9 +107,9 @@ public:
111 107 std::string name,
112 108 callback_t callback,
113 109 std::string description="",
114   - detail::Combiner opts=Validators
  110 + bool defaulted=true
115 111 ) {
116   - Option myopt{name, description, opts, callback};
  112 + Option myopt{name, description, callback, defaulted};
117 113 if(std::find(std::begin(options), std::end(options), myopt) == std::end(options))
118 114 options.push_back(myopt);
119 115 else
... ... @@ -128,12 +124,10 @@ public:
128 124 std::string name,
129 125 T &variable, ///< The variable to set
130 126 std::string description="",
131   - detail::Combiner opts=Validators
  127 + bool defaulted=true
132 128 ) {
133 129  
134 130  
135   - if(opts.num!=1)
136   - throw IncorrectConstruction("Must have Args(1) or be a vector.");
137 131 CLI::callback_t fun = [&variable](CLI::results_t res){
138 132 if(res.size()!=1) {
139 133 return false;
... ... @@ -144,9 +138,9 @@ public:
144 138 return detail::lexical_cast(res[0][0], variable);
145 139 };
146 140  
147   - Option* retval = add_option(name, fun, description, opts);
  141 + Option* retval = add_option(name, fun, description, defaulted);
148 142 retval->typeval = detail::type_name<T>();
149   - if(opts.defaulted) {
  143 + if(defaulted) {
150 144 std::stringstream out;
151 145 out << variable;
152 146 retval->defaultval = out.str();
... ... @@ -160,11 +154,9 @@ public:
160 154 std::string name,
161 155 std::vector<T> &variable, ///< The variable vector to set
162 156 std::string description="",
163   - detail::Combiner opts=Args
  157 + bool defaulted=true
164 158 ) {
165 159  
166   - if(opts.num==0)
167   - throw IncorrectConstruction("Must have Args or be a vector.");
168 160 CLI::callback_t fun = [&variable](CLI::results_t res){
169 161 bool retval = true;
170 162 variable.clear();
... ... @@ -176,27 +168,16 @@ public:
176 168 return variable.size() > 0 && retval;
177 169 };
178 170  
179   - Option* retval = add_option(name, fun, description, opts);
  171 + Option* retval = add_option(name, fun, description, defaulted);
  172 + retval->allow_vector = true;
  173 + retval->_expected = -1;
180 174 retval->typeval = detail::type_name<T>();
181   - if(opts.defaulted) {
  175 + if(defaulted)
182 176 retval->defaultval = "[" + detail::join(variable) + "]";
183   - }
184 177 return retval;
185 178 }
186 179  
187 180  
188   - /// Multiple options are supported
189   - template<typename T, typename... Args>
190   - Option* add_option(
191   - std::string name,
192   - T &variable, ///< The variable to set
193   - std::string description,
194   - detail::Combiner opts,
195   - detail::Combiner opts2,
196   - Args... args ///< More options
197   - ) {
198   - return add_option(name, variable, description, opts|opts2, args...);
199   - }
200 181 /// Add option for flag
201 182 Option* add_flag(
202 183 std::string name,
... ... @@ -206,9 +187,10 @@ public:
206 187 return true;
207 188 };
208 189  
209   - Option* opt = add_option(name, fun, description, Nothing);
210   - if(opt->positional())
  190 + Option* opt = add_option(name, fun, description, false);
  191 + if(opt->get_positional())
211 192 throw IncorrectConstruction("Flags cannot be positional");
  193 + opt->_expected = 0;
212 194 return opt;
213 195 }
214 196  
... ... @@ -227,9 +209,10 @@ public:
227 209 return true;
228 210 };
229 211  
230   - Option* opt = add_option(name, fun, description, Nothing);
231   - if(opt->positional())
  212 + Option* opt = add_option(name, fun, description, false);
  213 + if(opt->get_positional())
232 214 throw IncorrectConstruction("Flags cannot be positional");
  215 + opt->_expected = 0;
233 216 return opt;
234 217 }
235 218  
... ... @@ -248,9 +231,10 @@ public:
248 231 return res.size() == 1;
249 232 };
250 233  
251   - Option* opt = add_option(name, fun, description, Nothing);
252   - if(opt->positional())
  234 + Option* opt = add_option(name, fun, description, false);
  235 + if(opt->get_positional())
253 236 throw IncorrectConstruction("Flags cannot be positional");
  237 + opt->_expected = 0;
254 238 return opt;
255 239 }
256 240  
... ... @@ -262,12 +246,9 @@ public:
262 246 T &member, ///< The selected member of the set
263 247 std::set<T> options, ///< The set of posibilities
264 248 std::string description="",
265   - detail::Combiner opts=Validators
  249 + bool defaulted=true
266 250 ) {
267 251  
268   - if(opts.num!=1)
269   - throw IncorrectConstruction("Must have Args(1).");
270   -
271 252 CLI::callback_t fun = [&member, options](CLI::results_t res){
272 253 if(res.size()!=1) {
273 254 return false;
... ... @@ -281,223 +262,16 @@ public:
281 262 return std::find(std::begin(options), std::end(options), member) != std::end(options);
282 263 };
283 264  
284   - Option* retval = add_option(name, fun, description, opts);
  265 + Option* retval = add_option(name, fun, description, defaulted);
285 266 retval->typeval = detail::type_name<T>();
286 267 retval->typeval += " in {" + detail::join(options) + "}";
287   - if(opts.defaulted) {
288   - std::stringstream out;
289   - out << member;
290   - retval->defaultval = out.str();
291   - }
  268 + std::stringstream out;
  269 + out << member;
  270 + retval->defaultval = out.str();
292 271 return retval;
293 272 }
294 273  
295 274  
296   - template<typename T, typename... Args>
297   - Option* add_set(
298   - std::string name,
299   - T &member,
300   - std::set<T> options, ///< The set of posibilities
301   - std::string description,
302   - detail::Combiner opts,
303   - detail::Combiner opts2,
304   - Args... args
305   - ) {
306   - return add_set(name, member, options, description, opts|opts2, args...);
307   - }
308   -
309   -
310   - //------------ MAKE STYLE ---------//
311   -
312   - /// Prototype for new output style
313   - template<typename T = std::string,
314   - enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
315   - Value<T> make_option(
316   - std::string name,
317   - std::string description="",
318   - detail::Combiner opts=Validators
319   - ) {
320   -
321   - if(opts.num!=1)
322   - throw IncorrectConstruction("Must have Args(1).");
323   -
324   - Value<T> out(name);
325   - std::shared_ptr<std::unique_ptr<T>> ptr = out.value;
326   -
327   - CLI::callback_t fun = [ptr](CLI::results_t res){
328   - if(res.size()!=1) {
329   - return false;
330   - }
331   - if(res[0].size()!=1) {
332   - return false;
333   - }
334   - ptr->reset(new T()); // resets the internal ptr
335   - return detail::lexical_cast(res[0][0], **ptr);
336   - };
337   - Option* retval = add_option(name, fun, description, opts);
338   - retval->typeval = detail::type_name<T>();
339   - return out;
340   - }
341   -
342   - template<typename T = std::string, typename... Args>
343   - Value<T> make_option(
344   - std::string name,
345   - std::string description,
346   - detail::Combiner opts,
347   - detail::Combiner opts2,
348   - Args... args
349   - ) {
350   - return make_option(name, description, opts|opts2, args...);
351   - }
352   -
353   - /// Prototype for new output style with default
354   - template<typename T,
355   - enable_if_t<!is_vector<T>::value, detail::enabler> = detail::dummy>
356   - Value<T> make_option(
357   - std::string name,
358   - const T& default_value,
359   - std::string description="",
360   - detail::Combiner opts=Validators
361   - ) {
362   -
363   - if(opts.num!=1)
364   - throw IncorrectConstruction("Must have Args(1).");
365   -
366   - Value<T> out(name);
367   - std::shared_ptr<std::unique_ptr<T>> ptr = out.value;
368   - ptr->reset(new T(default_value)); // resets the internal ptr
369   -
370   - CLI::callback_t fun = [ptr](CLI::results_t res){
371   - if(res.size()!=1) {
372   - return false;
373   - }
374   - if(res[0].size()!=1) {
375   - return false;
376   - }
377   - ptr->reset(new T()); // resets the internal ptr
378   - return detail::lexical_cast(res[0][0], **ptr);
379   - };
380   - Option* retval = add_option(name, fun, description, opts);
381   - retval->typeval = detail::type_name<T>();
382   - std::stringstream ot;
383   - ot << default_value;
384   - retval->defaultval = ot.str();
385   - return out;
386   - }
387   -
388   - /// Prototype for new output style, vector
389   - template<typename T,
390   - enable_if_t<is_vector<T>::value, detail::enabler> = detail::dummy>
391   - Value<T> make_option(
392   - std::string name,
393   - std::string description="",
394   - detail::Combiner opts=Args
395   - ) {
396   -
397   - if(opts.num==0)
398   - throw IncorrectConstruction("Must have Args or be a vector.");
399   -
400   - Value<T> out(name);
401   - std::shared_ptr<std::unique_ptr<T>> ptr = out.value;
402   -
403   - CLI::callback_t fun = [ptr](CLI::results_t res){
404   - ptr->reset(new T()); // resets the internal ptr
405   - bool retval = true;
406   - for(const auto &a : res)
407   - for(const auto &b : a) {
408   - (*ptr)->emplace_back();
409   - retval &= detail::lexical_cast(b, (*ptr)->back());
410   - }
411   - return (*ptr)->size() > 0 && retval;
412   - };
413   - Option* retval = add_option(name, fun, description, opts);
414   - retval->typeval = detail::type_name<T>();
415   - return out;
416   - }
417   -
418   -
419   - template<typename T, typename... Args>
420   - Value<T> make_option(
421   - std::string name,
422   - const T& default_value,
423   - std::string description,
424   - detail::Combiner opts,
425   - detail::Combiner opts2,
426   - Args... args
427   - ) {
428   - return make_option(name, default_value, description, opts|opts2, args...);
429   - }
430   -
431   - /// Prototype for new output style: flag
432   - Value<int> make_flag(
433   - std::string name,
434   - std::string description=""
435   - ) {
436   -
437   - Value<int> out(name);
438   - std::shared_ptr<std::unique_ptr<int>> ptr = out.value;
439   - ptr->reset(new int()); // resets the internal ptr
440   - **ptr = 0;
441   -
442   - CLI::callback_t fun = [ptr](CLI::results_t res){
443   - **ptr = (int) res.size();
444   - return true;
445   - };
446   -
447   - Option* opt = add_option(name, fun, description, Nothing);
448   - if(opt->positional())
449   - throw IncorrectConstruction("Flags cannot be positional");
450   - return out;
451   - }
452   -
453   - /// Add set of options
454   - template<typename T>
455   - Value<T> make_set(
456   - std::string name,
457   - std::set<T> options, ///< The set of posibilities
458   - std::string description="",
459   - detail::Combiner opts=Validators
460   - ) {
461   -
462   - Value<T> out(name);
463   - std::shared_ptr<std::unique_ptr<T>> ptr = out.value;
464   -
465   - if(opts.num!=1)
466   - throw IncorrectConstruction("Must have Args(1).");
467   -
468   - CLI::callback_t fun = [ptr, options](CLI::results_t res){
469   - if(res.size()!=1) {
470   - return false;
471   - }
472   - if(res[0].size()!=1) {
473   - return false;
474   - }
475   - ptr->reset(new T());
476   - bool retval = detail::lexical_cast(res[0][0], **ptr);
477   - if(!retval)
478   - return false;
479   - return std::find(std::begin(options), std::end(options), **ptr) != std::end(options);
480   - };
481   -
482   - Option* retval = add_option(name, fun, description, opts);
483   - retval->typeval = detail::type_name<T>();
484   - retval->typeval += " in {" + detail::join(options) + "}";
485   - return out;
486   - }
487   -
488   -
489   - template<typename T, typename... Args>
490   - Value<T> make_set(
491   - std::string name,
492   - std::set<T> options,
493   - std::string description,
494   - detail::Combiner opts,
495   - detail::Combiner opts2,
496   - Args... args
497   - ) {
498   - return make_set(name, options, description, opts|opts2, args...);
499   - }
500   -
501 275 /// This allows subclasses to inject code before callbacks but after parse
502 276 virtual void pre_callback() {}
503 277  
... ... @@ -510,6 +284,7 @@ public:
510 284 parse(args);
511 285 }
512 286  
  287 + /// The real work is done here. Expects a reversed vector
513 288 void parse(std::vector<std::string> & args) {
514 289 parsed = true;
515 290  
... ... @@ -546,16 +321,16 @@ public:
546 321  
547 322  
548 323 for(Option& opt : options) {
549   - while (opt.positional() && opt.count() < opt.expected() && positionals.size() > 0) {
  324 + while (opt.get_positional() && opt.count() < opt.get_expected() && positionals.size() > 0) {
550 325 opt.get_new();
551 326 opt.add_result(0, positionals.front());
552 327 positionals.pop_front();
553 328 }
554   - if (opt.required() && opt.count() < opt.expected())
  329 + if (opt.get_required() && opt.count() < opt.get_expected())
555 330 throw RequiredError(opt.get_name());
556 331 if (opt.count() > 0) {
557 332 if(!opt.run_callback())
558   - throw ParseError(opt.get_name());
  333 + throw ConversionError(opt.get_name());
559 334 }
560 335  
561 336 }
... ... @@ -595,7 +370,7 @@ public:
595 370 }
596 371  
597 372 int vnum = op->get_new();
598   - int num = op->expected();
  373 + int num = op->get_expected();
599 374  
600 375 if(num == 0)
601 376 op->add_result(vnum, "");
... ... @@ -660,7 +435,7 @@ public:
660 435  
661 436  
662 437 int vnum = op->get_new();
663   - int num = op->expected();
  438 + int num = op->get_expected();
664 439  
665 440  
666 441 if(value != "") {
... ... @@ -742,7 +517,7 @@ public:
742 517 // Positionals
743 518 bool pos=false;
744 519 for(const Option &opt : options)
745   - if(opt.positional()) {
  520 + if(opt.get_positional()) {
746 521 out << " " << opt.help_positional();
747 522 if(opt.has_description())
748 523 pos=true;
... ... @@ -754,7 +529,7 @@ public:
754 529 if(pos) {
755 530 out << "Positionals:" << std::endl;
756 531 for(const Option &opt : options)
757   - if(opt.positional() && opt.has_description())
  532 + if(opt.get_positional() && opt.has_description())
758 533 detail::format_help(out, opt.get_pname(), opt.get_description(), wid);
759 534 out << std::endl;
760 535  
... ...
include/CLI/CLI.hpp
... ... @@ -8,8 +8,7 @@
8 8 #include "CLI/TypeTools.hpp"
9 9 #include "CLI/StringTools.hpp"
10 10 #include "CLI/Split.hpp"
11   -#include "CLI/Combiner.hpp"
  11 +#include "CLI/Validators.hpp"
12 12 #include "CLI/Option.hpp"
13   -#include "CLI/Value.hpp"
14 13 #include "CLI/App.hpp"
15 14  
... ...
include/CLI/Error.hpp
... ... @@ -11,54 +11,74 @@ namespace CLI {
11 11  
12 12 // Error definitions
13 13  
14   -
  14 +/// All errors derive from this one
15 15 struct Error : public std::runtime_error {
16 16 int exit_code;
17 17 bool print_help;
18 18 Error(std::string parent, std::string name, int exit_code=255, bool print_help=true) : runtime_error(parent + ": " + name), exit_code(exit_code), print_help(print_help) {}
19 19 };
20 20  
  21 +/// This is a successful completion on parsing, supposed to exit
21 22 struct Success : public Error {
22 23 Success() : Error("Success", "Successfully completed, should be caught and quit", 0, false) {}
23 24 };
24 25  
  26 +/// -h or --help on command line
25 27 struct CallForHelp : public Error {
26 28 CallForHelp() : Error("CallForHelp", "This should be caught in your main function, see examples", 0) {}
27 29 };
28 30  
29   -struct BadNameString : public Error {
30   - BadNameString(std::string name) : Error("BadNameString", name, 1) {}
  31 +// Construction errors (not in parsing)
  32 +
  33 +struct ConstructionError : public Error {
  34 + using Error::Error;
31 35 };
32 36  
  37 +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example)
  38 +struct IncorrectConstruction : public ConstructionError {
  39 + IncorrectConstruction(std::string name) : ConstructionError("ConstructionError", name, 8) {}
  40 +};
33 41  
34   -struct ParseError : public Error {
35   - ParseError(std::string name) : Error("ParseError", name, 2) {}
  42 +/// Thrown on construction of a bad name
  43 +struct BadNameString : public ConstructionError {
  44 + BadNameString(std::string name) : ConstructionError("BadNameString", name, 1) {}
36 45 };
37 46  
38   -struct OptionAlreadyAdded : public Error {
39   - OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name, 3) {}
  47 +/// Thrown when an option already exists
  48 +struct OptionAlreadyAdded : public ConstructionError {
  49 + OptionAlreadyAdded(std::string name) : ConstructionError("OptionAlreadyAdded", name, 3) {}
40 50 };
41 51  
42   -struct OptionNotFound : public Error {
43   - OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {}
  52 +// Parsing errors
  53 +
  54 +struct ParseError : public Error {
  55 + using Error::Error;
44 56 };
45 57  
46   -struct RequiredError : public Error {
47   - RequiredError(std::string name) : Error("RequiredError", name, 5) {}
  58 +/// Thrown when conversion call back fails, such as when an int fails to coerse to a string
  59 +struct ConversionError : public ParseError {
  60 + ConversionError(std::string name) : ParseError("ConversionError", name, 2) {}
48 61 };
49 62  
50   -struct PositionalError : public Error {
51   - PositionalError(std::string name) : Error("PositionalError", name, 6) {}
  63 +/// Thrown when a required option is missing
  64 +struct RequiredError : public ParseError {
  65 + RequiredError(std::string name) : ParseError("RequiredError", name, 5) {}
52 66 };
53 67  
54   -struct HorribleError : public Error {
55   - HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name, 7) {}
  68 +/// Thrown when too many positionals are found
  69 +struct PositionalError : public ParseError {
  70 + PositionalError(std::string name) : ParseError("PositionalError", name, 6) {}
56 71 };
57   -struct IncorrectConstruction : public Error {
58   - IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name, 8) {}
  72 +
  73 +/// This is just a safety check to verify selection and parsing match
  74 +struct HorribleError : public ParseError {
  75 + HorribleError(std::string name) : ParseError("HorribleError", "(You should never see this error) " + name, 7) {}
59 76 };
60   -struct EmptyError : public Error {
61   - EmptyError(std::string name) : Error("EmptyError", name, 9) {}
  77 +
  78 +/// Thrown when counting a non-existent option
  79 +struct OptionNotFound : public Error {
  80 + OptionNotFound(std::string name) : Error("OptionNotFound", name, 4) {}
62 81 };
63 82  
  83 +
64 84 }
... ...
include/CLI/Option.hpp
... ... @@ -9,16 +9,15 @@
9 9 #include <tuple>
10 10 #include <algorithm>
11 11  
  12 +#include "CLI/Error.hpp"
12 13 #include "CLI/StringTools.hpp"
13 14 #include "CLI/Split.hpp"
14   -#include "CLI/Combiner.hpp"
15 15  
16 16 namespace CLI {
17 17  
18 18 typedef std::vector<std::vector<std::string>> results_t;
19 19 typedef std::function<bool(results_t)> callback_t;
20 20  
21   -
22 21 class App;
23 22  
24 23 class Option {
... ... @@ -29,7 +28,6 @@ protected:
29 28 std::vector<std::string> lnames;
30 29 std::string pname;
31 30  
32   - detail::Combiner opts;
33 31 std::string description;
34 32 callback_t callback;
35 33  
... ... @@ -37,33 +35,66 @@ protected:
37 35 std::string defaultval;
38 36 std::string typeval;
39 37  
  38 +
  39 + bool _default {false};
  40 + bool _required {false};
  41 + int _expected {1};
  42 + bool allow_vector {false};
  43 + std::vector<std::function<bool(std::string)>> _validators;
  44 +
40 45 // Results
41 46 results_t results {};
42 47  
43 48  
44 49 public:
45   - Option(std::string name, std::string description = "", detail::Combiner opts=Nothing, std::function<bool(results_t)> callback=[](results_t){return true;}) :
46   - opts(opts), description(description), callback(callback){
  50 + Option(std::string name, std::string description = "", std::function<bool(results_t)> callback=[](results_t){return true;}, bool _default=true) :
  51 + description(description), callback(callback), _default(_default) {
47 52 std::tie(snames, lnames, pname) = detail::get_names(detail::split_names(name));
48 53 }
49 54  
  55 +
  56 + // This class is "true" if optio passed.
  57 + operator bool() const {
  58 + return results.size() > 0;
  59 + }
  60 +
50 61 /// Clear the parsed results (mostly for testing)
51 62 void clear() {
52 63 results.clear();
53 64 }
54 65  
55   - /// True if option is required
56   - bool required() const {
57   - return opts.required;
  66 + /// Set the option as required
  67 + Option* required(bool value = true) {
  68 + _required = value;
  69 + return this;
  70 + }
  71 +
  72 + bool get_required() const {
  73 + return _required;
  74 + }
  75 +
  76 + /// Set the number of expected arguments (Flags bypass this)
  77 + Option* expected(int value) {
  78 + if(value == 0)
  79 + throw IncorrectConstruction("Cannot set 0 expected, use a flag instead");
  80 + if(!allow_vector && value != 1)
  81 + throw IncorrectConstruction("You can only change the Expected arguments for vectors");
  82 + _expected = value;
  83 + return this;
58 84 }
59 85  
60 86 /// The number of arguments the option expects
61   - int expected() const {
62   - return opts.num;
  87 + int get_expected() const {
  88 + return _expected;
  89 + }
  90 +
  91 + /// True if this has a default value
  92 + int get_default() const {
  93 + return _default;
63 94 }
64 95  
65 96 /// True if the argument can be given directly
66   - bool positional() const {
  97 + bool get_positional() const {
67 98 return pname.length() > 0;
68 99 }
69 100  
... ... @@ -72,16 +103,18 @@ public:
72 103 return (snames.size() + lnames.size()) > 0;
73 104 }
74 105  
75   - /// True if this should print the default string
76   - bool defaulted() const {
77   - return opts.defaulted;
78   - }
79   -
80 106 /// True if option has description
81 107 bool has_description() const {
82 108 return description.length() > 0;
83 109 }
84 110  
  111 + /// Adds a validator
  112 + Option* check(std::function<bool(std::string)> validator) {
  113 +
  114 + _validators.push_back(validator);
  115 + return this;
  116 + }
  117 +
85 118 /// Get the description
86 119 const std::string& get_description() const {
87 120 return description;
... ... @@ -90,11 +123,11 @@ public:
90 123 /// The name and any extras needed for positionals
91 124 std::string help_positional() const {
92 125 std::string out = pname;
93   - if(expected()<1)
94   - out = out + "x" + std::to_string(expected());
95   - else if(expected()==-1)
  126 + if(get_expected()<1)
  127 + out = out + "x" + std::to_string(get_expected());
  128 + else if(get_expected()==-1)
96 129 out = out + "...";
97   - out = required() ? out : "["+out+"]";
  130 + out = get_required() ? out : "["+out+"]";
98 131 return out;
99 132 }
100 133  
... ... @@ -105,9 +138,9 @@ public:
105 138  
106 139 /// Process the callback
107 140 bool run_callback() const {
108   - if(opts.validators.size()>0) {
  141 + if(_validators.size()>0) {
109 142 for(const std::string & result : flatten_results())
110   - for(const std::function<bool(std::string)> &vali : opts.validators)
  143 + for(const std::function<bool(std::string)> &vali : _validators)
111 144 if(!vali(result))
112 145 return false;
113 146 }
... ... @@ -196,14 +229,14 @@ public:
196 229 std::string help_name() const {
197 230 std::stringstream out;
198 231 out << get_name();
199   - if(expected() != 0) {
  232 + if(get_expected() != 0) {
200 233 if(typeval != "")
201 234 out << " " << typeval;
202 235 if(defaultval != "")
203 236 out << "=" << defaultval;
204   - if(expected() > 1)
205   - out << " x " << expected();
206   - if(expected() == -1)
  237 + if(get_expected() > 1)
  238 + out << " x " << get_expected();
  239 + if(get_expected() == -1)
207 240 out << " ...";
208 241 }
209 242 return out.str();
... ...
include/CLI/Combiner.hpp renamed to include/CLI/Validators.hpp
... ... @@ -4,8 +4,6 @@
4 4 // file LICENSE or https://github.com/henryiii/CLI11 for details.
5 5  
6 6 #include <string>
7   -#include <functional>
8   -#include <vector>
9 7  
10 8  
11 9 // C standard library
... ... @@ -16,42 +14,9 @@
16 14  
17 15 namespace CLI {
18 16  
19   -namespace detail {
20   -
21   -struct Combiner {
22   - int num;
23   - bool required;
24   - bool defaulted;
25   - std::vector<std::function<bool(std::string)>> validators;
26   -
27   - /// Can be or-ed together
28   - Combiner operator | (Combiner b) const {
29   - Combiner self;
30   - self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num);
31   - self.required = required || b.required;
32   - self.defaulted = defaulted || b.defaulted;
33   - self.validators.reserve(validators.size() + b.validators.size());
34   - self.validators.insert(self.validators.end(), validators.begin(), validators.end());
35   - self.validators.insert(self.validators.end(), b.validators.begin(), b.validators.end());
36   - return self;
37   - }
38   -
39   - /// Call to give the number of arguments expected on cli
40   - Combiner operator() (int n) const {
41   - Combiner self = *this;
42   - self.num = n;
43   - return self;
44   - }
45   - /// Call to give a validator
46   - Combiner operator() (std::function<bool(std::string)> func) const {
47   - Combiner self = *this;
48   - self.validators.push_back(func);
49   - return self;
50   - }
51   -};
52 17  
53 18 /// Check for an existing file
54   -bool _ExistingFile(std::string filename) {
  19 +bool ExistingFile(std::string filename) {
55 20 // std::fstream f(name.c_str());
56 21 // return f.good();
57 22 // Fastest way according to http://stackoverflow.com/questions/12774207/fastest-way-to-check-if-a-file-exist-using-standard-c-c11-c
... ... @@ -60,7 +25,7 @@ bool _ExistingFile(std::string filename) {
60 25 }
61 26  
62 27 /// Check for an existing directory
63   -bool _ExistingDirectory(std::string filename) {
  28 +bool ExistingDirectory(std::string filename) {
64 29 struct stat buffer;
65 30 if(stat(filename.c_str(), &buffer) == 0 && (buffer.st_mode & S_IFDIR) )
66 31 return true;
... ... @@ -68,32 +33,10 @@ bool _ExistingDirectory(std::string filename) {
68 33 }
69 34  
70 35 /// Check for a non-existing path
71   -bool _NonexistentPath(std::string filename) {
  36 +bool NonexistentPath(std::string filename) {
72 37 struct stat buffer;
73 38 return stat(filename.c_str(), &buffer) != 0;
74 39 }
75 40  
76 41  
77   -
78   -
79   -}
80   -
81   -
82   -
83   -// Defines for common Combiners (don't use combiners directly)
84   -
85   -const detail::Combiner Nothing {0, false, false, {}};
86   -const detail::Combiner Required {1, true, false, {}};
87   -const detail::Combiner Default {1, false, true, {}};
88   -const detail::Combiner Args {-1, false, false, {}};
89   -const detail::Combiner Validators {1, false, false, {}};
90   -
91   -// Warning about using these validators:
92   -// The files could be added/deleted after the validation. This is not common,
93   -// but if this is a possibility, check the file you open afterwards
94   -const detail::Combiner ExistingFile {1, false, false, {detail::_ExistingFile}};
95   -const detail::Combiner ExistingDirectory {1, false, false, {detail::_ExistingDirectory}};
96   -const detail::Combiner NonexistentPath {1, false, false, {detail::_NonexistentPath}};
97   -
98   -
99 42 }
... ...
include/CLI/Value.hpp deleted
1   -#pragma once
2   -
3   -// Distributed under the LGPL version 3.0 license. See accompanying
4   -// file LICENSE or https://github.com/henryiii/CLI11 for details.
5   -
6   -#include <string>
7   -#include <memory>
8   -
9   -#include "CLI/Error.hpp"
10   -
11   -namespace CLI {
12   -
13   -class App;
14   -
15   -// Prototype return value test
16   -template <typename T>
17   -class Value {
18   - friend App;
19   -protected:
20   - std::shared_ptr<std::unique_ptr<T>> value {new std::unique_ptr<T>()};
21   - std::string name;
22   -public:
23   - Value(std::string name) : name(name) {}
24   -
25   - operator bool() const {return (bool) *value;}
26   -
27   - T& get() const {
28   - if(*value)
29   - return **value;
30   - else
31   - throw EmptyError(name);
32   - }
33   - /// Note this does not throw on assignment, though
34   - /// afterwards it seems to work fine. Best to use
35   - /// explicit * notation.
36   - T& operator *() const {
37   - return get();
38   - }
39   -};
40   -
41   -}
include/Program.hpp deleted
1   -#pragma once
2   -
3   -#include <string>
4   -
5   -#include <boost/program_options.hpp>
6   -
7   -
8   -// This is unreachable outside this file; you should not use Combiner directly
9   -namespace {
10   -
11   -struct Combiner {
12   - int positional;
13   - bool required;
14   - bool defaulted;
15   -
16   - /// Can be or-ed together
17   - Combiner operator | (Combiner b) const {
18   - Combiner self;
19   - self.positional = positional + b.positional;
20   - self.required = required || b.required;
21   - self.defaulted = defaulted || b.defaulted;
22   - return self;
23   - }
24   -
25   - /// Call to give the number of arguments expected on cli
26   - Combiner operator() (int n) const {
27   - return Combiner{n, required, defaulted};
28   - }
29   - Combiner operator, (Combiner b) const {
30   - return *this | b;
31   - }
32   -};
33   -}
34   -
35   -
36   -
37   -/// Creates a command line program, with very few defaults.
38   -/** To use, create a new Program() instance with argc, argv, and a help description. The templated
39   -* add_option methods make it easy to prepare options. Remember to call `.start` before starting your
40   -* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */
41   -class Program {
42   -public:
43   - static constexpr Combiner REQUIRED{0,true,false};
44   - static constexpr Combiner DEFAULT{0,false,true};
45   - static constexpr Combiner POSITIONAL{1,false,false};
46   -
47   -protected:
48   - boost::program_options::options_description desc;
49   - boost::program_options::positional_options_description p;
50   - boost::program_options::variables_map vm;
51   -
52   - int argc;
53   - char **argv;
54   -
55   - /// Parses the command line (internal function)
56   - void parse() {
57   - try {
58   - boost::program_options::store(boost::program_options::command_line_parser(argc, argv)
59   - .options(desc).positional(p).run(), vm);
60   -
61   - if(vm.count("help")){
62   - std::cout << desc;
63   - exit(0);
64   - }
65   -
66   - boost::program_options::notify(vm);
67   - } catch(const boost::program_options::error& e) {
68   - std::cerr << "ERROR: " << e.what() << std::endl << std::endl;
69   - std::cerr << desc << std::endl;
70   - exit(1);
71   - }
72   - }
73   -
74   -
75   -public:
76   -
77   - /// Create a new program. Pass in the same arguments as main(), along with a help string.
78   - Program(int argc, char** argv, std::string discription)
79   - : argc(argc), argv(argv), desc(discription) {
80   - desc.add_options()
81   - ("help,h", "Display this help message");
82   - }
83   -
84   - /// Allows you to manually add options in the boost style.
85   - /** Usually the specialized methods are easier, but this remains for people used to Boost and for
86   - * unusual situations. */
87   - boost::program_options::options_description_easy_init add_options() {
88   - return desc.add_options();
89   - }
90   -
91   - /// Add an option, will automatically understand the type for common types.
92   - /** To use, create a variable with the expected type, and pass it in after the name.
93   - * After start is called, you can use count to see if the value was passed, and
94   - * the value will be initialized properly.
95   - *
96   - * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|`
97   - * together. The positional options take an optional number of arguments.
98   - *
99   - * For example,
100   - *
101   - * std::string filename
102   - * program.add_option("filename", filename, "description of filename");
103   - */
104   - template<typename T>
105   - void add_option(
106   - std::string name, ///< The name, long,short
107   - T &value, ///< The value
108   - std::string description, ///< Discription string
109   - Combiner options ///< The options (REQUIRED, DEFAULT, POSITIONAL)
110   - ) {
111   - auto po_value = boost::program_options::value<T>(&value);
112   - if(options.defaulted)
113   - po_value = po_value->default_value(value);
114   - if(options.required)
115   - po_value = po_value->required();
116   - desc.add_options()(name.c_str(),po_value,description.c_str());
117   - if(options.positional!=0)
118   - p.add(name.c_str(), options.positional);
119   - }
120   -
121   - /// Adds a flag style option
122   - void add_option(std::string name, std::string description) {
123   - desc.add_options()(name.c_str(),description.c_str());
124   - }
125   -
126   -
127   - /// This must be called after the options are in but before the rest of the program.
128   - /** Calls the Boost boost::program_options initialization, causing the program to exit
129   - * if -h or an invalid option is passed. */
130   - void start() {
131   - parse();
132   - }
133   -
134   - /// Counts the number of times the given option was passed.
135   - int count(std::string name) const {
136   - return vm.count(name.c_str());
137   - }
138   -
139   -
140   -};
tests/CLITest.cpp
... ... @@ -165,7 +165,7 @@ TEST_F(TApp, BoolAndIntFlags) {
165 165 app.reset();
166 166  
167 167 args = {"-b", "-b"};
168   - EXPECT_THROW(run(), CLI::ParseError);
  168 + EXPECT_THROW(run(), CLI::ConversionError);
169 169  
170 170 app.reset();
171 171 bflag = false;
... ... @@ -199,8 +199,8 @@ TEST_F(TApp, Flags) {
199 199 int i = 3;
200 200 std::string s = "HI";
201 201  
202   - app.add_option("-i,i", i, "", CLI::Default);
203   - app.add_option("-s,s", s, "", CLI::Default);
  202 + app.add_option("-i,i", i, "", false);
  203 + app.add_option("-s,s", s, "", true);
204 204  
205 205 args = {"-i2", "9"};
206 206  
... ... @@ -276,10 +276,10 @@ TEST_F(TApp, Reset) {
276 276  
277 277 TEST_F(TApp, FileNotExists) {
278 278 std::string myfile{"TestNonFileNotUsed.txt"};
279   - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile));
  279 + EXPECT_TRUE(CLI::NonexistentPath(myfile));
280 280  
281 281 std::string filename;
282   - app.add_option("--file", filename, "", CLI::NonexistentPath);
  282 + app.add_option("--file", filename)->check(CLI::NonexistentPath);
283 283 args = {"--file", myfile};
284 284  
285 285 EXPECT_NO_THROW(run());
... ... @@ -290,21 +290,21 @@ TEST_F(TApp, FileNotExists) {
290 290  
291 291 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
292 292 EXPECT_TRUE(ok);
293   - EXPECT_THROW(run(), CLI::ParseError);
  293 + EXPECT_THROW(run(), CLI::ConversionError);
294 294  
295 295 std::remove(myfile.c_str());
296   - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile));
  296 + EXPECT_FALSE(CLI::ExistingFile(myfile));
297 297 }
298 298  
299 299 TEST_F(TApp, FileExists) {
300 300 std::string myfile{"TestNonFileNotUsed.txt"};
301   - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile));
  301 + EXPECT_FALSE(CLI::ExistingFile(myfile));
302 302  
303 303 std::string filename = "Failed";
304   - app.add_option("--file", filename, "", CLI::ExistingFile);
  304 + app.add_option("--file", filename)->check(CLI::ExistingFile);
305 305 args = {"--file", myfile};
306 306  
307   - EXPECT_THROW(run(), CLI::ParseError);
  307 + EXPECT_THROW(run(), CLI::ConversionError);
308 308 EXPECT_EQ("Failed", filename);
309 309  
310 310 app.reset();
... ... @@ -315,7 +315,7 @@ TEST_F(TApp, FileExists) {
315 315 EXPECT_EQ(myfile, filename);
316 316  
317 317 std::remove(myfile.c_str());
318   - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile));
  318 + EXPECT_FALSE(CLI::ExistingFile(myfile));
319 319 }
320 320  
321 321 TEST_F(TApp, InSet) {
... ... @@ -331,15 +331,15 @@ TEST_F(TApp, InSet) {
331 331 app.reset();
332 332  
333 333 args = {"--quick", "four"};
334   - EXPECT_THROW(run(), CLI::ParseError);
  334 + EXPECT_THROW(run(), CLI::ConversionError);
335 335 }
336 336  
337 337 TEST_F(TApp, VectorFixedString) {
338 338 std::vector<std::string> strvec;
339 339 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
340 340  
341   - CLI::Option* opt = app.add_option("-s,--string", strvec, "", CLI::Args(3));
342   - EXPECT_EQ(3, opt->expected());
  341 + CLI::Option* opt = app.add_option("-s,--string", strvec)->expected(3);
  342 + EXPECT_EQ(3, opt->get_expected());
343 343  
344 344 args = {"--string", "mystring", "mystring2", "mystring3"};
345 345 run();
... ... @@ -354,7 +354,7 @@ TEST_F(TApp, VectorUnlimString) {
354 354 std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
355 355  
356 356 CLI::Option* opt = app.add_option("-s,--string", strvec);
357   - EXPECT_EQ(-1, opt->expected());
  357 + EXPECT_EQ(-1, opt->get_expected());
358 358  
359 359 args = {"--string", "mystring", "mystring2", "mystring3"};
360 360 EXPECT_NO_THROW(run());
... ... @@ -363,6 +363,27 @@ TEST_F(TApp, VectorUnlimString) {
363 363 }
364 364  
365 365  
  366 +TEST_F(TApp, VectorFancyOpts) {
  367 + std::vector<std::string> strvec;
  368 + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"};
  369 +
  370 + CLI::Option* opt = app.add_option("-s,--string", strvec)->required()->expected(3);
  371 + EXPECT_EQ(3, opt->get_expected());
  372 +
  373 + args = {"--string", "mystring", "mystring2", "mystring3"};
  374 + EXPECT_NO_THROW(run());
  375 + EXPECT_EQ(3, app.count("--string"));
  376 + EXPECT_EQ(answer, strvec);
  377 +
  378 + app.reset();
  379 + args = {"one", "two"};
  380 + EXPECT_THROW(run(), CLI::RequiredError);
  381 +
  382 + app.reset();
  383 + EXPECT_THROW(run(), CLI::ConversionError);
  384 +}
  385 +
  386 +
366 387  
367 388 TEST_F(TApp, BasicSubcommands) {
368 389 auto sub1 = app.add_subcommand("sub1");
... ... @@ -450,99 +471,5 @@ TEST_F(SubcommandProgram, SpareSub) {
450 471 EXPECT_THROW(run(), CLI::PositionalError);
451 472 }
452 473  
453   -class TAppValue : public TApp {};
454   -
455   -TEST_F(TAppValue, OneString) {
456   - auto str = app.make_option("-s,--string");
457   - std::string v;
458   - args = {"--string", "mystring"};
459   - EXPECT_FALSE((bool) str);
460   - EXPECT_THROW(v = *str, CLI::EmptyError);
461   - //EXPECT_THROW(v = str, CLI::EmptyError);
462   - EXPECT_FALSE((bool) str);
463   - EXPECT_NO_THROW(run());
464   - EXPECT_TRUE((bool) str);
465   - EXPECT_NO_THROW(v = *str);
466   - EXPECT_NO_THROW(v = str);
467   -
468   - EXPECT_EQ(1, app.count("-s"));
469   - EXPECT_EQ(1, app.count("--string"));
470   - EXPECT_EQ(*str, "mystring");
471   -
472   -}
473   -
474   -TEST_F(TAppValue, SeveralInts) {
475   - auto value = app.make_option<int>("--first");
476   - CLI::Value<int> value2 = app.make_option<int>("-s");
477   - int v;
478   - args = {"--first", "12", "-s", "19"};
479   - EXPECT_FALSE((bool) value);
480   - EXPECT_FALSE((bool) value2);
481   -
482   - EXPECT_THROW(v = *value, CLI::EmptyError);
483   - //EXPECT_THROW(v = str, CLI::EmptyError);
484   - EXPECT_NO_THROW(run());
485   - EXPECT_TRUE((bool) value);
486   - EXPECT_NO_THROW(v = *value);
487   - EXPECT_NO_THROW(v = value);
488   -
489   - EXPECT_EQ(1, app.count("-s"));
490   - EXPECT_EQ(1, app.count("--first"));
491   - EXPECT_EQ(*value, 12);
492   - EXPECT_EQ(*value2, 19);
493   -
494   -}
495   -
496   -TEST_F(TAppValue, Vector) {
497   - auto value = app.make_option<std::vector<int>>("--first", "", CLI::Args);
498   - auto value2 = app.make_option<std::vector<std::string>>("--second");
499   -
500   - std::vector<int> i;
501   - std::vector<std::string> s;
502   -
503   - args = {"--first", "12", "3", "9", "--second", "thing", "try"};
504   -
505   - EXPECT_FALSE((bool) value);
506   - EXPECT_FALSE((bool) value2);
507   -
508   - EXPECT_THROW(i = *value, CLI::EmptyError);
509   - EXPECT_THROW(s = *value2, CLI::EmptyError);
510   -
511   - EXPECT_NO_THROW(run());
512   -
513   - EXPECT_TRUE((bool) value);
514   - EXPECT_TRUE((bool) value2);
515   -
516   - EXPECT_NO_THROW(i = *value);
517   - //EXPECT_NO_THROW(i = value);
518   -
519   - EXPECT_NO_THROW(s = *value2);
520   - //EXPECT_NO_THROW(s = value2);
521   -
522   - EXPECT_EQ(3, app.count("--first"));
523   - EXPECT_EQ(2, app.count("--second"));
524   -
525   - EXPECT_EQ(std::vector<int>({12,3,9}), *value);
526   - EXPECT_EQ(std::vector<std::string>({"thing", "try"}), *value2);
527   -
528   -}
529   -
530   -TEST_F(TAppValue, DoubleVector) {
531   - auto value = app.make_option<std::vector<double>>("--simple");
532   - std::vector<double> d;
533   -
534   - args = {"--simple", "1.2", "3.4", "-1"};
535   -
536   - EXPECT_THROW(d = *value, CLI::EmptyError);
537   -
538   - EXPECT_NO_THROW(run());
539   -
540   - EXPECT_NO_THROW(d = *value);
541   -
542   - EXPECT_EQ(3, app.count("--simple"));
543   - EXPECT_EQ(std::vector<double>({1.2, 3.4, -1}), *value);
544   -}
545 474  
546   -// TODO: Check help output, better formatting
547   -// TODO: Add default/type info to help
548   -// TODO: Add README
  475 +// TODO: Check help output and formatting
... ...
tests/SmallTest.cpp
... ... @@ -11,24 +11,24 @@
11 11  
12 12 TEST(Validators, FileExists) {
13 13 std::string myfile{"TestFileNotUsed.txt"};
14   - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile));
  14 + EXPECT_FALSE(CLI::ExistingFile(myfile));
15 15 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
16 16 EXPECT_TRUE(ok);
17   - EXPECT_TRUE(CLI::detail::_ExistingFile(myfile));
  17 + EXPECT_TRUE(CLI::ExistingFile(myfile));
18 18  
19 19 std::remove(myfile.c_str());
20   - EXPECT_FALSE(CLI::detail::_ExistingFile(myfile));
  20 + EXPECT_FALSE(CLI::ExistingFile(myfile));
21 21 }
22 22  
23 23 TEST(Validators, FileNotExists) {
24 24 std::string myfile{"TestFileNotUsed.txt"};
25   - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile));
  25 + EXPECT_TRUE(CLI::NonexistentPath(myfile));
26 26 bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file
27 27 EXPECT_TRUE(ok);
28   - EXPECT_FALSE(CLI::detail::_NonexistentPath(myfile));
  28 + EXPECT_FALSE(CLI::NonexistentPath(myfile));
29 29  
30 30 std::remove(myfile.c_str());
31   - EXPECT_TRUE(CLI::detail::_NonexistentPath(myfile));
  31 + EXPECT_TRUE(CLI::NonexistentPath(myfile));
32 32 }
33 33  
34 34 TEST(Split, StringList) {
... ...