diff --git a/.gitignore b/.gitignore index e91cd40..0be95f2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ CMakeFiles/ Testing/ CTestTestfile.cmake cmake_install.cmake +bazel-* \ No newline at end of file diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..a66fae7 --- /dev/null +++ b/BUILD @@ -0,0 +1,8 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + +cc_library( + name = "cxxopts", + hdrs = ["include/cxxopts.hpp"], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bddcff..df0e110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ This is the changelog for `cxxopts`, a C++11 library for parsing command line options. The project adheres to semantic versioning. -## Next version +## 3.0 ### Changed @@ -12,6 +12,13 @@ options. The project adheres to semantic versioning. * Fix duplicate default options when there is a short and long option. * Add `CXXOPTS_NO_EXCEPTIONS` to disable exceptions. * Fix char parsing for space and check for length. +* Change argument type in `Options::parse` from `char**` to `const char**`. +* Refactor parser to not change its arguments. +* `ParseResult` doesn't depend on a reference to the parser. + +### Added + +* A list of unmatched arguments is available in `ParseResult`. ## 2.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b524f7..3e9b862 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,8 +73,9 @@ target_include_directories(cxxopts INTERFACE ) if(CXXOPTS_ENABLE_INSTALL) + include(GNUInstallDirs) include(CMakePackageConfigHelpers) - set(CXXOPTS_CMAKE_DIR "lib/cmake/cxxopts" CACHE STRING + set(CXXOPTS_CMAKE_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/cxxopts" CACHE STRING "Installation directory for cmake files, relative to ${CMAKE_INSTALL_PREFIX}.") set(version_config "${PROJECT_BINARY_DIR}/cxxopts-config-version.cmake") set(project_config "${PROJECT_BINARY_DIR}/cxxopts-config.cmake") @@ -100,8 +101,8 @@ if(CXXOPTS_ENABLE_INSTALL) NAMESPACE cxxopts::) # Install the header file and export the target - install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION lib) - install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION include) + install(TARGETS cxxopts EXPORT ${targets_export_name} DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(FILES ${PROJECT_SOURCE_DIR}/include/cxxopts.hpp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() add_subdirectory(src) diff --git a/README.md b/README.md index f157052..9517993 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,25 @@ Note that `master` is generally a work in progress, and you probably want to use a tagged release version. +## Version 3 breaking changes + +If you have used version 2, there are a couple of breaking changes in (the as +yet unreleased, current master) version 3 that you should be aware of. If you are new to +`cxxopts` you can skip this section. + +The parser no longer modifies its arguments, so you can pass a const `argc` and +`argv` and expect them not to be changed. + +The `ParseResult` object no longer depends on the parser. So it can be returned +from a scope outside the parser and still work. Now that the inputs are not +modified, `ParseResult` stores a list of the unmatched arguments. These are +retrieved like follows: + +```cpp +auto result = options.parse(argc, argv); +result.unmatched(); // get the unmatched arguments +``` + # Quick start This is a lightweight C++ option parser library, supporting the standard GNU @@ -69,6 +88,23 @@ exception will be thrown. Note that the result of `options.parse` should only be used as long as the `options` object that created it is in scope. +## Unrecognised arguments + +You can allow unrecognised arguments to be skipped. This applies to both +positional arguments that are not parsed into another option, and `--` +arguments that do not match an argument that you specify. This is done by +calling: + +```cpp +options.allow_unrecognised_options(); +``` + +and in the result object they are retrieved with: + +```cpp +result.unmatched() +``` + ## Exceptions Exceptional situations throw C++ exceptions. There are two types of @@ -146,6 +182,22 @@ that can be parsed as a `std::vector`: --my_list=1,-2.1,3,4.5 ~~~ +## Options specified multiple times + +The same option can be specified several times, with different arguments, which will all +be recorded in order of appearance. An example: + +~~~ +--use train --use bus --use ferry +~~~ + +this is supported through the use of a vector of value for the option: + +~~~ +options.add_options() + ("use", "Usable means of transport", cxxopts::value>()) +~~~ + ## Custom help The string after the program name on the first line of the help can be diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 0cfc86a..f66f793 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -30,6 +30,7 @@ THE SOFTWARE. #include #include #include +#include #include #include #include @@ -45,12 +46,18 @@ THE SOFTWARE. #define CXXOPTS_HAS_OPTIONAL #endif +#if __cplusplus >= 201603L +#define CXXOPTS_NODISCARD [[nodiscard]] +#else +#define CXXOPTS_NODISCARD +#endif + #ifndef CXXOPTS_VECTOR_DELIMITER #define CXXOPTS_VECTOR_DELIMITER ',' #endif -#define CXXOPTS__VERSION_MAJOR 2 -#define CXXOPTS__VERSION_MINOR 2 +#define CXXOPTS__VERSION_MAJOR 3 +#define CXXOPTS__VERSION_MINOR 0 #define CXXOPTS__VERSION_PATCH 0 namespace cxxopts @@ -339,6 +346,7 @@ namespace cxxopts { } + CXXOPTS_NODISCARD const char* what() const noexcept override { @@ -452,6 +460,18 @@ namespace cxxopts } }; + class option_has_no_value_exception : public OptionException + { + public: + explicit option_has_no_value_exception(const std::string& option) + : OptionException( + option.empty() ? + ("Option " + LQUOTE + option + RQUOTE + " has no value") : + "Option has no value") + { + } + }; + class argument_incorrect_type : public OptionParseException { public: @@ -554,21 +574,20 @@ namespace cxxopts } // namespace detail template - R - checked_negate(T&& t, const std::string&, std::true_type) + void + checked_negate(R& r, T&& t, const std::string&, std::true_type) { // if we got to here, then `t` is a positive number that fits into // `R`. So to avoid MSVC C4146, we first cast it to `R`. // See https://github.com/jarro2783/cxxopts/issues/62 for more details. - return static_cast(-static_cast(t-1)-1); + r = static_cast(-static_cast(t-1)-1); } template - T - checked_negate(T&& t, const std::string& text, std::false_type) + void + checked_negate(R&, T&&, const std::string& text, std::false_type) { throw_or_mimic(text); - return t; } template @@ -633,9 +652,7 @@ namespace cxxopts if (negative) { - value = checked_negate(result, - text, - std::integral_constant()); + checked_negate(value, result, text, std::integral_constant()); } else { @@ -932,6 +949,7 @@ namespace cxxopts public: using abstract_value::abstract_value; + CXXOPTS_NODISCARD std::shared_ptr clone() const { @@ -1007,6 +1025,7 @@ namespace cxxopts , m_value(std::move(val)) , m_count(0) { + m_hash = std::hash{}(m_long + m_short); } OptionDetails(const OptionDetails& rhs) @@ -1018,40 +1037,54 @@ namespace cxxopts OptionDetails(OptionDetails&& rhs) = default; + CXXOPTS_NODISCARD const String& description() const { return m_desc; } - const Value& value() const { + CXXOPTS_NODISCARD + const Value& + value() const { return *m_value; } + CXXOPTS_NODISCARD std::shared_ptr make_storage() const { return m_value->clone(); } + CXXOPTS_NODISCARD const std::string& short_name() const { return m_short; } + CXXOPTS_NODISCARD const std::string& long_name() const { return m_long; } + size_t + hash() const + { + return m_hash; + } + private: std::string m_short{}; std::string m_long{}; String m_desc{}; std::shared_ptr m_value{}; int m_count; + + size_t m_hash; }; struct HelpOptionDetails @@ -1088,6 +1121,7 @@ namespace cxxopts ensure_value(details); ++m_count; m_value->parse(text); + m_long_name = &details->long_name(); } void @@ -1095,9 +1129,11 @@ namespace cxxopts { ensure_value(details); m_default = true; + m_long_name = &details->long_name(); m_value->parse(); } + CXXOPTS_NODISCARD size_t count() const noexcept { @@ -1105,6 +1141,7 @@ namespace cxxopts } // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD bool has_default() const noexcept { @@ -1116,7 +1153,8 @@ namespace cxxopts as() const { if (m_value == nullptr) { - throw_or_mimic("No value"); + throw_or_mimic( + m_long_name == nullptr ? "" : *m_long_name); } #ifdef CXXOPTS_NO_RTTI @@ -1136,6 +1174,10 @@ namespace cxxopts } } + + const std::string* m_long_name = nullptr; + // Holding this pointer is safe, since OptionValue's only exist in key-value pairs, + // where the key has the string we point to. std::shared_ptr m_value{}; size_t m_count = 0; bool m_default = false; @@ -1150,15 +1192,15 @@ namespace cxxopts { } - const - std::string& + CXXOPTS_NODISCARD + const std::string& key() const { return m_key; } - const - std::string& + CXXOPTS_NODISCARD + const std::string& value() const { return m_value; @@ -1178,45 +1220,65 @@ namespace cxxopts std::string m_value; }; + using ParsedHashMap = std::unordered_map; + using NameHashMap = std::unordered_map; + class ParseResult { public: - ParseResult( - std::shared_ptr< - std::unordered_map> - >, - std::vector, - bool allow_unrecognised, - int&, char**&); + ParseResult() {} + + ParseResult(const ParseResult&) = default; + + ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector sequential, std::vector&& unmatched_args) + : m_keys(std::move(keys)) + , m_values(std::move(values)) + , m_sequential(std::move(sequential)) + , m_unmatched(std::move(unmatched_args)) + { + } + + ParseResult& operator=(ParseResult&&) = default; + ParseResult& operator=(const ParseResult&) = default; size_t count(const std::string& o) const { - auto iter = m_options->find(o); - if (iter == m_options->end()) + auto iter = m_keys.find(o); + if (iter == m_keys.end()) { return 0; } - auto riter = m_results.find(iter->second); + auto viter = m_values.find(iter->second); - return riter->second.count(); + if (viter == m_values.end()) + { + return 0; + } + + return viter->second.count(); } const OptionValue& operator[](const std::string& option) const { - auto iter = m_options->find(option); + auto iter = m_keys.find(option); - if (iter == m_options->end()) + if (iter == m_keys.end()) { throw_or_mimic(option); } - auto riter = m_results.find(iter->second); + auto viter = m_values.find(iter->second); - return riter->second; + if (viter == m_values.end()) + { + throw_or_mimic(option); + } + + return viter->second; } const std::vector& @@ -1225,49 +1287,17 @@ namespace cxxopts return m_sequential; } + const std::vector& + unmatched() const + { + return m_unmatched; + } + private: - - void - parse(int& argc, char**& argv); - - void - add_to_option(const std::string& option, const std::string& arg); - - bool - consume_positional(const std::string& a); - - void - parse_option - ( - const std::shared_ptr& value, - const std::string& name, - const std::string& arg = "" - ); - - void - parse_default(const std::shared_ptr& details); - - void - checked_parse_arg - ( - int argc, - char* argv[], - int& current, - const std::shared_ptr& value, - const std::string& name - ); - - const std::shared_ptr< - std::unordered_map> - > m_options; - std::vector m_positional{}; - std::vector::iterator m_next_positional; - std::unordered_set m_positional_set{}; - std::unordered_map, OptionValue> m_results{}; - - bool m_allow_unrecognised; - + NameHashMap m_keys{}; + ParsedHashMap m_values{}; std::vector m_sequential{}; + std::vector m_unmatched{}; }; struct Option @@ -1292,9 +1322,66 @@ namespace cxxopts std::string arg_help_; }; + using OptionMap = std::unordered_map>; + using PositionalList = std::vector; + using PositionalListIterator = PositionalList::const_iterator; + + class OptionParser + { + public: + OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised) + : m_options(options) + , m_positional(positional) + , m_allow_unrecognised(allow_unrecognised) + { + } + + ParseResult + parse(int argc, const char** argv); + + bool + consume_positional(const std::string& a, PositionalListIterator& next); + + void + checked_parse_arg + ( + int argc, + const char* argv[], + int& current, + const std::shared_ptr& value, + const std::string& name + ); + + void + add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg); + + void + parse_option + ( + const std::shared_ptr& value, + const std::string& name, + const std::string& arg = "" + ); + + void + parse_default(const std::shared_ptr& details); + + private: + + void finalise_aliases(); + + const OptionMap& m_options; + const PositionalList& m_positional; + + std::vector m_sequential; + bool m_allow_unrecognised; + + ParsedHashMap m_parsed; + NameHashMap m_keys; + }; + class Options { - using OptionMap = std::unordered_map>; public: explicit Options(std::string program, std::string help_string = "") @@ -1305,7 +1392,6 @@ namespace cxxopts , m_show_positional(false) , m_allow_unrecognised(false) , m_options(std::make_shared()) - , m_next_positional(m_positional.end()) { } @@ -1338,7 +1424,7 @@ namespace cxxopts } ParseResult - parse(int& argc, char**& argv); + parse(int argc, const char** argv); OptionAdder add_options(std::string group = ""); @@ -1415,20 +1501,22 @@ namespace cxxopts void generate_all_groups_help(String& result) const; - std::string m_program; - String m_help_string; + std::string m_program{}; + String m_help_string{}; std::string m_custom_help{}; - std::string m_positional_help; + std::string m_positional_help{}; bool m_show_positional; bool m_allow_unrecognised; std::shared_ptr m_options; std::vector m_positional{}; - std::vector::iterator m_next_positional; std::unordered_set m_positional_set{}; //mapping from groups to help options std::map m_help{}; + + std::list m_option_list{}; + std::unordered_map m_option_map{}; }; class OptionAdder @@ -1589,24 +1677,6 @@ namespace cxxopts } } // namespace -inline -ParseResult::ParseResult -( - std::shared_ptr< - std::unordered_map> - > options, - std::vector positional, - bool allow_unrecognised, - int& argc, char**& argv -) -: m_options(std::move(options)) -, m_positional(std::move(positional)) -, m_next_positional(m_positional.begin()) -, m_allow_unrecognised(allow_unrecognised) -{ - parse(argc, argv); -} - inline void Options::add_options @@ -1686,21 +1756,24 @@ OptionAdder::operator() inline void -ParseResult::parse_default(const std::shared_ptr& details) +OptionParser::parse_default(const std::shared_ptr& details) { - m_results[details].parse_default(details); + // TODO: remove the duplicate code here + auto& store = m_parsed[details->hash()]; + store.parse_default(details); } inline void -ParseResult::parse_option +OptionParser::parse_option ( const std::shared_ptr& value, const std::string& /*name*/, const std::string& arg ) { - auto& result = m_results[value]; + auto hash = value->hash(); + auto& result = m_parsed[hash]; result.parse(value, arg); m_sequential.emplace_back(value->long_name(), arg); @@ -1708,10 +1781,10 @@ ParseResult::parse_option inline void -ParseResult::checked_parse_arg +OptionParser::checked_parse_arg ( int argc, - char* argv[], + const char* argv[], int& current, const std::shared_ptr& value, const std::string& name @@ -1744,43 +1817,36 @@ ParseResult::checked_parse_arg inline void -ParseResult::add_to_option(const std::string& option, const std::string& arg) +OptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg) { - auto iter = m_options->find(option); - - if (iter == m_options->end()) - { - throw_or_mimic(option); - } - parse_option(iter->second, option, arg); } inline bool -ParseResult::consume_positional(const std::string& a) +OptionParser::consume_positional(const std::string& a, PositionalListIterator& next) { - while (m_next_positional != m_positional.end()) + while (next != m_positional.end()) { - auto iter = m_options->find(*m_next_positional); - if (iter != m_options->end()) + auto iter = m_options.find(*next); + if (iter != m_options.end()) { - auto& result = m_results[iter->second]; + auto& result = m_parsed[iter->second->hash()]; if (!iter->second->value().is_container()) { if (result.count() == 0) { - add_to_option(*m_next_positional, a); - ++m_next_positional; + add_to_option(iter, *next, a); + ++next; return true; } - ++m_next_positional; + ++next; continue; } - add_to_option(*m_next_positional, a); + add_to_option(iter, *next, a); return true; } - throw_or_mimic(*m_next_positional); + throw_or_mimic(*next); } return false; @@ -1798,7 +1864,6 @@ void Options::parse_positional(std::vector options) { m_positional = std::move(options); - m_next_positional = m_positional.begin(); m_positional_set.insert(m_positional.begin(), m_positional.end()); } @@ -1812,21 +1877,21 @@ Options::parse_positional(std::initializer_list options) inline ParseResult -Options::parse(int& argc, char**& argv) +Options::parse(int argc, const char** argv) { - ParseResult result(m_options, m_positional, m_allow_unrecognised, argc, argv); - return result; + OptionParser parser(*m_options, m_positional, m_allow_unrecognised); + + return parser.parse(argc, argv); } -inline -void -ParseResult::parse(int& argc, char**& argv) +inline ParseResult +OptionParser::parse(int argc, const char** argv) { int current = 1; - - int nextKeep = 1; - bool consume_remaining = false; + PositionalListIterator next_positional = m_positional.begin(); + + std::vector unmatched; while (current != argc) { @@ -1853,13 +1918,12 @@ ParseResult::parse(int& argc, char**& argv) //if true is returned here then it was consumed, otherwise it is //ignored - if (consume_positional(argv[current])) + if (consume_positional(argv[current], next_positional)) { } else { - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.push_back(argv[current]); } //if we return from here then it was parsed successfully, so continue } @@ -1873,9 +1937,9 @@ ParseResult::parse(int& argc, char**& argv) for (std::size_t i = 0; i != s.size(); ++i) { std::string name(1, s[i]); - auto iter = m_options->find(name); + auto iter = m_options.find(name); - if (iter == m_options->end()) + if (iter == m_options.end()) { if (m_allow_unrecognised) { @@ -1907,15 +1971,14 @@ ParseResult::parse(int& argc, char**& argv) { const std::string& name = result[1]; - auto iter = m_options->find(name); + auto iter = m_options.find(name); - if (iter == m_options->end()) + if (iter == m_options.end()) { if (m_allow_unrecognised) { // keep unrecognised options in argument list, skip to next argument - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.push_back(argv[current]); ++current; continue; } @@ -1944,12 +2007,12 @@ ParseResult::parse(int& argc, char**& argv) ++current; } - for (auto& opt : *m_options) + for (auto& opt : m_options) { auto& detail = opt.second; const auto& value = detail->value(); - auto& store = m_results[detail]; + auto& store = m_parsed[detail->hash()]; if(value.has_default() && !store.count() && !store.has_default()){ parse_default(detail); @@ -1960,7 +2023,7 @@ ParseResult::parse(int& argc, char**& argv) { while (current < argc) { - if (!consume_positional(argv[current])) { + if (!consume_positional(argv[current], next_positional)) { break; } ++current; @@ -1968,14 +2031,30 @@ ParseResult::parse(int& argc, char**& argv) //adjust argv for any that couldn't be swallowed while (current != argc) { - argv[nextKeep] = argv[current]; - ++nextKeep; + unmatched.push_back(argv[current]); ++current; } } - argc = nextKeep; + finalise_aliases(); + ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched)); + return parsed; +} + +inline +void +OptionParser::finalise_aliases() +{ + for (auto& option: m_options) + { + auto& detail = *option.second; + auto hash = detail.hash(); + m_keys[detail.short_name()] = hash; + m_keys[detail.long_name()] = hash; + + m_parsed.emplace(hash, OptionValue()); + } } inline @@ -2014,6 +2093,11 @@ Options::add_option add_one_option(l, option); } + m_option_list.push_front(*option.get()); + auto iter = m_option_list.begin(); + m_option_map[s] = iter; + m_option_map[l] = iter; + //add the help details auto& options = m_help[group]; diff --git a/src/example.cpp b/src/example.cpp index 9b6ea88..0efb3a4 100644 --- a/src/example.cpp +++ b/src/example.cpp @@ -27,7 +27,7 @@ THE SOFTWARE. #include "cxxopts.hpp" void -parse(int argc, char* argv[]) +parse(int argc, const char* argv[]) { try { @@ -160,7 +160,7 @@ parse(int argc, char* argv[]) } } -int main(int argc, char* argv[]) +int main(int argc, const char* argv[]) { parse(argc, argv); diff --git a/test/options.cpp b/test/options.cpp index d3d3c7a..b98bade 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -8,7 +8,7 @@ class Argv { public: Argv(std::initializer_list args) - : m_argv(new char*[args.size()]) + : m_argv(new const char*[args.size()]) , m_argc(static_cast(args.size())) { int i = 0; @@ -26,7 +26,7 @@ class Argv { } } - char** argv() const { + const char** argv() const { return m_argv.get(); } @@ -37,7 +37,7 @@ class Argv { private: std::vector> m_args; - std::unique_ptr m_argv; + std::unique_ptr m_argv; int m_argc; }; @@ -69,7 +69,7 @@ TEST_CASE("Basic options", "[options]") "--space", }); - char** actual_argv = argv.argv(); + auto** actual_argv = argv.argv(); auto argc = argv.argc(); auto result = options.parse(argc, actual_argv); @@ -94,7 +94,7 @@ TEST_CASE("Basic options", "[options]") CHECK(arguments[2].key() == "value"); CHECK(arguments[3].key() == "av"); - CHECK_THROWS_AS(result["nothing"].as(), std::domain_error&); + CHECK_THROWS_AS(result["nothing"].as(), cxxopts::option_has_no_value_exception&); } TEST_CASE("Short options", "[options]") @@ -125,7 +125,7 @@ TEST_CASE("No positional", "[positional]") Argv av({"tester", "a", "b", "def"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -154,7 +154,7 @@ TEST_CASE("All positional", "[positional]") auto result = options.parse(argc, argv); - REQUIRE(argc == 1); + CHECK(result.unmatched().size() == 0); REQUIRE(positional.size() == 3); CHECK(positional[0] == "a"); @@ -177,12 +177,12 @@ TEST_CASE("Some positional explicit", "[positional]") Argv av({"tester", "--output", "a", "b", "c", "d"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); - CHECK(argc == 1); + CHECK(result.unmatched().size() == 0); CHECK(result.count("output")); CHECK(result["input"].as() == "b"); CHECK(result["output"].as() == "a"); @@ -203,17 +203,16 @@ TEST_CASE("No positional with extras", "[positional]") Argv av({"extras", "--", "a", "b", "c", "d"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto old_argv = argv; auto old_argc = argc; - options.parse(argc, argv); + auto result = options.parse(argc, argv); - REQUIRE(argc == old_argc - 1); - CHECK(argv[0] == std::string("extras")); - CHECK(argv[1] == std::string("a")); + auto& unmatched = result.unmatched(); + CHECK((unmatched == std::vector{"a", "b", "c", "d"})); } TEST_CASE("Positional not valid", "[positional]") { @@ -226,7 +225,7 @@ TEST_CASE("Positional not valid", "[positional]") { Argv av({"foobar", "bar", "baz"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); @@ -241,7 +240,7 @@ TEST_CASE("Empty with implicit value", "[implicit]") Argv av({"implicit", "--implicit="}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -260,7 +259,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("When no value provided") { Argv av({"no_implicit", "--bool"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::missing_argument_exception&); @@ -269,7 +268,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With equal-separated true") { Argv av({"no_implicit", "--bool=true"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -280,7 +279,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With equal-separated false") { Argv av({"no_implicit", "--bool=false"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -291,7 +290,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With space-separated true") { Argv av({"no_implicit", "--bool", "true"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -302,7 +301,7 @@ TEST_CASE("Boolean without implicit value", "[implicit]") SECTION("With space-separated false") { Argv av({"no_implicit", "--bool", "false"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -323,7 +322,7 @@ TEST_CASE("Default values", "[default]") SECTION("Sets defaults") { Argv av({"implicit"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -339,7 +338,7 @@ TEST_CASE("Default values", "[default]") SECTION("When values provided") { Argv av({"implicit", "--default", "5"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -374,7 +373,7 @@ TEST_CASE("Integers", "[options]") Argv av({"ints", "--", "5", "6", "-6", "0", "0xab", "0xAf", "0x0"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -401,7 +400,7 @@ TEST_CASE("Leading zero integers", "[options]") Argv av({"ints", "--", "05", "06", "0x0ab", "0x0001"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -425,7 +424,7 @@ TEST_CASE("Unsigned integers", "[options]") Argv av({"ints", "--", "-2"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -504,7 +503,7 @@ TEST_CASE("Floats", "[options]") Argv av({"floats", "--double", "0.5", "--", "4", "-4", "1.5e6", "-1.5e6"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -529,7 +528,7 @@ TEST_CASE("Invalid integers", "[integer]") { Argv av({"ints", "--", "Ae"}); - char **argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse_positional("positional"); @@ -554,7 +553,7 @@ TEST_CASE("Booleans", "[boolean]") { Argv av({"booleans", "--bool=false", "--debug=true", "--timing", "--verbose=1", "--dry-run=0", "extra"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); auto result = options.parse(argc, argv); @@ -588,7 +587,7 @@ TEST_CASE("std::vector", "[vector]") { Argv av({"vector", "--vector", "1,-2.1,3,4.5"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse(argc, argv); @@ -609,7 +608,7 @@ TEST_CASE("std::optional", "[optional]") { Argv av({"optional", "--optional", "foo"}); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); options.parse(argc, argv); @@ -634,7 +633,7 @@ TEST_CASE("Unrecognised options", "[options]") { "--another_unknown", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -643,9 +642,9 @@ TEST_CASE("Unrecognised options", "[options]") { SECTION("After allowing unrecognised options") { options.allow_unrecognised_options(); - CHECK_NOTHROW(options.parse(argc, argv)); - REQUIRE(argc == 3); - CHECK_THAT(argv[1], Catch::Equals("--unknown")); + auto result = options.parse(argc, argv); + auto& unmatched = result.unmatched(); + CHECK((unmatched == std::vector{"--unknown", "--another_unknown"})); } } @@ -661,7 +660,7 @@ TEST_CASE("Allow bad short syntax", "[options]") { "-some_bad_short", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -684,7 +683,7 @@ TEST_CASE("Invalid option syntax", "[options]") { "--a", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -704,7 +703,7 @@ TEST_CASE("Options empty", "[options]") { "--unknown" }); auto argc = argv_.argc(); - char** argv = argv_.argv(); + auto** argv = argv_.argv(); CHECK(options.groups().empty()); CHECK_THROWS_AS(options.parse(argc, argv), cxxopts::option_not_exists_exception&); @@ -733,7 +732,7 @@ TEST_CASE("Initializer list with group", "[options]") { "8000", "-t", }); - char** actual_argv = argv.argv(); + auto** actual_argv = argv.argv(); auto argc = argv.argc(); auto result = options.parse(argc, actual_argv); @@ -763,7 +762,7 @@ TEST_CASE("Option add with add_option(string, Option)", "[options]") { "4" }); auto argc = argv_.argc(); - char** argv = argv_.argv(); + auto** argv = argv_.argv(); auto result = options.parse(argc, argv); CHECK(result.arguments().size()==2);