Commit 2db15139cb34e799aebaadf876f9d96035bc0da9
0 parents
Initial commit of CLI library. A few things working, but not much
Showing
5 changed files
with
831 additions
and
0 deletions
CLI.hpp
0 โ 100644
| 1 | +++ a/CLI.hpp | |
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +#include <string> | |
| 4 | +#include <regex> | |
| 5 | +#include <iostream> | |
| 6 | +#include <functional> | |
| 7 | +#include <tuple> | |
| 8 | +#include <exception> | |
| 9 | +#include <algorithm> | |
| 10 | +#include <sstream> | |
| 11 | +#include <type_traits> | |
| 12 | + | |
| 13 | +// This is unreachable outside this file; you should not use Combiner directly | |
| 14 | +namespace { | |
| 15 | + | |
| 16 | +void logit(std::string output) { | |
| 17 | + std::cout << output << std::endl; | |
| 18 | +} | |
| 19 | + | |
| 20 | +template <typename T> | |
| 21 | +std::string join(const T& v, std::string delim = ",") { | |
| 22 | + std::ostringstream s; | |
| 23 | + for (const auto& i : v) { | |
| 24 | + if (&i != &v[0]) { | |
| 25 | + s << delim; | |
| 26 | + } | |
| 27 | + s << i; | |
| 28 | + } | |
| 29 | + return s.str(); | |
| 30 | +} | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | +struct Combiner { | |
| 36 | + int num; | |
| 37 | + bool positional; | |
| 38 | + bool required; | |
| 39 | + bool defaulted; | |
| 40 | + | |
| 41 | + /// Can be or-ed together | |
| 42 | + Combiner operator | (Combiner b) const { | |
| 43 | + Combiner self; | |
| 44 | + self.num = num + b.num; | |
| 45 | + self.positional = positional || b.positional; | |
| 46 | + self.required = required || b.required; | |
| 47 | + self.defaulted = defaulted || b.defaulted; | |
| 48 | + return self; | |
| 49 | + } | |
| 50 | + | |
| 51 | + /// Call to give the number of arguments expected on cli | |
| 52 | + Combiner operator() (int n) const { | |
| 53 | + return Combiner{n, positional, required, defaulted}; | |
| 54 | + } | |
| 55 | + Combiner operator, (Combiner b) const { | |
| 56 | + return *this | b; | |
| 57 | + } | |
| 58 | +}; | |
| 59 | + | |
| 60 | + | |
| 61 | +} | |
| 62 | + | |
| 63 | +namespace CLI { | |
| 64 | + | |
| 65 | +class BadNameString : public std::runtime_error { | |
| 66 | +public: | |
| 67 | + BadNameString(std::string name) : runtime_error("Failed to parse: " + name) {}; | |
| 68 | +}; | |
| 69 | + | |
| 70 | +class CallForHelp : public std::runtime_error { | |
| 71 | +public: | |
| 72 | + CallForHelp() : runtime_error("Help option passed") {}; | |
| 73 | +}; | |
| 74 | + | |
| 75 | +class ParseError : public std::runtime_error { | |
| 76 | +public: | |
| 77 | + ParseError(std::string info="") : runtime_error(info) {}; | |
| 78 | +}; | |
| 79 | + | |
| 80 | +class OptionAlreadyAdded : public std::runtime_error { | |
| 81 | +public: | |
| 82 | + OptionAlreadyAdded(std::string name) : runtime_error("Already added:" + name) {}; | |
| 83 | +}; | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | +const std::regex reg_split{R"regex((?:([a-zA-Z0-9]?)(?:,|$)|^)([a-zA-Z0-9][a-zA-Z0-9_\-]*)?)regex"}; | |
| 88 | +const std::regex reg_short{R"regex(-([^-])(.*))regex"}; | |
| 89 | +const std::regex reg_long{R"regex(--([^-^=][^=]*)=?(.*))regex"}; | |
| 90 | + | |
| 91 | + | |
| 92 | +std::tuple<std::string, std::string> split(std::string fullname) throw(BadNameString) { | |
| 93 | + | |
| 94 | + std::smatch match; | |
| 95 | + if (std::regex_match(fullname, match, reg_split)) { | |
| 96 | + std::string sname = match[1]; | |
| 97 | + std::string lname = match[2]; | |
| 98 | + if(sname == "" and lname == "") | |
| 99 | + throw BadNameString("EMPTY"); | |
| 100 | + return std::tuple<std::string, std::string>(sname, lname); | |
| 101 | + } else throw BadNameString(fullname); | |
| 102 | +} | |
| 103 | + | |
| 104 | +const Combiner NOTHING {0,false,false,false}; | |
| 105 | +const Combiner REQUIRED {0,false,true, false}; | |
| 106 | +const Combiner DEFAULT {0,false,false,true}; | |
| 107 | +const Combiner POSITIONAL{0,true, false,false}; | |
| 108 | +const Combiner ARGS {1,false,false,false}; | |
| 109 | + | |
| 110 | +typedef std::vector<std::vector<std::string>> results_t; | |
| 111 | +typedef std::function<bool(results_t)> callback_t; | |
| 112 | + | |
| 113 | +class Option { | |
| 114 | +public: | |
| 115 | +protected: | |
| 116 | + // Config | |
| 117 | + std::string sname; | |
| 118 | + std::string lname; | |
| 119 | + Combiner opts; | |
| 120 | + std::string discription; | |
| 121 | + callback_t callback; | |
| 122 | + | |
| 123 | + // Results | |
| 124 | + results_t results {}; | |
| 125 | + | |
| 126 | + | |
| 127 | +public: | |
| 128 | + Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function<bool(results_t)> callback=[](results_t){return true;}) throw (BadNameString) : | |
| 129 | + opts(opts), discription(discription), callback(callback){ | |
| 130 | + std::tie(sname, lname) = split(name); | |
| 131 | + } | |
| 132 | + | |
| 133 | + /// Process the callback | |
| 134 | + bool run_callback() const { | |
| 135 | + return callback(results); | |
| 136 | + } | |
| 137 | + | |
| 138 | + /// Indistinguishible options are equal | |
| 139 | + bool operator== (const Option& other) const { | |
| 140 | + if(sname=="" && other.sname=="") | |
| 141 | + return lname==other.lname; | |
| 142 | + else if(lname=="" && other.lname=="") | |
| 143 | + return sname==other.sname; | |
| 144 | + else | |
| 145 | + return sname==other.sname || lname==other.lname; | |
| 146 | + } | |
| 147 | + | |
| 148 | + std::string getName() const { | |
| 149 | + if(sname=="") | |
| 150 | + return "--" + lname; | |
| 151 | + else if (lname=="") | |
| 152 | + return "-" + sname; | |
| 153 | + else | |
| 154 | + return "-" + sname + ", --" + lname; | |
| 155 | + } | |
| 156 | + | |
| 157 | + bool check_sname(const std::string& name) const { | |
| 158 | + return name == sname; | |
| 159 | + } | |
| 160 | + | |
| 161 | + bool check_lname(const std::string& name) const { | |
| 162 | + return name == lname; | |
| 163 | + } | |
| 164 | + | |
| 165 | + std::string get_sname() const { | |
| 166 | + return sname; | |
| 167 | + } | |
| 168 | + | |
| 169 | + std::string get_lname() const { | |
| 170 | + return lname; | |
| 171 | + } | |
| 172 | + | |
| 173 | + | |
| 174 | + int get_num() const { | |
| 175 | + return opts.num; | |
| 176 | + } | |
| 177 | + | |
| 178 | + void add_result(int r, std::string s) { | |
| 179 | + results.at(r).push_back(s); | |
| 180 | + } | |
| 181 | + int get_new() { | |
| 182 | + results.emplace_back(); | |
| 183 | + return results.size() - 1; | |
| 184 | + } | |
| 185 | + int count() { | |
| 186 | + return results.size(); | |
| 187 | + } | |
| 188 | + | |
| 189 | + std::string string() const { | |
| 190 | + std::string val = "Option: " + getName() + "\n" | |
| 191 | + + " " + discription + "\n" | |
| 192 | + + " ["; | |
| 193 | + for(const auto& item : results) { | |
| 194 | + if(&item!=&results[0]) | |
| 195 | + val+="],["; | |
| 196 | + val += join(item); | |
| 197 | + } | |
| 198 | + val += "]"; | |
| 199 | + return val; | |
| 200 | + } | |
| 201 | + | |
| 202 | +}; | |
| 203 | + | |
| 204 | +/// Creates a command line program, with very few defaults. | |
| 205 | +/** To use, create a new Program() instance with argc, argv, and a help discription. The templated | |
| 206 | +* add_option methods make it easy to prepare options. Remember to call `.start` before starting your | |
| 207 | +* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ | |
| 208 | +class App { | |
| 209 | +public: | |
| 210 | + | |
| 211 | +protected: | |
| 212 | + | |
| 213 | + std::string desc; | |
| 214 | + std::vector<Option> options; | |
| 215 | + std::vector<std::string> missing_options; | |
| 216 | + std::vector<std::string> positionals; | |
| 217 | + | |
| 218 | +public: | |
| 219 | + | |
| 220 | + | |
| 221 | + /// Create a new program. Pass in the same arguments as main(), along with a help string. | |
| 222 | + App(std::string discription) | |
| 223 | + : desc(discription) { | |
| 224 | + } | |
| 225 | + | |
| 226 | + /// Add an option, will automatically understand the type for common types. | |
| 227 | + /** To use, create a variable with the expected type, and pass it in after the name. | |
| 228 | + * After start is called, you can use count to see if the value was passed, and | |
| 229 | + * the value will be initialized properly. | |
| 230 | + * | |
| 231 | + * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|` | |
| 232 | + * together. The positional options take an optional number of arguments. | |
| 233 | + * | |
| 234 | + * For example, | |
| 235 | + * | |
| 236 | + * std::string filename | |
| 237 | + * program.add_option("filename", filename, "discription of filename"); | |
| 238 | + */ | |
| 239 | + void add_option( | |
| 240 | + std::string name, ///< The name, long,short | |
| 241 | + callback_t callback, ///< The callback | |
| 242 | + std::string discription="", ///< Discription string | |
| 243 | + Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) | |
| 244 | + ) { | |
| 245 | + | |
| 246 | + Option myopt{name, discription, opts, callback}; | |
| 247 | + if(std::find(std::begin(options), std::end(options), myopt) == std::end(options)) | |
| 248 | + options.push_back(myopt); | |
| 249 | + else | |
| 250 | + throw OptionAlreadyAdded(myopt.getName()); | |
| 251 | + | |
| 252 | + } | |
| 253 | + | |
| 254 | + /// Add option for string | |
| 255 | + void add_option( | |
| 256 | + std::string name, ///< The name, long,short | |
| 257 | + std::string &variable, ///< The variable to set | |
| 258 | + std::string discription="", ///< Discription string | |
| 259 | + Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) | |
| 260 | + ) { | |
| 261 | + | |
| 262 | + CLI::callback_t fun = [&variable](CLI::results_t res){ | |
| 263 | + if(res.size()!=1) { | |
| 264 | + return false; | |
| 265 | + } | |
| 266 | + if(res[0].size()!=1) { | |
| 267 | + return false; | |
| 268 | + } | |
| 269 | + variable = res[0][0]; | |
| 270 | + return true; | |
| 271 | + }; | |
| 272 | + | |
| 273 | + add_option(name, fun, discription, opts); | |
| 274 | + } | |
| 275 | + | |
| 276 | + | |
| 277 | + /// Add option for flag | |
| 278 | + void add_flag( | |
| 279 | + std::string name, ///< The name, short,long | |
| 280 | + std::string discription="" ///< Discription string | |
| 281 | + ) { | |
| 282 | + | |
| 283 | + CLI::callback_t fun = [](CLI::results_t res){ | |
| 284 | + return true; | |
| 285 | + }; | |
| 286 | + | |
| 287 | + add_option(name, fun, discription, NOTHING); | |
| 288 | + } | |
| 289 | + | |
| 290 | + /// Add option for flag | |
| 291 | + template<typename T> | |
| 292 | + typename std::enable_if<std::is_integral<T>::value, void>::type | |
| 293 | + add_flag( | |
| 294 | + std::string name, ///< The name, short,long | |
| 295 | + T &count, ///< A varaible holding the count | |
| 296 | + std::string discription="" ///< Discription string | |
| 297 | + ) { | |
| 298 | + | |
| 299 | + count = 0; | |
| 300 | + CLI::callback_t fun = [&count](CLI::results_t res){ | |
| 301 | + count = res.size(); | |
| 302 | + return true; | |
| 303 | + }; | |
| 304 | + | |
| 305 | + add_option(name, fun, discription, NOTHING); | |
| 306 | + } | |
| 307 | + | |
| 308 | + | |
| 309 | + /// Parses the command line - throws errors | |
| 310 | + void parse(int argc, char **argv) throw(CallForHelp, ParseError) { | |
| 311 | + std::vector<std::string> args; | |
| 312 | + for(int i=1; i<argc; i++) | |
| 313 | + args.emplace_back(argv[i]); | |
| 314 | + parse(args); | |
| 315 | + } | |
| 316 | + | |
| 317 | + void parse(std::vector<std::string> args) throw(CallForHelp, ParseError) { | |
| 318 | + std::reverse(args.begin(), args.end()); | |
| 319 | + | |
| 320 | + bool positional_only = false; | |
| 321 | + | |
| 322 | + while(args.size()>0) { | |
| 323 | + | |
| 324 | + if(args.back() == "--") { | |
| 325 | + args.pop_back(); | |
| 326 | + positional_only = true; | |
| 327 | + } else if(positional_only || (!_parse_long(args) && !_parse_short(args))) { | |
| 328 | + | |
| 329 | + logit("Positional: "+args.back()); | |
| 330 | + positionals.push_back(args.back()); | |
| 331 | + args.pop_back(); | |
| 332 | + } | |
| 333 | + } | |
| 334 | + | |
| 335 | + for(Option& opt : options) | |
| 336 | + if (opt.count() > 0) { | |
| 337 | + logit(opt.string()); | |
| 338 | + if(!opt.run_callback()) | |
| 339 | + logit("Failed"); | |
| 340 | + } | |
| 341 | + | |
| 342 | + //TODO: Check for false callbacks | |
| 343 | + } | |
| 344 | + | |
| 345 | + bool _parse_short(std::vector<std::string> &args) { | |
| 346 | + std::string current = args.back(); | |
| 347 | + std::smatch match; | |
| 348 | + | |
| 349 | + if(!std::regex_match(current, match, reg_short)) | |
| 350 | + return false; | |
| 351 | + | |
| 352 | + args.pop_back(); | |
| 353 | + std::string name = match[1]; | |
| 354 | + std::string rest = match[2]; | |
| 355 | + | |
| 356 | + logit("Working on short:"); | |
| 357 | + logit(name); | |
| 358 | + logit(rest); | |
| 359 | + | |
| 360 | + auto op = std::find_if(std::begin(options), std::end(options), [name](const Option &v){return v.check_sname(name);}); | |
| 361 | + | |
| 362 | + if(op == std::end(options)) { | |
| 363 | + missing_options.push_back("-" + op->get_sname()); | |
| 364 | + return true; | |
| 365 | + } | |
| 366 | + | |
| 367 | + int vnum = op->get_new(); | |
| 368 | + int num = op->get_num(); | |
| 369 | + | |
| 370 | + | |
| 371 | + if(rest != "" && num > 0) { | |
| 372 | + num--; | |
| 373 | + op->add_result(vnum, rest); | |
| 374 | + rest = ""; | |
| 375 | + } | |
| 376 | + | |
| 377 | + while(num>0) { | |
| 378 | + num--; | |
| 379 | + std::string current = args.back(); | |
| 380 | + logit("Adding: "+current); | |
| 381 | + args.pop_back(); | |
| 382 | + op->add_result(vnum,current); | |
| 383 | + if(args.size()==0) | |
| 384 | + return true; | |
| 385 | + } | |
| 386 | + | |
| 387 | + if(rest != "") { | |
| 388 | + rest = "-" + rest; | |
| 389 | + args.push_back(rest); | |
| 390 | + } | |
| 391 | + return true; | |
| 392 | + } | |
| 393 | + | |
| 394 | + bool _parse_long(std::vector<std::string> &args) { | |
| 395 | + std::string current = args.back(); | |
| 396 | + std::smatch match; | |
| 397 | + | |
| 398 | + if(!std::regex_match(current, match, reg_long)) | |
| 399 | + return false; | |
| 400 | + | |
| 401 | + args.pop_back(); | |
| 402 | + std::string name = match[1]; | |
| 403 | + std::string value = match[2]; | |
| 404 | + | |
| 405 | + | |
| 406 | + logit("Working on long:"); | |
| 407 | + logit(name); | |
| 408 | + logit(value); | |
| 409 | + | |
| 410 | + auto op = std::find_if(std::begin(options), std::end(options), [name](const Option &v){return v.check_lname(name);}); | |
| 411 | + | |
| 412 | + if(op == std::end(options)) { | |
| 413 | + missing_options.push_back("--" + op->get_lname()); | |
| 414 | + return true; | |
| 415 | + } | |
| 416 | + | |
| 417 | + | |
| 418 | + int vnum = op->get_new(); | |
| 419 | + int num = op->get_num(); | |
| 420 | + | |
| 421 | + | |
| 422 | + if(value != "") { | |
| 423 | + num--; | |
| 424 | + op->add_result(vnum, value); | |
| 425 | + } | |
| 426 | + | |
| 427 | + while(num>0) { | |
| 428 | + num--; | |
| 429 | + std::string current = args.back(); | |
| 430 | + args.pop_back(); | |
| 431 | + op->add_result(vnum,current); | |
| 432 | + if(args.size()==0) | |
| 433 | + return true; | |
| 434 | + } | |
| 435 | + return true; | |
| 436 | + } | |
| 437 | + | |
| 438 | + /// This must be called after the options are in but before the rest of the program. | |
| 439 | + /** Instead of throwing erros, causes the program to exit | |
| 440 | + * if -h or an invalid option is passed. */ | |
| 441 | + void start(int argc, char** argv) { | |
| 442 | + try { | |
| 443 | + parse(argc, argv); | |
| 444 | + } catch(const CallForHelp &e) { | |
| 445 | + std::cout << help() << std::endl; | |
| 446 | + exit(0); | |
| 447 | + } catch(const ParseError &e) { | |
| 448 | + std::cerr << "ERROR:" << std::endl; | |
| 449 | + std::cerr << e.what() << std::endl; | |
| 450 | + std::cerr << help() << std::endl; | |
| 451 | + exit(1); | |
| 452 | + } | |
| 453 | + | |
| 454 | + } | |
| 455 | + | |
| 456 | + /// Counts the number of times the given option was passed. | |
| 457 | + int count(std::string name) const { | |
| 458 | + return 0; | |
| 459 | + } | |
| 460 | + | |
| 461 | + std::string help() const {return "";} | |
| 462 | + | |
| 463 | + | |
| 464 | +}; | |
| 465 | +} | ... | ... |
Program.hpp
0 โ 100644
| 1 | +++ a/Program.hpp | |
| 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 | +}; | ... | ... |
ProgramOP.hpp
0 โ 100644
| 1 | +++ a/ProgramOP.hpp | |
| 1 | +#pragma once | |
| 2 | + | |
| 3 | +#include <string> | |
| 4 | +#include <functional> | |
| 5 | +#include <unordered_set> | |
| 6 | +#include <vector> | |
| 7 | +#include <iostream> | |
| 8 | + | |
| 9 | +#include "optionparser.h" | |
| 10 | + | |
| 11 | +// This is unreachable outside this file; you should not use Combiner directly | |
| 12 | +namespace { | |
| 13 | + | |
| 14 | +struct Combiner { | |
| 15 | + int positional; | |
| 16 | + bool required; | |
| 17 | + bool defaulted; | |
| 18 | + | |
| 19 | + /// Can be or-ed together | |
| 20 | + Combiner operator | (Combiner b) const { | |
| 21 | + Combiner self; | |
| 22 | + self.positional = positional + b.positional; | |
| 23 | + self.required = required || b.required; | |
| 24 | + self.defaulted = defaulted || b.defaulted; | |
| 25 | + return self; | |
| 26 | + } | |
| 27 | + | |
| 28 | + /// Call to give the number of arguments expected on cli | |
| 29 | + Combiner operator() (int n) const { | |
| 30 | + return Combiner{n, required, defaulted}; | |
| 31 | + } | |
| 32 | +}; | |
| 33 | +} | |
| 34 | + | |
| 35 | +/// Creates a command line program, with very few defaults. | |
| 36 | +/** To use, create a new Program() instance with argc, argv, and a help description. The templated | |
| 37 | +* add_option methods make it easy to prepare options. Remember to call `.start` before starting your | |
| 38 | +* program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ | |
| 39 | +class Program { | |
| 40 | +public: | |
| 41 | + static constexpr Combiner REQUIRED{0,true,false}; | |
| 42 | + static constexpr Combiner DEFAULT{0,false,true}; | |
| 43 | + static constexpr Combiner POSITIONAL{1,false,false}; | |
| 44 | + | |
| 45 | +protected: | |
| 46 | + std::vector<option::Descriptor> usage; | |
| 47 | + std::vector<std::function<bool(std::vector<std::string>)>> convert; /// Number is loc+2 | |
| 48 | + std::unordered_set<int> required; | |
| 49 | + std::vector<int> counts; | |
| 50 | + std::vector<std::string> random_name_store; | |
| 51 | + | |
| 52 | + int argc; | |
| 53 | + char **argv; | |
| 54 | + | |
| 55 | + /// Parses the command line (internal function) | |
| 56 | + void parse() { | |
| 57 | + usage.push_back(option::Descriptor{0, 0, nullptr, nullptr, nullptr, nullptr}); | |
| 58 | + | |
| 59 | + option::Stats stats(usage.data(), argc, argv); | |
| 60 | + std::vector<option::Option> options(stats.options_max); | |
| 61 | + std::vector<option::Option> buffer(stats.buffer_max); | |
| 62 | + option::Parser parse(usage.data(), argc, argv, options.data(), buffer.data()); | |
| 63 | + | |
| 64 | + if(parse.error()) { | |
| 65 | + std::cerr << "ERROR. See usage:" << std::endl; | |
| 66 | + option::printUsage(std::cerr, usage.data()); | |
| 67 | + exit(1); | |
| 68 | + } | |
| 69 | + | |
| 70 | + | |
| 71 | + if(options[1]){ | |
| 72 | + option::printUsage(std::cerr, usage.data()); | |
| 73 | + exit(0); | |
| 74 | + } | |
| 75 | + | |
| 76 | + bool found_unk = false; | |
| 77 | + for (option::Option* opt = options[0]; opt; opt = opt->next()) { | |
| 78 | + std::cout << "Unknown option: " << opt->name << "\n"; | |
| 79 | + found_unk = true; | |
| 80 | + } | |
| 81 | + if(found_unk) | |
| 82 | + exit(2); | |
| 83 | + | |
| 84 | + for(int i=2; i<convert.size()+2; i++) { | |
| 85 | + counts.emplace_back(options[i].count()); | |
| 86 | + if(options[i]) { | |
| 87 | + std::vector<std::string> opt_list; | |
| 88 | + for(option::Option* opt = options[i]; opt; opt = opt->next()) | |
| 89 | + opt_list.emplace_back(opt->arg ? opt->arg : ""); | |
| 90 | + convert.at(i-2)(opt_list); | |
| 91 | + } | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 95 | +public: | |
| 96 | + | |
| 97 | + /// Create a new program. Pass in the same arguments as main(), along with a help string. | |
| 98 | + Program(int argc, char** argv, std::string description) | |
| 99 | + : argc(argc), argv(argv) { | |
| 100 | + random_name_store.emplace_back(description); | |
| 101 | + usage.push_back(option::Descriptor{0, 0, "", "", option::Arg::None, random_name_store.back().c_str()}); | |
| 102 | + usage.push_back(option::Descriptor{1, 0, "h", "help", option::Arg::None, "Display usage and exit."}); | |
| 103 | + } | |
| 104 | + | |
| 105 | + | |
| 106 | + /// Add an option, will automatically understand the type for common types. | |
| 107 | + /** To use, create a variable with the expected type, and pass it in after the name. | |
| 108 | + * After start is called, you can use count to see if the value was passed, and | |
| 109 | + * the value will be initialized properly. | |
| 110 | + * | |
| 111 | + * Program::REQUIRED, Program::DEFAULT, and Program::POSITIONAL are options, and can be `|` | |
| 112 | + * together. The positional options take an optional number of arguments. | |
| 113 | + * | |
| 114 | + * For example, | |
| 115 | + * | |
| 116 | + * std::string filename | |
| 117 | + * program.add_option("filename", filename, "description of filename"); | |
| 118 | + */ | |
| 119 | + template<typename T> | |
| 120 | + void add_option( | |
| 121 | + std::string name, ///< The name, long,short | |
| 122 | + T &value, ///< The value | |
| 123 | + std::string description, ///< Description string | |
| 124 | + Combiner options ///< The options (REQUIRED, DEFAULT, POSITIONAL) | |
| 125 | + ) { | |
| 126 | + | |
| 127 | + int curr_num = convert.size(); | |
| 128 | + | |
| 129 | + if(options.required) | |
| 130 | + required.emplace(curr_num); | |
| 131 | + | |
| 132 | + random_name_store.emplace_back(name); | |
| 133 | + random_name_store.emplace_back(description); | |
| 134 | + | |
| 135 | + usage.push_back(option::Descriptor{(unsigned int) convert.size()+2, 0, "", random_name_store.at(random_name_store.size()-2).c_str(), option::Arg::Optional, random_name_store.back().c_str()}); | |
| 136 | + add_option_internal(value); | |
| 137 | + | |
| 138 | + if(options.positional!=0) | |
| 139 | + std::cout << "positional args not yet supported" << std::endl; | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + } | |
| 144 | + | |
| 145 | + /// Adds a flag style option | |
| 146 | + void add_flag(std::string name, std::string description, int& flag) { | |
| 147 | + counts.emplace_back(0); | |
| 148 | + random_name_store.emplace_back(name); | |
| 149 | + random_name_store.emplace_back(description); | |
| 150 | + usage.push_back(option::Descriptor{(unsigned int) convert.size()+2, 0, "", random_name_store.at(random_name_store.size()-2).c_str(), option::Arg::None, random_name_store.back().c_str()}); | |
| 151 | + convert.push_back([&flag](std::vector<std::string> v){flag = v.size(); return true;}); | |
| 152 | + } | |
| 153 | + | |
| 154 | + void add_option_internal(int &val) { | |
| 155 | + convert.push_back([&val](std::vector<std::string> v){val = std::stoi(v.at(0)); return v.size()==1;}); | |
| 156 | + } | |
| 157 | + | |
| 158 | + void add_option_internal(std::string &val) { | |
| 159 | + convert.push_back([&val](std::vector<std::string> v){val = v.at(0); return v.size()==1;}); | |
| 160 | + } | |
| 161 | + /// This must be called after the options are in but before the rest of the program. | |
| 162 | + /** Calls the Boost boost::program_options initialization, causing the program to exit | |
| 163 | + * if -h or an invalid option is passed. */ | |
| 164 | + void start() { | |
| 165 | + parse(); | |
| 166 | + } | |
| 167 | + | |
| 168 | + /// Counts the number of times the given option was passed. | |
| 169 | + int count(std::string name) const { | |
| 170 | + return 0; | |
| 171 | + } | |
| 172 | + | |
| 173 | + | |
| 174 | +}; | ... | ... |
try.cpp
0 โ 100644
| 1 | +++ a/try.cpp | |
| 1 | +#include "CLI.hpp" | |
| 2 | + | |
| 3 | + | |
| 4 | +int main (int argc, char** argv) { | |
| 5 | + | |
| 6 | + CLI::App app("K3Pi goofit fitter"); | |
| 7 | + | |
| 8 | + std::string file; | |
| 9 | + app.add_option("f,file", file, "File name"); | |
| 10 | + | |
| 11 | + int count; | |
| 12 | + app.add_flag<int>("c,count", count, "File name"); | |
| 13 | + | |
| 14 | + app.parse(argc, argv); | |
| 15 | + | |
| 16 | + std::cout << "Working on file: " << file << std::endl; | |
| 17 | + std::cout << "Working on count: " << count << std::endl; | |
| 18 | + | |
| 19 | + return 0; | |
| 20 | +} | ... | ... |
try1.cpp
0 โ 100644
| 1 | +++ a/try1.cpp | |
| 1 | +#include "CLI.hpp" | |
| 2 | + | |
| 3 | + | |
| 4 | +int main (int argc, char** argv) { | |
| 5 | + | |
| 6 | + std::vector<std::string> test_strings = {"a,boo", ",coo", "d,", "Q,this-is", "s", "single"}; | |
| 7 | + | |
| 8 | + for(std::string name : test_strings) { | |
| 9 | + std::string one; | |
| 10 | + std::string two; | |
| 11 | + | |
| 12 | + std::tie(one, two) = CLI::split(name); | |
| 13 | + std::cout << one << ", " << two << std::endl; | |
| 14 | + } | |
| 15 | + | |
| 16 | + std::vector<std::string> test_fails= {"a,,boo", "a,b,c", "ssd,sfd", "-a", "", ",", "one two"}; | |
| 17 | + | |
| 18 | + for(std::string name : test_fails) { | |
| 19 | + std::string one; | |
| 20 | + std::string two; | |
| 21 | + | |
| 22 | + try { | |
| 23 | + std::tie(one, two) = CLI::split(name); | |
| 24 | + std::cout << "Failed to catch: " << name << std::endl; | |
| 25 | + return 1; | |
| 26 | + } catch (const CLI::BadNameString &e) { | |
| 27 | + std::cout << "Hooray! Caught: " << name << std::endl; | |
| 28 | + } | |
| 29 | + } | |
| 30 | + | |
| 31 | + | |
| 32 | +} | ... | ... |