[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