[med-svn] [Git][med-team/libargparse][master] 5 commits: New upstream version 3.2

Étienne Mollier (@emollier) gitlab at salsa.debian.org
Wed Mar 5 13:01:12 GMT 2025



Étienne Mollier pushed to branch master at Debian Med / libargparse


Commits:
5d6e5693 by Étienne Mollier at 2025-03-05T13:56:28+01:00
New upstream version 3.2
- - - - -
00a4cc26 by Étienne Mollier at 2025-03-05T13:56:28+01:00
Update upstream source from tag 'upstream/3.2'

Update to upstream version '3.2'
with Debian dir 6c26d88d4a670789dc2a8146c799e4190ab6763b
- - - - -
9b59a158 by Étienne Mollier at 2025-03-05T13:56:46+01:00
d/control: declare compliance to standards version 4.7.2.

- - - - -
8650e179 by Étienne Mollier at 2025-03-05T13:57:10+01:00
d/control: add myself to uploaders.

- - - - -
385a55d5 by Étienne Mollier at 2025-03-05T14:00:42+01:00
d/changelog: ready for upload to unstable.

- - - - -


13 changed files:

- .github/workflows/tidy-analysis-stage-01.yml
- CMakeLists.txt
- README.md
- debian/changelog
- debian/control
- include/argparse/argparse.hpp
- test/test_actions.cpp
- test/test_choices.cpp
- test/test_help.cpp
- test/test_scan.cpp
- test/test_store_into.cpp
- test/test_subparsers.cpp
- xmake.lua


Changes:

=====================================
.github/workflows/tidy-analysis-stage-01.yml
=====================================
@@ -36,7 +36,7 @@ jobs:
         echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt
         echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt
 
-    - uses: actions/upload-artifact at v2
+    - uses: actions/upload-artifact at v4
       with:
         name: clang-tidy-result
         path: clang-tidy-result/


