[med-svn] [Git][med-team/libargparse][master] 5 commits: New upstream version 3.1
Étienne Mollier (@emollier)
gitlab at salsa.debian.org
Fri Aug 23 11:49:03 BST 2024
Étienne Mollier pushed to branch master at Debian Med / libargparse
Commits:
1a04cdd0 by Étienne Mollier at 2024-08-23T12:37:16+02:00
New upstream version 3.1
- - - - -
8e9ec24b by Étienne Mollier at 2024-08-23T12:37:17+02:00
Update upstream source from tag 'upstream/3.1'
Update to upstream version '3.1'
with Debian dir d53c00ff54b8575da4a87f61a7cec2ba60b9fd7c
- - - - -
f455a727 by Étienne Mollier at 2024-08-23T12:37:56+02:00
d/control: declare compliance to standards version 4.7.0.
- - - - -
43013df3 by Étienne Mollier at 2024-08-23T12:38:15+02:00
d/control: migrate to pkgconf.
- - - - -
6af5ec8d by Étienne Mollier at 2024-08-23T12:47:12+02:00
Ready for upload to unstable.
- - - - -
23 changed files:
- + .bazelrc
- .gitignore
- + BUILD.bazel
- CMakeLists.txt
- README.md
- + WORKSPACE.bazel
- conanfile.py
- debian/changelog
- debian/control
- include/argparse/argparse.hpp
- module/argparse.cppm
- + samples/BUILD.bazel
- + samples/add_sample.bzl
- + test/BUILD.bazel
- test/CMakeLists.txt
- test/test_choices.cpp
- test/test_help.cpp
- + test/test_hidden_alias.cpp
- + test/test_hidden_argument.cpp
- test/test_parse_args.cpp
- test/test_positional_arguments.cpp
- + test/test_store_into.cpp
- xmake.lua
Changes:
=====================================
.bazelrc
=====================================
@@ -0,0 +1,8 @@
+build --enable_platform_specific_config
+
+build:linux --cxxopt=-std=c++17
+
+build:windows --copt=/utf-8
+build:windows --copt=/Zc:preprocessor
+build:windows --cxxopt=/std:c++17
+build:windows --cxxopt=/Zc:__cplusplus
=====================================
.gitignore
=====================================
@@ -271,3 +271,7 @@ analysis-cppcheck-build-dir
ideas
desktop.iniimages/
+
+# Ignore all bazel-* symlinks. There is no full list since this can change
+# based on the name of the directory bazel is cloned into.
+/bazel-*
=====================================
BUILD.bazel
=====================================
@@ -0,0 +1,6 @@
+cc_library(
+ name = "argparse",
+ hdrs = ["include/argparse/argparse.hpp"],
+ includes = ["include"],
+ visibility = ["//visibility:public"],
+)
=====================================
CMakeLists.txt
=====================================
@@ -7,14 +7,14 @@ else()
endif()
project(argparse
- VERSION 3.0.0
+ VERSION 3.1.0
DESCRIPTION "A single header argument parser for C++17"
HOMEPAGE_URL "https://github.com/p-ranav/argparse"
LANGUAGES CXX
)
-option(ARGPARSE_INSTALL "Include an install target" ON)
-option(ARGPARSE_BUILD_TESTS "Build tests" ON)
+option(ARGPARSE_INSTALL "Include an install target" ${ARGPARSE_IS_TOP_LEVEL})
+option(ARGPARSE_BUILD_TESTS "Build tests" ${ARGPARSE_IS_TOP_LEVEL})
option(ARGPARSE_BUILD_SAMPLES "Build samples" OFF)
include(GNUInstallDirs)
@@ -32,11 +32,11 @@ if(ARGPARSE_BUILD_SAMPLES)
add_subdirectory(samples)
endif()
-if(ARGPARSE_BUILD_TESTS AND ARGPARSE_IS_TOP_LEVEL)
+if(ARGPARSE_BUILD_TESTS)
add_subdirectory(test)
endif()
-if(ARGPARSE_INSTALL AND ARGPARSE_IS_TOP_LEVEL)
+if(ARGPARSE_INSTALL)
install(TARGETS argparse EXPORT argparseConfig)
install(EXPORT argparseConfig
NAMESPACE argparse::
=====================================
README.md
=====================================
@@ -6,7 +6,7 @@
<a href="https://github.com/p-ranav/argparse/blob/master/LICENSE">
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="license"/>
</a>
- <img src="https://img.shields.io/badge/version-3.0-blue.svg?cacheSeconds=2592000" alt="version"/>
+ <img src="https://img.shields.io/badge/version-3.1-blue.svg?cacheSeconds=2592000" alt="version"/>
</p>
## Highlights
@@ -26,6 +26,7 @@
* [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments)
* [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value)
* [Mutually Exclusive Group](#mutually-exclusive-group)
+ * [Storing values into variables](#store-into)
* [Negative Numbers](#negative-numbers)
* [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments)
* [Printing Help](#printing-help)
@@ -38,6 +39,7 @@
* [Parent Parsers](#parent-parsers)
* [Subcommands](#subcommands)
* [Parse Known Args](#parse-known-args)
+ * [Hidden argument and alias](#hidden-argument-alias)
* [ArgumentParser in bool Context](#argumentparser-in-bool-context)
* [Custom Prefix Characters](#custom-prefix-characters)
* [Custom Assignment Characters](#custom-assignment-characters)
@@ -46,6 +48,7 @@
* [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments)
* [Restricting the set of values for an argument](#restricting-the-set-of-values-for-an-argument)
* [Using `option=value` syntax](#using-optionvalue-syntax)
+ * [Advanced usage formatting](#advanced-usage-formatting)
* [Developer Notes](#developer-notes)
* [Copying and Moving](#copying-and-moving)
* [CMake Integration](#cmake-integration)
@@ -315,6 +318,43 @@ foo at bar:/home/dev/$ ./main
One of the arguments '--first VAR' or '--second VAR' is required
```
+### Storing values into variables
+
+It is possible to bind arguments to a variable storing their value, as an
+alternative to explicitly calling ``program.get<T>(arg_name)`` or ``program[arg_name]``
+
+This is currently implementeted for variables of type ``bool`` (this also
+implicitly calls ``flag()``), ``int``, ``double``, ``std::string``,
+``std::vector<std::string>`` and ``std::vector<int>``.
+If the argument is not specified in the command
+line, the default value (if set) is set into the variable.
+
+```cpp
+bool flagvar = false;
+program.add_argument("--flagvar").store_into(flagvar);
+
+int intvar = 0;
+program.add_argument("--intvar").store_into(intvar);
+
+double doublevar = 0;
+program.add_argument("--doublevar").store_into(doublevar);
+
+std::string strvar;
+program.add_argument("--strvar").store_into(strvar);
+
+std::vector<std::string> strvar_repeated;
+program.add_argument("--strvar-repeated").append().store_into(strvar_repeated);
+
+std::vector<std::string> strvar_multi_valued;
+program.add_argument("--strvar-multi-valued").nargs(2).store_into(strvar_multi_valued);
+
+std::vector<int> intvar_repeated;
+program.add_argument("--intvar-repeated").append().store_into(intvar_repeated);
+
+std::vector<int> intvar_multi_valued;
+program.add_argument("--intvar-multi-valued").nargs(2).store_into(intvar_multi_valued);
+```
+
### Negative Numbers
Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
@@ -759,8 +799,8 @@ 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 surface_parser("surface", 1.0, argparse::default_arguments::none);
-parent_parser.add_argument("--area")
+argparse::ArgumentParser surface_parser("surface", "1.0", argparse::default_arguments::none);
+surface_parser.add_argument("--area")
.default_value(0)
.scan<'i', int>();
@@ -970,6 +1010,32 @@ int main(int argc, char *argv[]) {
}
```
+### Hidden argument and alias
+
+It is sometimes desirable to offer an alias for an argument, but without it
+appearing it in the usage. For example, to phase out a deprecated wording of
+an argument while not breaking backwards compatible. This can be done with
+the ``ArgumentParser::add_hidden_alias_for()` method.
+
+```cpp
+argparse::ArgumentParser program("test");
+
+auto &arg = program.add_argument("--suppress").flag();
+program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias
+```
+
+The ``Argument::hidden()`` method can also be used to prevent a (generally
+optional) argument from appearing in the usage or help.
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("--non-documented").flag().hidden();
+```
+
+This can also be used on positional arguments, but in that later case it only
+makes sense in practice for the last ones.
+
### ArgumentParser in bool Context
An `ArgumentParser` is `false` until it (or one of its subparsers) have extracted
@@ -1236,6 +1302,68 @@ foo at bar:/home/dev/$ ./test --bar=BAR --foo
--bar: BAR
```
+### Advanced usage formatting
+
+By default usage is reported on a single line.
+
+The ``ArgumentParser::set_usage_max_line_width(width)`` method can be used
+to display the usage() on multiple lines, by defining the maximum line width.
+
+It can be combined with a call to ``ArgumentParser::set_usage_break_on_mutex()``
+to ask grouped mutually exclusive arguments to be displayed on a separate line.
+
+``ArgumentParser::add_usage_newline()`` can also be used to force the next
+argument to be displayed on a new line in the usage output.
+
+The following snippet
+
+```cpp
+ argparse::ArgumentParser program("program");
+ program.set_usage_max_line_width(80);
+ program.set_usage_break_on_mutex();
+ program.add_argument("--quite-long-option-name").flag();
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ program.add_argument("-c").flag();
+ program.add_argument("--another-one").flag();
+ program.add_argument("-d").flag();
+ program.add_argument("--yet-another-long-one").flag();
+ program.add_argument("--will-go-on-new-line").flag();
+ program.add_usage_newline();
+ program.add_argument("--new-line").flag();
+ std::cout << program.usage() << std::endl;
+```
+
+will display:
+```console
+Usage: program [--help] [--version] [--quite-long-option-name]
+ [[-a]|[-b]]
+ [-c] [--another-one] [-d] [--yet-another-long-one]
+ [--will-go-on-new-line]
+ [--new-line]
+```
+
+Furthermore arguments can be separated into several groups by calling
+``ArgumentParser::add_group(group_name)``. Only optional arguments should
+be specified after the first call to add_group().
+
+```cpp
+ argparse::ArgumentParser program("program");
+ program.set_usage_max_line_width(80);
+ program.add_argument("-a").flag().help("help_a");
+ program.add_group("Advanced options");
+ program.add_argument("-b").flag().help("help_b");
+```
+
+will display:
+```console
+Usage: program [--help] [--version] [-a]
+
+Advanced options:
+ [-b]
+```
+
## Developer Notes
### Copying and Moving
@@ -1263,6 +1391,19 @@ add_executable(myproject main.cpp)
target_link_libraries(myproject argparse)
```
+## Bazel Integration
+
+Add an `http_archive` in WORKSPACE.bazel, for example
+
+```starlark
+http_archive(
+ name = "argparse",
+ sha256 = "674e724c2702f0bfef1619161815257a407e1babce30d908327729fba6ce4124",
+ strip_prefix = "argparse-3.1",
+ url = "https://github.com/p-ranav/argparse/archive/refs/tags/v3.1.zip",
+)
+```
+
## Building, Installing, and Testing
```bash
=====================================
WORKSPACE.bazel
=====================================
@@ -0,0 +1 @@
+workspace(name="argparse")
\ No newline at end of file
=====================================
conanfile.py
=====================================
@@ -2,7 +2,7 @@ from conans import ConanFile
class ArgparseConan(ConanFile):
name = "argparse"
- version = "3.0"
+ version = "3.1"
exports_sources = "include/argparse.hpp"
no_copy_source = True
=====================================
debian/changelog
=====================================
@@ -1,3 +1,12 @@
+libargparse (3.1-1) unstable; urgency=medium
+
+ * Team upload.
+ * New upstream version 3.1
+ * d/control: declare compliance to standards version 4.7.0.
+ * d/control: migrate to pkgconf.
+
+ -- Étienne Mollier <emollier at debian.org> Fri, 23 Aug 2024 12:39:12 +0200
+
libargparse (3.0-2) unstable; urgency=medium
* Set upstream metadata fields: Repository.
=====================================
debian/control
=====================================
@@ -5,8 +5,8 @@ Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.
Uploaders: Andreas Tille <tille at debian.org>
Build-Depends: debhelper-compat (= 13),
cmake,
- pkg-config
-Standards-Version: 4.6.2
+ pkgconf
+Standards-Version: 4.7.0
Vcs-Browser: https://salsa.debian.org/med-team/libargparse
Vcs-Git: https://salsa.debian.org/med-team/libargparse.git
Homepage: https://github.com/p-ranav/argparse
=====================================
include/argparse/argparse.hpp
=====================================
@@ -30,11 +30,13 @@ SOFTWARE.
*/
#pragma once
+#include <cerrno>
+
#ifndef ARGPARSE_MODULE_USE_STD_MODULE
#include <algorithm>
#include <any>
#include <array>
-#include <cerrno>
+#include <set>
#include <charconv>
#include <cstdlib>
#include <functional>
@@ -57,6 +59,18 @@ SOFTWARE.
#include <vector>
#endif
+#ifndef ARGPARSE_CUSTOM_STRTOF
+#define ARGPARSE_CUSTOM_STRTOF strtof
+#endif
+
+#ifndef ARGPARSE_CUSTOM_STRTOD
+#define ARGPARSE_CUSTOM_STRTOD strtod
+#endif
+
+#ifndef ARGPARSE_CUSTOM_STRTOLD
+#define ARGPARSE_CUSTOM_STRTOLD strtold
+#endif
+
namespace argparse {
namespace details { // namespace for helper methods
@@ -224,7 +238,7 @@ constexpr auto consume_hex_prefix(std::string_view s)
template <class T, auto Param>
inline auto do_from_chars(std::string_view s) -> T {
- T x;
+ T x{0};
auto [first, last] = pointer_range(s);
auto [ptr, ec] = std::from_chars(first, last, x, Param);
if (ec == std::errc()) {
@@ -346,9 +360,10 @@ template <class T> struct parse_number<T> {
namespace {
template <class T> inline const auto generic_strtod = nullptr;
-template <> inline const auto generic_strtod<float> = strtof;
-template <> inline const auto generic_strtod<double> = strtod;
-template <> inline const auto generic_strtod<long double> = strtold;
+template <> inline const auto generic_strtod<float> = ARGPARSE_CUSTOM_STRTOF;
+template <> inline const auto generic_strtod<double> = ARGPARSE_CUSTOM_STRTOD;
+template <>
+inline const auto generic_strtod<long double> = ARGPARSE_CUSTOM_STRTOLD;
} // namespace
@@ -544,10 +559,9 @@ std::size_t get_levenshtein_distance(const StringType &s1,
}
template <typename ValueType>
-std::string_view
-get_most_similar_string(const std::map<std::string_view, ValueType> &map,
- const std::string_view input) {
- std::string_view most_similar{};
+std::string get_most_similar_string(const std::map<std::string, ValueType> &map,
+ const std::string &input) {
+ std::string most_similar{};
std::size_t min_distance = std::numeric_limits<std::size_t>::max();
for (const auto &entry : map) {
@@ -593,7 +607,7 @@ class Argument {
: 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) {
+ m_is_hidden(false), m_prefix_chars(prefix_chars) {
((void)m_names.emplace_back(a[I]), ...);
std::sort(
m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
@@ -657,7 +671,7 @@ public:
}
template <class F, class... Args>
- auto action(F &&callable, Args &&...bound_args)
+ auto action(F &&callable, Args &&... bound_args)
-> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
Argument &> {
using action_type = std::conditional_t<
@@ -676,11 +690,111 @@ public:
return *this;
}
+ auto &store_into(bool &var) {
+ flag();
+ if (m_default_value.has_value()) {
+ var = std::any_cast<bool>(m_default_value);
+ }
+ action([&var](const auto & /*unused*/) { var = true; });
+ return *this;
+ }
+
+ template <typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
+ auto &store_into(T &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<T>(m_default_value);
+ }
+ action([&var](const auto &s) {
+ var = details::parse_number<T, details::radix_10>()(s);
+ });
+ return *this;
+ }
+
+ auto &store_into(double &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<double>(m_default_value);
+ }
+ action([&var](const auto &s) {
+ var = details::parse_number<double, details::chars_format::general>()(s);
+ });
+ return *this;
+ }
+
+ auto &store_into(std::string &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<std::string>(m_default_value);
+ }
+ action([&var](const std::string &s) { var = s; });
+ return *this;
+ }
+
+ auto &store_into(std::vector<std::string> &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<std::vector<std::string>>(m_default_value);
+ }
+ action([this, &var](const std::string &s) {
+ if (!m_is_used) {
+ var.clear();
+ }
+ m_is_used = true;
+ var.push_back(s);
+ });
+ return *this;
+ }
+
+ auto &store_into(std::vector<int> &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<std::vector<int>>(m_default_value);
+ }
+ action([this, &var](const std::string &s) {
+ if (!m_is_used) {
+ var.clear();
+ }
+ m_is_used = true;
+ var.push_back(details::parse_number<int, details::radix_10>()(s));
+ });
+ return *this;
+ }
+
+ auto &store_into(std::set<std::string> &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<std::set<std::string>>(m_default_value);
+ }
+ action([this, &var](const std::string &s) {
+ if (!m_is_used) {
+ var.clear();
+ }
+ m_is_used = true;
+ var.insert(s);
+ });
+ return *this;
+ }
+
+ auto &store_into(std::set<int> &var) {
+ if (m_default_value.has_value()) {
+ var = std::any_cast<std::set<int>>(m_default_value);
+ }
+ action([this, &var](const std::string &s) {
+ if (!m_is_used) {
+ var.clear();
+ }
+ m_is_used = true;
+ var.insert(details::parse_number<int, details::radix_10>()(s));
+ });
+ return *this;
+ }
+
auto &append() {
m_is_repeatable = true;
return *this;
}
+ // Cause the argument to be invisible in usage and help
+ auto &hidden() {
+ m_is_hidden = true;
+ return *this;
+ }
+
template <char Shape, typename T>
auto scan() -> std::enable_if_t<std::is_arithmetic_v<T>, Argument &> {
static_assert(!(std::is_const_v<T> || std::is_volatile_v<T>),
@@ -783,7 +897,7 @@ public:
}
template <typename T, typename... U>
- Argument &choices(T &&first, U &&...rest) {
+ Argument &choices(T &&first, U &&... rest) {
add_choice(std::forward<T>(first));
choices(std::forward<U>(rest)...);
return *this;
@@ -833,20 +947,30 @@ public:
}
}
+ /* The dry_run parameter can be set to true to avoid running the actions,
+ * and setting m_is_used. This may be used by a pre-processing step to do
+ * a first iteration over arguments.
+ */
template <typename Iterator>
Iterator consume(Iterator start, Iterator end,
- std::string_view used_name = {}) {
+ std::string_view used_name = {}, bool dry_run = false) {
if (!m_is_repeatable && m_is_used) {
- throw std::runtime_error("Duplicate argument");
+ throw std::runtime_error(
+ std::string("Duplicate argument ").append(used_name));
}
- m_is_used = true;
m_used_name = used_name;
if (m_choices.has_value()) {
// Check each value in (start, end) and make sure
// it is in the list of allowed choices/options
+ std::size_t i = 0;
+ auto max_number_of_args = m_num_args_range.get_max();
for (auto it = start; it != end; ++it) {
+ if (i == max_number_of_args) {
+ break;
+ }
find_value_in_choices_or_throw(it);
+ i += 1;
}
}
@@ -854,8 +978,11 @@ public:
const auto num_args_min = m_num_args_range.get_min();
std::size_t dist = 0;
if (num_args_max == 0) {
- m_values.emplace_back(m_implicit_value);
- std::visit([](const auto &f) { f({}); }, m_action);
+ if (!dry_run) {
+ m_values.emplace_back(m_implicit_value);
+ std::visit([](const auto &f) { f({}); }, m_action);
+ m_is_used = true;
+ }
return start;
}
if ((dist = static_cast<std::size_t>(std::distance(start, end))) >=
@@ -870,7 +997,8 @@ public:
std::bind(is_optional, std::placeholders::_1, m_prefix_chars));
dist = static_cast<std::size_t>(std::distance(start, end));
if (dist < num_args_min) {
- throw std::runtime_error("Too few arguments");
+ throw std::runtime_error("Too few arguments for '" +
+ std::string(m_used_name) + "'.");
}
}
@@ -892,10 +1020,16 @@ public:
Iterator first, last;
Argument &self;
};
- std::visit(ActionApply{start, end, *this}, m_action);
+ if (!dry_run) {
+ std::visit(ActionApply{start, end, *this}, m_action);
+ m_is_used = true;
+ }
return end;
}
if (m_default_value.has_value()) {
+ if (!dry_run) {
+ m_is_used = true;
+ }
return start;
}
throw std::runtime_error("Too few arguments for '" +
@@ -966,13 +1100,17 @@ public:
const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
if (m_num_args_range.get_max() > 0) {
usage << " " << metavar;
- if (m_num_args_range.get_max() > 1) {
+ if (m_num_args_range.get_max() > 1 &&
+ m_metavar.find("> <") == std::string::npos) {
usage << "...";
}
}
if (!m_is_required) {
usage << "]";
}
+ if (m_is_repeatable) {
+ usage << "...";
+ }
return usage.str();
}
@@ -1021,6 +1159,11 @@ public:
argument.m_num_args_range == NArgsRange{1, 1}) {
name_stream << " " << argument.m_metavar;
}
+ else if (!argument.m_metavar.empty() &&
+ argument.m_num_args_range.get_min() == argument.m_num_args_range.get_max() &&
+ argument.m_metavar.find("> <") != std::string::npos) {
+ name_stream << " " << argument.m_metavar;
+ }
}
// align multiline help message
@@ -1059,11 +1202,20 @@ public:
}
stream << argument.m_num_args_range;
+ bool add_space = false;
if (argument.m_default_value.has_value() &&
argument.m_num_args_range != NArgsRange{0, 0}) {
stream << "[default: " << argument.m_default_value_repr << "]";
+ add_space = true;
} else if (argument.m_is_required) {
stream << "[required]";
+ add_space = true;
+ }
+ if (argument.m_is_repeatable) {
+ if (add_space) {
+ stream << " ";
+ }
+ stream << "[may be repeated]";
}
stream << "\n";
return stream;
@@ -1090,6 +1242,31 @@ public:
}
}
+ /*
+ * positional:
+ * _empty_
+ * '-'
+ * '-' decimal-literal
+ * !'-' anything
+ */
+ static bool is_positional(std::string_view name,
+ std::string_view prefix_chars) {
+ auto first = lookahead(name);
+
+ if (first == eof) {
+ return true;
+ }
+ if (prefix_chars.find(static_cast<char>(first)) !=
+ std::string_view::npos) {
+ name.remove_prefix(1);
+ if (name.empty()) {
+ return true;
+ }
+ return is_decimal_literal(name);
+ }
+ return true;
+ }
+
private:
class NArgsRange {
std::size_t m_min;
@@ -1325,30 +1502,6 @@ private:
return !is_positional(name, prefix_chars);
}
- /*
- * positional:
- * _empty_
- * '-'
- * '-' decimal-literal
- * !'-' anything
- */
- static bool is_positional(std::string_view name,
- std::string_view prefix_chars) {
- auto first = lookahead(name);
-
- if (first == eof) {
- return true;
- } else if (prefix_chars.find(static_cast<char>(first)) !=
- std::string_view::npos) {
- name.remove_prefix(1);
- if (name.empty()) {
- return true;
- }
- return is_decimal_literal(name);
- }
- return true;
- }
-
/*
* Get argument value given a type
* @throws std::logic_error in case of incompatible types
@@ -1402,6 +1555,10 @@ private:
return result;
}
+ void set_usage_newline_counter(int i) { m_usage_newline_counter = i; }
+
+ void set_group_idx(std::size_t i) { m_group_idx = i; }
+
std::vector<std::string> m_names;
std::string_view m_used_name;
std::string m_help;
@@ -1425,7 +1582,10 @@ private:
bool m_is_required : 1;
bool m_is_repeatable : 1;
bool m_is_used : 1;
+ bool m_is_hidden : 1; // if set, does not appear in usage or help
std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars
+ int m_usage_newline_counter = 0;
+ std::size_t m_group_idx = 0;
};
class ArgumentParser {
@@ -1501,6 +1661,8 @@ public:
m_positional_arguments.splice(std::cend(m_positional_arguments),
m_optional_arguments, argument);
}
+ argument->set_usage_newline_counter(m_usage_newline_counter);
+ argument->set_group_idx(m_group_names.size());
index_argument(argument);
return *argument;
@@ -1529,6 +1691,8 @@ public:
template <typename... Targs> Argument &add_argument(Targs... f_args) {
auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
m_elements.push_back(&argument);
+ argument.set_usage_newline_counter(m_parent.m_usage_newline_counter);
+ argument.set_group_idx(m_parent.m_group_names.size());
return argument;
}
@@ -1546,7 +1710,7 @@ public:
// Parameter packed add_parents method
// Accepts a variadic number of ArgumentParser objects
template <typename... Targs>
- ArgumentParser &add_parents(const Targs &...f_args) {
+ ArgumentParser &add_parents(const Targs &... f_args) {
for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) {
for (const auto &argument : parent_parser.m_positional_arguments) {
auto it = m_positional_arguments.insert(
@@ -1562,6 +1726,23 @@ public:
return *this;
}
+ // Ask for the next optional arguments to be displayed on a separate
+ // line in usage() output. Only effective if set_usage_max_line_width() is
+ // also used.
+ ArgumentParser &add_usage_newline() {
+ ++m_usage_newline_counter;
+ return *this;
+ }
+
+ // Ask for the next optional arguments to be displayed in a separate section
+ // in usage() and help (<< *this) output.
+ // For usage(), this is only effective if set_usage_max_line_width() is
+ // also used.
+ ArgumentParser &add_group(std::string group_name) {
+ m_group_names.emplace_back(std::move(group_name));
+ return *this;
+ }
+
ArgumentParser &add_description(std::string description) {
m_description = std::move(description);
return *this;
@@ -1572,6 +1753,21 @@ public:
return *this;
}
+ // Add a un-documented/hidden alias for an argument.
+ // Ideally we'd want this to be a method of Argument, but Argument
+ // does not own its owing ArgumentParser.
+ ArgumentParser &add_hidden_alias_for(Argument &arg, std::string_view alias) {
+ for (auto it = m_optional_arguments.begin();
+ it != m_optional_arguments.end(); ++it) {
+ if (&(*it) == &arg) {
+ m_argument_map.insert_or_assign(std::string(alias), it);
+ return *this;
+ }
+ }
+ throw std::logic_error(
+ "Argument is not an optional argument of this parser");
+ }
+
/* Getter for arguments and subparsers.
* @throws std::logic_error in case of an invalid argument or subparser name
*/
@@ -1579,11 +1775,12 @@ public:
if constexpr (std::is_same_v<T, Argument>) {
return (*this)[name];
} else {
- auto subparser_it = m_subparser_map.find(name);
+ std::string str_name(name);
+ auto subparser_it = m_subparser_map.find(str_name);
if (subparser_it != m_subparser_map.end()) {
return subparser_it->second->get();
}
- throw std::logic_error("No such subparser: " + std::string(name));
+ throw std::logic_error("No such subparser: " + str_name);
}
}
@@ -1635,9 +1832,9 @@ public:
for (Argument *arg : group.m_elements) {
if (i + 1 == size) {
// last
- argument_names += "'" + arg->get_usage_full() + "' ";
+ argument_names += std::string("'") + arg->get_usage_full() + std::string("' ");
} else {
- argument_names += "'" + arg->get_usage_full() + "' or ";
+ argument_names += std::string("'") + arg->get_usage_full() + std::string("' or ");
}
i += 1;
}
@@ -1713,7 +1910,7 @@ public:
/* Getter that returns true if a subcommand is used.
*/
auto is_subcommand_used(std::string_view subcommand_name) const {
- return m_subparser_used.at(subcommand_name);
+ return m_subparser_used.at(std::string(subcommand_name));
}
/* Getter that returns true if a subcommand is used.
@@ -1727,12 +1924,12 @@ public:
* @throws std::logic_error in case of an invalid argument name
*/
Argument &operator[](std::string_view arg_name) const {
- auto it = m_argument_map.find(arg_name);
+ std::string name(arg_name);
+ auto it = m_argument_map.find(name);
if (it != m_argument_map.end()) {
return *(it->second);
}
if (!is_valid_prefix_char(arg_name.front())) {
- std::string name(arg_name);
const auto legal_prefix_char = get_any_valid_prefix_char();
const auto prefix = std::string(1, legal_prefix_char);
@@ -1765,23 +1962,43 @@ public:
stream << parser.m_description << "\n\n";
}
- if (!parser.m_positional_arguments.empty()) {
+ const bool has_visible_positional_args = std::find_if(
+ parser.m_positional_arguments.begin(),
+ parser.m_positional_arguments.end(),
+ [](const auto &argument) {
+ return !argument.m_is_hidden; }) !=
+ parser.m_positional_arguments.end();
+ if (has_visible_positional_args) {
stream << "Positional arguments:\n";
}
for (const auto &argument : parser.m_positional_arguments) {
- stream.width(static_cast<std::streamsize>(longest_arg_length));
- stream << argument;
+ if (!argument.m_is_hidden) {
+ stream.width(static_cast<std::streamsize>(longest_arg_length));
+ stream << argument;
+ }
}
if (!parser.m_optional_arguments.empty()) {
- stream << (parser.m_positional_arguments.empty() ? "" : "\n")
+ stream << (!has_visible_positional_args ? "" : "\n")
<< "Optional arguments:\n";
}
for (const auto &argument : parser.m_optional_arguments) {
- stream.width(static_cast<std::streamsize>(longest_arg_length));
- stream << argument;
+ if (argument.m_group_idx == 0 && !argument.m_is_hidden) {
+ stream.width(static_cast<std::streamsize>(longest_arg_length));
+ stream << argument;
+ }
+ }
+
+ for (size_t i_group = 0; i_group < parser.m_group_names.size(); ++i_group) {
+ stream << "\n" << parser.m_group_names[i_group] << " (detailed usage):\n";
+ for (const auto &argument : parser.m_optional_arguments) {
+ if (argument.m_group_idx == i_group + 1 && !argument.m_is_hidden) {
+ stream.width(static_cast<std::streamsize>(longest_arg_length));
+ stream << argument;
+ }
+ }
}
bool has_visible_subcommands = std::any_of(
@@ -1820,24 +2037,147 @@ public:
return out;
}
+ // Sets the maximum width for a line of the Usage message
+ ArgumentParser &set_usage_max_line_width(size_t w) {
+ this->m_usage_max_line_width = w;
+ return *this;
+ }
+
+ // Asks to display arguments of mutually exclusive group on separate lines in
+ // the Usage message
+ ArgumentParser &set_usage_break_on_mutex() {
+ this->m_usage_break_on_mutex = true;
+ return *this;
+ }
+
// Format usage part of help only
auto usage() const -> std::string {
std::stringstream stream;
- stream << "Usage: " << this->m_program_name;
+ std::string curline("Usage: ");
+ curline += this->m_program_name;
+ const bool multiline_usage =
+ this->m_usage_max_line_width < std::numeric_limits<std::size_t>::max();
+ const size_t indent_size = curline.size();
+
+ const auto deal_with_options_of_group = [&](std::size_t group_idx) {
+ bool found_options = false;
+ // Add any options inline here
+ const MutuallyExclusiveGroup *cur_mutex = nullptr;
+ int usage_newline_counter = -1;
+ for (const auto &argument : this->m_optional_arguments) {
+ if (argument.m_is_hidden) {
+ continue;
+ }
+ if (multiline_usage) {
+ if (argument.m_group_idx != group_idx) {
+ continue;
+ }
+ if (usage_newline_counter != argument.m_usage_newline_counter) {
+ if (usage_newline_counter >= 0) {
+ if (curline.size() > indent_size) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ }
+ }
+ usage_newline_counter = argument.m_usage_newline_counter;
+ }
+ }
+ found_options = true;
+ const std::string arg_inline_usage = argument.get_inline_usage();
+ const MutuallyExclusiveGroup *arg_mutex =
+ get_belonging_mutex(&argument);
+ if ((cur_mutex != nullptr) && (arg_mutex == nullptr)) {
+ curline += ']';
+ if (this->m_usage_break_on_mutex) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ }
+ } else if ((cur_mutex == nullptr) && (arg_mutex != nullptr)) {
+ if ((this->m_usage_break_on_mutex && curline.size() > indent_size) ||
+ curline.size() + 3 + arg_inline_usage.size() >
+ this->m_usage_max_line_width) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ }
+ curline += " [";
+ } else if ((cur_mutex != nullptr) && (arg_mutex != nullptr)) {
+ if (cur_mutex != arg_mutex) {
+ curline += ']';
+ if (this->m_usage_break_on_mutex ||
+ curline.size() + 3 + arg_inline_usage.size() >
+ this->m_usage_max_line_width) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ }
+ curline += " [";
+ } else {
+ curline += '|';
+ }
+ }
+ cur_mutex = arg_mutex;
+ if (curline.size() + 1 + arg_inline_usage.size() >
+ this->m_usage_max_line_width) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ curline += " ";
+ } else if (cur_mutex == nullptr) {
+ curline += " ";
+ }
+ curline += arg_inline_usage;
+ }
+ if (cur_mutex != nullptr) {
+ curline += ']';
+ }
+ return found_options;
+ };
+
+ const bool found_options = deal_with_options_of_group(0);
- // Add any options inline here
- for (const auto &argument : this->m_optional_arguments) {
- stream << " " << argument.get_inline_usage();
+ if (found_options && multiline_usage &&
+ !this->m_positional_arguments.empty()) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
}
// Put positional arguments after the optionals
for (const auto &argument : this->m_positional_arguments) {
- if (!argument.m_metavar.empty()) {
- stream << " " << argument.m_metavar;
+ if (argument.m_is_hidden) {
+ continue;
+ }
+ const std::string pos_arg = !argument.m_metavar.empty()
+ ? argument.m_metavar
+ : argument.m_names.front();
+ if (curline.size() + 1 + pos_arg.size() > this->m_usage_max_line_width) {
+ stream << curline << std::endl;
+ curline = std::string(indent_size, ' ');
+ }
+ curline += " ";
+ if (argument.m_num_args_range.get_min() == 0 &&
+ !argument.m_num_args_range.is_right_bounded()) {
+ curline += "[";
+ curline += pos_arg;
+ curline += "]...";
+ } else if (argument.m_num_args_range.get_min() == 1 &&
+ !argument.m_num_args_range.is_right_bounded()) {
+ curline += pos_arg;
+ curline += "...";
} else {
- stream << " " << argument.m_names.front();
+ curline += pos_arg;
}
}
+
+ if (multiline_usage) {
+ // Display options of other groups
+ for (std::size_t i = 0; i < m_group_names.size(); ++i) {
+ stream << curline << std::endl << std::endl;
+ stream << m_group_names[i] << ":" << std::endl;
+ curline = std::string(indent_size, ' ');
+ deal_with_options_of_group(i + 1);
+ }
+ }
+
+ stream << curline;
+
// Put subcommands after positional arguments
if (!m_subparser_map.empty()) {
stream << " {";
@@ -1878,7 +2218,17 @@ public:
void set_suppress(bool suppress) { m_suppress = suppress; }
-private:
+protected:
+ const MutuallyExclusiveGroup *get_belonging_mutex(const Argument *arg) const {
+ for (const auto &mutex : m_mutually_exclusive_groups) {
+ if (std::find(mutex.m_elements.begin(), mutex.m_elements.end(), arg) !=
+ mutex.m_elements.end()) {
+ return &mutex;
+ }
+ }
+ return nullptr;
+ }
+
bool is_valid_prefix_char(char c) const {
return m_prefix_chars.find(c) != std::string::npos;
}
@@ -1965,10 +2315,8 @@ private:
if (Argument::is_positional(current_argument, m_prefix_chars)) {
if (positional_argument_it == std::end(m_positional_arguments)) {
- std::string_view maybe_command = current_argument;
-
// Check sub-parsers
- auto subparser_it = m_subparser_map.find(maybe_command);
+ auto subparser_it = m_subparser_map.find(current_argument);
if (subparser_it != m_subparser_map.end()) {
// build list of remaining args
@@ -1977,7 +2325,7 @@ private:
// invoke subparser
m_is_parsed = true;
- m_subparser_used[maybe_command] = true;
+ m_subparser_used[current_argument] = true;
return subparser_it->second->get().parse_args(
unprocessed_arguments);
}
@@ -2019,6 +2367,22 @@ private:
}
}
auto argument = positional_argument_it++;
+
+ // Deal with the situation of <positional_arg1>... <positional_arg2>
+ if (argument->m_num_args_range.get_min() == 1 &&
+ argument->m_num_args_range.get_max() == (std::numeric_limits<std::size_t>::max)() &&
+ positional_argument_it != std::end(m_positional_arguments) &&
+ std::next(positional_argument_it) == std::end(m_positional_arguments) &&
+ positional_argument_it->m_num_args_range.get_min() == 1 &&
+ positional_argument_it->m_num_args_range.get_max() == 1 ) {
+ if (std::next(it) != end) {
+ positional_argument_it->consume(std::prev(end), end);
+ end = std::prev(end);
+ } else {
+ throw std::runtime_error("Missing " + positional_argument_it->m_names.front());
+ }
+ }
+
it = argument->consume(it, end);
continue;
}
@@ -2068,10 +2432,8 @@ private:
if (Argument::is_positional(current_argument, m_prefix_chars)) {
if (positional_argument_it == std::end(m_positional_arguments)) {
- std::string_view maybe_command = current_argument;
-
// Check sub-parsers
- auto subparser_it = m_subparser_map.find(maybe_command);
+ auto subparser_it = m_subparser_map.find(current_argument);
if (subparser_it != m_subparser_map.end()) {
// build list of remaining args
@@ -2080,7 +2442,7 @@ private:
// invoke subparser
m_is_parsed = true;
- m_subparser_used[maybe_command] = true;
+ m_subparser_used[current_argument] = true;
return subparser_it->second->get().parse_known_args_internal(
unprocessed_arguments);
}
@@ -2165,13 +2527,17 @@ private:
bool m_is_parsed = false;
std::list<Argument> m_positional_arguments;
std::list<Argument> m_optional_arguments;
- std::map<std::string_view, argument_it> m_argument_map;
+ std::map<std::string, argument_it> m_argument_map;
std::string m_parser_path;
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
- std::map<std::string_view, argument_parser_it> m_subparser_map;
- std::map<std::string_view, bool> m_subparser_used;
+ std::map<std::string, argument_parser_it> m_subparser_map;
+ std::map<std::string, bool> m_subparser_used;
std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
bool m_suppress = false;
+ std::size_t m_usage_max_line_width = std::numeric_limits<std::size_t>::max();
+ bool m_usage_break_on_mutex = false;
+ int m_usage_newline_counter = 0;
+ std::vector<std::string> m_group_names;
};
} // namespace argparse
=====================================
module/argparse.cppm
=====================================
@@ -39,9 +39,13 @@ export module argparse;
#ifdef ARGPARSE_MODULE_USE_STD_MODULE
import std;
+import std.compat;
extern "C++" {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Winclude-angled-in-module-purview"
#include <argparse/argparse.hpp>
+#pragma clang diagnostic pop
}
#endif
=====================================
samples/BUILD.bazel
=====================================
@@ -0,0 +1,31 @@
+load(":add_sample.bzl", "add_sample")
+
+add_sample(name = "positional_argument")
+
+add_sample(name = "optional_flag_argument")
+
+add_sample(name = "required_optional_argument")
+
+add_sample(name = "is_used")
+
+add_sample(name = "joining_repeated_optional_arguments")
+
+add_sample(name = "repeating_argument_to_increase_value")
+
+add_sample(name = "negative_numbers")
+
+add_sample(name = "description_epilog_metavar")
+
+add_sample(name = "list_of_arguments")
+
+add_sample(name = "compound_arguments")
+
+add_sample(name = "gathering_remaining_arguments")
+
+add_sample(name = "subcommands")
+
+add_sample(name = "parse_known_args")
+
+add_sample(name = "custom_prefix_characters")
+
+add_sample(name = "custom_assignment_characters")
=====================================
samples/add_sample.bzl
=====================================
@@ -0,0 +1,6 @@
+def add_sample(name):
+ native.cc_binary(
+ name = name,
+ srcs = ["{}.cpp".format(name)],
+ deps = ["//:argparse"],
+ )
=====================================
test/BUILD.bazel
=====================================
@@ -0,0 +1,23 @@
+cc_library(
+ name = "doctest",
+ srcs = [
+ "main.cpp",
+ ],
+ hdrs = ["doctest.hpp"],
+ includes = ["."],
+ local_defines = [
+ "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN",
+ ],
+)
+
+cc_test(
+ name = "test",
+ srcs = glob(["test_*.cpp"]) + [
+ "test_utility.hpp",
+ ],
+ includes = ["."],
+ deps = [
+ ":doctest",
+ "//:argparse",
+ ],
+)
=====================================
test/CMakeLists.txt
=====================================
@@ -37,6 +37,8 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_error_reporting.cpp
test_get.cpp
test_help.cpp
+ test_hidden_alias.cpp
+ test_hidden_argument.cpp
test_invalid_arguments.cpp
test_is_used.cpp
test_issue_37.cpp
@@ -49,6 +51,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_repr.cpp
test_required_arguments.cpp
test_scan.cpp
+ test_store_into.cpp
test_stringstream.cpp
test_version.cpp
test_subparsers.cpp
=====================================
test/test_choices.cpp
=====================================
@@ -23,6 +23,72 @@ TEST_CASE("Parse argument that is in the fixed number of allowed choices" *
program.parse_args({"test", "red"});
}
+TEST_CASE("Parse argument that is in the fixed number of allowed choices, with "
+ "other positional argument" *
+ test_suite("choices")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("--input")
+ .default_value(std::string{"baz"})
+ .choices("foo", "bar", "baz");
+ program.add_argument("--value").scan<'i', int>().default_value(0);
+
+ REQUIRE_NOTHROW(
+ program.parse_args({"test", "--input", "foo", "--value", "1"}));
+ REQUIRE(program.get("--input") == "foo");
+ REQUIRE(program.get<int>("--value") == 1);
+}
+
+TEST_CASE(
+ "Parse nargs argument that is in the fixed number of allowed choices, with "
+ "other positional argument" *
+ test_suite("choices")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("--input")
+ .default_value(std::string{"baz"})
+ .choices("foo", "bar", "baz")
+ .nargs(2);
+ program.add_argument("--value").scan<'i', int>().default_value(0);
+
+ REQUIRE_NOTHROW(
+ program.parse_args({"test", "--input", "foo", "bar", "--value", "1"}));
+ REQUIRE((program.get<std::vector<std::string>>("--input") ==
+ std::vector<std::string>{"foo", "bar"}));
+ REQUIRE(program.get<int>("--value") == 1);
+}
+
+TEST_CASE("Parse argument that is in the fixed number of allowed choices, with "
+ "other positional argument (reversed)" *
+ test_suite("choices")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("--input")
+ .default_value(std::string{"baz"})
+ .choices("foo", "bar", "baz");
+ program.add_argument("--value").scan<'i', int>().default_value(0);
+
+ REQUIRE_NOTHROW(
+ program.parse_args({"test", "--value", "1", "--input", "foo"}));
+ REQUIRE(program.get("--input") == "foo");
+ REQUIRE(program.get<int>("--value") == 1);
+}
+
+TEST_CASE(
+ "Parse nargs argument that is in the fixed number of allowed choices, with "
+ "other positional argument (reversed)" *
+ test_suite("choices")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("--input")
+ .default_value(std::string{"baz"})
+ .choices("foo", "bar", "baz")
+ .nargs(2);
+ program.add_argument("--value").scan<'i', int>().default_value(0);
+
+ REQUIRE_NOTHROW(
+ program.parse_args({"test", "--value", "1", "--input", "foo", "bar"}));
+ REQUIRE((program.get<std::vector<std::string>>("--input") ==
+ std::vector<std::string>{"foo", "bar"}));
+ REQUIRE(program.get<int>("--value") == 1);
+}
+
TEST_CASE("Parse argument that is in the fixed number of allowed choices, with "
"invalid default" *
test_suite("choices")) {
@@ -88,4 +154,4 @@ TEST_CASE("Parse multiple arguments that are not in fixed number of allowed "
program.parse_args({"test", "6", "7"}),
"Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}",
std::runtime_error);
-}
\ No newline at end of file
+}
=====================================
test/test_help.cpp
=====================================
@@ -122,3 +122,125 @@ TEST_CASE("Multiline help message alignment") {
// Make sure we have at least one help message
REQUIRE(help_message_start != -1);
}
+
+TEST_CASE("Exclusive arguments, only") {
+ argparse::ArgumentParser program("program");
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]]");
+}
+
+TEST_CASE("Exclusive arguments, several groups") {
+ argparse::ArgumentParser program("program");
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ auto &group2 = program.add_mutually_exclusive_group();
+ group2.add_argument("-c").flag();
+ group2.add_argument("-d").flag();
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [[-c]|[-d]]");
+}
+
+TEST_CASE("Exclusive arguments, several groups, in between arg") {
+ argparse::ArgumentParser program("program");
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ program.add_argument("-X").flag();
+ auto &group2 = program.add_mutually_exclusive_group();
+ group2.add_argument("-c").flag();
+ group2.add_argument("-d").flag();
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [-X] [[-c]|[-d]]");
+}
+
+TEST_CASE("Argument repeatable") {
+ argparse::ArgumentParser program("program");
+ program.add_argument("-a").flag().append();
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a]...");
+
+ std::ostringstream s;
+ s << program;
+ // std::cout << "DEBUG:" << s.str() << std::endl;
+ REQUIRE(s.str().find(" -a [may be repeated]") != std::string::npos);
+}
+
+TEST_CASE("Argument with nargs(2) and metavar <x> <y>") {
+ argparse::ArgumentParser program("program");
+ program.add_argument("-foo").metavar("<x> <y>").nargs(2);
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [-foo <x> <y>]");
+}
+
+TEST_CASE("add_group help") {
+ argparse::ArgumentParser program("program");
+ program.add_argument("-a").flag().help("help_a");
+ program.add_group("Advanced options");
+ program.add_argument("-b").flag().help("help_b");
+ REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a] [-b]");
+
+ std::ostringstream s;
+ s << program;
+ // std::cout << "DEBUG:" << s.str() << std::endl;
+ REQUIRE(s.str().find(
+ " -a help_a \n"
+ "\n"
+ "Advanced options (detailed usage):\n"
+ " -b help_b") != std::string::npos);
+}
+
+TEST_CASE("multiline usage, several groups") {
+ argparse::ArgumentParser program("program");
+ program.set_usage_max_line_width(80);
+ program.add_argument("-a").flag().help("help_a");
+ program.add_group("Advanced options");
+ program.add_argument("-b").flag().help("help_b");
+ // std::cout << "DEBUG:" << program.usage() << std::endl;
+ REQUIRE(program.usage() ==
+ "Usage: program [--help] [--version] [-a]\n"
+ "\n"
+ "Advanced options:\n"
+ " [-b]");
+}
+
+TEST_CASE("multiline usage, no break on mutex") {
+ argparse::ArgumentParser program("program");
+ program.set_usage_max_line_width(80);
+ program.set_usage_break_on_mutex();
+ program.add_argument("--quite-long-option-name").flag();
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ program.add_argument("-c").flag();
+ program.add_argument("--another-one").flag();
+ program.add_argument("-d").flag();
+ program.add_argument("--yet-another-long-one").flag();
+ program.add_argument("--will-go-on-new-line").flag();
+ // std::cout << "DEBUG:" << program.usage() << std::endl;
+ REQUIRE(program.usage() ==
+ "Usage: program [--help] [--version] [--quite-long-option-name]\n"
+ " [[-a]|[-b]]\n"
+ " [-c] [--another-one] [-d] [--yet-another-long-one]\n"
+ " [--will-go-on-new-line]");
+}
+
+TEST_CASE("multiline usage, break on mutex") {
+ argparse::ArgumentParser program("program");
+ program.set_usage_max_line_width(80);
+ program.add_argument("--quite-long-option-name").flag();
+ auto &group = program.add_mutually_exclusive_group();
+ group.add_argument("-a").flag();
+ group.add_argument("-b").flag();
+ program.add_argument("-c").flag();
+ program.add_argument("--another-one").flag();
+ program.add_argument("-d").flag();
+ program.add_argument("--yet-another-long-one").flag();
+ program.add_argument("--will-go-on-new-line").flag();
+ program.add_usage_newline();
+ program.add_argument("--on-a-dedicated-line").flag();
+ // std::cout << "DEBUG:" << program.usage() << std::endl;
+ REQUIRE(program.usage() ==
+ "Usage: program [--help] [--version] [--quite-long-option-name] [[-a]|[-b]] [-c]\n"
+ " [--another-one] [-d] [--yet-another-long-one]\n"
+ " [--will-go-on-new-line]\n"
+ " [--on-a-dedicated-line]");
+}
=====================================
test/test_hidden_alias.cpp
=====================================
@@ -0,0 +1,18 @@
+#ifdef WITH_MODULE
+import argparse;
+#else
+#include <argparse/argparse.hpp>
+#endif
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Test setting a hidden alias for an argument" *
+ test_suite("hidden_alias")) {
+ argparse::ArgumentParser program("test");
+ auto &arg = program.add_argument("--suppress").flag();
+ program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias
+
+ program.parse_args({"./test.exe", "--supress"});
+ REQUIRE(program.get<bool>("--suppress") == true);
+}
=====================================
test/test_hidden_argument.cpp
=====================================
@@ -0,0 +1,36 @@
+#ifdef WITH_MODULE
+import argparse;
+#else
+#include <argparse/argparse.hpp>
+#endif
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Test setting a hidden argument" * test_suite("hidden_argument")) {
+ argparse::ArgumentParser program("program");
+ program.add_argument("--hidden").flag().hidden();
+ program.add_argument("--regular").flag();
+ program.add_argument("regular_positional");
+ // only makes sense if last and optional...
+ program.add_argument("hidden_positional").nargs(0, 1).hidden();
+
+ program.parse_args({"./test.exe", "--hidden", "--regular",
+ "regular_positional_val", "hidden_positional_val"});
+ REQUIRE(program.get<bool>("--hidden") == true);
+ REQUIRE(program.get<bool>("--regular") == true);
+ REQUIRE(program.get<std::string>("regular_positional") ==
+ "regular_positional_val");
+ REQUIRE(program.get<std::string>("hidden_positional") ==
+ "hidden_positional_val");
+
+ REQUIRE(program.usage() ==
+ "Usage: program [--help] [--version] [--regular] regular_positional");
+
+ std::ostringstream s;
+ s << program;
+ // std::cout << "DEBUG:" << s.str() << std::endl;
+ REQUIRE(s.str().find("hidden") == std::string::npos);
+ REQUIRE(s.str().find("--regular") != std::string::npos);
+ REQUIRE(s.str().find("regular_positional") != std::string::npos);
+}
=====================================
test/test_parse_args.cpp
=====================================
@@ -17,6 +17,15 @@ TEST_CASE("Missing argument" * test_suite("parse_args")) {
std::runtime_error);
}
+TEST_CASE("Missing argument, not last" * test_suite("parse_args")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("--config").nargs(1);
+ program.add_argument("--foo");
+ REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config", "--foo"}),
+ "Too few arguments for '--config'.",
+ std::runtime_error);
+}
+
TEST_CASE("Parse a string argument with value" * test_suite("parse_args")) {
argparse::ArgumentParser program("test");
program.add_argument("--config");
=====================================
test/test_positional_arguments.cpp
=====================================
@@ -264,3 +264,40 @@ TEST_CASE("Square a number" * test_suite("positional_arguments")) {
program.parse_args({"./main", "15"});
REQUIRE(program.get<double>("square") == 225);
}
+
+TEST_CASE("At_least_one_followed_by_exactly_one" * test_suite("positional_arguments")) {
+ GIVEN("a program that accepts a positional argument with at_least_one cardinality followed by another positional argument with 1:1") {
+ argparse::ArgumentParser program;
+
+ std::vector<std::string> at_least_one;
+ program.add_argument("at_least_one")
+ .nargs(argparse::nargs_pattern::at_least_one)
+ .store_into(at_least_one);
+
+ std::string exactly_one;
+ program.add_argument("exactly_one")
+ .store_into(exactly_one);
+
+ WHEN("provided one, two") {
+ THEN("parse_args works") {
+ program.parse_args({"./main", "one", "two"});
+ REQUIRE(at_least_one == std::vector<std::string>{"one"});
+ REQUIRE(exactly_one == "two");
+ }
+ }
+
+ WHEN("provided one, two, three") {
+ THEN("parse_args works") {
+ program.parse_args({"./main", "one", "two", "three"});
+ REQUIRE(at_least_one == std::vector<std::string>{"one", "two"});
+ REQUIRE(exactly_one == "three");
+ }
+ }
+
+ WHEN("provided one, two") {
+ THEN("parse_args throws") {
+ REQUIRE_THROWS(program.parse_args({"./main", "one"}));
+ }
+ }
+ }
+}
=====================================
test/test_store_into.cpp
=====================================
@@ -0,0 +1,288 @@
+#ifdef WITH_MODULE
+import argparse;
+#else
+#include <argparse/argparse.hpp>
+#endif
+#include <cstdint>
+#include <doctest.hpp>
+
+using doctest::test_suite;
+
+TEST_CASE("Test store_into(bool), flag not specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ bool flag;
+ program.add_argument("--flag").store_into(flag);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(flag == false);
+}
+
+TEST_CASE("Test store_into(bool), flag specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ bool flag;
+ program.add_argument("--flag").store_into(flag);
+
+ program.parse_args({"./test.exe", "--flag"});
+ REQUIRE(flag == true);
+}
+
+// int cases
+
+TEST_CASE("Test store_into(int), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ int res = -1;
+ program.add_argument("--int-opt").store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == -1);
+}
+
+TEST_CASE("Test store_into(int), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ int res = -1;
+ program.add_argument("--int-opt").default_value(3).store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == 3);
+}
+
+TEST_CASE("Test store_into(int), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ int res = -1;
+ program.add_argument("--int-opt").default_value(3).store_into(res);
+
+ program.parse_args({"./test.exe", "--int-opt", "5"});
+ REQUIRE(res == 5);
+}
+
+// integral cases
+
+TEST_CASE("Test store_into(uint8_t), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ uint8_t res = 55;
+ program.add_argument("--int-opt").store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == 55);
+}
+
+TEST_CASE("Test store_into(uint8_t), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ uint8_t res = 55;
+ program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == 3);
+}
+
+TEST_CASE("Test store_into(uint8_t), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ uint8_t res = 55;
+ program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res);
+
+ program.parse_args({"./test.exe", "--int-opt", "5"});
+ REQUIRE(res == 5);
+}
+
+// Double cases
+
+TEST_CASE("Test store_into(double), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ double res = -1;
+ program.add_argument("--double-opt").store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == -1);
+}
+
+TEST_CASE("Test store_into(double), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ double res = -1;
+ program.add_argument("--double-opt").default_value(3.5).store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == 3.5);
+}
+
+TEST_CASE("Test store_into(double), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ double res = -1;
+ program.add_argument("--double-opt").default_value(3.5).store_into(res);
+
+ program.parse_args({"./test.exe", "--double-opt", "5.5"});
+ REQUIRE(res == 5.5);
+}
+
+TEST_CASE("Test store_into(string), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::string res = "init";
+ program.add_argument("--str-opt").store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == "init");
+}
+
+TEST_CASE("Test store_into(string), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::string res;
+ program.add_argument("--str-opt").default_value("default").store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == "default");
+}
+
+TEST_CASE("Test store_into(string), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::string res;
+ program.add_argument("--str-opt").default_value("default").store_into(res);
+
+ program.parse_args({"./test.exe", "--str-opt", "foo"});
+ REQUIRE(res == "foo");
+}
+
+TEST_CASE("Test store_into(vector of string), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<std::string> res;
+ program.add_argument("--strvector-opt").append().store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::vector<std::string>{});
+}
+
+TEST_CASE("Test store_into(vector of string), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<std::string> res;
+ program.add_argument("--strvector-opt").append().default_value(
+ std::vector<std::string>{"a", "b"}).store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::vector<std::string>{"a", "b"});
+}
+
+TEST_CASE("Test store_into(vector of string), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<std::string> res;
+ program.add_argument("--strvector-opt").append().default_value(
+ std::vector<std::string>{"a", "b"}).store_into(res);
+
+ program.parse_args({"./test.exe", "--strvector-opt", "foo", "--strvector-opt", "bar"});
+ REQUIRE(res == std::vector<std::string>{"foo", "bar"});
+}
+
+TEST_CASE("Test store_into(vector of string), default value, multi valued, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<std::string> res;
+ program.add_argument("--strvector-opt").nargs(2).default_value(
+ std::vector<std::string>{"a", "b"}).store_into(res);
+
+ program.parse_args({"./test.exe", "--strvector-opt", "foo", "bar"});
+ REQUIRE(res == std::vector<std::string>{"foo", "bar"});
+}
+
+TEST_CASE("Test store_into(vector of int), no default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<int> res;
+ program.add_argument("--intvector-opt").append().store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::vector<int>{});
+}
+
+TEST_CASE("Test store_into(vector of int), default value, non specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<int> res;
+ program.add_argument("--intvector-opt").append().default_value(
+ std::vector<int>{1, 2}).store_into(res);
+
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::vector<int>{1, 2});
+}
+
+TEST_CASE("Test store_into(vector of int), default value, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<int> res;
+ program.add_argument("--intvector-opt").append().default_value(
+ std::vector<int>{1, 2}).store_into(res);
+
+ program.parse_args({"./test.exe", "--intvector-opt", "3", "--intvector-opt", "4"});
+ REQUIRE(res == std::vector<int>{3, 4});
+}
+
+TEST_CASE("Test store_into(vector of int), default value, multi valued, specified" *
+ test_suite("store_into")) {
+ argparse::ArgumentParser program("test");
+ std::vector<int> res;
+ program.add_argument("--intvector-opt").nargs(2).default_value(
+ std::vector<int>{1, 2}).store_into(res);
+
+ program.parse_args({"./test.exe", "--intvector-opt", "3", "4"});
+ REQUIRE(res == std::vector<int>{3, 4});
+}
+
+TEST_CASE("Test store_into(set of int), default value, multi valued, specified" *
+ test_suite("store_into")) {
+
+ {
+ argparse::ArgumentParser program("test");
+ std::set<int> res;
+ program.add_argument("--intset-opt").nargs(2).default_value(
+ std::set<int>{1, 2}).store_into(res);
+
+ program.parse_args({"./test.exe", "--intset-opt", "3", "4"});
+ REQUIRE(res == std::set<int>{3, 4});
+ }
+
+ {
+ argparse::ArgumentParser program("test");
+ std::set<int> res;
+ program.add_argument("--intset-opt").nargs(2).default_value(
+ std::set<int>{1, 2}).store_into(res);
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::set<int>{1, 2});
+ }
+}
+
+TEST_CASE("Test store_into(set of string), default value, multi valued, specified" *
+ test_suite("store_into")) {
+
+ {
+ argparse::ArgumentParser program("test");
+ std::set<std::string> res;
+ program.add_argument("--stringset-opt").nargs(2).default_value(
+ std::set<std::string>{"1", "2"}).store_into(res);
+
+ program.parse_args({"./test.exe", "--stringset-opt", "3", "4"});
+ REQUIRE(res == std::set<std::string>{"3", "4"});
+ }
+
+ {
+ argparse::ArgumentParser program("test");
+ std::set<std::string> res;
+ program.add_argument("--stringset-opt").nargs(2).default_value(
+ std::set<std::string>{"1", "2"}).store_into(res);
+ program.parse_args({"./test.exe"});
+ REQUIRE(res == std::set<std::string>{"1", "2"});
+ }
+}
+
=====================================
xmake.lua
=====================================
@@ -1,7 +1,7 @@
set_xmakever("2.8.2")
set_project("argparse")
-set_version("3.0.0", { build = "%Y%m%d%H%M" })
+set_version("3.1.0", { build = "%Y%m%d%H%M" })
option("enable_module")
option("enable_std_import", { defines = "ARGPARSE_MODULE_USE_STD_MODULE" })
View it on GitLab: https://salsa.debian.org/med-team/libargparse/-/compare/6891a95ca0c03aba2fd60276edb957ed478fd801...6af5ec8ddb5814b681fe136fc3bffed48ad48430
--
View it on GitLab: https://salsa.debian.org/med-team/libargparse/-/compare/6891a95ca0c03aba2fd60276edb957ed478fd801...6af5ec8ddb5814b681fe136fc3bffed48ad48430
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20240823/972bc626/attachment-0001.htm>
More information about the debian-med-commit
mailing list