Skip to content

Commit

Permalink
Merge pull request #254 from skrobinson/fix-maintenance
Browse files Browse the repository at this point in the history
Various maintenance tasks
  • Loading branch information
p-ranav authored Feb 19, 2023
2 parents e077137 + d0beb40 commit e516556
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 36 deletions.
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 @@ -702,10 +703,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 @@ -972,18 +976,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 @@ -1019,7 +1021,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 @@ -1037,11 +1039,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 @@ -1249,9 +1252,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);
}

0 comments on commit e516556

Please sign in to comment.