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) diff --git a/include/argparse.h b/include/argparse.h index 9aecb3de..e56397af 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 @@ -147,6 +148,7 @@ namespace argparse using optstring = std::optional; class HelpRequested {}; + class VersionRequested {}; public: template @@ -176,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) @@ -251,6 +263,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() @@ -475,6 +497,7 @@ namespace argparse { std::vector names; std::string help; + std::string version; std::string metavar; std::string dest; Action action = store; @@ -550,6 +573,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; @@ -788,6 +821,9 @@ namespace argparse case help: m_value = true; throw HelpRequested(); + case version: + m_value = true; + throw VersionRequested(); } m_present = true; } @@ -797,6 +833,7 @@ namespace argparse { case store_true: case help: + case version: m_value = false; break; case store_false: @@ -1327,6 +1364,14 @@ namespace argparse ~ArgumentBuilder() { + if (m_options.action == argparse::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))); @@ -1343,6 +1388,12 @@ namespace argparse return *this; } + auto version(std::string const & version) -> ArgumentBuilder & + { + m_options.version = version; + return *this; + } + auto metavar(std::string const & metavar) -> ArgumentBuilder & { m_options.metavar = metavar; 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_help_message.cpp b/test/unittest/test_help_message.cpp index 0ff55f4a..60c1c81e 100644 --- a/test/unittest/test_help_message.cpp +++ b/test/unittest/test_help_message.cpp @@ -374,6 +374,27 @@ 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 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 show program's version number and exit"s); + } + + 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 show program's version number and exit"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"); diff --git a/test/unittest/test_parsing.cpp b/test/unittest/test_parsing.cpp index 4ac7a302..6004d5e1 100644 --- a/test/unittest/test_parsing.cpp +++ b/test/unittest/test_parsing.cpp @@ -158,6 +158,26 @@ TEST_CASE("Parsing an optional argument with help action...") } } +TEST_CASE("Parsing an optional argument with version action yields false when it's missing") +{ + 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"}); + + 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().handle(argparse::Handle::none); + 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(); 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); +}