Commit b935fcd12998c01841d72b0a62e1542708c3d347
1 parent
ce308129
Fixes for subcommands, still can cause allocate errors
Showing
2 changed files
with
70 additions
and
41 deletions
CLI.hpp
| ... | ... | @@ -17,7 +17,7 @@ |
| 17 | 17 | namespace { |
| 18 | 18 | |
| 19 | 19 | void logit(std::string output) { |
| 20 | - std::cout << "std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;33[1;31m" << output << "std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;33[0m" << std::endl; | |
| 20 | + //std::cout << "std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;33[1;31m" << output << "std::cout << "\033[1;31m" << output << "\033[0m" << std::endl;33[0m" << std::endl; | |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | 23 | template <typename T> |
| ... | ... | @@ -64,51 +64,51 @@ namespace CLI { |
| 64 | 64 | |
| 65 | 65 | class Error : public std::runtime_error { |
| 66 | 66 | public: |
| 67 | - Error(std::string name) : runtime_error(name) {}; | |
| 67 | + Error(std::string parent, std::string name) : runtime_error(parent + ": " + name) {} | |
| 68 | 68 | }; |
| 69 | 69 | |
| 70 | 70 | class BadNameString : public Error { |
| 71 | 71 | public: |
| 72 | - BadNameString(std::string name) : Error("Failed to parse: " + name) {}; | |
| 72 | + BadNameString(std::string name) : Error("BadNameString", name) {} | |
| 73 | 73 | }; |
| 74 | 74 | |
| 75 | 75 | class CallForHelp : public Error { |
| 76 | 76 | public: |
| 77 | - CallForHelp() : Error("Help option passed") {}; | |
| 77 | + CallForHelp() : Error("CallForHelp","") {} | |
| 78 | 78 | }; |
| 79 | 79 | |
| 80 | 80 | class ParseError : public Error { |
| 81 | 81 | public: |
| 82 | - ParseError(std::string info="") : Error(info) {}; | |
| 82 | + ParseError(std::string name) : Error("ParseError", name) {} | |
| 83 | 83 | }; |
| 84 | 84 | |
| 85 | 85 | class OptionAlreadyAdded : public Error { |
| 86 | 86 | public: |
| 87 | - OptionAlreadyAdded(std::string name) : Error("Already added:" + name) {}; | |
| 87 | + OptionAlreadyAdded(std::string name) : Error("OptionAlreadyAdded", name) {} | |
| 88 | 88 | }; |
| 89 | 89 | |
| 90 | 90 | class OptionNotFound : public Error { |
| 91 | 91 | public: |
| 92 | - OptionNotFound(std::string name) : Error(name) {}; | |
| 92 | + OptionNotFound(std::string name) : Error("OptionNotFound", name) {} | |
| 93 | 93 | }; |
| 94 | 94 | |
| 95 | 95 | class RequiredError : public Error { |
| 96 | 96 | public: |
| 97 | - RequiredError(std::string name="") : Error(name) {}; | |
| 97 | + RequiredError(std::string name) : Error("RequiredError", name) {} | |
| 98 | 98 | }; |
| 99 | 99 | |
| 100 | 100 | class ExtraPositionalsError : public Error { |
| 101 | 101 | public: |
| 102 | - ExtraPositionalsError(std::string name="") : Error(name) {}; | |
| 102 | + ExtraPositionalsError(std::string name) : Error("ExtraPositionalsError", name) {} | |
| 103 | 103 | }; |
| 104 | 104 | |
| 105 | 105 | class HorribleError : public Error { |
| 106 | 106 | public: |
| 107 | - HorribleError(std::string name="") : Error("You should never see this error! "+name) {}; | |
| 107 | + HorribleError(std::string name) : Error("HorribleError", "(You should never see this error) " + name) {} | |
| 108 | 108 | }; |
| 109 | 109 | class IncorrectConstruction : public Error { |
| 110 | 110 | public: |
| 111 | - IncorrectConstruction(std::string name="") : Error(name) {}; | |
| 111 | + IncorrectConstruction(std::string name) : Error("IncorrectConstruction", name) {} | |
| 112 | 112 | }; |
| 113 | 113 | |
| 114 | 114 | const std::regex reg_split{R"regex((?:([a-zA-Z0-9]?)(?:,|$)|^)([a-zA-Z0-9][a-zA-Z0-9_\-]*)?)regex"}; |
| ... | ... | @@ -297,22 +297,21 @@ bool lexical_cast(std::string input, T& output) { |
| 297 | 297 | |
| 298 | 298 | enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; |
| 299 | 299 | |
| 300 | +class App; | |
| 300 | 301 | |
| 301 | 302 | /// Creates a command line program, with very few defaults. |
| 302 | 303 | /** To use, create a new Program() instance with argc, argv, and a help discription. The templated |
| 303 | 304 | * add_option methods make it easy to prepare options. Remember to call `.start` before starting your |
| 304 | 305 | * program, so that the options can be evaluated and the help option doesn't accidentally run your program. */ |
| 305 | 306 | class App { |
| 306 | -public: | |
| 307 | - | |
| 308 | 307 | protected: |
| 309 | 308 | |
| 310 | 309 | std::string name; |
| 311 | - std::string discription; | |
| 310 | + std::string prog_discription; | |
| 312 | 311 | std::vector<Option> options; |
| 313 | 312 | std::vector<std::string> missing_options; |
| 314 | 313 | std::vector<std::string> positionals; |
| 315 | - std::vector<App> subcommands; | |
| 314 | + std::vector<App*> subcommands; | |
| 316 | 315 | bool parsed{false}; |
| 317 | 316 | App* subcommand = nullptr; |
| 318 | 317 | |
| ... | ... | @@ -320,16 +319,22 @@ public: |
| 320 | 319 | |
| 321 | 320 | |
| 322 | 321 | /// Create a new program. Pass in the same arguments as main(), along with a help string. |
| 323 | - App(std::string discription="") | |
| 324 | - : discription(discription) { | |
| 322 | + App(std::string prog_discription="") | |
| 323 | + : prog_discription(prog_discription) { | |
| 325 | 324 | |
| 326 | 325 | add_flag("h,help", "Print this help message and exit"); |
| 327 | 326 | |
| 328 | 327 | } |
| 329 | 328 | |
| 330 | - App& add_subcommand(std::string name) { | |
| 331 | - subcommands.emplace_back(); | |
| 332 | - subcommands.back().name = name; | |
| 329 | + ~App() { | |
| 330 | + for(App* app : subcommands) | |
| 331 | + delete app; | |
| 332 | + } | |
| 333 | + | |
| 334 | + App* add_subcommand(std::string name, std::string discription="") { | |
| 335 | + subcommands.push_back(new App(discription)); | |
| 336 | + subcommands.back()->name = name; | |
| 337 | + logit(subcommands.back()->name); | |
| 333 | 338 | return subcommands.back(); |
| 334 | 339 | } |
| 335 | 340 | /// Add an option, will automatically understand the type for common types. |
| ... | ... | @@ -514,7 +519,6 @@ public: |
| 514 | 519 | throw CallForHelp(); |
| 515 | 520 | } |
| 516 | 521 | |
| 517 | - bool success = true; | |
| 518 | 522 | for(Option& opt : options) { |
| 519 | 523 | while (opt.positional() && opt.count() < opt.expected() && positionals.size() > 0) { |
| 520 | 524 | opt.get_new(); |
| ... | ... | @@ -524,22 +528,21 @@ public: |
| 524 | 528 | if (opt.required() && opt.count() < opt.expected()) |
| 525 | 529 | throw RequiredError(opt.get_name()); |
| 526 | 530 | if (opt.count() > 0) { |
| 527 | - success &= opt.run_callback(); | |
| 531 | + if(!opt.run_callback()) | |
| 532 | + throw ParseError(opt.get_name()); | |
| 528 | 533 | } |
| 529 | 534 | |
| 530 | 535 | } |
| 531 | - if(!success) | |
| 532 | - throw ParseError(); | |
| 533 | 536 | if(positionals.size()>0) |
| 534 | - throw ExtraPositionalsError(); | |
| 537 | + throw ExtraPositionalsError("[" + join(positionals) + "]"); | |
| 535 | 538 | } |
| 536 | 539 | |
| 537 | 540 | void _parse_subcommand(std::vector<std::string> &args) { |
| 538 | - for(App &com : subcommands) { | |
| 539 | - if(com.name == args.back()){ | |
| 541 | + for(App *com : subcommands) { | |
| 542 | + if(com->name == args.back()){ | |
| 540 | 543 | args.pop_back(); |
| 541 | - com.parse(args); | |
| 542 | - subcommand = &com; | |
| 544 | + com->parse(args); | |
| 545 | + subcommand = com; | |
| 543 | 546 | return; |
| 544 | 547 | } |
| 545 | 548 | } |
| ... | ... | @@ -551,7 +554,7 @@ public: |
| 551 | 554 | std::smatch match; |
| 552 | 555 | |
| 553 | 556 | if(!std::regex_match(current, match, reg_short)) |
| 554 | - throw HorribleError("Subcommand"); | |
| 557 | + throw HorribleError("Short"); | |
| 555 | 558 | |
| 556 | 559 | args.pop_back(); |
| 557 | 560 | std::string name = match[1]; |
| ... | ... | @@ -608,8 +611,8 @@ public: |
| 608 | 611 | Classifer _recognize(std::string current) const { |
| 609 | 612 | if(current == "--") |
| 610 | 613 | return Classifer::POSITIONAL_MARK; |
| 611 | - for(const App &com : subcommands) { | |
| 612 | - if(com.name == current) | |
| 614 | + for(const App* com : subcommands) { | |
| 615 | + if(com->name == current) | |
| 613 | 616 | return Classifer::SUBCOMMAND; |
| 614 | 617 | } |
| 615 | 618 | if(std::regex_match(current, reg_long)) |
| ... | ... | @@ -685,7 +688,7 @@ public: |
| 685 | 688 | std::cout << help() << std::endl; |
| 686 | 689 | exit(0); |
| 687 | 690 | } catch(const Error &e) { |
| 688 | - std::cerr << "ERROR:" << std::endl; | |
| 691 | + std::cerr << "ERROR: "; | |
| 689 | 692 | std::cerr << e.what() << std::endl; |
| 690 | 693 | std::cerr << help() << std::endl; |
| 691 | 694 | exit(1); |
| ... | ... | @@ -707,21 +710,22 @@ public: |
| 707 | 710 | |
| 708 | 711 | std::string help() const { |
| 709 | 712 | std::stringstream out; |
| 710 | - out << discription << std::endl; | |
| 713 | + if(name != "") | |
| 714 | + out << "Subcommand: " << name << " "; | |
| 715 | + out << prog_discription << std::endl; | |
| 711 | 716 | int len = std::accumulate(std::begin(options), std::end(options), 0, |
| 712 | 717 | [](int val, const Option &opt){ |
| 713 | - return std::max(opt.help_len(), val);}); | |
| 718 | + return std::max(opt.help_len()+1, val);}); | |
| 714 | 719 | for(const Option &opt : options) { |
| 715 | 720 | out << opt.help(len) << std::endl; |
| 716 | 721 | } |
| 717 | 722 | 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 | + out << "Subcommands:" << std::endl; | |
| 724 | + int max = std::accumulate(std::begin(subcommands), std::end(subcommands), 0, | |
| 725 | + [](int i, const App* j){return std::max(i, (int) j->get_name().length()+1);}); | |
| 726 | + for(const App* com : subcommands) { | |
| 727 | + out << std::setw(max) << std::left << com->get_name() << " " << com->prog_discription << std::endl; | |
| 723 | 728 | } |
| 724 | - out << std::endl; | |
| 725 | 729 | } |
| 726 | 730 | return out.str(); |
| 727 | 731 | } | ... | ... |
try2.cpp
0 → 100644
| 1 | +#include "CLI.hpp" | |
| 2 | + | |
| 3 | + | |
| 4 | +int main (int argc, char** argv) { | |
| 5 | + | |
| 6 | + CLI::App app("K3Pi goofit fitter"); | |
| 7 | + CLI::App* start = app.add_subcommand("start"); | |
| 8 | + CLI::App* stop = app.add_subcommand("stop"); | |
| 9 | + | |
| 10 | + std::cout << app.help(); | |
| 11 | + std::string file; | |
| 12 | + start->add_option("f,file", file, "File name"); | |
| 13 | + | |
| 14 | + int count; | |
| 15 | + stop->add_flag("c,count", count, "File name"); | |
| 16 | + | |
| 17 | + app.start(argc, argv); | |
| 18 | + | |
| 19 | + std::cout << "Working on file: " << file << ", direct count: " << start->count("file") << std::endl; | |
| 20 | + std::cout << "Working on count: " << count << ", direct count: " << stop->count("count") << std::endl; | |
| 21 | + if(app.get_subcommand() != nullptr) | |
| 22 | + std::cout << "Subcommand:" << app.get_subcommand()->get_name() << std::endl; | |
| 23 | + | |
| 24 | + return 0; | |
| 25 | +} | ... | ... |