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.
This commit is contained in:
Jarryd Beck 2017-10-18 18:27:13 +11:00
parent d8458a8c1a
commit 8010e06952
4 changed files with 160 additions and 150 deletions

6
CHANGELOG.md Normal file
View File

@ -0,0 +1,6 @@
# 2.0
## Changed
* `Options::parse` returns a ParseResult rather than storing the parse
result internally.

View File

@ -34,6 +34,7 @@ THE SOFTWARE.
#include <regex> #include <regex>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <vector> #include <vector>
@ -893,41 +894,16 @@ namespace cxxopts
std::vector<HelpOptionDetails> options; std::vector<HelpOptionDetails> options;
}; };
class Options class ParseResult
{ {
public: public:
Options(std::string program, std::string help_string = "") template <typename Iterator>
: m_program(std::move(program)) ParseResult(
, m_help_string(toLocalString(std::move(help_string))) Iterator,
, m_positional_help("positional parameters") Iterator,
, m_next_positional(m_positional.end()) std::vector<std::string>,
{ int&, char**&);
}
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<const Value> value,
std::string arg_help
);
int int
count(const std::string& o) const count(const std::string& o) const
@ -954,6 +930,77 @@ namespace cxxopts
return *iter->second; 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<OptionDetails> value,
const std::string& name,
const std::string& arg = ""
);
void
checked_parse_arg
(
int argc,
char* argv[],
int& current,
std::shared_ptr<OptionDetails> value,
const std::string& name
);
std::unordered_map<std::string, std::shared_ptr<OptionDetails>> m_options;
std::vector<std::string> m_positional;
std::vector<std::string>::iterator m_next_positional;
std::unordered_set<std::string> 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<const Value> value,
std::string arg_help
);
//parse positional arguments into the given option //parse positional arguments into the given option
void void
parse_positional(std::string option); parse_positional(std::string option);
@ -979,30 +1026,6 @@ namespace cxxopts
std::shared_ptr<OptionDetails> details std::shared_ptr<OptionDetails> 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<OptionDetails> value,
const std::string& name,
const std::string& arg = ""
);
void
checked_parse_arg
(
int argc,
char* argv[],
int& current,
std::shared_ptr<OptionDetails> value,
const std::string& name
);
String String
help_one_group(const std::string& group) const; help_one_group(const std::string& group) const;
@ -1053,24 +1076,6 @@ namespace cxxopts
std::string m_group; std::string m_group;
}; };
// A helper function for setting required arguments
inline
void
check_required
(
const Options& options,
const std::vector<std::string>& required
)
{
for (auto& r : required)
{
if (options.count(r) == 0)
{
throw option_required_exception(r);
}
}
}
namespace namespace
{ {
constexpr int OPTION_LONGEST = 30; constexpr int OPTION_LONGEST = 30;
@ -1188,6 +1193,18 @@ namespace cxxopts
} }
} }
template <typename Iterator>
ParseResult::ParseResult(Iterator begin, Iterator end,
std::vector<std::string> positional,
int& argc, char**& argv
)
: m_options(begin, end)
, m_positional(std::move(positional))
, m_next_positional(m_positional.begin())
{
parse(argc, argv);
}
inline inline
OptionAdder OptionAdder
Options::add_options(std::string group) Options::add_options(std::string group)
@ -1255,7 +1272,7 @@ OptionAdder::operator()
inline inline
void void
Options::parse_option ParseResult::parse_option
( (
std::shared_ptr<OptionDetails> value, std::shared_ptr<OptionDetails> value,
const std::string& /*name*/, const std::string& /*name*/,
@ -1267,7 +1284,7 @@ Options::parse_option
inline inline
void void
Options::checked_parse_arg ParseResult::checked_parse_arg
( (
int argc, int argc,
char* argv[], char* argv[],
@ -1303,7 +1320,7 @@ Options::checked_parse_arg
inline inline
void 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); auto iter = m_options.find(option);
@ -1317,7 +1334,7 @@ Options::add_to_option(const std::string& option, const std::string& arg)
inline inline
bool bool
Options::consume_positional(std::string a) ParseResult::consume_positional(std::string a)
{ {
while (m_next_positional != m_positional.end()) while (m_next_positional != m_positional.end())
{ {
@ -1368,8 +1385,16 @@ Options::parse_positional(std::vector<std::string> options)
} }
inline inline
void ParseResult
Options::parse(int& argc, char**& argv) 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 current = 1;

View File

@ -63,9 +63,9 @@ int main(int argc, char* argv[])
options.parse_positional({"input", "output", "positional"}); 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; std::cout << options.help({"", "Group"}) << std::endl;
exit(0); exit(0);
@ -73,18 +73,18 @@ int main(int argc, char* argv[])
if (apple) if (apple)
{ {
std::cout << "Saw option a " << options.count("a") << " times " << std::cout << "Saw option a " << result.count("a") << " times " <<
std::endl; std::endl;
} }
if (options.count("b")) if (result.count("b"))
{ {
std::cout << "Saw option b" << std::endl; std::cout << "Saw option b" << std::endl;
} }
if (options.count("f")) if (result.count("f"))
{ {
auto& ff = options["f"].as<std::vector<std::string>>(); auto& ff = result["f"].as<std::vector<std::string>>();
std::cout << "Files" << std::endl; std::cout << "Files" << std::endl;
for (const auto& f : ff) 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::string>() std::cout << "Input = " << result["input"].as<std::string>()
<< std::endl; << std::endl;
} }
if (options.count("output")) if (result.count("output"))
{ {
std::cout << "Output = " << options["output"].as<std::string>() std::cout << "Output = " << result["output"].as<std::string>()
<< std::endl; << std::endl;
} }
if (options.count("positional")) if (result.count("positional"))
{ {
std::cout << "Positional = {"; std::cout << "Positional = {";
auto& v = options["positional"].as<std::vector<std::string>>(); auto& v = result["positional"].as<std::vector<std::string>>();
for (const auto& s : v) { for (const auto& s : v) {
std::cout << s << ", "; std::cout << s << ", ";
} }
std::cout << "}" << std::endl; std::cout << "}" << std::endl;
} }
if (options.count("int")) if (result.count("int"))
{ {
std::cout << "int = " << options["int"].as<int>() << std::endl; std::cout << "int = " << result["int"].as<int>() << std::endl;
} }
if (options.count("float")) if (result.count("float"))
{ {
std::cout << "float = " << options["float"].as<float>() << std::endl; std::cout << "float = " << result["float"].as<float>() << std::endl;
} }
std::cout << "Arguments remain = " << argc << std::endl; std::cout << "Arguments remain = " << argc << std::endl;

View File

@ -71,17 +71,17 @@ TEST_CASE("Basic options", "[options]")
char** actual_argv = argv.argv(); char** actual_argv = argv.argv();
auto argc = argv.argc(); auto argc = argv.argc();
options.parse(argc, actual_argv); auto result = options.parse(argc, actual_argv);
CHECK(options.count("long") == 1); CHECK(result.count("long") == 1);
CHECK(options.count("s") == 1); CHECK(result.count("s") == 1);
CHECK(options.count("value") == 1); CHECK(result.count("value") == 1);
CHECK(options.count("a") == 1); CHECK(result.count("a") == 1);
CHECK(options["value"].as<std::string>() == "value"); CHECK(result["value"].as<std::string>() == "value");
CHECK(options["a"].as<std::string>() == "b"); CHECK(result["a"].as<std::string>() == "b");
CHECK(options.count("6") == 1); CHECK(result.count("6") == 1);
CHECK(options.count("p") == 2); CHECK(result.count("p") == 2);
CHECK(options.count("space") == 2); CHECK(result.count("space") == 2);
} }
TEST_CASE("Short options", "[options]") TEST_CASE("Short options", "[options]")
@ -96,36 +96,15 @@ TEST_CASE("Short options", "[options]")
auto actual_argv = argv.argv(); auto actual_argv = argv.argv();
auto argc = argv.argc(); auto argc = argv.argc();
options.parse(argc, actual_argv); auto result = options.parse(argc, actual_argv);
CHECK(options.count("a") == 1); CHECK(result.count("a") == 1);
CHECK(options["a"].as<std::string>() == "value"); CHECK(result["a"].as<std::string>() == "value");
REQUIRE_THROWS_AS(options.add_options()("", "nothing option"), REQUIRE_THROWS_AS(options.add_options()("", "nothing option"),
cxxopts::invalid_option_format_error); 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]") TEST_CASE("No positional", "[positional]")
{ {
cxxopts::Options options("test_no_positional", cxxopts::Options options("test_no_positional",
@ -135,7 +114,7 @@ TEST_CASE("No positional", "[positional]")
char** argv = av.argv(); char** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(argc == 4); REQUIRE(argc == 4);
CHECK(strcmp(argv[1], "a") == 0); CHECK(strcmp(argv[1], "a") == 0);
@ -158,7 +137,7 @@ TEST_CASE("All positional", "[positional]")
options.parse_positional("positional"); options.parse_positional("positional");
options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(argc == 1); REQUIRE(argc == 1);
REQUIRE(positional.size() == 3); REQUIRE(positional.size() == 3);
@ -186,14 +165,14 @@ TEST_CASE("Some positional explicit", "[positional]")
char** argv = av.argv(); char** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse(argc, argv); auto result = options.parse(argc, argv);
CHECK(argc == 1); CHECK(argc == 1);
CHECK(options.count("output")); CHECK(result.count("output"));
CHECK(options["input"].as<std::string>() == "b"); CHECK(result["input"].as<std::string>() == "b");
CHECK(options["output"].as<std::string>() == "a"); CHECK(result["output"].as<std::string>() == "a");
auto& positional = options["positional"].as<std::vector<std::string>>(); auto& positional = result["positional"].as<std::vector<std::string>>();
REQUIRE(positional.size() == 2); REQUIRE(positional.size() == 2);
CHECK(positional[0] == "c"); CHECK(positional[0] == "c");
@ -234,10 +213,10 @@ TEST_CASE("Empty with implicit value", "[implicit]")
char** argv = av.argv(); char** argv = av.argv();
auto argc = av.argc(); auto argc = av.argc();
options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(options.count("implicit") == 1); REQUIRE(result.count("implicit") == 1);
REQUIRE(options["implicit"].as<std::string>() == ""); REQUIRE(result["implicit"].as<std::string>() == "");
} }
TEST_CASE("Integers", "[options]") TEST_CASE("Integers", "[options]")
@ -252,11 +231,11 @@ TEST_CASE("Integers", "[options]")
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); 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<std::vector<int>>(); auto& positional = result["positional"].as<std::vector<int>>();
CHECK(positional[0] == 5); CHECK(positional[0] == 5);
CHECK(positional[1] == 6); CHECK(positional[1] == 6);
CHECK(positional[2] == -6); CHECK(positional[2] == -6);
@ -294,11 +273,11 @@ TEST_CASE("Integer bounds", "[integer]")
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); 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<std::vector<int8_t>>(); auto& positional = result["positional"].as<std::vector<int8_t>>();
CHECK(positional[0] == 127); CHECK(positional[0] == 127);
CHECK(positional[1] == -128); CHECK(positional[1] == -128);
CHECK(positional[2] == 0x7f); CHECK(positional[2] == 0x7f);
@ -350,14 +329,14 @@ TEST_CASE("Floats", "[options]")
auto argc = av.argc(); auto argc = av.argc();
options.parse_positional("positional"); options.parse_positional("positional");
options.parse(argc, argv); auto result = options.parse(argc, argv);
REQUIRE(options.count("double") == 1); REQUIRE(result.count("double") == 1);
REQUIRE(options.count("positional") == 4); REQUIRE(result.count("positional") == 4);
CHECK(options["double"].as<double>() == 0.5); CHECK(result["double"].as<double>() == 0.5);
auto& positional = options["positional"].as<std::vector<float>>(); auto& positional = result["positional"].as<std::vector<float>>();
CHECK(positional[0] == 4); CHECK(positional[0] == 4);
CHECK(positional[1] == -4); CHECK(positional[1] == -4);
CHECK(positional[2] == 1.5e6); CHECK(positional[2] == 1.5e6);