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,6 +34,7 @@ THE SOFTWARE.
34 #include <regex> 34 #include <regex>
35 #include <sstream> 35 #include <sstream>
36 #include <string> 36 #include <string>
  37 +#include <unordered_map>
37 #include <unordered_set> 38 #include <unordered_set>
38 #include <vector> 39 #include <vector>
39 40
@@ -893,6 +894,77 @@ namespace cxxopts @@ -893,6 +894,77 @@ namespace cxxopts
893 std::vector<HelpOptionDetails> options; 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 class Options 968 class Options
897 { 969 {
898 public: 970 public:
@@ -912,7 +984,7 @@ namespace cxxopts @@ -912,7 +984,7 @@ namespace cxxopts
912 return *this; 984 return *this;
913 } 985 }
914 986
915 - void 987 + ParseResult
916 parse(int& argc, char**& argv); 988 parse(int& argc, char**& argv);
917 989
918 OptionAdder 990 OptionAdder
@@ -929,31 +1001,6 @@ namespace cxxopts @@ -929,31 +1001,6 @@ namespace cxxopts
929 std::string arg_help 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 //parse positional arguments into the given option 1004 //parse positional arguments into the given option
958 void 1005 void
959 parse_positional(std::string option); 1006 parse_positional(std::string option);
@@ -979,30 +1026,6 @@ namespace cxxopts @@ -979,30 +1026,6 @@ namespace cxxopts
979 std::shared_ptr<OptionDetails> details 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 String 1029 String
1007 help_one_group(const std::string& group) const; 1030 help_one_group(const std::string& group) const;
1008 1031
@@ -1053,24 +1076,6 @@ namespace cxxopts @@ -1053,24 +1076,6 @@ namespace cxxopts
1053 std::string m_group; 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 namespace 1079 namespace
1075 { 1080 {
1076 constexpr int OPTION_LONGEST = 30; 1081 constexpr int OPTION_LONGEST = 30;
@@ -1188,6 +1193,18 @@ namespace cxxopts @@ -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 inline 1208 inline
1192 OptionAdder 1209 OptionAdder
1193 Options::add_options(std::string group) 1210 Options::add_options(std::string group)
@@ -1255,7 +1272,7 @@ OptionAdder::operator() @@ -1255,7 +1272,7 @@ OptionAdder::operator()
1255 1272
1256 inline 1273 inline
1257 void 1274 void
1258 -Options::parse_option 1275 +ParseResult::parse_option
1259 ( 1276 (
1260 std::shared_ptr<OptionDetails> value, 1277 std::shared_ptr<OptionDetails> value,
1261 const std::string& /*name*/, 1278 const std::string& /*name*/,
@@ -1267,7 +1284,7 @@ Options::parse_option @@ -1267,7 +1284,7 @@ Options::parse_option
1267 1284
1268 inline 1285 inline
1269 void 1286 void
1270 -Options::checked_parse_arg 1287 +ParseResult::checked_parse_arg
1271 ( 1288 (
1272 int argc, 1289 int argc,
1273 char* argv[], 1290 char* argv[],
@@ -1303,7 +1320,7 @@ Options::checked_parse_arg @@ -1303,7 +1320,7 @@ Options::checked_parse_arg
1303 1320
1304 inline 1321 inline
1305 void 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 auto iter = m_options.find(option); 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,7 +1334,7 @@ Options::add_to_option(const std::string&amp; option, const std::string&amp; arg)
1317 1334
1318 inline 1335 inline
1319 bool 1336 bool
1320 -Options::consume_positional(std::string a) 1337 +ParseResult::consume_positional(std::string a)
1321 { 1338 {
1322 while (m_next_positional != m_positional.end()) 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,9 +1385,17 @@ Options::parse_positional(std::vector&lt;std::string&gt; options)
1368 } 1385 }
1369 1386
1370 inline 1387 inline
1371 -void 1388 +ParseResult
1372 Options::parse(int& argc, char**& argv) 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 int current = 1; 1399 int current = 1;
1375 1400
1376 int nextKeep = 1; 1401 int nextKeep = 1;
src/example.cpp
@@ -63,9 +63,9 @@ int main(int argc, char* argv[]) @@ -63,9 +63,9 @@ int main(int argc, char* argv[])
63 63
64 options.parse_positional({"input", "output", "positional"}); 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 std::cout << options.help({"", "Group"}) << std::endl; 70 std::cout << options.help({"", "Group"}) << std::endl;
71 exit(0); 71 exit(0);
@@ -73,18 +73,18 @@ int main(int argc, char* argv[]) @@ -73,18 +73,18 @@ int main(int argc, char* argv[])
73 73
74 if (apple) 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 std::endl; 77 std::endl;
78 } 78 }
79 79
80 - if (options.count("b")) 80 + if (result.count("b"))
81 { 81 {
82 std::cout << "Saw option ‘b’" << std::endl; 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 std::cout << "Files" << std::endl; 88 std::cout << "Files" << std::endl;
89 for (const auto& f : ff) 89 for (const auto& f : ff)
90 { 90 {
@@ -92,36 +92,36 @@ int main(int argc, char* argv[]) @@ -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 << std::endl; 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 << std::endl; 104 << std::endl;
105 } 105 }
106 106
107 - if (options.count("positional")) 107 + if (result.count("positional"))
108 { 108 {
109 std::cout << "Positional = {"; 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 for (const auto& s : v) { 111 for (const auto& s : v) {
112 std::cout << s << ", "; 112 std::cout << s << ", ";
113 } 113 }
114 std::cout << "}" << std::endl; 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 std::cout << "Arguments remain = " << argc << std::endl; 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,17 +71,17 @@ TEST_CASE(&quot;Basic options&quot;, &quot;[options]&quot;)
71 char** actual_argv = argv.argv(); 71 char** actual_argv = argv.argv();
72 auto argc = argv.argc(); 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 TEST_CASE("Short options", "[options]") 87 TEST_CASE("Short options", "[options]")
@@ -96,36 +96,15 @@ TEST_CASE(&quot;Short options&quot;, &quot;[options]&quot;) @@ -96,36 +96,15 @@ TEST_CASE(&quot;Short options&quot;, &quot;[options]&quot;)
96 auto actual_argv = argv.argv(); 96 auto actual_argv = argv.argv();
97 auto argc = argv.argc(); 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 REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), 104 REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
105 cxxopts::invalid_option_format_error); 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 TEST_CASE("No positional", "[positional]") 108 TEST_CASE("No positional", "[positional]")
130 { 109 {
131 cxxopts::Options options("test_no_positional", 110 cxxopts::Options options("test_no_positional",
@@ -135,7 +114,7 @@ TEST_CASE(&quot;No positional&quot;, &quot;[positional]&quot;) @@ -135,7 +114,7 @@ TEST_CASE(&quot;No positional&quot;, &quot;[positional]&quot;)
135 114
136 char** argv = av.argv(); 115 char** argv = av.argv();
137 auto argc = av.argc(); 116 auto argc = av.argc();
138 - options.parse(argc, argv); 117 + auto result = options.parse(argc, argv);
139 118
140 REQUIRE(argc == 4); 119 REQUIRE(argc == 4);
141 CHECK(strcmp(argv[1], "a") == 0); 120 CHECK(strcmp(argv[1], "a") == 0);
@@ -158,7 +137,7 @@ TEST_CASE(&quot;All positional&quot;, &quot;[positional]&quot;) @@ -158,7 +137,7 @@ TEST_CASE(&quot;All positional&quot;, &quot;[positional]&quot;)
158 137
159 options.parse_positional("positional"); 138 options.parse_positional("positional");
160 139
161 - options.parse(argc, argv); 140 + auto result = options.parse(argc, argv);
162 141
163 REQUIRE(argc == 1); 142 REQUIRE(argc == 1);
164 REQUIRE(positional.size() == 3); 143 REQUIRE(positional.size() == 3);
@@ -186,14 +165,14 @@ TEST_CASE(&quot;Some positional explicit&quot;, &quot;[positional]&quot;) @@ -186,14 +165,14 @@ TEST_CASE(&quot;Some positional explicit&quot;, &quot;[positional]&quot;)
186 char** argv = av.argv(); 165 char** argv = av.argv();
187 auto argc = av.argc(); 166 auto argc = av.argc();
188 167
189 - options.parse(argc, argv); 168 + auto result = options.parse(argc, argv);
190 169
191 CHECK(argc == 1); 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 REQUIRE(positional.size() == 2); 177 REQUIRE(positional.size() == 2);
199 CHECK(positional[0] == "c"); 178 CHECK(positional[0] == "c");
@@ -234,10 +213,10 @@ TEST_CASE(&quot;Empty with implicit value&quot;, &quot;[implicit]&quot;) @@ -234,10 +213,10 @@ TEST_CASE(&quot;Empty with implicit value&quot;, &quot;[implicit]&quot;)
234 char** argv = av.argv(); 213 char** argv = av.argv();
235 auto argc = av.argc(); 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 TEST_CASE("Integers", "[options]") 222 TEST_CASE("Integers", "[options]")
@@ -252,11 +231,11 @@ TEST_CASE(&quot;Integers&quot;, &quot;[options]&quot;) @@ -252,11 +231,11 @@ TEST_CASE(&quot;Integers&quot;, &quot;[options]&quot;)
252 auto argc = av.argc(); 231 auto argc = av.argc();
253 232
254 options.parse_positional("positional"); 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 CHECK(positional[0] == 5); 239 CHECK(positional[0] == 5);
261 CHECK(positional[1] == 6); 240 CHECK(positional[1] == 6);
262 CHECK(positional[2] == -6); 241 CHECK(positional[2] == -6);
@@ -294,11 +273,11 @@ TEST_CASE(&quot;Integer bounds&quot;, &quot;[integer]&quot;) @@ -294,11 +273,11 @@ TEST_CASE(&quot;Integer bounds&quot;, &quot;[integer]&quot;)
294 auto argc = av.argc(); 273 auto argc = av.argc();
295 274
296 options.parse_positional("positional"); 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 CHECK(positional[0] == 127); 281 CHECK(positional[0] == 127);
303 CHECK(positional[1] == -128); 282 CHECK(positional[1] == -128);
304 CHECK(positional[2] == 0x7f); 283 CHECK(positional[2] == 0x7f);
@@ -350,14 +329,14 @@ TEST_CASE(&quot;Floats&quot;, &quot;[options]&quot;) @@ -350,14 +329,14 @@ TEST_CASE(&quot;Floats&quot;, &quot;[options]&quot;)
350 auto argc = av.argc(); 329 auto argc = av.argc();
351 330
352 options.parse_positional("positional"); 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 CHECK(positional[0] == 4); 340 CHECK(positional[0] == 4);
362 CHECK(positional[1] == -4); 341 CHECK(positional[1] == -4);
363 CHECK(positional[2] == 1.5e6); 342 CHECK(positional[2] == 1.5e6);