=====================================
CMakeLists.txt
=====================================
@@ -7,7 +7,7 @@ else()
 endif()
 
 project(argparse
-        VERSION 3.1.0 
+        VERSION 3.2.0 
         DESCRIPTION "A single header argument parser for C++17"
         HOMEPAGE_URL "https://github.com/p-ranav/argparse"
         LANGUAGES CXX
@@ -55,7 +55,7 @@ if(ARGPARSE_INSTALL)
   endif()
 
   write_basic_package_version_file("${CMAKE_CONFIG_VERSION_FILE_NAME}"
-      COMPATIBILITY ExactVersion
+      COMPATIBILITY SameMajorVersion
       ${OPTIONAL_ARCH_INDEPENDENT}
   )
 


=====================================
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.1-blue.svg?cacheSeconds=2592000" alt="version"/>
+  <img src="https://img.shields.io/badge/version-3.2-blue.svg?cacheSeconds=2592000" alt="version"/>
 </p>
 
 ## Highlights
@@ -38,6 +38,7 @@
      *    [Gathering Remaining Arguments](#gathering-remaining-arguments)
      *    [Parent Parsers](#parent-parsers)
      *    [Subcommands](#subcommands)
+     *    [Getting Argument and Subparser Instances](#getting-argument-and-subparser-instances)
      *    [Parse Known Args](#parse-known-args)
      *    [Hidden argument and alias](#hidden-argument-alias)
      *    [ArgumentParser in bool Context](#argumentparser-in-bool-context)


=====================================
debian/changelog
=====================================
@@ -1,3 +1,11 @@
+libargparse (3.2-1) unstable; urgency=medium
+
+  * New upstream version 3.2
+  * d/control: declare compliance to standards version 4.7.2.
+  * d/control: add myself to uploaders.
+
+ -- Étienne Mollier <emollier at debian.org>  Wed, 05 Mar 2025 13:58:29 +0100
+
 libargparse (3.1-1) unstable; urgency=medium
 
   * Team upload.


=====================================
debian/control
=====================================
@@ -2,11 +2,12 @@ Source: libargparse
 Section: libdevel
 Priority: optional
 Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
+Uploaders: Andreas Tille <tille at debian.org>,
+           Étienne Mollier <emollier at debian.org>
 Build-Depends: debhelper-compat (= 13),
                cmake,
                pkgconf
-Standards-Version: 4.7.0
+Standards-Version: 4.7.2
 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
=====================================
@@ -57,6 +57,7 @@ SOFTWARE.
 #include <utility>
 #include <variant>
 #include <vector>
+#include <filesystem>
 #endif
 
 #ifndef ARGPARSE_CUSTOM_STRTOF
@@ -550,7 +551,7 @@ std::size_t get_levenshtein_distance(const StringType &s1,
       } else if (s1[i - 1] == s2[j - 1]) {
         dp[i][j] = dp[i - 1][j - 1];
       } else {
-        dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
+        dp[i][j] = 1 + std::min<std::size_t>({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
       }
     }
   }
@@ -562,7 +563,7 @@ template <typename ValueType>
 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();
+  std::size_t min_distance = (std::numeric_limits<std::size_t>::max)();
 
   for (const auto &entry : map) {
     std::size_t distance = get_levenshtein_distance(entry.first, input);
@@ -678,9 +679,9 @@ public:
         std::is_void_v<std::invoke_result_t<F, Args..., std::string const>>,
         void_action, valued_action>;
     if constexpr (sizeof...(Args) == 0) {
-      m_action.emplace<action_type>(std::forward<F>(callable));
+      m_actions.emplace_back<action_type>(std::forward<F>(callable));
     } else {
-      m_action.emplace<action_type>(
+      m_actions.emplace_back<action_type>(
           [f = std::forward<F>(callable),
            tup = std::make_tuple(std::forward<Args>(bound_args)...)](
               std::string const &opt) mutable {
@@ -691,11 +692,16 @@ public:
   }
 
   auto &store_into(bool &var) {
-    flag();
+    if ((!m_default_value.has_value()) && (!m_implicit_value.has_value())) {
+      flag();
+    }
     if (m_default_value.has_value()) {
       var = std::any_cast<bool>(m_default_value);
     }
-    action([&var](const auto & /*unused*/) { var = true; });
+    action([&var](const auto & /*unused*/) {
+      var = true;
+      return var;
+    });
     return *this;
   }
 
@@ -706,6 +712,7 @@ public:
     }
     action([&var](const auto &s) {
       var = details::parse_number<T, details::radix_10>()(s);
+      return var;
     });
     return *this;
   }
@@ -716,6 +723,7 @@ public:
     }
     action([&var](const auto &s) {
       var = details::parse_number<double, details::chars_format::general>()(s);
+      return var;
     });
     return *this;
   }
@@ -724,6 +732,17 @@ public:
     if (m_default_value.has_value()) {
       var = std::any_cast<std::string>(m_default_value);
     }
+    action([&var](const std::string &s) {
+      var = s;
+      return var;
+    });
+    return *this;
+  }
+
+  auto &store_into(std::filesystem::path &var) {
+    if (m_default_value.has_value()) {
+      var = std::any_cast<std::filesystem::path>(m_default_value);
+    }
     action([&var](const std::string &s) { var = s; });
     return *this;
   }
@@ -738,6 +757,7 @@ public:
       }
       m_is_used = true;
       var.push_back(s);
+      return var;
     });
     return *this;
   }
@@ -752,6 +772,7 @@ public:
       }
       m_is_used = true;
       var.push_back(details::parse_number<int, details::radix_10>()(s));
+      return var;
     });
     return *this;
   }
@@ -766,6 +787,7 @@ public:
       }
       m_is_used = true;
       var.insert(s);
+      return var;
     });
     return *this;
   }
@@ -780,6 +802,7 @@ public:
       }
       m_is_used = true;
       var.insert(details::parse_number<int, details::radix_10>()(s));
+      return var;
     });
     return *this;
   }
@@ -927,24 +950,26 @@ public:
   }
 
   template <typename Iterator>
