[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