diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..86b2889 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# 2.0 + +## Changed + +* `Options::parse` returns a ParseResult rather than storing the parse + result internally. diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 69ed92f..fd83822 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -34,6 +34,7 @@ THE SOFTWARE. #include #include #include +#include #include #include @@ -893,6 +894,77 @@ namespace cxxopts std::vector options; }; + class ParseResult + { + public: + + template + ParseResult( + Iterator, + Iterator, + std::vector, + int&, char**&); + + int + count(const std::string& o) const + { + auto iter = m_options.find(o); + if (iter == m_options.end()) + { + return 0; + } + + return iter->second->count(); + } + + const OptionDetails& + operator[](const std::string& option) const + { + auto iter = m_options.find(option); + + if (iter == m_options.end()) + { + throw option_not_present_exception(option); + } + + return *iter->second; + } + + private: + + void + parse(int& argc, char**& argv); + + void + add_to_option(const std::string& option, const std::string& arg); + + bool + consume_positional(std::string a); + + void + parse_option + ( + std::shared_ptr value, + const std::string& name, + const std::string& arg = "" + ); + + void + checked_parse_arg + ( + int argc, + char* argv[], + int& current, + std::shared_ptr value, + const std::string& name + ); + + std::unordered_map> m_options; + std::vector m_positional; + std::vector::iterator m_next_positional; + std::unordered_set m_positional_set; + }; + class Options { public: @@ -912,7 +984,7 @@ namespace cxxopts return *this; } - void + ParseResult parse(int& argc, char**& argv); OptionAdder @@ -929,31 +1001,6 @@ namespace cxxopts std::string arg_help ); - int - count(const std::string& o) const - { - auto iter = m_options.find(o); - if (iter == m_options.end()) - { - return 0; - } - - return iter->second->count(); - } - - const OptionDetails& - operator[](const std::string& option) const - { - auto iter = m_options.find(option); - - if (iter == m_options.end()) - { - throw option_not_present_exception(option); - } - - return *iter->second; - } - //parse positional arguments into the given option void parse_positional(std::string option); @@ -979,30 +1026,6 @@ namespace cxxopts std::shared_ptr details ); - bool - consume_positional(std::string a); - - void - add_to_option(const std::string& option, const std::string& arg); - - void - parse_option - ( - std::shared_ptr value, - const std::string& name, - const std::string& arg = "" - ); - - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - std::shared_ptr value, - const std::string& name - ); - String help_one_group(const std::string& group) const; @@ -1053,24 +1076,6 @@ namespace cxxopts std::string m_group; }; - // A helper function for setting required arguments - inline - void - check_required - ( - const Options& options, - const std::vector& required - ) - { - for (auto& r : required) - { - if (options.count(r) == 0) - { - throw option_required_exception(r); - } - } - } - namespace { constexpr int OPTION_LONGEST = 30; @@ -1188,6 +1193,18 @@ namespace cxxopts } } +template +ParseResult::ParseResult(Iterator begin, Iterator end, + std::vector positional, + int& argc, char**& argv +) +: m_options(begin, end) +, m_positional(std::move(positional)) +, m_next_positional(m_positional.begin()) +{ + parse(argc, argv); +} + inline OptionAdder Options::add_options(std::string group) @@ -1255,7 +1272,7 @@ OptionAdder::operator() inline void -Options::parse_option +ParseResult::parse_option ( std::shared_ptr value, const std::string& /*name*/, @@ -1267,7 +1284,7 @@ Options::parse_option inline void -Options::checked_parse_arg +ParseResult::checked_parse_arg ( int argc, char* argv[], @@ -1303,7 +1320,7 @@ Options::checked_parse_arg inline void -Options::add_to_option(const std::string& option, const std::string& arg) +ParseResult::add_to_option(const std::string& option, const std::string& arg) { auto iter = m_options.find(option); @@ -1317,7 +1334,7 @@ Options::add_to_option(const std::string& option, const std::string& arg) inline bool -Options::consume_positional(std::string a) +ParseResult::consume_positional(std::string a) { while (m_next_positional != m_positional.end()) { @@ -1368,9 +1385,17 @@ Options::parse_positional(std::vector options) } inline -void +ParseResult Options::parse(int& argc, char**& argv) { + ParseResult result(m_options.begin(), m_options.end(), m_positional, argc, argv); + return result; +} + +inline +void +ParseResult::parse(int& argc, char**& argv) +{ int current = 1; int nextKeep = 1; diff --git a/src/example.cpp b/src/example.cpp index 44271b4..5678dd7 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -63,9 +63,9 @@ int main(int argc, char* argv[]) options.parse_positional({"input", "output", "positional"}); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - if (options.count("help")) + if (result.count("help")) { std::cout << options.help({"", "Group"}) << std::endl; exit(0); @@ -73,18 +73,18 @@ int main(int argc, char* argv[]) if (apple) { - std::cout << "Saw option ‘a’ " << options.count("a") << " times " << + std::cout << "Saw option ‘a’ " << result.count("a") << " times " << std::endl; } - if (options.count("b")) + if (result.count("b")) { std::cout << "Saw option ‘b’" << std::endl; } - if (options.count("f")) + if (result.count("f")) { - auto& ff = options["f"].as>(); + auto& ff = result["f"].as>(); std::cout << "Files" << std::endl; for (const auto& f : ff) { @@ -92,36 +92,36 @@ int main(int argc, char* argv[]) } } - if (options.count("input")) + if (result.count("input")) { - std::cout << "Input = " << options["input"].as() + std::cout << "Input = " << result["input"].as() << std::endl; } - if (options.count("output")) + if (result.count("output")) { - std::cout << "Output = " << options["output"].as() + std::cout << "Output = " << result["output"].as() << std::endl; } - if (options.count("positional")) + if (result.count("positional")) { std::cout << "Positional = {"; - auto& v = options["positional"].as>(); + auto& v = result["positional"].as>(); for (const auto& s : v) { std::cout << s << ", "; } std::cout << "}" << std::endl; } - if (options.count("int")) + if (result.count("int")) { - std::cout << "int = " << options["int"].as() << std::endl; + std::cout << "int = " << result["int"].as() << std::endl; } - if (options.count("float")) + if (result.count("float")) { - std::cout << "float = " << options["float"].as() << std::endl; + std::cout << "float = " << result["float"].as() << std::endl; } std::cout << "Arguments remain = " << argc << std::endl; diff --git a/test/options.cpp b/test/options.cpp index 1446929..6d11427 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -71,17 +71,17 @@ TEST_CASE("Basic options", "[options]") char** actual_argv = argv.argv(); auto argc = argv.argc(); - options.parse(argc, actual_argv); - - CHECK(options.count("long") == 1); - CHECK(options.count("s") == 1); - CHECK(options.count("value") == 1); - CHECK(options.count("a") == 1); - CHECK(options["value"].as() == "value"); - CHECK(options["a"].as() == "b"); - CHECK(options.count("6") == 1); - CHECK(options.count("p") == 2); - CHECK(options.count("space") == 2); + auto result = options.parse(argc, actual_argv); + + CHECK(result.count("long") == 1); + CHECK(result.count("s") == 1); + CHECK(result.count("value") == 1); + CHECK(result.count("a") == 1); + CHECK(result["value"].as() == "value"); + CHECK(result["a"].as() == "b"); + CHECK(result.count("6") == 1); + CHECK(result.count("p") == 2); + CHECK(result.count("space") == 2); } TEST_CASE("Short options", "[options]") @@ -96,36 +96,15 @@ TEST_CASE("Short options", "[options]") auto actual_argv = argv.argv(); auto argc = argv.argc(); - options.parse(argc, actual_argv); + auto result = options.parse(argc, actual_argv); - CHECK(options.count("a") == 1); - CHECK(options["a"].as() == "value"); + CHECK(result.count("a") == 1); + CHECK(result["a"].as() == "value"); REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), cxxopts::invalid_option_format_error); } -TEST_CASE("Required arguments", "[options]") -{ - cxxopts::Options options("required", " - test required options"); - options.add_options() - ("one", "one option") - ("two", "second option") - ; - - Argv argv({ - "required", - "--one" - }); - - auto aargv = argv.argv(); - auto argc = argv.argc(); - - options.parse(argc, aargv); - REQUIRE_THROWS_AS(cxxopts::check_required(options, {"two"}), - cxxopts::option_required_exception); -} - TEST_CASE("No positional", "[positional]") { cxxopts::Options options("test_no_positional", @@ -135,7 +114,7 @@ TEST_CASE("No positional", "[positional]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); REQUIRE(argc == 4); CHECK(strcmp(argv[1], "a") == 0); @@ -158,7 +137,7 @@ TEST_CASE("All positional", "[positional]") options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); REQUIRE(argc == 1); REQUIRE(positional.size() == 3); @@ -186,14 +165,14 @@ TEST_CASE("Some positional explicit", "[positional]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); CHECK(argc == 1); - CHECK(options.count("output")); - CHECK(options["input"].as() == "b"); - CHECK(options["output"].as() == "a"); + CHECK(result.count("output")); + CHECK(result["input"].as() == "b"); + CHECK(result["output"].as() == "a"); - auto& positional = options["positional"].as>(); + auto& positional = result["positional"].as>(); REQUIRE(positional.size() == 2); CHECK(positional[0] == "c"); @@ -234,10 +213,10 @@ TEST_CASE("Empty with implicit value", "[implicit]") char** argv = av.argv(); auto argc = av.argc(); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("implicit") == 1); - REQUIRE(options["implicit"].as() == ""); + REQUIRE(result.count("implicit") == 1); + REQUIRE(result["implicit"].as() == ""); } TEST_CASE("Integers", "[options]") @@ -252,11 +231,11 @@ TEST_CASE("Integers", "[options]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("positional") == 6); + REQUIRE(result.count("positional") == 6); - auto& positional = options["positional"].as>(); + auto& positional = result["positional"].as>(); CHECK(positional[0] == 5); CHECK(positional[1] == 6); CHECK(positional[2] == -6); @@ -294,11 +273,11 @@ TEST_CASE("Integer bounds", "[integer]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("positional") == 5); + REQUIRE(result.count("positional") == 5); - auto& positional = options["positional"].as>(); + auto& positional = result["positional"].as>(); CHECK(positional[0] == 127); CHECK(positional[1] == -128); CHECK(positional[2] == 0x7f); @@ -350,14 +329,14 @@ TEST_CASE("Floats", "[options]") auto argc = av.argc(); options.parse_positional("positional"); - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(options.count("double") == 1); - REQUIRE(options.count("positional") == 4); + REQUIRE(result.count("double") == 1); + REQUIRE(result.count("positional") == 4); - CHECK(options["double"].as() == 0.5); + CHECK(result["double"].as() == 0.5); - auto& positional = options["positional"].as>(); + auto& positional = result["positional"].as>(); CHECK(positional[0] == 4); CHECK(positional[1] == -4); CHECK(positional[2] == 1.5e6);