diff --git a/CMakeLists.txt b/CMakeLists.txt index c269bb1..19d8942 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,9 +7,8 @@ project(argparse LANGUAGES CXX ) -option(ARGPARSE_INSTALL ON) -option(ARGPARSE_BUILD_TESTS OFF) -option(ARGPARSE_LONG_VERSION_ARG_ONLY OFF) +option(ARGPARSE_INSTALL "Include an install target" ON) +option(ARGPARSE_BUILD_TESTS "Build tests" OFF) include(GNUInstallDirs) include(CMakePackageConfigHelpers) @@ -17,11 +16,6 @@ include(CMakePackageConfigHelpers) add_library(argparse INTERFACE) add_library(argparse::argparse ALIAS argparse) - -if (ARGPARSE_LONG_VERSION_ARG_ONLY) - target_compile_definitions(argparse INTERFACE ARGPARSE_LONG_VERSION_ARG_ONLY=true) -endif () - target_compile_features(argparse INTERFACE cxx_std_17) target_include_directories(argparse INTERFACE $ diff --git a/README.md b/README.md index 1398455..3736fc5 100644 --- a/README.md +++ b/README.md @@ -687,25 +687,30 @@ main ### Parent Parsers -Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the common arguments can be added as a parent to another ArgumentParser instance. The ```.add_parents``` method takes a list of ArgumentParser objects, collects all the positional and optional actions from them, and adds these actions to the ArgumentParser object being constructed: +A parser may use arguments that could be used by other parsers. + +These shared arguments can be added to a parser which is then used as a "parent" for parsers which also need those arguments. One or more parent parsers may be added to a parser with `.add_parents`. The positional and optional arguments in each parent is added to the child parser. ```cpp -argparse::ArgumentParser parent_parser("main"); -parent_parser.add_argument("--parent") +argparse::ArgumentParser surface_parser("surface", 1.0, argparse::default_arguments::none); +parent_parser.add_argument("--area") .default_value(0) .scan<'i', int>(); -argparse::ArgumentParser foo_parser("foo"); -foo_parser.add_argument("foo"); -foo_parser.add_parents(parent_parser); -foo_parser.parse_args({ "./main", "--parent", "2", "XXX" }); // parent = 2, foo = XXX +argparse::ArgumentParser floor_parser("floor"); +floor_parser.add_argument("tile_size").scan<'i', int>(); +floor_parser.add_parents(surface_parser); +floor_parser.parse_args({ "./main", "--area", "200", "12" }); // --area = 200, tile_size = 12 -argparse::ArgumentParser bar_parser("bar"); -bar_parser.add_argument("--bar"); -bar_parser.parse_args({ "./main", "--bar", "YYY" }); // bar = YYY +argparse::ArgumentParser ceiling_parser("ceiling"); +ceiling_parser.add_argument("--color"); +ceiling_parser.add_parents(surface_parser); +ceiling_parser.parse_args({ "./main", "--color", "gray" }); // --area = 0, --color = "gray" ``` -Note You must fully initialize the parsers before passing them via ```.add_parents```. If you change the parent parsers after the child parser, those changes will not be reflected in the child. +Changes made to parents after they are added to a parser are not reflected in any child parsers. Completely initialize parent parsers before adding them to a parser. + +Each parser will have the standard set of default arguments. Disable the default arguments in parent parsers to avoid duplicate help output. ### Subcommands @@ -1157,7 +1162,7 @@ sudo make install | :------------------- | :--------------- | :----------------- | | GCC >= 8.3.0 | libstdc++ | Ubuntu 18.04 | | Clang >= 7.0.0 | libc++ | Xcode 10.2 | -| MSVC >= 14.16 | Microsoft STL | Visual Studio 2017 | +| MSVC >= 16.8 | Microsoft STL | Visual Studio 2019 | ## Contributing Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information. diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp index 49e15c8..485964d 100644 --- a/include/argparse/argparse.hpp +++ b/include/argparse/argparse.hpp @@ -376,7 +376,8 @@ class Argument { explicit Argument(std::string_view prefix_chars, std::array &&a, std::index_sequence /*unused*/) - : m_is_optional((is_optional(a[I], prefix_chars) || ...)), + : m_accepts_optional_like_value(false), + m_is_optional((is_optional(a[I], prefix_chars) || ...)), m_is_required(false), m_is_repeatable(false), m_is_used(false), m_prefix_chars(prefix_chars) { ((void)m_names.emplace_back(a[I]), ...); @@ -702,10 +703,13 @@ public: if constexpr (!details::IsContainer) { return get() == rhs; } else { + using ValueType = typename T::value_type; auto lhs = get(); return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), std::end(rhs), - [](const auto &a, const auto &b) { return a == b; }); + [](const auto &a, const auto &b) { + return std::any_cast(a) == b; + }); } } @@ -972,18 +976,16 @@ private: * Get argument value given a type * @throws std::logic_error in case of incompatible types */ - template - auto get() const - -> std::conditional_t, T, const T &> { + template T get() const { if (!m_values.empty()) { if constexpr (details::IsContainer) { return any_cast_container(m_values); } else { - return *std::any_cast(&m_values.front()); + return std::any_cast(m_values.front()); } } if (m_default_value.has_value()) { - return *std::any_cast(&m_default_value); + return std::any_cast(m_default_value); } if constexpr (details::IsContainer) { if (!m_accepts_optional_like_value) { @@ -1019,7 +1021,7 @@ private: T result; std::transform( std::begin(operand), std::end(operand), std::back_inserter(result), - [](const auto &value) { return *std::any_cast(&value); }); + [](const auto &value) { return std::any_cast(value); }); return result; } @@ -1037,11 +1039,12 @@ private: [](const std::string &value) { return value; }}; std::vector m_values; NArgsRange m_num_args_range{1, 1}; - bool m_accepts_optional_like_value = false; - bool m_is_optional : true; - bool m_is_required : true; - bool m_is_repeatable : true; - bool m_is_used : true; // True if the optional argument is used by user + // Bit field of bool values. Set default value in ctor. + bool m_accepts_optional_like_value : 1; + bool m_is_optional : 1; + bool m_is_required : 1; + bool m_is_repeatable : 1; + bool m_is_used : 1; std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars }; @@ -1249,9 +1252,7 @@ public: * @throws std::logic_error if the option has no value * @throws std::bad_any_cast if the option is not of type T */ - template - auto get(std::string_view arg_name) const - -> std::conditional_t, T, const T &> { + template T get(std::string_view arg_name) const { if (!m_is_parsed) { throw std::logic_error("Nothing parsed, no arguments are available."); } diff --git a/test/test_get.cpp b/test/test_get.cpp index 9cc046c..ad719b0 100644 --- a/test/test_get.cpp +++ b/test/test_get.cpp @@ -33,3 +33,10 @@ TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { REQUIRE_THROWS_WITH_AS(program.get("--stuff"), "No value provided for '--stuff'.", std::logic_error); } + +TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) { + argparse::ArgumentParser program("test"); + program.add_argument("-s", "--stuff"); // as default type, a std::string + REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"})); + REQUIRE_THROWS_AS(program.get("--stuff"), std::bad_any_cast); +}