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: 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 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 to get its value. If "opt" doesn't exist, or isn't of the right type, then an
exception will be thrown. 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"`, writing it on the command line as `--option` would give the value `"implicit"`,
and writing `--option=another` would give it the value `"another"`. 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 # Linking
This is a header only library. 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 The only build requirement is a C++ compiler that supports C++11 regular
expressions. For example GCC >= 4.9 or clang with libc++. expressions. For example GCC >= 4.9 or clang with libc++.
# TODO list # TODO list
* Allow unrecognised options. * Allow unrecognised options.
* Various help strings. * 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 try
{ {
cxxopts::Options options(argv[0], " - example command line options"); 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; bool apple = false;
@ -63,9 +65,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 +75,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 +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; << 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

@ -29,4 +29,7 @@ if (CXXOPTS_BUILD_TESTS)
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
) )
add_executable(link_test link_a.cpp link_b.cpp)
target_link_libraries(link_test cxxopts)
endif() 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(); 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);
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]") TEST_CASE("Short options", "[options]")
@ -96,36 +106,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 +124,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 +147,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 +175,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 +223,58 @@ 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("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]") TEST_CASE("Integers", "[options]")
@ -252,11 +289,12 @@ 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") == 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[0] == 5);
CHECK(positional[1] == 6); CHECK(positional[1] == 6);
CHECK(positional[2] == -6); CHECK(positional[2] == -6);
@ -295,11 +333,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);
@ -351,14 +389,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);
@ -378,3 +416,27 @@ TEST_CASE("Invalid integers", "[integer]") {
options.parse_positional("positional"); options.parse_positional("positional");
CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::argument_incorrect_type); 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);
}