From 8010e06952858276d31ddf4e90497c87a919aca6 Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Wed, 18 Oct 2017 18:27:13 +1100 Subject: [PATCH] Move parse result to an immutable object This is far from ideal, but it's the first step in moving the parse result to an immutable object so that the parser can be reused. --- CHANGELOG.md | 6 ++ include/cxxopts.hpp | 183 +++++++++++++++++++++++++------------------- src/example.cpp | 32 ++++---- test/options.cpp | 89 ++++++++------------- 4 files changed, 160 insertions(+), 150 deletions(-) create mode 100644 CHANGELOG.md 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,41 +894,16 @@ namespace cxxopts std::vector options; }; - class Options + class ParseResult { public: - Options(std::string program, std::string help_string = "") - : m_program(std::move(program)) - , m_help_string(toLocalString(std::move(help_string))) - , m_positional_help("positional parameters") - , m_next_positional(m_positional.end()) - { - } - - Options& - positional_help(std::string help_text) - { - m_positional_help = std::move(help_text); - return *this; - } - - void - parse(int& argc, char**& argv); - - OptionAdder - add_options(std::string group = ""); - - void - add_option - ( - const std::string& group, - const std::string& s, - const std::string& l, - std::string desc, - std::shared_ptr value, - std::string arg_help - ); + template + ParseResult( + Iterator, + Iterator, + std::vector, + int&, char**&); int count(const std::string& o) const @@ -954,6 +930,77 @@ namespace cxxopts 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: + + Options(std::string program, std::string help_string = "") + : m_program(std::move(program)) + , m_help_string(toLocalString(std::move(help_string))) + , m_positional_help("positional parameters") + , m_next_positional(m_positional.end()) + { + } + + Options& + positional_help(std::string help_text) + { + m_positional_help = std::move(help_text); + return *this; + } + + ParseResult + parse(int& argc, char**& argv); + + OptionAdder + add_options(std::string group = ""); + + void + add_option + ( + const std::string& group, + const std::string& s, + const std::string& l, + std::string desc, + std::shared_ptr value, + std::string arg_help + ); + //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,8 +1385,16 @@ 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; 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); + auto result = 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); + 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);