Commit ce3081299825e6aaf5529b13f0b248a86b394722
1 parent
2db15139
Adding better parser
Showing
3 changed files
with
358 additions
and
85 deletions
.gitignore
0 → 100644
| 1 | +a.out* | ... | ... |
CLI.hpp
| ... | ... | @@ -9,12 +9,15 @@ |
| 9 | 9 | #include <algorithm> |
| 10 | 10 | #include <sstream> |
| 11 | 11 | #include <type_traits> |
| 12 | +#include <unordered_set> | |
| 13 | +#include <iomanip> | |
| 14 | +#include <numeric> | |
| 12 | 15 | |
| 13 | 16 | // This is unreachable outside this file; you should not use Combiner directly |
| 14 | 17 | namespace { |
| 15 | 18 | |
| 16 | 19 | void logit(std::string output) { |
| 17 | - std::cout << output << std::endl; | |
| 20 | + std::cout << "\033[1;31m" << output << "\033[0m" << std::endl; | |
| 18 | 21 | } |
| 19 | 22 | |
| 20 | 23 | template <typename T> |
| ... | ... | @@ -30,8 +33,6 @@ std::string join(const T& v, std::string delim = ",") { |
| 30 | 33 | } |
| 31 | 34 | |
| 32 | 35 | |
| 33 | - | |
| 34 | - | |
| 35 | 36 | struct Combiner { |
| 36 | 37 | int num; |
| 37 | 38 | bool positional; |
| ... | ... | @@ -41,7 +42,7 @@ struct Combiner { |
| 41 | 42 | /// Can be or-ed together |
| 42 | 43 | Combiner operator | (Combiner b) const { |
| 43 | 44 | Combiner self; |
| 44 | - self.num = num + b.num; | |
| 45 | + self.num = std::min(num, b.num) == -1 ? -1 : std::max(num, b.num); | |
| 45 | 46 | self.positional = positional || b.positional; |
| 46 | 47 | self.required = required || b.required; |
| 47 | 48 | self.defaulted = defaulted || b.defaulted; |
| ... | ... | @@ -56,40 +57,66 @@ struct Combiner { |
| 56 | 57 | return *this | b; |
| 57 | 58 | } |
| 58 | 59 | }; |
| 59 | - | |
| 60 | - | |
| 61 | 60 | } |
| 62 | 61 | |
| 62 | + | |
| 63 | 63 | namespace CLI { |
| 64 | 64 | |
| 65 | -class BadNameString : public std::runtime_error { | |
| 65 | +class Error : public std::runtime_error { | |
| 66 | 66 | public: |
| 67 | - BadNameString(std::string name) : runtime_error("Failed to parse: " + name) {}; | |
| 67 | + Error(std::string name) : runtime_error(name) {}; | |
| 68 | 68 | }; |
| 69 | 69 | |
| 70 | -class CallForHelp : public std::runtime_error { | |
| 70 | +class BadNameString : public Error { | |
| 71 | 71 | public: |
| 72 | - CallForHelp() : runtime_error("Help option passed") {}; | |
| 72 | + BadNameString(std::string name) : Error("Failed to parse: " + name) {}; | |
| 73 | 73 | }; |
| 74 | 74 | |
| 75 | -class ParseError : public std::runtime_error { | |
| 75 | +class CallForHelp : public Error { | |
| 76 | 76 | public: |
| 77 | - ParseError(std::string info="") : runtime_error(info) {}; | |
| 77 | + CallForHelp() : Error("Help option passed") {}; | |
| 78 | 78 | }; |
| 79 | 79 | |
| 80 | -class OptionAlreadyAdded : public std::runtime_error { | |
| 80 | +class ParseError : public Error { | |
| 81 | 81 | public: |
| 82 | - OptionAlreadyAdded(std::string name) : runtime_error("Already added:" + name) {}; | |
| 82 | + ParseError(std::string info="") : Error(info) {}; | |
| 83 | 83 | }; |
| 84 | 84 | |
| 85 | +class OptionAlreadyAdded : public Error { | |
| 86 | +public: | |
| 87 | + OptionAlreadyAdded(std::string name) : Error("Already added:" + name) {}; | |
| 88 | +}; | |
| 85 | 89 | |
| 90 | +class OptionNotFound : public Error { | |
| 91 | +public: | |
| 92 | + OptionNotFound(std::string name) : Error(name) {}; | |
| 93 | +}; | |
| 94 | + | |
| 95 | +class RequiredError : public Error { | |
| 96 | +public: | |
| 97 | + RequiredError(std::string name="") : Error(name) {}; | |
| 98 | +}; | |
| 99 | + | |
| 100 | +class ExtraPositionalsError : public Error { | |
| 101 | +public: | |
| 102 | + ExtraPositionalsError(std::string name="") : Error(name) {}; | |
| 103 | +}; | |
| 104 | + | |
| 105 | +class HorribleError : public Error { | |
| 106 | +public: | |
| 107 | + HorribleError(std::string name="") : Error("You should never see this error! "+name) {}; | |
| 108 | +}; | |
| 109 | +class IncorrectConstruction : public Error { | |
| 110 | +public: | |
| 111 | + IncorrectConstruction(std::string name="") : Error(name) {}; | |
| 112 | +}; | |
| 86 | 113 | |
| 87 | 114 | const std::regex reg_split{R"regex((?:([a-zA-Z0-9]?)(?:,|$)|^)([a-zA-Z0-9][a-zA-Z0-9_\-]*)?)regex"}; |
| 88 | 115 | const std::regex reg_short{R"regex(-([^-])(.*))regex"}; |
| 89 | 116 | const std::regex reg_long{R"regex(--([^-^=][^=]*)=?(.*))regex"}; |
| 90 | 117 | |
| 91 | 118 | |
| 92 | -std::tuple<std::string, std::string> split(std::string fullname) throw(BadNameString) { | |
| 119 | +std::tuple<std::string, std::string> split(std::string fullname) { | |
| 93 | 120 | |
| 94 | 121 | std::smatch match; |
| 95 | 122 | if (std::regex_match(fullname, match, reg_split)) { |
| ... | ... | @@ -101,11 +128,12 @@ std::tuple<std::string, std::string> split(std::string fullname) throw(BadNameSt |
| 101 | 128 | } else throw BadNameString(fullname); |
| 102 | 129 | } |
| 103 | 130 | |
| 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}; | |
| 131 | +const Combiner NOTHING {0, false,false,false}; | |
| 132 | +const Combiner REQUIRED {1, false,true, false}; | |
| 133 | +const Combiner DEFAULT {1, false,false,true}; | |
| 134 | +const Combiner POSITIONAL{1, true, false,false}; | |
| 135 | +const Combiner ARGS {1, false,false,false}; | |
| 136 | +const Combiner UNLIMITED {-1,false,false,false}; | |
| 109 | 137 | |
| 110 | 138 | typedef std::vector<std::vector<std::string>> results_t; |
| 111 | 139 | typedef std::function<bool(results_t)> callback_t; |
| ... | ... | @@ -125,11 +153,27 @@ protected: |
| 125 | 153 | |
| 126 | 154 | |
| 127 | 155 | public: |
| 128 | - Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function<bool(results_t)> callback=[](results_t){return true;}) throw (BadNameString) : | |
| 156 | + Option(std::string name, std::string discription = "", Combiner opts=NOTHING, std::function<bool(results_t)> callback=[](results_t){return true;}) : | |
| 129 | 157 | opts(opts), discription(discription), callback(callback){ |
| 130 | 158 | std::tie(sname, lname) = split(name); |
| 131 | 159 | } |
| 132 | 160 | |
| 161 | + bool required() const { | |
| 162 | + return opts.required; | |
| 163 | + } | |
| 164 | + | |
| 165 | + int expected() const { | |
| 166 | + return opts.num; | |
| 167 | + } | |
| 168 | + | |
| 169 | + bool positional() const { | |
| 170 | + return opts.positional; | |
| 171 | + } | |
| 172 | + | |
| 173 | + bool defaulted() const { | |
| 174 | + return opts.defaulted; | |
| 175 | + } | |
| 176 | + | |
| 133 | 177 | /// Process the callback |
| 134 | 178 | bool run_callback() const { |
| 135 | 179 | return callback(results); |
| ... | ... | @@ -145,7 +189,7 @@ public: |
| 145 | 189 | return sname==other.sname || lname==other.lname; |
| 146 | 190 | } |
| 147 | 191 | |
| 148 | - std::string getName() const { | |
| 192 | + std::string get_name() const { | |
| 149 | 193 | if(sname=="") |
| 150 | 194 | return "--" + lname; |
| 151 | 195 | else if (lname=="") |
| ... | ... | @@ -154,6 +198,10 @@ public: |
| 154 | 198 | return "-" + sname + ", --" + lname; |
| 155 | 199 | } |
| 156 | 200 | |
| 201 | + bool check_name(const std::string& name) const { | |
| 202 | + return name == sname || name == lname || name == sname + "," + lname; | |
| 203 | + } | |
| 204 | + | |
| 157 | 205 | bool check_sname(const std::string& name) const { |
| 158 | 206 | return name == sname; |
| 159 | 207 | } |
| ... | ... | @@ -170,24 +218,23 @@ public: |
| 170 | 218 | return lname; |
| 171 | 219 | } |
| 172 | 220 | |
| 173 | - | |
| 174 | - int get_num() const { | |
| 175 | - return opts.num; | |
| 176 | - } | |
| 177 | - | |
| 178 | 221 | void add_result(int r, std::string s) { |
| 222 | + logit("Adding result: " + s); | |
| 179 | 223 | results.at(r).push_back(s); |
| 180 | 224 | } |
| 181 | 225 | int get_new() { |
| 182 | 226 | results.emplace_back(); |
| 183 | 227 | return results.size() - 1; |
| 184 | 228 | } |
| 185 | - int count() { | |
| 186 | - return results.size(); | |
| 229 | + int count() const { | |
| 230 | + int out = 0; | |
| 231 | + for(const std::vector<std::string>& v : results) | |
| 232 | + out += v.size(); | |
| 233 | + return out; | |
| 187 | 234 | } |
| 188 | 235 | |
| 189 | 236 | std::string string() const { |
| 190 | - std::string val = "Option: " + getName() + "\n" | |
| 237 | + std::string val = "Option: " + get_name() + "\n" | |
| 191 | 238 | + " " + discription + "\n" |
| 192 | 239 | + " ["; |
| 193 | 240 | for(const auto& item : results) { |
| ... | ... | @@ -199,8 +246,58 @@ public: |
| 199 | 246 | return val; |
| 200 | 247 | } |
| 201 | 248 | |
| 249 | + int help_len() const { | |
| 250 | + return std::min((int) get_name().length(), 40); | |
| 251 | + } | |
| 252 | + | |
| 253 | + std::string help(int len = 0) const { | |
| 254 | + std::stringstream out; | |
| 255 | + out << std::setw(len) << std::left << get_name() << discription; | |
| 256 | + return out.str(); | |
| 257 | + } | |
| 258 | + | |
| 202 | 259 | }; |
| 203 | 260 | |
| 261 | + | |
| 262 | +template<typename T> | |
| 263 | +typename std::enable_if<std::is_integral<T>::value, bool>::type | |
| 264 | +lexical_cast(std::string input, T& output) { | |
| 265 | + logit("Int lexical cast " + input); | |
| 266 | + try{ | |
| 267 | + output = (T) std::stoll(input); | |
| 268 | + return true; | |
| 269 | + } catch (std::invalid_argument) { | |
| 270 | + return false; | |
| 271 | + } catch (std::out_of_range) { | |
| 272 | + return false; | |
| 273 | + } | |
| 274 | +} | |
| 275 | + | |
| 276 | +template<typename T> | |
| 277 | +typename std::enable_if<std::is_floating_point<T>::value, bool>::type | |
| 278 | +lexical_cast(std::string input, T& output) { | |
| 279 | + logit("Floating lexical cast " + input); | |
| 280 | + try{ | |
| 281 | + output = (T) std::stold(input); | |
| 282 | + return true; | |
| 283 | + } catch (std::invalid_argument) { | |
| 284 | + return false; | |
| 285 | + } catch (std::out_of_range) { | |
| 286 | + return false; | |
| 287 | + } | |
| 288 | +} | |
| 289 | + | |
| 290 | +// String and similar | |
| 291 | +template<typename T> | |
| 292 | +bool lexical_cast(std::string input, T& output) { | |
| 293 | + logit("Direct lexical cast: " + input); | |
| 294 | + output = input; | |
| 295 | + return true; | |
| 296 | +} | |
| 297 | + | |
| 298 | +enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; | |
| 299 | + | |
| 300 | + | |
| 204 | 301 | /// Creates a command line program, with very few defaults. |
| 205 | 302 | /** To use, create a new Program() instance with argc, argv, and a help discription. The templated |
| 206 | 303 | * add_option methods make it easy to prepare options. Remember to call `.start` before starting your |
| ... | ... | @@ -210,19 +307,31 @@ public: |
| 210 | 307 | |
| 211 | 308 | protected: |
| 212 | 309 | |
| 213 | - std::string desc; | |
| 310 | + std::string name; | |
| 311 | + std::string discription; | |
| 214 | 312 | std::vector<Option> options; |
| 215 | 313 | std::vector<std::string> missing_options; |
| 216 | 314 | std::vector<std::string> positionals; |
| 315 | + std::vector<App> subcommands; | |
| 316 | + bool parsed{false}; | |
| 317 | + App* subcommand = nullptr; | |
| 217 | 318 | |
| 218 | 319 | public: |
| 219 | 320 | |
| 220 | 321 | |
| 221 | 322 | /// Create a new program. Pass in the same arguments as main(), along with a help string. |
| 222 | - App(std::string discription) | |
| 223 | - : desc(discription) { | |
| 323 | + App(std::string discription="") | |
| 324 | + : discription(discription) { | |
| 325 | + | |
| 326 | + add_flag("h,help", "Print this help message and exit"); | |
| 327 | + | |
| 328 | + } | |
| 329 | + | |
| 330 | + App& add_subcommand(std::string name) { | |
| 331 | + subcommands.emplace_back(); | |
| 332 | + subcommands.back().name = name; | |
| 333 | + return subcommands.back(); | |
| 224 | 334 | } |
| 225 | - | |
| 226 | 335 | /// Add an option, will automatically understand the type for common types. |
| 227 | 336 | /** To use, create a variable with the expected type, and pass it in after the name. |
| 228 | 337 | * After start is called, you can use count to see if the value was passed, and |
| ... | ... | @@ -242,23 +351,27 @@ public: |
| 242 | 351 | std::string discription="", ///< Discription string |
| 243 | 352 | Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) |
| 244 | 353 | ) { |
| 245 | - | |
| 246 | 354 | Option myopt{name, discription, opts, callback}; |
| 247 | 355 | if(std::find(std::begin(options), std::end(options), myopt) == std::end(options)) |
| 248 | 356 | options.push_back(myopt); |
| 249 | 357 | else |
| 250 | - throw OptionAlreadyAdded(myopt.getName()); | |
| 358 | + throw OptionAlreadyAdded(myopt.get_name()); | |
| 251 | 359 | |
| 252 | 360 | } |
| 253 | 361 | |
| 254 | 362 | /// Add option for string |
| 255 | - void add_option( | |
| 363 | + template<typename T> | |
| 364 | + typename std::enable_if<!std::is_array<T>::value, void>::type | |
| 365 | + add_option( | |
| 256 | 366 | std::string name, ///< The name, long,short |
| 257 | - std::string &variable, ///< The variable to set | |
| 367 | + T &variable, ///< The variable to set | |
| 258 | 368 | std::string discription="", ///< Discription string |
| 259 | 369 | Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) |
| 260 | 370 | ) { |
| 261 | 371 | |
| 372 | + | |
| 373 | + if(opts.num!=1) | |
| 374 | + throw IncorrectConstruction("Must have ARGS(1) or be a vector."); | |
| 262 | 375 | CLI::callback_t fun = [&variable](CLI::results_t res){ |
| 263 | 376 | if(res.size()!=1) { |
| 264 | 377 | return false; |
| ... | ... | @@ -266,8 +379,33 @@ public: |
| 266 | 379 | if(res[0].size()!=1) { |
| 267 | 380 | return false; |
| 268 | 381 | } |
| 269 | - variable = res[0][0]; | |
| 270 | - return true; | |
| 382 | + return lexical_cast(res[0][0], variable); | |
| 383 | + }; | |
| 384 | + | |
| 385 | + add_option(name, fun, discription, opts); | |
| 386 | + } | |
| 387 | + | |
| 388 | + /// Add option for vector of results | |
| 389 | + template<typename T> | |
| 390 | + void add_option( | |
| 391 | + std::string name, ///< The name, long,short | |
| 392 | + std::vector<T> &variable, ///< The variable to set | |
| 393 | + std::string discription="", ///< Discription string | |
| 394 | + Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) | |
| 395 | + ) { | |
| 396 | + | |
| 397 | + if(opts.num==0) | |
| 398 | + throw IncorrectConstruction("Must have ARGS(1) or be a vector."); | |
| 399 | + CLI::callback_t fun = [&variable](CLI::results_t res){ | |
| 400 | + bool retval = true; | |
| 401 | + int count = 0; | |
| 402 | + variable.clear(); | |
| 403 | + for(const auto &a : res) | |
| 404 | + for(const auto &b : a) { | |
| 405 | + variable.emplace_back(); | |
| 406 | + retval &= lexical_cast(b, variable.back()); | |
| 407 | + } | |
| 408 | + return count != 0 && retval; | |
| 271 | 409 | }; |
| 272 | 410 | |
| 273 | 411 | add_option(name, fun, discription, opts); |
| ... | ... | @@ -279,7 +417,6 @@ public: |
| 279 | 417 | std::string name, ///< The name, short,long |
| 280 | 418 | std::string discription="" ///< Discription string |
| 281 | 419 | ) { |
| 282 | - | |
| 283 | 420 | CLI::callback_t fun = [](CLI::results_t res){ |
| 284 | 421 | return true; |
| 285 | 422 | }; |
| ... | ... | @@ -298,141 +435,244 @@ public: |
| 298 | 435 | |
| 299 | 436 | count = 0; |
| 300 | 437 | CLI::callback_t fun = [&count](CLI::results_t res){ |
| 301 | - count = res.size(); | |
| 438 | + count = (T) res.size(); | |
| 302 | 439 | return true; |
| 303 | 440 | }; |
| 304 | 441 | |
| 305 | 442 | add_option(name, fun, discription, NOTHING); |
| 306 | 443 | } |
| 444 | + | |
| 445 | + /// Add set of options | |
| 446 | + template<typename T> | |
| 447 | + void add_set( | |
| 448 | + std::string name, ///< The name, short,long | |
| 449 | + T &member, ///< The selected member of the set | |
| 450 | + std::unordered_set<T> options, ///< The set of posibilities | |
| 451 | + std::string discription="", ///< Discription string | |
| 452 | + Combiner opts=ARGS ///< The options (REQUIRED, DEFAULT, POSITIONAL, ARGS()) | |
| 453 | + ) { | |
| 454 | + | |
| 455 | + CLI::callback_t fun = [&member, options](CLI::results_t res){ | |
| 456 | + if(res.size()!=1) { | |
| 457 | + return false; | |
| 458 | + } | |
| 459 | + if(res[0].size()!=1) { | |
| 460 | + return false; | |
| 461 | + } | |
| 462 | + bool retval = lexical_cast(res[0][0], member); | |
| 463 | + if(!retval) | |
| 464 | + return false; | |
| 465 | + return std::find(std::begin(options), std::end(options), retval) != std::end(options); | |
| 466 | + }; | |
| 467 | + | |
| 468 | + add_option(name, fun, discription, opts); | |
| 469 | + } | |
| 470 | + | |
| 307 | 471 | |
| 308 | 472 | |
| 309 | 473 | /// Parses the command line - throws errors |
| 310 | - void parse(int argc, char **argv) throw(CallForHelp, ParseError) { | |
| 474 | + void parse(int argc, char **argv) { | |
| 311 | 475 | std::vector<std::string> args; |
| 312 | 476 | for(int i=1; i<argc; i++) |
| 313 | 477 | args.emplace_back(argv[i]); |
| 314 | 478 | parse(args); |
| 315 | 479 | } |
| 316 | 480 | |
| 317 | - void parse(std::vector<std::string> args) throw(CallForHelp, ParseError) { | |
| 481 | + void parse(std::vector<std::string> args) { | |
| 482 | + parsed = true; | |
| 318 | 483 | std::reverse(args.begin(), args.end()); |
| 319 | 484 | |
| 320 | 485 | bool positional_only = false; |
| 321 | 486 | |
| 322 | 487 | while(args.size()>0) { |
| 323 | 488 | |
| 324 | - if(args.back() == "--") { | |
| 489 | + logit(join(args)); | |
| 490 | + | |
| 491 | + Classifer classifer = positional_only ? Classifer::NONE : _recognize(args.back()); | |
| 492 | + switch(classifer) { | |
| 493 | + case Classifer::POSITIONAL_MARK: | |
| 325 | 494 | args.pop_back(); |
| 326 | 495 | positional_only = true; |
| 327 | - } else if(positional_only || (!_parse_long(args) && !_parse_short(args))) { | |
| 328 | - | |
| 496 | + break; | |
| 497 | + case Classifer::SUBCOMMAND: | |
| 498 | + _parse_subcommand(args); | |
| 499 | + break; | |
| 500 | + case Classifer::LONG: | |
| 501 | + _parse_long(args); | |
| 502 | + break; | |
| 503 | + case Classifer::SHORT: | |
| 504 | + _parse_short(args); | |
| 505 | + break; | |
| 506 | + case Classifer::NONE: | |
| 329 | 507 | logit("Positional: "+args.back()); |
| 330 | 508 | positionals.push_back(args.back()); |
| 331 | 509 | args.pop_back(); |
| 332 | 510 | } |
| 333 | 511 | } |
| 334 | 512 | |
| 335 | - for(Option& opt : options) | |
| 513 | + if (count("help") > 0) { | |
| 514 | + throw CallForHelp(); | |
| 515 | + } | |
| 516 | + | |
| 517 | + bool success = true; | |
| 518 | + for(Option& opt : options) { | |
| 519 | + while (opt.positional() && opt.count() < opt.expected() && positionals.size() > 0) { | |
| 520 | + opt.get_new(); | |
| 521 | + opt.add_result(0, positionals.back()); | |
| 522 | + positionals.pop_back(); | |
| 523 | + } | |
| 524 | + if (opt.required() && opt.count() < opt.expected()) | |
| 525 | + throw RequiredError(opt.get_name()); | |
| 336 | 526 | if (opt.count() > 0) { |
| 337 | - logit(opt.string()); | |
| 338 | - if(!opt.run_callback()) | |
| 339 | - logit("Failed"); | |
| 527 | + success &= opt.run_callback(); | |
| 340 | 528 | } |
| 341 | 529 | |
| 342 | - //TODO: Check for false callbacks | |
| 530 | + } | |
| 531 | + if(!success) | |
| 532 | + throw ParseError(); | |
| 533 | + if(positionals.size()>0) | |
| 534 | + throw ExtraPositionalsError(); | |
| 535 | + } | |
| 536 | + | |
| 537 | + void _parse_subcommand(std::vector<std::string> &args) { | |
| 538 | + for(App &com : subcommands) { | |
| 539 | + if(com.name == args.back()){ | |
| 540 | + args.pop_back(); | |
| 541 | + com.parse(args); | |
| 542 | + subcommand = &com; | |
| 543 | + return; | |
| 544 | + } | |
| 545 | + } | |
| 546 | + throw HorribleError("Subcommand"); | |
| 343 | 547 | } |
| 344 | 548 | |
| 345 | - bool _parse_short(std::vector<std::string> &args) { | |
| 549 | + void _parse_short(std::vector<std::string> &args) { | |
| 346 | 550 | std::string current = args.back(); |
| 347 | 551 | std::smatch match; |
| 348 | 552 | |
| 349 | 553 | if(!std::regex_match(current, match, reg_short)) |
| 350 | - return false; | |
| 554 | + throw HorribleError("Subcommand"); | |
| 351 | 555 | |
| 352 | 556 | args.pop_back(); |
| 353 | 557 | std::string name = match[1]; |
| 354 | 558 | std::string rest = match[2]; |
| 355 | 559 | |
| 356 | - logit("Working on short:"); | |
| 357 | - logit(name); | |
| 358 | - logit(rest); | |
| 560 | + logit("Working on short: " + name + " (" + rest + ")"); | |
| 359 | 561 | |
| 360 | 562 | auto op = std::find_if(std::begin(options), std::end(options), [name](const Option &v){return v.check_sname(name);}); |
| 361 | 563 | |
| 362 | 564 | if(op == std::end(options)) { |
| 363 | 565 | missing_options.push_back("-" + op->get_sname()); |
| 364 | - return true; | |
| 566 | + return; | |
| 365 | 567 | } |
| 366 | 568 | |
| 367 | 569 | int vnum = op->get_new(); |
| 368 | - int num = op->get_num(); | |
| 369 | - | |
| 370 | - | |
| 371 | - if(rest != "" && num > 0) { | |
| 372 | - num--; | |
| 570 | + int num = op->expected(); | |
| 571 | + | |
| 572 | + if(num == 0) | |
| 573 | + op->add_result(vnum, ""); | |
| 574 | + else if(rest!="") { | |
| 575 | + if (num > 0) | |
| 576 | + num--; | |
| 373 | 577 | op->add_result(vnum, rest); |
| 374 | 578 | rest = ""; |
| 375 | 579 | } |
| 376 | 580 | |
| 377 | - while(num>0) { | |
| 581 | + | |
| 582 | + if(num == -1) { | |
| 583 | + std::string current = args.back(); | |
| 584 | + while(_recognize(current) == Classifer::NONE) { | |
| 585 | + std::string current = args.back(); | |
| 586 | + args.pop_back(); | |
| 587 | + op->add_result(vnum,current); | |
| 588 | + if(args.size()==0) | |
| 589 | + return; | |
| 590 | + | |
| 591 | + } | |
| 592 | + } else while(num>0) { | |
| 378 | 593 | num--; |
| 379 | 594 | std::string current = args.back(); |
| 380 | 595 | logit("Adding: "+current); |
| 381 | 596 | args.pop_back(); |
| 382 | 597 | op->add_result(vnum,current); |
| 383 | 598 | if(args.size()==0) |
| 384 | - return true; | |
| 599 | + return; | |
| 385 | 600 | } |
| 386 | 601 | |
| 387 | 602 | if(rest != "") { |
| 388 | 603 | rest = "-" + rest; |
| 389 | 604 | args.push_back(rest); |
| 390 | 605 | } |
| 391 | - return true; | |
| 392 | 606 | } |
| 393 | 607 | |
| 394 | - bool _parse_long(std::vector<std::string> &args) { | |
| 608 | + Classifer _recognize(std::string current) const { | |
| 609 | + if(current == "--") | |
| 610 | + return Classifer::POSITIONAL_MARK; | |
| 611 | + for(const App &com : subcommands) { | |
| 612 | + if(com.name == current) | |
| 613 | + return Classifer::SUBCOMMAND; | |
| 614 | + } | |
| 615 | + if(std::regex_match(current, reg_long)) | |
| 616 | + return Classifer::LONG; | |
| 617 | + if(std::regex_match(current, reg_short)) | |
| 618 | + return Classifer::SHORT; | |
| 619 | + return Classifer::NONE; | |
| 620 | + | |
| 621 | + | |
| 622 | + } | |
| 623 | + | |
| 624 | + void _parse_long(std::vector<std::string> &args) { | |
| 395 | 625 | std::string current = args.back(); |
| 396 | 626 | std::smatch match; |
| 397 | 627 | |
| 398 | 628 | if(!std::regex_match(current, match, reg_long)) |
| 399 | - return false; | |
| 400 | - | |
| 629 | + throw HorribleError("Long"); | |
| 630 | + | |
| 401 | 631 | args.pop_back(); |
| 402 | 632 | std::string name = match[1]; |
| 403 | 633 | std::string value = match[2]; |
| 404 | 634 | |
| 405 | 635 | |
| 406 | - logit("Working on long:"); | |
| 407 | - logit(name); | |
| 408 | - logit(value); | |
| 636 | + logit("Working on long: " + name + " (" + value + ")"); | |
| 409 | 637 | |
| 410 | 638 | auto op = std::find_if(std::begin(options), std::end(options), [name](const Option &v){return v.check_lname(name);}); |
| 411 | 639 | |
| 412 | 640 | if(op == std::end(options)) { |
| 413 | 641 | missing_options.push_back("--" + op->get_lname()); |
| 414 | - return true; | |
| 642 | + return; | |
| 415 | 643 | } |
| 416 | 644 | |
| 417 | 645 | |
| 418 | 646 | int vnum = op->get_new(); |
| 419 | - int num = op->get_num(); | |
| 647 | + int num = op->expected(); | |
| 420 | 648 | |
| 421 | 649 | |
| 422 | 650 | if(value != "") { |
| 423 | - num--; | |
| 651 | + if(num!=-1) num--; | |
| 424 | 652 | op->add_result(vnum, value); |
| 653 | + } else if (num == 0) { | |
| 654 | + op->add_result(vnum, ""); | |
| 425 | 655 | } |
| 426 | 656 | |
| 427 | - while(num>0) { | |
| 657 | + if(num == -1) { | |
| 658 | + std::string current = args.back(); | |
| 659 | + while(_recognize(current) == Classifer::NONE) { | |
| 660 | + std::string current = args.back(); | |
| 661 | + args.pop_back(); | |
| 662 | + op->add_result(vnum,current); | |
| 663 | + if(args.size()==0) | |
| 664 | + return; | |
| 665 | + | |
| 666 | + } | |
| 667 | + } else while(num>0) { | |
| 428 | 668 | num--; |
| 429 | 669 | std::string current = args.back(); |
| 430 | 670 | args.pop_back(); |
| 431 | 671 | op->add_result(vnum,current); |
| 432 | 672 | if(args.size()==0) |
| 433 | - return true; | |
| 673 | + return; | |
| 434 | 674 | } |
| 435 | - return true; | |
| 675 | + return; | |
| 436 | 676 | } |
| 437 | 677 | |
| 438 | 678 | /// This must be called after the options are in but before the rest of the program. |
| ... | ... | @@ -444,7 +684,7 @@ public: |
| 444 | 684 | } catch(const CallForHelp &e) { |
| 445 | 685 | std::cout << help() << std::endl; |
| 446 | 686 | exit(0); |
| 447 | - } catch(const ParseError &e) { | |
| 687 | + } catch(const Error &e) { | |
| 448 | 688 | std::cerr << "ERROR:" << std::endl; |
| 449 | 689 | std::cerr << e.what() << std::endl; |
| 450 | 690 | std::cerr << help() << std::endl; |
| ... | ... | @@ -455,11 +695,43 @@ public: |
| 455 | 695 | |
| 456 | 696 | /// Counts the number of times the given option was passed. |
| 457 | 697 | int count(std::string name) const { |
| 458 | - return 0; | |
| 698 | + for(const Option &opt : options) { | |
| 699 | + logit("Computing: " + opt.get_name()); | |
| 700 | + if(opt.check_name(name)) { | |
| 701 | + logit("Counting: " + opt.get_name() + std::to_string(opt.count())); | |
| 702 | + return opt.count(); | |
| 703 | + } | |
| 704 | + } | |
| 705 | + throw OptionNotFound(name); | |
| 459 | 706 | } |
| 460 | 707 | |
| 461 | - std::string help() const {return "";} | |
| 708 | + std::string help() const { | |
| 709 | + std::stringstream out; | |
| 710 | + out << discription << std::endl; | |
| 711 | + int len = std::accumulate(std::begin(options), std::end(options), 0, | |
| 712 | + [](int val, const Option &opt){ | |
| 713 | + return std::max(opt.help_len(), val);}); | |
| 714 | + for(const Option &opt : options) { | |
| 715 | + out << opt.help(len) << std::endl; | |
| 716 | + } | |
| 717 | + if(subcommands.size()> 0) { | |
| 718 | + out << "Subcommands: "; | |
| 719 | + for(const App &com : subcommands) { | |
| 720 | + if(&com != &subcommands[0]) | |
| 721 | + out << ", "; | |
| 722 | + std::cout << com.get_name(); | |
| 723 | + } | |
| 724 | + out << std::endl; | |
| 725 | + } | |
| 726 | + return out.str(); | |
| 727 | + } | |
| 462 | 728 | |
| 729 | + App* get_subcommand() { | |
| 730 | + return subcommand; | |
| 731 | + } | |
| 463 | 732 | |
| 733 | + std::string get_name() const { | |
| 734 | + return name; | |
| 735 | + } | |
| 464 | 736 | }; |
| 465 | 737 | } | ... | ... |
try.cpp
| ... | ... | @@ -9,12 +9,12 @@ int main (int argc, char** argv) { |
| 9 | 9 | app.add_option("f,file", file, "File name"); |
| 10 | 10 | |
| 11 | 11 | int count; |
| 12 | - app.add_flag<int>("c,count", count, "File name"); | |
| 12 | + app.add_flag("c,count", count, "File name"); | |
| 13 | 13 | |
| 14 | - app.parse(argc, argv); | |
| 14 | + app.start(argc, argv); | |
| 15 | 15 | |
| 16 | - std::cout << "Working on file: " << file << std::endl; | |
| 17 | - std::cout << "Working on count: " << count << std::endl; | |
| 16 | + std::cout << "Working on file: " << file << ", direct count: " << app.count("file") << std::endl; | |
| 17 | + std::cout << "Working on count: " << count << ", direct count: " << app.count("count") << std::endl; | |
| 18 | 18 | |
| 19 | 19 | return 0; |
| 20 | 20 | } | ... | ... |