From 1b660d56801283905b0f0fdd43e7e7ba3c5c396e Mon Sep 17 00:00:00 2001 From: Eyal Rozenberg Date: Thu, 23 Jul 2020 09:36:54 +0300 Subject: [PATCH 01/12] Regards #244: Explain how vectors of values allow repeated use of the same option on the command-line. (#247) --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index f157052..88b92d4 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,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 From 5f43f4cbfee5d92560ece7811a2a44c763f9fb73 Mon Sep 17 00:00:00 2001 From: Eyal Rozenberg Date: Thu, 23 Jul 2020 10:27:04 +0300 Subject: [PATCH 02/12] Fixes #245: Mention the option name when throwing on "no value" (#246) * Fixes #245: * Added a new exception type: `option_has_no_value_exception`; throwing it when an option has no value we can cast with `as()`, instead of an `std::domain_error`. * The `OptionValue` type now holds a pointer to the long option name (in its corresponding key within ParseResults's `m_results` field. --- include/cxxopts.hpp | 20 +++++++++++++++++++- test/options.cpp | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 97381a9..dade761 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -443,6 +443,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: @@ -1077,6 +1089,7 @@ namespace cxxopts ensure_value(details); ++m_count; m_value->parse(text); + m_long_name = &details->long_name(); } void @@ -1084,6 +1097,7 @@ namespace cxxopts { ensure_value(details); m_default = true; + m_long_name = &details->long_name(); m_value->parse(); } @@ -1105,7 +1119,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 @@ -1125,6 +1140,9 @@ 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; diff --git a/test/options.cpp b/test/options.cpp index d3d3c7a..5026ea0 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -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]") From 07f5cb24f1d75aad6c27eafd83863a78a37f16cb Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 27 Jul 2020 22:40:16 -0700 Subject: [PATCH 03/12] [clang-tidy] use nodiscard (#234) Found with modernize-use-nodiscard Signed-off-by: Rosen Penev --- include/cxxopts.hpp | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index dade761..ae95343 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -45,6 +45,12 @@ 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 @@ -330,6 +336,7 @@ namespace cxxopts { } + CXXOPTS_NODISCARD const char* what() const noexcept override { @@ -933,6 +940,7 @@ namespace cxxopts public: using abstract_value::abstract_value; + CXXOPTS_NODISCARD std::shared_ptr clone() const { @@ -1019,28 +1027,34 @@ 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 { @@ -1101,6 +1115,7 @@ namespace cxxopts m_value->parse(); } + CXXOPTS_NODISCARD size_t count() const noexcept { @@ -1108,6 +1123,7 @@ namespace cxxopts } // TODO: maybe default options should count towards the number of arguments + CXXOPTS_NODISCARD bool has_default() const noexcept { @@ -1157,15 +1173,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; From 15e8a74e95ac0f1c45068f5db8ea31776d946b30 Mon Sep 17 00:00:00 2001 From: Kjetil Andresen Date: Tue, 11 Aug 2020 00:01:29 +0200 Subject: [PATCH 04/12] Support 'const char**' arguments in Options::parse (#250) `cxxopts` doesn't modify the contents of the argv strings. This changes the parse function to take a reference to a `const char**`. --- CHANGELOG.md | 1 + include/cxxopts.hpp | 16 ++++++------ src/example.cpp | 4 +-- test/options.cpp | 60 ++++++++++++++++++++++----------------------- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bddcff..16b5a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ 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**`. ## 2.2 diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index ae95343..995b893 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -1211,7 +1211,7 @@ namespace cxxopts >, std::vector, bool allow_unrecognised, - int&, char**&); + int&, const char**&); size_t count(const std::string& o) const @@ -1251,7 +1251,7 @@ namespace cxxopts private: void - parse(int& argc, char**& argv); + parse(int& argc, const char**& argv); void add_to_option(const std::string& option, const std::string& arg); @@ -1274,7 +1274,7 @@ namespace cxxopts checked_parse_arg ( int argc, - char* argv[], + const char* argv[], int& current, const std::shared_ptr& value, const std::string& name @@ -1361,7 +1361,7 @@ namespace cxxopts } ParseResult - parse(int& argc, char**& argv); + parse(int& argc, const char**& argv); OptionAdder add_options(std::string group = ""); @@ -1620,7 +1620,7 @@ ParseResult::ParseResult > options, std::vector positional, bool allow_unrecognised, - int& argc, char**& argv + int& argc, const char**& argv ) : m_options(std::move(options)) , m_positional(std::move(positional)) @@ -1734,7 +1734,7 @@ void ParseResult::checked_parse_arg ( int argc, - char* argv[], + const char* argv[], int& current, const std::shared_ptr& value, const std::string& name @@ -1835,7 +1835,7 @@ 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; @@ -1843,7 +1843,7 @@ Options::parse(int& argc, char**& argv) inline void -ParseResult::parse(int& argc, char**& argv) +ParseResult::parse(int& argc, const char**& argv) { int current = 1; 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 5026ea0..0dddfe4 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); @@ -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); @@ -177,7 +177,7 @@ 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); @@ -203,7 +203,7 @@ 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; @@ -226,7 +226,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 +241,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 +260,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 +269,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 +280,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 +291,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 +302,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 +323,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 +339,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 +374,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 +401,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 +425,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 +504,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 +529,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 +554,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 +588,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 +609,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 +634,7 @@ TEST_CASE("Unrecognised options", "[options]") { "--another_unknown", }); - char** argv = av.argv(); + auto** argv = av.argv(); auto argc = av.argc(); SECTION("Default behaviour") { @@ -661,7 +661,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 +684,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 +704,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 +733,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 +763,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); From 05ca8e1cacb5db9152411ad8df1d2d61f0fe82aa Mon Sep 17 00:00:00 2001 From: silvergasp Date: Wed, 12 Aug 2020 06:48:07 +0800 Subject: [PATCH 05/12] Adding bazel build targets (#251) Adding support for the bazel build system --- .gitignore | 1 + BUILD | 8 ++++++++ WORKSPACE | 0 3 files changed, 9 insertions(+) create mode 100644 BUILD create mode 100644 WORKSPACE 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/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..e69de29 From 584e0c3dd3eb5ac0631b0840c3852ea274960862 Mon Sep 17 00:00:00 2001 From: Vitaly Zaitsev Date: Wed, 19 Aug 2020 01:16:11 +0200 Subject: [PATCH 06/12] Fixed installation on other than Ubuntu GNU/Linux distributions. (#226) Fixes the installation paths by using cmake variables. --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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) From fd5cdfd5476a63f2cd5f764b50c315f040be5efe Mon Sep 17 00:00:00 2001 From: Daniel Gomez Antonio Date: Mon, 14 Sep 2020 19:07:53 -0500 Subject: [PATCH 07/12] Fix compiler warning C4702 for MSVC-14 (#225) * Fix compiler warning C4702 for MSVC-14 --- include/cxxopts.hpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 995b893..e91987d 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -564,21 +564,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 @@ -643,9 +642,7 @@ namespace cxxopts if (negative) { - value = checked_negate(result, - text, - std::integral_constant()); + checked_negate(value, result, text, std::integral_constant()); } else { From fedf9d7b57297765c18b95aecc9625f0104a0fde Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Wed, 23 Sep 2020 20:05:00 +1000 Subject: [PATCH 08/12] Refactor parser Major refactor of the parsing code organisation to improve encapsulation and not modify the input arguments. The returned result no longer has pointers into the original option specification. --- include/cxxopts.hpp | 308 ++++++++++++++++++++++++++------------------ test/options.cpp | 17 ++- 2 files changed, 190 insertions(+), 135 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index e91987d..1de4717 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -30,6 +30,7 @@ THE SOFTWARE. #include #include #include +#include #include #include #include @@ -1013,6 +1014,7 @@ namespace cxxopts , m_value(std::move(val)) , m_count(0) { + m_hash = std::hash{}(m_long + m_short); } OptionDetails(const OptionDetails& rhs) @@ -1058,12 +1060,20 @@ namespace cxxopts 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 @@ -1198,45 +1208,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&, const 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& @@ -1245,49 +1275,17 @@ namespace cxxopts return m_sequential; } + const std::vector& + unmatched() const + { + return m_unmatched; + } + private: - - void - parse(int& argc, const 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, - const 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 @@ -1312,9 +1310,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 = "") @@ -1325,7 +1380,6 @@ namespace cxxopts , m_show_positional(false) , m_allow_unrecognised(false) , m_options(std::make_shared()) - , m_next_positional(m_positional.end()) { } @@ -1358,7 +1412,7 @@ namespace cxxopts } ParseResult - parse(int& argc, const char**& argv); + parse(int argc, const char** argv); OptionAdder add_options(std::string group = ""); @@ -1444,11 +1498,13 @@ namespace cxxopts 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 @@ -1609,24 +1665,6 @@ namespace cxxopts } } // namespace -inline -ParseResult::ParseResult -( - std::shared_ptr< - std::unordered_map> - > options, - std::vector positional, - bool allow_unrecognised, - int& argc, const 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 @@ -1706,21 +1744,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); @@ -1728,7 +1769,7 @@ ParseResult::parse_option inline void -ParseResult::checked_parse_arg +OptionParser::checked_parse_arg ( int argc, const char* argv[], @@ -1764,43 +1805,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; @@ -1818,7 +1852,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()); } @@ -1832,21 +1865,21 @@ Options::parse_positional(std::initializer_list options) inline ParseResult -Options::parse(int& argc, const 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, const 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) { @@ -1873,13 +1906,12 @@ ParseResult::parse(int& argc, const 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 } @@ -1893,9 +1925,9 @@ ParseResult::parse(int& argc, const 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) { @@ -1927,15 +1959,14 @@ ParseResult::parse(int& argc, const 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; } @@ -1964,12 +1995,13 @@ ParseResult::parse(int& argc, const 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_results[detail]; + auto& store = m_parsed[detail->hash()]; if(value.has_default() && !store.count() && !store.has_default()){ parse_default(detail); @@ -1980,7 +2012,7 @@ ParseResult::parse(int& argc, const char**& argv) { while (current < argc) { - if (!consume_positional(argv[current])) { + if (!consume_positional(argv[current], next_positional)) { break; } ++current; @@ -1988,14 +2020,33 @@ ParseResult::parse(int& argc, const 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(); + //if (m_parsed.find(hash) != m_parsed.end()) + { + m_keys[detail.short_name()] = hash; + m_keys[detail.long_name()] = hash; + } + + m_parsed.emplace(hash, OptionValue()); + } } inline @@ -2034,6 +2085,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/test/options.cpp b/test/options.cpp index 0dddfe4..b98bade 100644 --- a/test/options.cpp +++ b/test/options.cpp @@ -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"); @@ -182,7 +182,7 @@ TEST_CASE("Some positional explicit", "[positional]") 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"); @@ -209,11 +209,10 @@ TEST_CASE("No positional with extras", "[positional]") 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]") { @@ -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"})); } } From 31b77f11af2bbf634a53e2828c3987777d22c2ab Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Wed, 30 Sep 2020 17:58:59 +1000 Subject: [PATCH 09/12] Prepare notes for v3 --- CHANGELOG.md | 8 +++++++- README.md | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16b5a99..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 @@ -13,6 +13,12 @@ options. The project adheres to semantic versioning. * 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/README.md b/README.md index 88b92d4..b41b96a 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 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 From 703eeef90611e592eb5fc9f0bac2fe3e1aa16614 Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Thu, 1 Oct 2020 16:47:47 +1000 Subject: [PATCH 10/12] Update version for 3 --- include/cxxopts.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 1de4717..60d3cbf 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -56,8 +56,8 @@ THE SOFTWARE. #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 From 748ac3527752f73b8a59ae85a2170be250a65b2d Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Thu, 1 Oct 2020 17:26:32 +1000 Subject: [PATCH 11/12] small cleanup --- include/cxxopts.hpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/cxxopts.hpp b/include/cxxopts.hpp index 60d3cbf..96881d6 100644 --- a/include/cxxopts.hpp +++ b/include/cxxopts.hpp @@ -1164,8 +1164,8 @@ 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. + // 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; @@ -2000,7 +2000,6 @@ OptionParser::parse(int argc, const char** argv) 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()){ @@ -2039,11 +2038,8 @@ OptionParser::finalise_aliases() { auto& detail = *option.second; auto hash = detail.hash(); - //if (m_parsed.find(hash) != m_parsed.end()) - { - m_keys[detail.short_name()] = hash; - m_keys[detail.long_name()] = hash; - } + m_keys[detail.short_name()] = hash; + m_keys[detail.long_name()] = hash; m_parsed.emplace(hash, OptionValue()); } From 4b63c333a842295b1bfb79d05863633037328300 Mon Sep 17 00:00:00 2001 From: Jarryd Beck Date: Thu, 1 Oct 2020 20:08:52 +1000 Subject: [PATCH 12/12] Improve README note --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b41b96a..9517993 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,9 @@ tagged release version. ## Version 3 breaking changes -If you have used version 2, there are a couple of breaking changes in version 3 -that you should be aware of. If you are new to `cxxopts` you can skip this -section. +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.