-  void find_value_in_choices_or_throw(Iterator it) const {
+  bool is_value_in_choices(Iterator option_it) const {
 
     const auto &choices = m_choices.value();
 
-    if (std::find(choices.begin(), choices.end(), *it) == choices.end()) {
-      // provided arg not in list of allowed choices
-      // report error
+    return (std::find(choices.begin(), choices.end(), *option_it) !=
+            choices.end());
+  }
 
-      std::string choices_as_csv =
-          std::accumulate(choices.begin(), choices.end(), std::string(),
-                          [](const std::string &a, const std::string &b) {
-                            return a + (a.empty() ? "" : ", ") + b;
-                          });
+  template <typename Iterator>
+  void throw_invalid_arguments_error(Iterator option_it) const {
+    const auto &choices = m_choices.value();
+    const std::string choices_as_csv = std::accumulate(
+        choices.begin(), choices.end(), std::string(),
+        [](const std::string &option_a, const std::string &option_b) {
+          return option_a + (option_a.empty() ? "" : ", ") + option_b;
+        });
 
-      throw std::runtime_error(std::string{"Invalid argument "} +
-                               details::repr(*it) + " - allowed options: {" +
-                               choices_as_csv + "}");
-    }
+    throw std::runtime_error(std::string{"Invalid argument "} +
+                             details::repr(*option_it) +
+                             " - allowed options: {" + choices_as_csv + "}");
   }
 
   /* The dry_run parameter can be set to true to avoid running the actions,
@@ -960,27 +985,41 @@ public:
     }
     m_used_name = used_name;
 
+    std::size_t passed_options = 0;
+
     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();
+      const auto max_number_of_args = m_num_args_range.get_max();
+      const auto min_number_of_args = m_num_args_range.get_min();
       for (auto it = start; it != end; ++it) {
-        if (i == max_number_of_args) {
+        if (is_value_in_choices(it)) {
+          passed_options += 1;
+          continue;
+        }
+
+        if ((passed_options >= min_number_of_args) &&
+            (passed_options <= max_number_of_args)) {
           break;
         }
-        find_value_in_choices_or_throw(it);
-        i += 1;
+
+        throw_invalid_arguments_error(it);
       }
     }
 
-    const auto num_args_max = m_num_args_range.get_max();
+    const auto num_args_max =
+        (m_choices.has_value()) ? passed_options : m_num_args_range.get_max();
     const auto num_args_min = m_num_args_range.get_min();
     std::size_t dist = 0;
     if (num_args_max == 0) {
       if (!dry_run) {
         m_values.emplace_back(m_implicit_value);
-        std::visit([](const auto &f) { f({}); }, m_action);
+        for(auto &action: m_actions) {
+          std::visit([&](const auto &f) { f({}); }, action);
+        }
+        if(m_actions.empty()){
+          std::visit([&](const auto &f) { f({}); }, m_default_action);
+        }
         m_is_used = true;
       }
       return start;
@@ -1001,7 +1040,6 @@ public:
                                    std::string(m_used_name) + "'.");
         }
       }
-
       struct ActionApply {
         void operator()(valued_action &f) {
           std::transform(first, last, std::back_inserter(self.m_values), f);
@@ -1021,7 +1059,12 @@ public:
         Argument &self;
       };
       if (!dry_run) {
-        std::visit(ActionApply{start, end, *this}, m_action);
+        for(auto &action: m_actions) {
+          std::visit(ActionApply{start, end, *this}, action);
+        }
+        if(m_actions.empty()){
+          std::visit(ActionApply{start, end, *this}, m_default_action);
+        }
         m_is_used = true;
       }
       return end;
@@ -1571,9 +1614,10 @@ private:
   std::optional<std::vector<std::string>> m_choices{std::nullopt};
   using valued_action = std::function<std::any(const std::string &)>;
   using void_action = std::function<void(const std::string &)>;
-  std::variant<valued_action, void_action> m_action{
-      std::in_place_type<valued_action>,
-      [](const std::string &value) { return value; }};
+  std::vector<std::variant<valued_action, void_action>> m_actions;
+  std::variant<valued_action, void_action> m_default_action{
+    std::in_place_type<valued_action>,
+    [](const std::string &value) { return value; }};
   std::vector<std::any> m_values;
   NArgsRange m_num_args_range{1, 1};
   // Bit field of bool values. Set default value in ctor.
@@ -2055,9 +2099,9 @@ public:
     std::stringstream stream;
 
     std::string curline("Usage: ");
-    curline += this->m_program_name;
+    curline += this->m_parser_path;
     const bool multiline_usage =
-        this->m_usage_max_line_width < std::numeric_limits<std::size_t>::max();
+        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) {
@@ -2116,7 +2160,8 @@ public:
           }
         }
         cur_mutex = arg_mutex;
-        if (curline.size() + 1 + arg_inline_usage.size() >
+        if (curline.size() != indent_size &&
+            curline.size() + 1 + arg_inline_usage.size() >
             this->m_usage_max_line_width) {
           stream << curline << std::endl;
           curline = std::string(indent_size, ' ');
@@ -2244,7 +2289,7 @@ protected:
   preprocess_arguments(const std::vector<std::string> &raw_arguments) const {
     std::vector<std::string> arguments{};
     for (const auto &arg : raw_arguments) {
-
+      
       const auto argument_starts_with_prefix_chars =
           [this](const std::string &a) -> bool {
         if (!a.empty()) {
@@ -2534,7 +2579,7 @@ protected:
   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();
+  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;


=====================================
test/test_actions.cpp
=====================================
@@ -175,3 +175,28 @@ TEST_CASE("Users can run actions on parameterless optional arguments" *
     }
   }
 }
+
+TEST_CASE("Users can add multiple actions and they are all run" *
+          test_suite("actions")) {
+  argparse::ArgumentParser program("test");
+
+  GIVEN("a flag argument with two counting actions") {
+    int count = 0;
+    program.add_argument("-V", "--verbose")
+        .action([&](const auto &) { ++count; })
+        .action([&](const auto &) { ++count; })
+        .append()
+        .default_value(false)
+        .implicit_value(true)
+        .nargs(0);
+
+    WHEN("the flag is parsed") {
+      program.parse_args({"test", "-V"});
+
+      THEN("the count increments twice") {
+        REQUIRE(program.get<bool>("-V"));
+        REQUIRE(count == 2);
+      }
+    }
+  }
+}


=====================================
test/test_choices.cpp
=====================================
@@ -155,3 +155,51 @@ TEST_CASE("Parse multiple arguments that are not in fixed number of allowed "
       "Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}",
       std::runtime_error);
 }
+
+TEST_CASE("Parse multiple arguments that are in range of allowed "
+          "INTEGER choices (Min Range case)" *
+          test_suite("choices")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("indices").nargs(1, 3).choices(1, 2, 3, 4, 5);
+
+  REQUIRE_NOTHROW(program.parse_args({"test", "1"}));
+  REQUIRE(program.get<std::vector<std::string>>("indices") ==
+          std::vector<std::string>{"1"});
+}
+
+TEST_CASE("Parse multiple arguments that are in range of allowed choices (In "
+          "Range case)" *
+          test_suite("choices")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo");
+  program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c");
+
+  REQUIRE_NOTHROW(
+      program.parse_args({"test", "--bar", "a", "b", "--foo", "x"}));
+  REQUIRE(program.get<std::vector<std::string>>("--bar") ==
+          std::vector<std::string>{"a", "b"});
+  REQUIRE(program.get<std::string>("--foo") == "x");
+}
+
+TEST_CASE("Parse multiple arguments that are in range of allowed "
+          "INTEGER choices (Max Range case)" *
+          test_suite("choices")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("indices").nargs(2, 3).choices(1, 2, 3, 4, 5);
+
+  REQUIRE_NOTHROW(program.parse_args({"test", "3", "4", "5"}));
+  REQUIRE(program.get<std::vector<std::string>>("indices") ==
+          std::vector<std::string>{"3", "4", "5"});
+}
+
+TEST_CASE("Parse multiple arguments that are not in range of allowed choices" *
+          test_suite("choices")) {
+  argparse::ArgumentParser program("test");
+  program.add_argument("--foo");
+  program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c");
+
+  REQUIRE_THROWS_WITH_AS(
+      program.parse_args({"test", "--bar", "d", "--foo", "x"}),
+      "Invalid argument \"d\" - allowed options: {a, b, c}",
+      std::runtime_error);
+}


=====================================
test/test_help.cpp
=====================================
@@ -244,3 +244,13 @@ TEST_CASE("multiline usage, break on mutex") {
         "               [--will-go-on-new-line]\n"
         "               [--on-a-dedicated-line]");
 }
+
+TEST_CASE("multiline usage, single arg that is larger than the max width") {
+    argparse::ArgumentParser program("program");
+    program.set_usage_max_line_width(80);
+    program.add_argument("--lots-of-choices").metavar("<veeeeeeeeeeeeeeeeeeeeeeeeeeery_long|veeeeeeeeeeeeeeeeeeeery_long2>");
+    // std::cout << "DEBUG:" << program.usage() << std::endl;
+    REQUIRE(program.usage() ==
+        "Usage: program [--help] [--version]\n"
+        "               [--lots-of-choices <veeeeeeeeeeeeeeeeeeeeeeeeeeery_long|veeeeeeeeeeeeeeeeeeeery_long2>]");
+}


=====================================
test/test_scan.cpp
=====================================
@@ -426,3 +426,50 @@ TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" *
                       std::invalid_argument);
   }
 }
+
+TEST_CASE("Test that scan also works with a custom action" *
+          test_suite("scan")) {
+
+  GIVEN("an argument with scan followed by a custom action") {
+    argparse::ArgumentParser program("test");
+    int res;
+    program.add_argument("--int").scan<'i', int>().action([&](const auto &s) {res = std::stoi(s);});
+
+    WHEN("the argument is parsed") {
+
+      SUBCASE("with a valid value") {
+        program.parse_args({"./test.exe", "--int", "3"});
+        THEN("the value is stored") {
+          REQUIRE(res == 3);
+        }
+      }
+
+      SUBCASE("with an invalid value") {
+        REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}),
+                          std::invalid_argument);
+      }
+    }
+  }
+
+  GIVEN("an argument with a custom action followed by scan") {
+    argparse::ArgumentParser program("test");
+    int res;
+    program.add_argument("--int").action([&](const auto &s) {res = std::stoi(s);}).scan<'i', int>();
+
+    WHEN("the argument is parsed") {
+
+      SUBCASE("with a valid value") {
+        program.parse_args({"./test.exe", "--int", "3"});
+        THEN("the value is stored") {
+          REQUIRE(res == 3);
+        }
+      }
+
+      SUBCASE("with an invalid value") {
+        REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}),
+                          std::invalid_argument);
+      }
+    }
+  }
+
+}


=====================================
test/test_store_into.cpp
=====================================
@@ -286,3 +286,38 @@ TEST_CASE("Test store_into(set of string), default value, multi valued, specifie
   }
 }
 
+TEST_CASE("Test store_into(int) still works with a custom action" *
+          test_suite("store_into")) {
+
+  GIVEN("an argument with store_into followed by a custom action ") {
+    argparse::ArgumentParser program("test");
+    int res;
+    std::string string_res;
+    program.add_argument("--int").store_into(res).action([&](const auto &s) {string_res.append(s);});
+
+    WHEN("the argument is parsed") {
+    program.parse_args({"./test.exe", "--int", "3"});
+      THEN("the value is stored and the action was executed") {
+        REQUIRE(res == 3);
+        REQUIRE(string_res == "3");
+      }
+    }
+  }
+
+  GIVEN("an argument with a custom action followed by store_into")
+  {
+    argparse::ArgumentParser program("test");
+    int res;
+    std::string string_res;
+    program.add_argument("--int").action([&](const auto &s) {string_res.append(s);}).store_into(res);
+
+    WHEN("the argument is parsed") {
+    program.parse_args({"./test.exe", "--int", "3"});
+      THEN("the value is stored and the action was executed") {
+        REQUIRE(res == 3);
+        REQUIRE(string_res == "3");
+      }
+    }
+  }
+}
+


=====================================
test/test_subparsers.cpp
=====================================
@@ -280,3 +280,29 @@ TEST_CASE("Check set_suppress" * test_suite("subparsers")) {
     REQUIRE(contains(program.help().str(), "command_2") == true);
   }
 }
+
+
+TEST_CASE("Help of subparsers" * test_suite("subparsers")) {
+  argparse::ArgumentParser program("test");
+
+  argparse::ArgumentParser command_1("add", "1.0", argparse::default_arguments::version);
+
+  std::stringstream buffer;
+  command_1.add_argument("--help")
+      .action([&](const auto &) { buffer << command_1; })
+      .default_value(false)
+      .implicit_value(true)
+      .nargs(0);
+
+  program.add_subparser(command_1);
+
+  REQUIRE(command_1.usage() == "Usage: test add [--version] [--help]");
+
+  REQUIRE(buffer.str().empty());
+  program.parse_args({"test", "add", "--help"});
+  REQUIRE(buffer.str() == "Usage: test add [--version] [--help]\n"
+                          "\n"
+                          "Optional arguments:\n"
+                          "  -v, --version  prints version information and exits \n"
+                          "  --help         \n");
+}


=====================================
xmake.lua
=====================================
@@ -1,7 +1,7 @@
 set_xmakever("2.8.2")
 set_project("argparse")
 
-set_version("3.1.0", { build = "%Y%m%d%H%M" })
+set_version("3.2.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/6af5ec8ddb5814b681fe136fc3bffed48ad48430...385a55d5044f4cc3f66b44ce13d2e650327f491a

-- 
View it on GitLab: https://salsa.debian.org/med-team/libargparse/-/compare/6af5ec8ddb5814b681fe136fc3bffed48ad48430...385a55d5044f4cc3f66b44ce13d2e650327f491a
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/20250305/fcd0790d/attachment-0001.htm>


More information about the debian-med-commit mailing list