Merge branch '2_0'

This commit is contained in:
Jarryd Beck 2017-11-15 18:04:20 +11:00
commit 8893afe13c
8 changed files with 623 additions and 297 deletions

29
CHANGELOG.md Normal file
View File

@ -0,0 +1,29 @@
# Changelog
This is the changelog for `cxxopts`, a C++11 library for parsing command line
options. The project adheres to semantic versioning.
## 2.0
### Changed
* `Options::parse` returns a ParseResult rather than storing the parse
result internally.
* Options with default values now get counted as appearing once if they
were not specified by the user.
### Added
* A new `ParseResult` object that is the immutable result of parsing. It
responds to the same `count` and `operator[]` as `Options` of 1.x did.
* The function `ParseResult::arguments` returns a vector of the parsed
arguments to iterate through in the order they were provided.
* The symbol `cxxopts::version` for the version of the library.
* Booleans can be specified with various strings and explicitly set false.
## 1.x
The 1.x series was the first major version of the library, with release numbers
starting to follow semantic versioning, after 0.x being unstable. It never had
a changelog maintained for it. Releases mostly contained bug fixes, with the
occasional feature added.

View File

@ -39,12 +39,12 @@ Any type can be given as long as it can be parsed, with operator>>.
To parse the command line do:
options.parse(argc, argv);
auto result = options.parse(argc, argv);
To retrieve an option use `options.count("option")` to get the number of times
To retrieve an option use `result.count("option")` to get the number of times
it appeared, and
options["opt"].as<type>()
result["opt"].as<type>()
to get its value. If "opt" doesn't exist, or isn't of the right type, then an
exception will be thrown.
@ -84,6 +84,17 @@ If an option had both, then not specifying it would give the value `"value"`,
writing it on the command line as `--option` would give the value `"implicit"`,
and writing `--option=another` would give it the value `"another"`.
Note that the default and implicit value is always stored as a string,
regardless of the type that you want to store it in. It will be parsed as
though it was given on the command line.
## Boolean values
Boolean options have a default implicit value of `"true"`, which can be
overridden. The effect is that writing `-o` by itself will set option `o` to
`true`. However, they can also be written with various strings using either
`=value` or the next argument.
# Linking
This is a header only library.
@ -93,9 +104,7 @@ This is a header only library.
The only build requirement is a C++ compiler that supports C++11 regular
expressions. For example GCC >= 4.9 or clang with libc++.
# TODO list
* Allow unrecognised options.
* Various help strings.
* Unicode aware for help strings.

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,9 @@ int main(int argc, char* argv[])
try
{
cxxopts::Options options(argv[0], " - example command line options");
options.positional_help("[optional args]");
options
.positional_help("[optional args]")
.show_positional_help();
bool apple = false;
@ -63,9 +65,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 +75,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 +94,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

@ -29,4 +29,7 @@ if (CXXOPTS_BUILD_TESTS)
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
)
add_executable(link_test link_a.cpp link_b.cpp)
target_link_libraries(link_test cxxopts)
endif()

6
test/link_a.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "cxxopts.hpp"
int main(int, char**)
{
return 0;
}

1
test/link_b.cpp Normal file
View File

@ -0,0 +1 @@
#include <cxxopts.hpp>

View File

@ -71,17 +71,27 @@ 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);
auto& arguments = result.arguments();
REQUIRE(arguments.size() == 7);
CHECK(arguments[0].key() == "long");
CHECK(arguments[0].value() == "true");
CHECK(arguments[0].as<bool>() == true);
CHECK(arguments[1].key() == "short");
CHECK(arguments[2].key() == "value");
CHECK(arguments[3].key() == "av");
}
TEST_CASE("Short options", "[options]")
@ -96,36 +106,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 +124,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 +147,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 +175,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 +223,58 @@ 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("Default values", "[default]")
{
cxxopts::Options options("defaults", "has defaults");
options.add_options()
("default", "Has implicit", cxxopts::value<int>()
->default_value("42"));
SECTION("Sets defaults") {
Argv av({"implicit"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("default") == 1);
CHECK(result["default"].as<int>() == 42);
}
SECTION("When values provided") {
Argv av({"implicit", "--default", "5"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("default") == 1);
CHECK(result["default"].as<int>() == 5);
}
}
TEST_CASE("Parse into a reference", "[reference]")
{
int value = 0;
cxxopts::Options options("into_reference", "parses into a reference");
options.add_options()
("ref", "A reference", cxxopts::value(value));
Argv av({"into_reference", "--ref", "42"});
auto argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
CHECK(result.count("ref") == 1);
CHECK(value == 42);
}
TEST_CASE("Integers", "[options]")
@ -252,11 +289,12 @@ 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") == 7);
REQUIRE(result.count("positional") == 7);
auto& positional = options["positional"].as<std::vector<int>>();
auto& positional = result["positional"].as<std::vector<int>>();
REQUIRE(positional.size() == 7);
CHECK(positional[0] == 5);
CHECK(positional[1] == 6);
CHECK(positional[2] == -6);
@ -295,11 +333,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);
@ -351,14 +389,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);
@ -378,3 +416,27 @@ TEST_CASE("Invalid integers", "[integer]") {
options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::argument_incorrect_type);
}
TEST_CASE("Booleans", "[boolean]") {
cxxopts::Options options("parses_floats", "parses floats correctly");
options.add_options()
("bool", "A Boolean", cxxopts::value<bool>())
("debug", "Debugging", cxxopts::value<bool>())
("timing", "Timing", cxxopts::value<bool>())
;
Argv av({"booleans", "--bool=false", "--debug", "true", "--timing"});
char** argv = av.argv();
auto argc = av.argc();
auto result = options.parse(argc, argv);
REQUIRE(result.count("bool") == 1);
REQUIRE(result.count("debug") == 1);
REQUIRE(result.count("timing") == 1);
CHECK(result["bool"].as<bool>() == false);
CHECK(result["debug"].as<bool>() == true);
CHECK(result["timing"].as<bool>() == true);
}