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 <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@ -893,41 +894,16 @@ namespace cxxopts
std::vector<HelpOptionDetails> 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<const Value> value,
std::string arg_help
);
template <typename Iterator>
ParseResult(
Iterator,
Iterator,
std::vector<std::string>,
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<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
void
parse_positional(std::string option);
@ -979,30 +1026,6 @@ namespace cxxopts
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
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<std::string>& 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 <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
OptionAdder
Options::add_options(std::string group)
@ -1255,7 +1272,7 @@ OptionAdder::operator()
inline
void
Options::parse_option
ParseResult::parse_option
(
std::shared_ptr<OptionDetails> 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<std::string> 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;

View File

@ -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<std::vector<std::string>>();
auto& ff = result["f"].as<std::vector<std::string>>();
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::string>()
std::cout << "Input = " << result["input"].as<std::string>()
<< 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;
}
if (options.count("positional"))
if (result.count("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) {
std::cout << s << ", ";
}
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;

View File

@ -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<std::string>() == "value");
CHECK(options["a"].as<std::string>() == "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<std::string>() == "value");
CHECK(result["a"].as<std::string>() == "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<std::string>() == "value");
CHECK(result.count("a") == 1);
CHECK(result["a"].as<std::string>() == "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<std::string>() == "b");
CHECK(options["output"].as<std::string>() == "a");
CHECK(result.count("output"));
CHECK(result["input"].as<std::string>() == "b");
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);
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<std::string>() == "");
REQUIRE(result.count("implicit") == 1);
REQUIRE(result["implicit"].as<std::string>() == "");
}
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<std::vector<int>>();
auto& positional = result["positional"].as<std::vector<int>>();
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<std::vector<int8_t>>();
auto& positional = result["positional"].as<std::vector<int8_t>>();
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<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[1] == -4);
CHECK(positional[2] == 1.5e6);