Commit 8010e06952858276d31ddf4e90497c87a919aca6

Authored by Jarryd Beck
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.
CHANGELOG.md 0 → 100644
  1 +# 2.0
  2 +
  3 +## Changed
  4 +
  5 +* `Options::parse` returns a ParseResult rather than storing the parse
  6 + result internally.
... ...
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&amp; option, const std::string&amp; 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&lt;std::string&gt; 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(&quot;Basic options&quot;, &quot;[options]&quot;)
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(&quot;Short options&quot;, &quot;[options]&quot;)
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(&quot;No positional&quot;, &quot;[positional]&quot;)
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(&quot;All positional&quot;, &quot;[positional]&quot;)
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(&quot;Some positional explicit&quot;, &quot;[positional]&quot;)
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(&quot;Empty with implicit value&quot;, &quot;[implicit]&quot;)
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(&quot;Integers&quot;, &quot;[options]&quot;)
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(&quot;Integer bounds&quot;, &quot;[integer]&quot;)
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(&quot;Floats&quot;, &quot;[options]&quot;)
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);
... ...