Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various maintenance tasks #254

Merged
merged 7 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,15 @@ 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)

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
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
Expand Down
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
33 changes: 17 additions & 16 deletions include/argparse/argparse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,8 @@ class Argument {
explicit Argument(std::string_view prefix_chars,
std::array<std::string_view, N> &&a,
std::index_sequence<I...> /*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]), ...);
Expand Down Expand Up @@ -698,10 +699,13 @@ class Argument {
if constexpr (!details::IsContainer<T>) {
return get<T>() == rhs;
} else {
using ValueType = typename T::value_type;
auto lhs = get<T>();
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<const ValueType &>(a) == b;
});
}
}

Expand Down Expand Up @@ -968,18 +972,16 @@ class Argument {
* Get argument value given a type
* @throws std::logic_error in case of incompatible types
*/
template <typename T>
auto get() const
-> std::conditional_t<details::IsContainer<T>, T, const T &> {
template <typename T> T get() const {
if (!m_values.empty()) {
if constexpr (details::IsContainer<T>) {
return any_cast_container<T>(m_values);
} else {
return *std::any_cast<T>(&m_values.front());
return std::any_cast<T>(m_values.front());
}
}
if (m_default_value.has_value()) {
return *std::any_cast<T>(&m_default_value);
return std::any_cast<T>(m_default_value);
}
if constexpr (details::IsContainer<T>) {
if (!m_accepts_optional_like_value) {
Expand Down Expand Up @@ -1015,7 +1017,7 @@ class Argument {
T result;
std::transform(
std::begin(operand), std::end(operand), std::back_inserter(result),
[](const auto &value) { return *std::any_cast<ValueType>(&value); });
[](const auto &value) { return std::any_cast<ValueType>(value); });
return result;
}

Expand All @@ -1033,11 +1035,12 @@ class Argument {
[](const std::string &value) { return value; }};
std::vector<std::any> 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
};

Expand Down Expand Up @@ -1245,9 +1248,7 @@ class ArgumentParser {
* @throws std::logic_error if the option has no value
* @throws std::bad_any_cast if the option is not of type T
*/
template <typename T = std::string>
auto get(std::string_view arg_name) const
-> std::conditional_t<details::IsContainer<T>, T, const T &> {
template <typename T = std::string> T get(std::string_view arg_name) const {
if (!m_is_parsed) {
throw std::logic_error("Nothing parsed, no arguments are available.");
}
Expand Down
7 changes: 7 additions & 0 deletions test/test_get.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int>("--stuff"), std::bad_any_cast);
}