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 | +} |