Commit 8010e06952858276d31ddf4e90497c87a919aca6
1 parent
d8458a8c
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.
Showing
4 changed files
with
155 additions
and
145 deletions
CHANGELOG.md
0 → 100644
include/cxxopts.hpp
| ... | ... | @@ -34,6 +34,7 @@ THE SOFTWARE. |
| 34 | 34 | #include <regex> |
| 35 | 35 | #include <sstream> |
| 36 | 36 | #include <string> |
| 37 | +#include <unordered_map> | |
| 37 | 38 | #include <unordered_set> |
| 38 | 39 | #include <vector> |
| 39 | 40 | |
| ... | ... | @@ -893,6 +894,77 @@ namespace cxxopts |
| 893 | 894 | std::vector<HelpOptionDetails> options; |
| 894 | 895 | }; |
| 895 | 896 | |
| 897 | + class ParseResult | |
| 898 | + { | |
| 899 | + public: | |
| 900 | + | |
| 901 | + template <typename Iterator> | |
| 902 | + ParseResult( | |
| 903 | + Iterator, | |
| 904 | + Iterator, | |
| 905 | + std::vector<std::string>, | |
| 906 | + int&, char**&); | |
| 907 | + | |
| 908 | + int | |
| 909 | + count(const std::string& o) const | |
| 910 | + { | |
| 911 | + auto iter = m_options.find(o); | |
| 912 | + if (iter == m_options.end()) | |
| 913 | + { | |
| 914 | + return 0; | |
| 915 | + } | |
| 916 | + | |
| 917 | + return iter->second->count(); | |
| 918 | + } | |
| 919 | + | |
| 920 | + const OptionDetails& | |
| 921 | + operator[](const std::string& option) const | |
| 922 | + { | |
| 923 | + auto iter = m_options.find(option); | |
| 924 | + | |
| 925 | + if (iter == m_options.end()) | |
| 926 | + { | |
| 927 | + throw option_not_present_exception(option); | |
| 928 | + } | |
| 929 | + | |
| 930 | + return *iter->second; | |
| 931 | + } | |
| 932 | + | |
| 933 | + private: | |
| 934 | + | |
| 935 | + void | |
| 936 | + parse(int& argc, char**& argv); | |
| 937 | + | |
| 938 | + void | |
| 939 | + add_to_option(const std::string& option, const std::string& arg); | |
| 940 | + | |
| 941 | + bool | |
| 942 | + consume_positional(std::string a); | |
| 943 | + | |
| 944 | + void | |
| 945 | + parse_option | |
| 946 | + ( | |
| 947 | + std::shared_ptr<OptionDetails> value, | |
| 948 | + const std::string& name, | |
| 949 | + const std::string& arg = "" | |
| 950 | + ); | |
| 951 | + | |
| 952 | + void | |
| 953 | + checked_parse_arg | |
| 954 | + ( | |
| 955 | + int argc, | |
| 956 | + char* argv[], | |
| 957 | + int& current, | |
| 958 | + std::shared_ptr<OptionDetails> value, | |
| 959 | + const std::string& name | |
| 960 | + ); | |
| 961 | + | |
| 962 | + std::unordered_map<std::string, std::shared_ptr<OptionDetails>> m_options; | |
| 963 | + std::vector<std::string> m_positional; | |
| 964 | + std::vector<std::string>::iterator m_next_positional; | |
| 965 | + std::unordered_set<std::string> m_positional_set; | |
| 966 | + }; | |
| 967 | + | |
| 896 | 968 | class Options |
| 897 | 969 | { |
| 898 | 970 | public: |
| ... | ... | @@ -912,7 +984,7 @@ namespace cxxopts |
| 912 | 984 | return *this; |
| 913 | 985 | } |
| 914 | 986 | |
| 915 | - void | |
| 987 | + ParseResult | |
| 916 | 988 | parse(int& argc, char**& argv); |
| 917 | 989 | |
| 918 | 990 | OptionAdder |
| ... | ... | @@ -929,31 +1001,6 @@ namespace cxxopts |
| 929 | 1001 | std::string arg_help |
| 930 | 1002 | ); |
| 931 | 1003 | |
| 932 | - int | |
| 933 | - count(const std::string& o) const | |
| 934 | - { | |
| 935 | - auto iter = m_options.find(o); | |
| 936 | - if (iter == m_options.end()) | |
| 937 | - { | |
| 938 | - return 0; | |
| 939 | - } | |
| 940 | - | |
| 941 | - return iter->second->count(); | |
| 942 | - } | |
| 943 | - | |
| 944 | - const OptionDetails& | |
| 945 | - operator[](const std::string& option) const | |
| 946 | - { | |
| 947 | - auto iter = m_options.find(option); | |
| 948 | - | |
| 949 | - if (iter == m_options.end()) | |
| 950 | - { | |
| 951 | - throw option_not_present_exception(option); | |
| 952 | - } | |
| 953 | - | |
| 954 | - return *iter->second; | |
| 955 | - } | |
| 956 | - | |
| 957 | 1004 | //parse positional arguments into the given option |
| 958 | 1005 | void |
| 959 | 1006 | parse_positional(std::string option); |
| ... | ... | @@ -979,30 +1026,6 @@ namespace cxxopts |
| 979 | 1026 | std::shared_ptr<OptionDetails> details |
| 980 | 1027 | ); |
| 981 | 1028 | |
| 982 | - bool | |
| 983 | - consume_positional(std::string a); | |
| 984 | - | |
| 985 | - void | |
| 986 | - add_to_option(const std::string& option, const std::string& arg); | |
| 987 | - | |
| 988 | - void | |
| 989 | - parse_option | |
| 990 | - ( | |
| 991 | - std::shared_ptr<OptionDetails> value, | |
| 992 | - const std::string& name, | |
| 993 | - const std::string& arg = "" | |
| 994 | - ); | |
| 995 | - | |
| 996 | - void | |
| 997 | - checked_parse_arg | |
| 998 | - ( | |
| 999 | - int argc, | |
| 1000 | - char* argv[], | |
| 1001 | - int& current, | |
| 1002 | - std::shared_ptr<OptionDetails> value, | |
| 1003 | - const std::string& name | |
| 1004 | - ); | |
| 1005 | - | |
| 1006 | 1029 | String |
| 1007 | 1030 | help_one_group(const std::string& group) const; |
| 1008 | 1031 | |
| ... | ... | @@ -1053,24 +1076,6 @@ namespace cxxopts |
| 1053 | 1076 | std::string m_group; |
| 1054 | 1077 | }; |
| 1055 | 1078 | |
| 1056 | - // A helper function for setting required arguments | |
| 1057 | - inline | |
| 1058 | - void | |
| 1059 | - check_required | |
| 1060 | - ( | |
| 1061 | - const Options& options, | |
| 1062 | - const std::vector<std::string>& required | |
| 1063 | - ) | |
| 1064 | - { | |
| 1065 | - for (auto& r : required) | |
| 1066 | - { | |
| 1067 | - if (options.count(r) == 0) | |
| 1068 | - { | |
| 1069 | - throw option_required_exception(r); | |
| 1070 | - } | |
| 1071 | - } | |
| 1072 | - } | |
| 1073 | - | |
| 1074 | 1079 | namespace |
| 1075 | 1080 | { |
| 1076 | 1081 | constexpr int OPTION_LONGEST = 30; |
| ... | ... | @@ -1188,6 +1193,18 @@ namespace cxxopts |
| 1188 | 1193 | } |
| 1189 | 1194 | } |
| 1190 | 1195 | |
| 1196 | +template <typename Iterator> | |
| 1197 | +ParseResult::ParseResult(Iterator begin, Iterator end, | |
| 1198 | + std::vector<std::string> positional, | |
| 1199 | + int& argc, char**& argv | |
| 1200 | +) | |
| 1201 | +: m_options(begin, end) | |
| 1202 | +, m_positional(std::move(positional)) | |
| 1203 | +, m_next_positional(m_positional.begin()) | |
| 1204 | +{ | |
| 1205 | + parse(argc, argv); | |
| 1206 | +} | |
| 1207 | + | |
| 1191 | 1208 | inline |
| 1192 | 1209 | OptionAdder |
| 1193 | 1210 | Options::add_options(std::string group) |
| ... | ... | @@ -1255,7 +1272,7 @@ OptionAdder::operator() |
| 1255 | 1272 | |
| 1256 | 1273 | inline |
| 1257 | 1274 | void |
| 1258 | -Options::parse_option | |
| 1275 | +ParseResult::parse_option | |
| 1259 | 1276 | ( |
| 1260 | 1277 | std::shared_ptr<OptionDetails> value, |
| 1261 | 1278 | const std::string& /*name*/, |
| ... | ... | @@ -1267,7 +1284,7 @@ Options::parse_option |
| 1267 | 1284 | |
| 1268 | 1285 | inline |
| 1269 | 1286 | void |
| 1270 | -Options::checked_parse_arg | |
| 1287 | +ParseResult::checked_parse_arg | |
| 1271 | 1288 | ( |
| 1272 | 1289 | int argc, |
| 1273 | 1290 | char* argv[], |
| ... | ... | @@ -1303,7 +1320,7 @@ Options::checked_parse_arg |
| 1303 | 1320 | |
| 1304 | 1321 | inline |
| 1305 | 1322 | void |
| 1306 | -Options::add_to_option(const std::string& option, const std::string& arg) | |
| 1323 | +ParseResult::add_to_option(const std::string& option, const std::string& arg) | |
| 1307 | 1324 | { |
| 1308 | 1325 | auto iter = m_options.find(option); |
| 1309 | 1326 | |
| ... | ... | @@ -1317,7 +1334,7 @@ Options::add_to_option(const std::string& option, const std::string& arg) |
| 1317 | 1334 | |
| 1318 | 1335 | inline |
| 1319 | 1336 | bool |
| 1320 | -Options::consume_positional(std::string a) | |
| 1337 | +ParseResult::consume_positional(std::string a) | |
| 1321 | 1338 | { |
| 1322 | 1339 | while (m_next_positional != m_positional.end()) |
| 1323 | 1340 | { |
| ... | ... | @@ -1368,9 +1385,17 @@ Options::parse_positional(std::vector<std::string> options) |
| 1368 | 1385 | } |
| 1369 | 1386 | |
| 1370 | 1387 | inline |
| 1371 | -void | |
| 1388 | +ParseResult | |
| 1372 | 1389 | Options::parse(int& argc, char**& argv) |
| 1373 | 1390 | { |
| 1391 | + ParseResult result(m_options.begin(), m_options.end(), m_positional, argc, argv); | |
| 1392 | + return result; | |
| 1393 | +} | |
| 1394 | + | |
| 1395 | +inline | |
| 1396 | +void | |
| 1397 | +ParseResult::parse(int& argc, char**& argv) | |
| 1398 | +{ | |
| 1374 | 1399 | int current = 1; |
| 1375 | 1400 | |
| 1376 | 1401 | int nextKeep = 1; | ... | ... |
src/example.cpp
| ... | ... | @@ -63,9 +63,9 @@ int main(int argc, char* argv[]) |
| 63 | 63 | |
| 64 | 64 | options.parse_positional({"input", "output", "positional"}); |
| 65 | 65 | |
| 66 | - options.parse(argc, argv); | |
| 66 | + auto result = options.parse(argc, argv); | |
| 67 | 67 | |
| 68 | - if (options.count("help")) | |
| 68 | + if (result.count("help")) | |
| 69 | 69 | { |
| 70 | 70 | std::cout << options.help({"", "Group"}) << std::endl; |
| 71 | 71 | exit(0); |
| ... | ... | @@ -73,18 +73,18 @@ int main(int argc, char* argv[]) |
| 73 | 73 | |
| 74 | 74 | if (apple) |
| 75 | 75 | { |
| 76 | - std::cout << "Saw option ‘a’ " << options.count("a") << " times " << | |
| 76 | + std::cout << "Saw option ‘a’ " << result.count("a") << " times " << | |
| 77 | 77 | std::endl; |
| 78 | 78 | } |
| 79 | 79 | |
| 80 | - if (options.count("b")) | |
| 80 | + if (result.count("b")) | |
| 81 | 81 | { |
| 82 | 82 | std::cout << "Saw option ‘b’" << std::endl; |
| 83 | 83 | } |
| 84 | 84 | |
| 85 | - if (options.count("f")) | |
| 85 | + if (result.count("f")) | |
| 86 | 86 | { |
| 87 | - auto& ff = options["f"].as<std::vector<std::string>>(); | |
| 87 | + auto& ff = result["f"].as<std::vector<std::string>>(); | |
| 88 | 88 | std::cout << "Files" << std::endl; |
| 89 | 89 | for (const auto& f : ff) |
| 90 | 90 | { |
| ... | ... | @@ -92,36 +92,36 @@ int main(int argc, char* argv[]) |
| 92 | 92 | } |
| 93 | 93 | } |
| 94 | 94 | |
| 95 | - if (options.count("input")) | |
| 95 | + if (result.count("input")) | |
| 96 | 96 | { |
| 97 | - std::cout << "Input = " << options["input"].as<std::string>() | |
| 97 | + std::cout << "Input = " << result["input"].as<std::string>() | |
| 98 | 98 | << std::endl; |
| 99 | 99 | } |
| 100 | 100 | |
| 101 | - if (options.count("output")) | |
| 101 | + if (result.count("output")) | |
| 102 | 102 | { |
| 103 | - std::cout << "Output = " << options["output"].as<std::string>() | |
| 103 | + std::cout << "Output = " << result["output"].as<std::string>() | |
| 104 | 104 | << std::endl; |
| 105 | 105 | } |
| 106 | 106 | |
| 107 | - if (options.count("positional")) | |
| 107 | + if (result.count("positional")) | |
| 108 | 108 | { |
| 109 | 109 | std::cout << "Positional = {"; |
| 110 | - auto& v = options["positional"].as<std::vector<std::string>>(); | |
| 110 | + auto& v = result["positional"].as<std::vector<std::string>>(); | |
| 111 | 111 | for (const auto& s : v) { |
| 112 | 112 | std::cout << s << ", "; |
| 113 | 113 | } |
| 114 | 114 | std::cout << "}" << std::endl; |
| 115 | 115 | } |
| 116 | 116 | |
| 117 | - if (options.count("int")) | |
| 117 | + if (result.count("int")) | |
| 118 | 118 | { |
| 119 | - std::cout << "int = " << options["int"].as<int>() << std::endl; | |
| 119 | + std::cout << "int = " << result["int"].as<int>() << std::endl; | |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | - if (options.count("float")) | |
| 122 | + if (result.count("float")) | |
| 123 | 123 | { |
| 124 | - std::cout << "float = " << options["float"].as<float>() << std::endl; | |
| 124 | + std::cout << "float = " << result["float"].as<float>() << std::endl; | |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | 127 | std::cout << "Arguments remain = " << argc << std::endl; | ... | ... |
test/options.cpp
| ... | ... | @@ -71,17 +71,17 @@ TEST_CASE("Basic options", "[options]") |
| 71 | 71 | char** actual_argv = argv.argv(); |
| 72 | 72 | auto argc = argv.argc(); |
| 73 | 73 | |
| 74 | - options.parse(argc, actual_argv); | |
| 75 | - | |
| 76 | - CHECK(options.count("long") == 1); | |
| 77 | - CHECK(options.count("s") == 1); | |
| 78 | - CHECK(options.count("value") == 1); | |
| 79 | - CHECK(options.count("a") == 1); | |
| 80 | - CHECK(options["value"].as<std::string>() == "value"); | |
| 81 | - CHECK(options["a"].as<std::string>() == "b"); | |
| 82 | - CHECK(options.count("6") == 1); | |
| 83 | - CHECK(options.count("p") == 2); | |
| 84 | - CHECK(options.count("space") == 2); | |
| 74 | + auto result = options.parse(argc, actual_argv); | |
| 75 | + | |
| 76 | + CHECK(result.count("long") == 1); | |
| 77 | + CHECK(result.count("s") == 1); | |
| 78 | + CHECK(result.count("value") == 1); | |
| 79 | + CHECK(result.count("a") == 1); | |
| 80 | + CHECK(result["value"].as<std::string>() == "value"); | |
| 81 | + CHECK(result["a"].as<std::string>() == "b"); | |
| 82 | + CHECK(result.count("6") == 1); | |
| 83 | + CHECK(result.count("p") == 2); | |
| 84 | + CHECK(result.count("space") == 2); | |
| 85 | 85 | } |
| 86 | 86 | |
| 87 | 87 | TEST_CASE("Short options", "[options]") |
| ... | ... | @@ -96,36 +96,15 @@ TEST_CASE("Short options", "[options]") |
| 96 | 96 | auto actual_argv = argv.argv(); |
| 97 | 97 | auto argc = argv.argc(); |
| 98 | 98 | |
| 99 | - options.parse(argc, actual_argv); | |
| 99 | + auto result = options.parse(argc, actual_argv); | |
| 100 | 100 | |
| 101 | - CHECK(options.count("a") == 1); | |
| 102 | - CHECK(options["a"].as<std::string>() == "value"); | |
| 101 | + CHECK(result.count("a") == 1); | |
| 102 | + CHECK(result["a"].as<std::string>() == "value"); | |
| 103 | 103 | |
| 104 | 104 | REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), |
| 105 | 105 | cxxopts::invalid_option_format_error); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | -TEST_CASE("Required arguments", "[options]") | |
| 109 | -{ | |
| 110 | - cxxopts::Options options("required", " - test required options"); | |
| 111 | - options.add_options() | |
| 112 | - ("one", "one option") | |
| 113 | - ("two", "second option") | |
| 114 | - ; | |
| 115 | - | |
| 116 | - Argv argv({ | |
| 117 | - "required", | |
| 118 | - "--one" | |
| 119 | - }); | |
| 120 | - | |
| 121 | - auto aargv = argv.argv(); | |
| 122 | - auto argc = argv.argc(); | |
| 123 | - | |
| 124 | - options.parse(argc, aargv); | |
| 125 | - REQUIRE_THROWS_AS(cxxopts::check_required(options, {"two"}), | |
| 126 | - cxxopts::option_required_exception); | |
| 127 | -} | |
| 128 | - | |
| 129 | 108 | TEST_CASE("No positional", "[positional]") |
| 130 | 109 | { |
| 131 | 110 | cxxopts::Options options("test_no_positional", |
| ... | ... | @@ -135,7 +114,7 @@ TEST_CASE("No positional", "[positional]") |
| 135 | 114 | |
| 136 | 115 | char** argv = av.argv(); |
| 137 | 116 | auto argc = av.argc(); |
| 138 | - options.parse(argc, argv); | |
| 117 | + auto result = options.parse(argc, argv); | |
| 139 | 118 | |
| 140 | 119 | REQUIRE(argc == 4); |
| 141 | 120 | CHECK(strcmp(argv[1], "a") == 0); |
| ... | ... | @@ -158,7 +137,7 @@ TEST_CASE("All positional", "[positional]") |
| 158 | 137 | |
| 159 | 138 | options.parse_positional("positional"); |
| 160 | 139 | |
| 161 | - options.parse(argc, argv); | |
| 140 | + auto result = options.parse(argc, argv); | |
| 162 | 141 | |
| 163 | 142 | REQUIRE(argc == 1); |
| 164 | 143 | REQUIRE(positional.size() == 3); |
| ... | ... | @@ -186,14 +165,14 @@ TEST_CASE("Some positional explicit", "[positional]") |
| 186 | 165 | char** argv = av.argv(); |
| 187 | 166 | auto argc = av.argc(); |
| 188 | 167 | |
| 189 | - options.parse(argc, argv); | |
| 168 | + auto result = options.parse(argc, argv); | |
| 190 | 169 | |
| 191 | 170 | CHECK(argc == 1); |
| 192 | - CHECK(options.count("output")); | |
| 193 | - CHECK(options["input"].as<std::string>() == "b"); | |
| 194 | - CHECK(options["output"].as<std::string>() == "a"); | |
| 171 | + CHECK(result.count("output")); | |
| 172 | + CHECK(result["input"].as<std::string>() == "b"); | |
| 173 | + CHECK(result["output"].as<std::string>() == "a"); | |
| 195 | 174 | |
| 196 | - auto& positional = options["positional"].as<std::vector<std::string>>(); | |
| 175 | + auto& positional = result["positional"].as<std::vector<std::string>>(); | |
| 197 | 176 | |
| 198 | 177 | REQUIRE(positional.size() == 2); |
| 199 | 178 | CHECK(positional[0] == "c"); |
| ... | ... | @@ -234,10 +213,10 @@ TEST_CASE("Empty with implicit value", "[implicit]") |
| 234 | 213 | char** argv = av.argv(); |
| 235 | 214 | auto argc = av.argc(); |
| 236 | 215 | |
| 237 | - options.parse(argc, argv); | |
| 216 | + auto result = options.parse(argc, argv); | |
| 238 | 217 | |
| 239 | - REQUIRE(options.count("implicit") == 1); | |
| 240 | - REQUIRE(options["implicit"].as<std::string>() == ""); | |
| 218 | + REQUIRE(result.count("implicit") == 1); | |
| 219 | + REQUIRE(result["implicit"].as<std::string>() == ""); | |
| 241 | 220 | } |
| 242 | 221 | |
| 243 | 222 | TEST_CASE("Integers", "[options]") |
| ... | ... | @@ -252,11 +231,11 @@ TEST_CASE("Integers", "[options]") |
| 252 | 231 | auto argc = av.argc(); |
| 253 | 232 | |
| 254 | 233 | options.parse_positional("positional"); |
| 255 | - options.parse(argc, argv); | |
| 234 | + auto result = options.parse(argc, argv); | |
| 256 | 235 | |
| 257 | - REQUIRE(options.count("positional") == 6); | |
| 236 | + REQUIRE(result.count("positional") == 6); | |
| 258 | 237 | |
| 259 | - auto& positional = options["positional"].as<std::vector<int>>(); | |
| 238 | + auto& positional = result["positional"].as<std::vector<int>>(); | |
| 260 | 239 | CHECK(positional[0] == 5); |
| 261 | 240 | CHECK(positional[1] == 6); |
| 262 | 241 | CHECK(positional[2] == -6); |
| ... | ... | @@ -294,11 +273,11 @@ TEST_CASE("Integer bounds", "[integer]") |
| 294 | 273 | auto argc = av.argc(); |
| 295 | 274 | |
| 296 | 275 | options.parse_positional("positional"); |
| 297 | - options.parse(argc, argv); | |
| 276 | + auto result = options.parse(argc, argv); | |
| 298 | 277 | |
| 299 | - REQUIRE(options.count("positional") == 5); | |
| 278 | + REQUIRE(result.count("positional") == 5); | |
| 300 | 279 | |
| 301 | - auto& positional = options["positional"].as<std::vector<int8_t>>(); | |
| 280 | + auto& positional = result["positional"].as<std::vector<int8_t>>(); | |
| 302 | 281 | CHECK(positional[0] == 127); |
| 303 | 282 | CHECK(positional[1] == -128); |
| 304 | 283 | CHECK(positional[2] == 0x7f); |
| ... | ... | @@ -350,14 +329,14 @@ TEST_CASE("Floats", "[options]") |
| 350 | 329 | auto argc = av.argc(); |
| 351 | 330 | |
| 352 | 331 | options.parse_positional("positional"); |
| 353 | - options.parse(argc, argv); | |
| 332 | + auto result = options.parse(argc, argv); | |
| 354 | 333 | |
| 355 | - REQUIRE(options.count("double") == 1); | |
| 356 | - REQUIRE(options.count("positional") == 4); | |
| 334 | + REQUIRE(result.count("double") == 1); | |
| 335 | + REQUIRE(result.count("positional") == 4); | |
| 357 | 336 | |
| 358 | - CHECK(options["double"].as<double>() == 0.5); | |
| 337 | + CHECK(result["double"].as<double>() == 0.5); | |
| 359 | 338 | |
| 360 | - auto& positional = options["positional"].as<std::vector<float>>(); | |
| 339 | + auto& positional = result["positional"].as<std::vector<float>>(); | |
| 361 | 340 | CHECK(positional[0] == 4); |
| 362 | 341 | CHECK(positional[1] == -4); |
| 363 | 342 | CHECK(positional[2] == 1.5e6); | ... | ... |