From 37f6555b648ecc0acecda5e26e7015167a87a785 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 17:43:52 +0100 Subject: [PATCH 01/12] Add initial optional argument support for version action --- include/argparse.h | 5 ++++- test/unittest/test_parsing.cpp | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/include/argparse.h b/include/argparse.h index 9aecb3de..4a42086c 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -37,7 +37,8 @@ namespace argparse store_true, store_false, store_const, - help + help, + version }; enum Nargs @@ -788,6 +789,8 @@ namespace argparse case help: m_value = true; throw HelpRequested(); + case version: + break; } m_present = true; } diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index 4ac7a302..674530f1 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -158,6 +158,13 @@ TEST_CASE("Parsing an optional argument with help action...") } } +TEST_CASE("Parser supports version action") +{ + auto parser = argparse::ArgumentParser(); + + parser.add_argument("-v").action(argparse::version); +} + TEST_CASE("Optional argument can be used with either...") { auto parser = argparse::ArgumentParser(); From 58862d55cfe35c3226b2a9535e7bb5211f58e38c Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 17:55:05 +0100 Subject: [PATCH 02/12] Extend optional argument support for version action --- include/argparse.h | 1 + test/unittest/test_parsing.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/argparse.h b/include/argparse.h index 4a42086c..d2eda0cf 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -800,6 +800,7 @@ namespace argparse { case store_true: case help: + case version: m_value = false; break; case store_false: diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index 674530f1..168cd553 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -165,6 +165,16 @@ TEST_CASE("Parser supports version action") parser.add_argument("-v").action(argparse::version); } +TEST_CASE("Parsing an optional argument with version action yields false when it's missing") +{ + auto parser = argparse::ArgumentParser(); + parser.add_argument("-v").action(argparse::version); + + auto const parsed = parser.parse_args(1, cstr_arr{"prog"}); + + CHECK(parsed.get_value("v") == false); +} + TEST_CASE("Optional argument can be used with either...") { auto parser = argparse::ArgumentParser(); From b2c2d4d4bfb20062f720167f403cdbb73413cb96 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 18:06:18 +0100 Subject: [PATCH 03/12] Extend optional argument support for version action --- include/argparse.h | 1 + test/unittest/test_parsing.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/include/argparse.h b/include/argparse.h index d2eda0cf..2889d45b 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -790,6 +790,7 @@ namespace argparse m_value = true; throw HelpRequested(); case version: + m_value = true; break; } m_present = true; diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index 168cd553..c7298fa2 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -175,6 +175,16 @@ TEST_CASE("Parsing an optional argument with version action yields false when it CHECK(parsed.get_value("v") == false); } +TEST_CASE("Parsing an optional argument with version action yields true when it's present") +{ + auto parser = argparse::ArgumentParser(); + parser.add_argument("-v").action(argparse::version); + + auto const parsed = parser.parse_args(2, cstr_arr{"prog", "-v"}); + + CHECK(parsed.get_value("v") == true); +} + TEST_CASE("Optional argument can be used with either...") { auto parser = argparse::ArgumentParser(); From 8541cdfc956f30460304e52c85a4bf2b5c2e2d7f Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 18:37:29 +0100 Subject: [PATCH 04/12] Extend unit test --- test/unittest/test_help_message.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/unittest/test_help_message.cpp b/test/unittest/test_help_message.cpp index 0ff55f4a..118a2016 100644 --- a/test/unittest/test_help_message.cpp +++ b/test/unittest/test_help_message.cpp @@ -374,6 +374,13 @@ TEST_CASE("Help message contains...") CHECK(parser.format_help() == "usage: prog [-h]\n\noptional arguments:\n -h, --help show this help message and exit"s); } + SUBCASE("...name for argument with version action") + { + parser.add_argument("-v").action(argparse::version); + + CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v"s); + } + SUBCASE("...name and automatic metavar") { parser.add_argument("-o"); From 1280c9857c6a9188a3c78b7b14e1b7f7509feea3 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 18:39:31 +0100 Subject: [PATCH 05/12] Extend unit test --- test/unittest/test_help_message.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/unittest/test_help_message.cpp b/test/unittest/test_help_message.cpp index 118a2016..7ba5cfb0 100644 --- a/test/unittest/test_help_message.cpp +++ b/test/unittest/test_help_message.cpp @@ -381,6 +381,13 @@ TEST_CASE("Help message contains...") CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v"s); } + SUBCASE("...name and long name for argument with version action") + { + parser.add_argument("-v", "--version").action(argparse::version); + + CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v, --version"s); + } + SUBCASE("...name and automatic metavar") { parser.add_argument("-o"); From 791030a866f4e60cb4623698dc9bbc4b9b9f1d0f Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 18:41:16 +0100 Subject: [PATCH 06/12] Extend unit test --- test/unittest/test_help_message.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/unittest/test_help_message.cpp b/test/unittest/test_help_message.cpp index 7ba5cfb0..0a793533 100644 --- a/test/unittest/test_help_message.cpp +++ b/test/unittest/test_help_message.cpp @@ -388,6 +388,13 @@ TEST_CASE("Help message contains...") CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v, --version"s); } + SUBCASE("...name and help for argument with version action and help string") + { + parser.add_argument("-v").action(argparse::version).help("version1"); + + CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v version1"s); + } + SUBCASE("...name and automatic metavar") { parser.add_argument("-o"); From c9c579128e18bc305734fe2f6406b4e36ad35742 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 18:56:25 +0100 Subject: [PATCH 07/12] Extend optional argument support for version action --- include/argparse.h | 8 ++++++++ test/unittest/test_help_message.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/include/argparse.h b/include/argparse.h index 2889d45b..c413aebf 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -1332,6 +1332,14 @@ namespace argparse ~ArgumentBuilder() { + if (m_options.action == version) + { + if (m_options.help.empty()) + { + m_options.help = "show program's version number and exit"; + } + } + if (is_positional()) { m_arguments.push_back(std::make_unique(std::move(m_options))); diff --git a/test/unittest/test_help_message.cpp b/test/unittest/test_help_message.cpp index 0a793533..60c1c81e 100644 --- a/test/unittest/test_help_message.cpp +++ b/test/unittest/test_help_message.cpp @@ -374,18 +374,18 @@ TEST_CASE("Help message contains...") CHECK(parser.format_help() == "usage: prog [-h]\n\noptional arguments:\n -h, --help show this help message and exit"s); } - SUBCASE("...name for argument with version action") + SUBCASE("...name and automatically added help for argument with version action") { parser.add_argument("-v").action(argparse::version); - CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v"s); + CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v show program's version number and exit"s); } - SUBCASE("...name and long name for argument with version action") + SUBCASE("...name, long name, and automatically added help for argument with version action") { parser.add_argument("-v", "--version").action(argparse::version); - CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v, --version"s); + CHECK(parser.format_help() == "usage: prog [-v]\n\noptional arguments:\n -v, --version show program's version number and exit"s); } SUBCASE("...name and help for argument with version action and help string") From ca394add721d2eb483d4e15d157057471d214b40 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 19:38:30 +0100 Subject: [PATCH 08/12] Extend optional argument support for version action --- include/argparse.h | 7 ++++++- test/unittest/test_parsing.cpp | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/include/argparse.h b/include/argparse.h index c413aebf..538f27bb 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -1332,7 +1332,7 @@ namespace argparse ~ArgumentBuilder() { - if (m_options.action == version) + if (m_options.action == argparse::version) { if (m_options.help.empty()) { @@ -1356,6 +1356,11 @@ namespace argparse return *this; } + auto version(std::string const & /* version */) -> ArgumentBuilder & + { + return *this; + } + auto metavar(std::string const & metavar) -> ArgumentBuilder & { m_options.metavar = metavar; diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index c7298fa2..8b505570 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -165,6 +165,13 @@ TEST_CASE("Parser supports version action") parser.add_argument("-v").action(argparse::version); } +TEST_CASE("Parser supports version number") +{ + auto parser = argparse::ArgumentParser(); + + parser.add_argument("-v").action(argparse::version).version("0.0.1"); +} + TEST_CASE("Parsing an optional argument with version action yields false when it's missing") { auto parser = argparse::ArgumentParser(); From f4b1a918b7e07f90151769d536ab7e4189694126 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 20:47:42 +0100 Subject: [PATCH 09/12] Extend optional argument support for version action --- include/argparse.h | 24 +++++++++++++++++++++++- test/unittest/CMakeLists.txt | 1 + test/unittest/test_version.cpp | 17 +++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 test/unittest/test_version.cpp diff --git a/include/argparse.h b/include/argparse.h index 538f27bb..428ade3a 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -252,6 +252,16 @@ namespace argparse return formatter.format_help(); } + auto format_version() const -> std::string + { + if (auto it = std::ranges::find_if(m_arguments, [](auto && arg) { return arg->has_version_action(); }); it != m_arguments.end()) + { + return (*it)->get_version(); + } + + return ""; + } + ArgumentParser() : m_arguments() , m_prog() @@ -476,6 +486,7 @@ namespace argparse { std::vector names; std::string help; + std::string version; std::string metavar; std::string dest; Action action = store; @@ -551,6 +562,16 @@ namespace argparse return m_options.action == store; } + auto has_version_action() const -> bool + { + return m_options.action == version; + } + + auto get_version() const -> std::string const & + { + return m_options.version; + } + auto get_help_message() const -> std::string const & { return m_options.help; @@ -1356,8 +1377,9 @@ namespace argparse return *this; } - auto version(std::string const & /* version */) -> ArgumentBuilder & + auto version(std::string const & version) -> ArgumentBuilder & { + m_options.version = version; return *this; } diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index db480334..b8763787 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -12,6 +12,7 @@ target_sources(unittest test_usage_message.cpp test_help_message.cpp test_error_message.cpp + test_version.cpp cstring_array.h custom_a.h custom_b.h) diff --git a/test/unittest/test_version.cpp b/test/unittest/test_version.cpp new file mode 100644 index 00000000..d629e663 --- /dev/null +++ b/test/unittest/test_version.cpp @@ -0,0 +1,17 @@ +#include "argparse.h" + +#include "doctest.h" + +#include + + +using namespace std::string_literals; + +TEST_CASE("ArgumentParser provides program version") +{ + auto parser = argparse::ArgumentParser(); + + parser.add_argument("-v").action(argparse::version).version("0.0.1"); + + CHECK(parser.format_version() == "0.0.1"s); +} From e45e825eae3071d1bc62c58123107d390ecbb793 Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 22:08:43 +0100 Subject: [PATCH 10/12] Extend optional argument support for version action --- include/argparse.h | 13 ++++++++++++- test/unittest/test_parsing.cpp | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/include/argparse.h b/include/argparse.h index 428ade3a..e56397af 100644 --- a/include/argparse.h +++ b/include/argparse.h @@ -148,6 +148,7 @@ namespace argparse using optstring = std::optional; class HelpRequested {}; + class VersionRequested {}; public: template @@ -177,6 +178,16 @@ namespace argparse return get_parameters(); } + catch (VersionRequested const &) + { + if (m_handle == Handle::errors_and_help || m_handle == Handle::help) + { + std::cout << format_version() << std::endl; + std::exit(EXIT_SUCCESS); + } + + return get_parameters(); + } catch (parsing_error const & e) { if (m_handle == Handle::errors_and_help || m_handle == Handle::errors) @@ -812,7 +823,7 @@ namespace argparse throw HelpRequested(); case version: m_value = true; - break; + throw VersionRequested(); } m_present = true; } diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index 8b505570..a79b7d0f 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -174,7 +174,7 @@ TEST_CASE("Parser supports version number") TEST_CASE("Parsing an optional argument with version action yields false when it's missing") { - auto parser = argparse::ArgumentParser(); + auto parser = argparse::ArgumentParser().handle(argparse::Handle::none); parser.add_argument("-v").action(argparse::version); auto const parsed = parser.parse_args(1, cstr_arr{"prog"}); @@ -184,7 +184,7 @@ TEST_CASE("Parsing an optional argument with version action yields false when it TEST_CASE("Parsing an optional argument with version action yields true when it's present") { - auto parser = argparse::ArgumentParser(); + auto parser = argparse::ArgumentParser().handle(argparse::Handle::none); parser.add_argument("-v").action(argparse::version); auto const parsed = parser.parse_args(2, cstr_arr{"prog", "-v"}); From 3506ae777b623a7a6e61aed5db8aacca4b8783fb Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 22:18:00 +0100 Subject: [PATCH 11/12] Cleanup: remove redundant tests --- test/unittest/test_parsing.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index a79b7d0f..6004d5e1 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -158,20 +158,6 @@ TEST_CASE("Parsing an optional argument with help action...") } } -TEST_CASE("Parser supports version action") -{ - auto parser = argparse::ArgumentParser(); - - parser.add_argument("-v").action(argparse::version); -} - -TEST_CASE("Parser supports version number") -{ - auto parser = argparse::ArgumentParser(); - - parser.add_argument("-v").action(argparse::version).version("0.0.1"); -} - TEST_CASE("Parsing an optional argument with version action yields false when it's missing") { auto parser = argparse::ArgumentParser().handle(argparse::Handle::none); From 4c3cca0e7036deb4f5550b0ef14782595a92f0ad Mon Sep 17 00:00:00 2001 From: Krzysiek Karbowiak Date: Wed, 29 Jan 2025 22:20:31 +0100 Subject: [PATCH 12/12] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be411b68..11413665 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ The below lists features of the `argparse` module that this implementation suppo * The `add_argument()` method * [x] name or flags - * [x] `action` (only `store`, `store_true`, `store_false`, `store_const`, and `help`) + * [x] `action` (only `store`, `store_true`, `store_false`, `store_const`, `help`, and `version`) * [x] `nargs` (except for `REMAINDER`) * [x] `const` (renamed to `const_` due to keyword clash) * [x] `default` (renamed to `default_` due to keyword clash; only for optional arguments and with no string parsing)