diff --git a/.clang-format b/.clang-format
index 35cdf3c..2cfd8b1 100644
--- a/.clang-format
+++ b/.clang-format
@@ -20,7 +20,7 @@ AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: false
BinPackArguments: true
BinPackParameters: true
-BraceWrapping:
+BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
@@ -55,12 +55,12 @@ DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
-ForEachMacros:
+ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
-IncludeCategories:
+IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
@@ -110,7 +110,7 @@ SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
-Standard: Cpp11
+Standard: c++17
TabWidth: 8
UseTab: Never
...
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..d0b28f4
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,21 @@
+Checks:
+ -*,
+ clang-analyzer-*,
+ cppcoreguidelines-avoid-c-arrays,
+ cppcoreguidelines-special-member-functions,
+ readability-*,
+
+CheckOptions:
+ - { key: readability-identifier-naming.ClassCase, value: CamelCase }
+ - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case }
+ - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" }
+ - { key: readability-identifier-naming.FunctionCase, value: lower_case }
+ - { key: readability-identifier-naming.NamespaceCase, value: lower_case }
+ - { key: readability-identifier-naming.ParameterCase, value: lower_case }
+ - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case }
+ - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ }
+ - { key: readability-identifier-naming.StructCase, value: CamelCase }
+ - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" }
+ - { key: readability-identifier-naming.VariableCase, value: lower_case }
+
+HeaderFilterRegex: '.*'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e7e4648..32379e8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,6 @@ jobs:
- macos-latest-clang
- ubuntu-latest-clang
- ubuntu-latest-gcc
- - windows-2016-msvc
- windows-latest-msvc
include:
@@ -42,11 +41,6 @@ jobs:
c_compiler: msvc
cxx_compiler: msvc
- - toolchain: windows-2016-msvc
- os: windows-2016
- c_compiler: msvc
- cxx_compiler: msvc
-
steps:
- name: Checkout Code
diff --git a/CMakeLists.txt b/CMakeLists.txt
index db224c0..0e267a8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,8 +1,11 @@
-cmake_minimum_required(VERSION 3.8)
+cmake_minimum_required(VERSION 3.12.4)
-project(argparse VERSION 1.0.0 LANGUAGES CXX)
-set("PROJECT_DESCRIPTION" "A single header argument parser for C++17")
-set("PROJECT_HOMEPAGE_URL" "https://github.com/p-ranav/argparse")
+project(argparse
+ VERSION 2.5.0
+ DESCRIPTION "A single header argument parser for C++17"
+ HOMEPAGE_URL "https://github.com/p-ranav/argparse"
+ LANGUAGES CXX
+)
option(ARGPARSE_BUILD_TESTS OFF)
option(ARGPARSE_LONG_VERSION_ARG_ONLY OFF)
diff --git a/README.md b/README.md
index fa6f120..a318b00 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
-
+
## Highlights
@@ -27,7 +27,7 @@ Simply include argparse.hpp and you're good to go.
To start parsing command-line arguments, create an ```ArgumentParser```.
```cpp
-argparse::ArgumentParser program("program name");
+argparse::ArgumentParser program("program_name");
```
**NOTE:** There is an optional second argument to the `ArgumentParser` which is the program version. Example: `argparse::ArgumentParser program("libfoo", "1.9.0");`
@@ -49,7 +49,7 @@ Here's an example of a ***positional argument***:
#include
int main(int argc, char *argv[]) {
- argparse::ArgumentParser program("program name");
+ argparse::ArgumentParser program("program_name");
program.add_argument("square")
.help("display the square of a given integer")
@@ -59,9 +59,9 @@ int main(int argc, char *argv[]) {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto input = program.get("square");
@@ -101,9 +101,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
if (program["--verbose"] == true) {
@@ -160,16 +160,16 @@ If you want to know whether the user supplied a value for an argument that has a
```cpp
program.add_argument("--color")
- .default_value("orange")
+ .default_value(std::string{"orange"}) // might otherwise be type const char* leading to an error when trying program.get
.help("specify the cat's fur color");
try {
program.parse_args(argc, argv); // Example: ./main --color orange
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto color = program.get("--color"); // "orange"
@@ -190,9 +190,9 @@ try {
program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto colors = program.get>("--color"); // {"red", "green", "blue"}
@@ -200,6 +200,24 @@ auto colors = program.get>("--color"); // {"red", "gre
Notice that ```.default_value``` is given an explicit template parameter to match the type you want to ```.get```.
+#### Repeating an argument to increase a value
+
+A common pattern is to repeat an argument to indicate a greater value.
+
+```cpp
+int verbosity = 0;
+program.add_argument("-V", "--verbose")
+ .action([&](const auto &) { ++verbosity; })
+ .append()
+ .default_value(false)
+ .implicit_value(true)
+ .nargs(0);
+
+program.parse_args(argc, argv); // Example: ./main -VVVV
+
+std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
+```
+
### Negative Numbers
Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
@@ -220,9 +238,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
// Some code to print arguments
@@ -239,7 +257,7 @@ As you can see here, ```argparse``` supports negative integers, negative floats
### Combining Positional and Optional Arguments
```cpp
-argparse::ArgumentParser program("test");
+argparse::ArgumentParser program("main");
program.add_argument("square")
.help("display the square of a given number")
@@ -253,9 +271,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
int input = program.get("square");
@@ -285,18 +303,53 @@ The square of 4 is 16
```
$ ./main --help
-Usage: ./main [options] square
+Usage: main [options] square
Positional arguments:
-square display a square of a given number
+square display the square of a given number
Optional arguments:
--h, --help show this help message and exit
--v, --verbose enable verbose logging
+-h --help shows help message and exits [default: false]
+-v --version prints version information and exits [default: false]
+--verbose [default: false]
```
You may also get the help message in string via `program.help().str()`.
+#### Adding a description and an epilog to help
+
+`ArgumentParser::add_description` will add text before the detailed argument
+information. `ArgumentParser::add_epilog` will add text after all other help output.
+
+```cpp
+argparse::ArgumentParser program("main");
+
+program.add_argument("thing")
+ .help("Thing to use.");
+program.add_description("Forward a thing to the next member.");
+program.add_epilog("Possible things include betingalw, chiz, and res.");
+
+program.parse_args(argc, argv);
+
+std::cout << program << std::endl;
+```
+
+```bash
+$ ./main --help
+Usage: main thing
+
+Forward a thing to the next member.
+
+Positional arguments:
+thing Thing to use.
+
+Optional arguments:
+-h --help shows help message and exits [default: false]
+-v --version prints version information and exits [default: false]
+
+Possible things include betingalw, chiz, and res.
+```
+
### List of Arguments
ArgumentParser objects usually associate a single command-line argument with a single action to be taken. The ```.nargs``` associates a different number of command-line arguments with a single action. When using ```nargs(N)```, N arguments from the command line will be gathered together into a list.
@@ -312,9 +365,9 @@ try {
program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto files = program.get>("--input_files"); // {"config.yml", "System.xml"}
@@ -341,9 +394,9 @@ try {
program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto query_point = program.get>("--query_point"); // {3.5, 4.7, 9.2}
@@ -373,9 +426,9 @@ try {
program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto a = program.get("-a"); // true
@@ -437,6 +490,27 @@ The grammar follows `std::from_chars`, but does not exactly duplicate it. For ex
| 'u' | decimal (unsigned) |
| 'x' or 'X' | hexadecimal (unsigned) |
+### Default Arguments
+
+`argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. These default actions exit the program after displaying a help or version message, respectively. These defaults arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
+
+```cpp
+argparse::ArgumentParser program("test", "1.0", default_arguments::none);
+
+program.add_argument("-h", "--help")
+ .action([=](const std::string& s) {
+ std::cout << help().str();
+ })
+ .default_value(false)
+ .help("shows help message")
+ .implicit_value(true)
+ .nargs(0);
+```
+
+The above code snippet outputs a help message and continues to run. It does not support a `--version` argument.
+
+The default is `default_arguments::all` for included arguments. No default arguments will be added with `default_arguments::none`. `default_arguments::help` and `default_arguments::version` will individually add `--help` and `--version`.
+
### Gathering Remaining Arguments
`argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
@@ -457,9 +531,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
try {
@@ -504,9 +578,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto output_filename = program.get("-o");
@@ -586,9 +660,9 @@ try {
program.parse_args({"./test", "config.json"});
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
nlohmann::json config = program.get("config");
@@ -622,9 +696,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto numbers = program.get>("numbers"); // {1, 2, 3}
@@ -664,9 +738,9 @@ try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
- std::cout << err.what() << std::endl;
- std::cout << program;
- exit(0);
+ std::cerr << err.what() << std::endl;
+ std::cerr << program;
+ std::exit(1);
}
auto input = program.get("input");
@@ -707,6 +781,24 @@ Thanks goes to these wonderful people:
 mupp |
 Ethan Slattery |
+  skrobinson |
+  Mike Zozu |
+  Chuvi-w |
+  KOLANICH |
+
+
+  Rafał Będźkowski |
+  Yoshihiro Hokazono |
+  Kenny Shen |
+  The 42nd Mu00 |
+  Ubpa |
+  Oliver Smith |
+
+
+  Joseph Durel |
+  rysson |
+  aashwinr |
+  bufferbase |
diff --git a/conanfile.py b/conanfile.py
index 1851287..4043e0b 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -2,9 +2,9 @@ from conans import ConanFile
class ArgparseConan(ConanFile):
name = "argparse"
- version = "1.0"
+ version = "2.5"
exports_sources = "include/argparse.hpp"
no_copy_source = True
def package(self):
- self.copy("argparse.hpp")
\ No newline at end of file
+ self.copy("argparse.hpp")
diff --git a/include/argparse/argparse.hpp b/include/argparse/argparse.hpp
index 9954245..f248fb6 100644
--- a/include/argparse/argparse.hpp
+++ b/include/argparse/argparse.hpp
@@ -31,6 +31,7 @@ SOFTWARE.
#pragma once
#include
#include
+#include
#include
#include
#include
@@ -48,6 +49,7 @@ SOFTWARE.
#include
#include
#include
+#include
#include
#include
@@ -56,34 +58,30 @@ namespace argparse {
namespace details { // namespace for helper methods
template
-struct is_container : std::false_type {};
+struct HasContainerTraits : std::false_type {};
-template <> struct is_container : std::false_type {};
+template <> struct HasContainerTraits : std::false_type {};
template
-struct is_container().begin()),
- decltype(std::declval().end()),
- decltype(std::declval().size())>>
- : std::true_type {};
+struct HasContainerTraits<
+ T, std::void_t().begin()),
+ decltype(std::declval().end()),
+ decltype(std::declval().size())>> : std::true_type {};
template
-static constexpr bool is_container_v = is_container::value;
+static constexpr bool IsContainer = HasContainerTraits::value;
template
-struct is_streamable : std::false_type {};
+struct HasStreamableTraits : std::false_type {};
template
-struct is_streamable<
- T, std::void_t() << std::declval())>>
- : std::true_type {};
+struct HasStreamableTraits<
+ T,
+ std::void_t() << std::declval())>>
+ : std::true_type {};
template
-static constexpr bool is_streamable_v = is_streamable::value;
-
-template
-static constexpr bool is_representable_v =
- is_streamable_v || is_container_v;
+static constexpr bool IsStreamable = HasStreamableTraits::value;
constexpr std::size_t repr_max_container_size = 5;
@@ -92,7 +90,7 @@ template std::string repr(T const &val) {
return val ? "true" : "false";
} else if constexpr (std::is_convertible_v) {
return '"' + std::string{std::string_view{val}} + '"';
- } else if constexpr (is_container_v) {
+ } else if constexpr (IsContainer) {
std::stringstream out;
out << "{";
const auto size = val.size();
@@ -100,18 +98,21 @@ template std::string repr(T const &val) {
out << repr(*val.begin());
std::for_each(
std::next(val.begin()),
- std::next(val.begin(), std::min(size, repr_max_container_size) - 1),
+ std::next(val.begin(),
+ std::min(size, repr_max_container_size) - 1),
[&out](const auto &v) { out << " " << repr(v); });
- if (size <= repr_max_container_size)
+ if (size <= repr_max_container_size) {
out << " ";
- else
+ } else {
out << "...";
+ }
}
- if (size > 0)
+ if (size > 0) {
out << repr(*std::prev(val.end()));
+ }
out << "}";
return out.str();
- } else if constexpr (is_streamable_v) {
+ } else if constexpr (IsStreamable) {
std::stringstream out;
out << val;
return out.str();
@@ -139,13 +140,17 @@ constexpr bool standard_unsigned_integer = true;
} // namespace
+constexpr int radix_8 = 8;
+constexpr int radix_10 = 10;
+constexpr int radix_16 = 16;
+
template
constexpr bool standard_integer =
standard_signed_integer || standard_unsigned_integer;
template
constexpr decltype(auto) apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x,
- std::index_sequence) {
+ std::index_sequence /*unused*/) {
return std::invoke(std::forward(f), std::get(std::forward(t))...,
std::forward(x));
}
@@ -175,7 +180,7 @@ enum class chars_format {
general = fixed | scientific
};
-struct consume_hex_prefix_result {
+struct ConsumeHexPrefixResult {
bool is_hexadecimal;
std::string_view rest;
};
@@ -183,13 +188,12 @@ struct consume_hex_prefix_result {
using namespace std::literals;
constexpr auto consume_hex_prefix(std::string_view s)
- -> consume_hex_prefix_result {
+ -> ConsumeHexPrefixResult {
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
s.remove_prefix(2);
return {true, s};
- } else {
- return {false, s};
}
+ return {false, s};
}
template
@@ -198,17 +202,18 @@ inline auto do_from_chars(std::string_view s) -> T {
auto [first, last] = pointer_range(s);
auto [ptr, ec] = std::from_chars(first, last, x, Param);
if (ec == std::errc()) {
- if (ptr == last)
+ if (ptr == last) {
return x;
- else
- throw std::invalid_argument{"pattern does not match to the end"};
- } else if (ec == std::errc::invalid_argument) {
- throw std::invalid_argument{"pattern not found"};
- } else if (ec == std::errc::result_out_of_range) {
- throw std::range_error{"not representable"};
- } else {
- return x; // unreachable
+ }
+ throw std::invalid_argument{"pattern does not match to the end"};
}
+ if (ec == std::errc::invalid_argument) {
+ throw std::invalid_argument{"pattern not found"};
+ }
+ if (ec == std::errc::result_out_of_range) {
+ throw std::range_error{"not representable"};
+ }
+ return x; // unreachable
}
template struct parse_number {
@@ -217,23 +222,25 @@ template struct parse_number {
}
};
-template struct parse_number {
+template struct parse_number {
auto operator()(std::string_view s) -> T {
- if (auto [ok, rest] = consume_hex_prefix(s); ok)
- return do_from_chars(rest);
- else
- throw std::invalid_argument{"pattern not found"};
+ if (auto [ok, rest] = consume_hex_prefix(s); ok) {
+ return do_from_chars(rest);
+ }
+ throw std::invalid_argument{"pattern not found"};
}
};
template struct parse_number {
auto operator()(std::string_view s) -> T {
- if (auto [ok, rest] = consume_hex_prefix(s); ok)
- return do_from_chars(rest);
- else if (starts_with("0"sv, s))
- return do_from_chars(rest);
- else
- return do_from_chars(rest);
+ auto [ok, rest] = consume_hex_prefix(s);
+ if (ok) {
+ return do_from_chars(rest);
+ }
+ if (starts_with("0"sv, s)) {
+ return do_from_chars(rest);
+ }
+ return do_from_chars(rest);
}
};
@@ -247,30 +254,33 @@ template <> constexpr auto generic_strtod = strtold;
} // namespace
template inline auto do_strtod(std::string const &s) -> T {
- if (isspace(static_cast(s[0])) || s[0] == '+')
+ if (isspace(static_cast(s[0])) || s[0] == '+') {
throw std::invalid_argument{"pattern not found"};
+ }
auto [first, last] = pointer_range(s);
char *ptr;
errno = 0;
- if (auto x = generic_strtod(first, &ptr); errno == 0) {
- if (ptr == last)
+ auto x = generic_strtod(first, &ptr);
+ if (errno == 0) {
+ if (ptr == last) {
return x;
- else
- throw std::invalid_argument{"pattern does not match to the end"};
- } else if (errno == ERANGE) {
- throw std::range_error{"not representable"};
- } else {
- return x; // unreachable
+ }
+ throw std::invalid_argument{"pattern does not match to the end"};
}
+ if (errno == ERANGE) {
+ throw std::range_error{"not representable"};
+ }
+ return x; // unreachable
}
template struct parse_number {
auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal)
+ if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
throw std::invalid_argument{
"chars_format::general does not parse hexfloat"};
+ }
return do_strtod(s);
}
@@ -278,8 +288,9 @@ template struct parse_number {
template struct parse_number {
auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); !r.is_hexadecimal)
+ if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
throw std::invalid_argument{"chars_format::hex parses hexfloat"};
+ }
return do_strtod(s);
}
@@ -287,12 +298,14 @@ template struct parse_number {
template struct parse_number {
auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal)
+ if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
throw std::invalid_argument{
"chars_format::scientific does not parse hexfloat"};
- if (s.find_first_of("eE") == s.npos)
+ }
+ if (s.find_first_of("eE") == std::string::npos) {
throw std::invalid_argument{
"chars_format::scientific requires exponent part"};
+ }
return do_strtod(s);
}
@@ -300,12 +313,14 @@ template struct parse_number {
template struct parse_number {
auto operator()(std::string const &s) -> T {
- if (auto r = consume_hex_prefix(s); r.is_hexadecimal)
+ if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
throw std::invalid_argument{
"chars_format::fixed does not parse hexfloat"};
- if (s.find_first_of("eE") != s.npos)
+ }
+ if (s.find_first_of("eE") != std::string::npos) {
throw std::invalid_argument{
"chars_format::fixed does not parse exponent part"};
+ }
return do_strtod(s);
}
@@ -314,33 +329,33 @@ template struct parse_number {
} // namespace details
class SizeRange {
- std::size_t mMin;
- std::size_t mMax;
+ std::size_t m_min;
+ std::size_t m_max;
public:
- SizeRange(std::size_t aMin, std::size_t aMax) : mMin(aMin), mMax(aMax) {
- if (aMin > aMax)
+ SizeRange(std::size_t minimum, std::size_t maximum) : m_min(minimum), m_max(maximum) {
+ if (minimum > maximum)
throw std::logic_error("Range of number of arguments is invalid");
}
bool contains(std::size_t value) const {
- return value >= mMin && value <= mMax;
+ return value >= m_min && value <= m_max;
}
bool is_exact() const {
- return mMin == mMax;
+ return m_min == m_max;
}
bool is_right_bounded() const {
- return mMax < std::numeric_limits::max();
+ return m_max < std::numeric_limits::max();
}
std::size_t get_min() const {
- return mMin;
+ return m_min;
}
std::size_t get_max() const {
- return mMax;
+ return m_max;
}
};
@@ -350,72 +365,88 @@ enum class NArgsPattern {
AtLeastOne
};
+enum class default_arguments : unsigned int {
+ none = 0,
+ help = 1,
+ version = 2,
+ all = help | version,
+};
+
+inline default_arguments operator&(const default_arguments &a,
+ const default_arguments &b) {
+ return static_cast(
+ static_cast::type>(a) &
+ static_cast::type>(b));
+}
+
class ArgumentParser;
class Argument {
friend class ArgumentParser;
- friend auto operator<<(std::ostream &, ArgumentParser const &)
+ friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
-> std::ostream &;
template
- explicit Argument(std::string_view(&&a)[N], std::index_sequence)
- : mIsOptional((is_optional(a[I]) || ...)), mIsRequired(false),
- mIsRepeatable(false), mIsUsed(false) {
- ((void)mNames.emplace_back(a[I]), ...);
+ explicit Argument(std::array &&a,
+ std::index_sequence /*unused*/)
+ : m_is_optional((is_optional(a[I]) || ...)), m_is_required(false),
+ m_is_repeatable(false), m_is_used(false) {
+ ((void)m_names.emplace_back(a[I]), ...);
std::sort(
- mNames.begin(), mNames.end(), [](const auto &lhs, const auto &rhs) {
+ m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) {
return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size();
});
}
public:
template
- explicit Argument(std::string_view(&&a)[N])
+ explicit Argument(std::array &&a)
: Argument(std::move(a), std::make_index_sequence{}) {}
- Argument &help(std::string aHelp) {
- mHelp = std::move(aHelp);
+ Argument &help(std::string help_text) {
+ m_help = std::move(help_text);
return *this;
}
- template Argument &default_value(T &&aDefaultValue) {
- mDefaultValueRepr = details::repr(aDefaultValue);
- mDefaultValue = std::forward(aDefaultValue);
+ template Argument &default_value(T &&value) {
+ m_default_value_repr = details::repr(value);
+ m_default_value = std::forward(value);
return *this;
}
Argument &required() {
- mIsRequired = true;
+ m_is_required = true;
return *this;
}
- Argument &implicit_value(std::any aImplicitValue) {
- mImplicitValue = std::move(aImplicitValue);
- mNumArgsRange = SizeRange{0, 0};
+ Argument &implicit_value(std::any value) {
+ m_implicit_value = std::move(value);
+ m_num_args_range = SizeRange{0, 0};
return *this;
}
template
- auto action(F &&aAction, Args &&... aBound)
+ auto action(F &&callable, Args &&...bound_args)
-> std::enable_if_t,
Argument &> {
using action_type = std::conditional_t<
std::is_void_v>,
void_action, valued_action>;
- if constexpr (sizeof...(Args) == 0)
- mAction.emplace(std::forward(aAction));
- else
- mAction.emplace(
- [f = std::forward(aAction),
- tup = std::make_tuple(std::forward(aBound)...)](
+ if constexpr (sizeof...(Args) == 0) {
+ m_action.emplace(std::forward(callable));
+ } else {
+ m_action.emplace(
+ [f = std::forward(callable),
+ tup = std::make_tuple(std::forward(bound_args)...)](
std::string const &opt) mutable {
return details::apply_plus_one(f, tup, opt);
});
+ }
return *this;
}
auto &append() {
- mIsRepeatable = true;
+ m_is_repeatable = true;
return *this;
}
@@ -427,152 +458,154 @@ public:
return ((c == x) || ...);
};
- if constexpr (is_one_of(Shape, 'd') && details::standard_integer)
- action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'i') && details::standard_integer)
+ if constexpr (is_one_of(Shape, 'd') && details::standard_integer) {
+ action(details::parse_number());
+ } else if constexpr (is_one_of(Shape, 'i') && details::standard_integer) {
action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'u') &&
- details::standard_unsigned_integer)
- action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'o') &&
- details::standard_unsigned_integer)
- action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'x', 'X') &&
- details::standard_unsigned_integer)
- action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'a', 'A') &&
- std::is_floating_point_v)
+ } else if constexpr (is_one_of(Shape, 'u') &&
+ details::standard_unsigned_integer) {
+ action(details::parse_number());
+ } else if constexpr (is_one_of(Shape, 'o') &&
+ details::standard_unsigned_integer) {
+ action(details::parse_number());
+ } else if constexpr (is_one_of(Shape, 'x', 'X') &&
+ details::standard_unsigned_integer) {
+ action(details::parse_number());
+ } else if constexpr (is_one_of(Shape, 'a', 'A') &&
+ std::is_floating_point_v) {
action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'e', 'E') &&
- std::is_floating_point_v)
+ } else if constexpr (is_one_of(Shape, 'e', 'E') &&
+ std::is_floating_point_v) {
action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'f', 'F') &&
- std::is_floating_point_v)
+ } else if constexpr (is_one_of(Shape, 'f', 'F') &&
+ std::is_floating_point_v) {
action(details::parse_number());
- else if constexpr (is_one_of(Shape, 'g', 'G') &&
- std::is_floating_point_v)
+ } else if constexpr (is_one_of(Shape, 'g', 'G') &&
+ std::is_floating_point_v) {
action(details::parse_number());
- else
+ } else {
static_assert(alignof(T) == 0, "No scan specification for T");
+ }
return *this;
}
- Argument &nargs(std::size_t aNumArgs) {
- mNumArgsRange = SizeRange{aNumArgs, aNumArgs};
+ Argument &nargs(std::size_t num_args) {
+ m_num_args_range = SizeRange{num_args, num_args};
return *this;
}
- Argument &nargs(std::size_t aNumArgsMin, std::size_t aNumArgsMax) {
- mNumArgsRange = SizeRange{aNumArgsMin, aNumArgsMax};
+ Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) {
+ m_num_args_range = SizeRange{num_args_min, num_args_max};
return *this;
}
- Argument &nargs(SizeRange aNumArgsRange) {
- mNumArgsRange = aNumArgsRange;
+ Argument &nargs(SizeRange num_args_range) {
+ m_num_args_range = num_args_range;
return *this;
}
- Argument &nargs(NArgsPattern aNargs) {
- switch (aNargs) {
+ Argument &nargs(NArgsPattern num_args_pattern) {
+ switch (num_args_pattern) {
case NArgsPattern::ZeroOrOne:
- mNumArgsRange = SizeRange{0, 1};
+ m_num_args_range = SizeRange{0, 1};
break;
case NArgsPattern::Any:
- mNumArgsRange = SizeRange{0, std::numeric_limits::max()};
+ m_num_args_range = SizeRange{0, std::numeric_limits::max()};
break;
case NArgsPattern::AtLeastOne:
- mNumArgsRange = SizeRange{1, std::numeric_limits::max()};
+ m_num_args_range = SizeRange{1, std::numeric_limits::max()};
break;
}
return *this;
}
Argument &remaining() {
- mAcceptsOptionalLikeValue = true;
+ m_accepts_optional_like_value = true;
return nargs(NArgsPattern::Any);
}
template
Iterator consume(Iterator start, Iterator end,
- std::string_view usedName = {}) {
- if (!mIsRepeatable && mIsUsed) {
+ std::string_view used_name = {}) {
+ if (!m_is_repeatable && m_is_used) {
throw std::runtime_error("Duplicate argument");
}
- mIsUsed = true;
- mUsedName = usedName;
+ m_is_used = true;
+ m_used_name = used_name;
- const auto numArgsMax = mNumArgsRange.get_max();
- const auto numArgsMin = mNumArgsRange.get_min();
+ const auto num_args_max = m_num_args_range.get_max();
+ const auto num_args_min = m_num_args_range.get_min();
std::size_t dist = 0;
- if (numArgsMax == 0) {
- mValues.emplace_back(mImplicitValue);
+ if (num_args_max == 0) {
+ m_values.emplace_back(m_implicit_value);
+ std::visit([](const auto &f) { f({}); }, m_action);
return start;
- } else if ((dist = static_cast(std::distance(start, end))) >= numArgsMin) {
- if (numArgsMax < dist) {
- end = std::next(start, numArgsMax);
+ } else if ((dist = static_cast(std::distance(start, end))) >= num_args_min) {
+ if (num_args_max < dist) {
+ end = std::next(start, num_args_max);
}
- if (!mAcceptsOptionalLikeValue) {
+ if (!m_accepts_optional_like_value) {
end = std::find_if(start, end, Argument::is_optional);
dist = static_cast(std::distance(start, end));
- if (dist < numArgsMin) {
+ if (dist < num_args_min) {
throw std::runtime_error("Too few arguments");
}
}
- struct action_apply {
+ struct ActionApply {
void operator()(valued_action &f) {
- std::transform(start, end, std::back_inserter(self.mValues), f);
+ std::transform(first, last, std::back_inserter(self.m_values), f);
}
void operator()(void_action &f) {
- std::for_each(start, end, f);
- if (!self.mDefaultValue.has_value()) {
- if (!self.mAcceptsOptionalLikeValue)
- self.mValues.resize(std::distance(start, end));
+ std::for_each(first, last, f);
+ if (!self.m_default_value.has_value()) {
+ if (!self.m_accepts_optional_like_value)
+ self.m_values.resize(std::distance(first, last));
}
}
- Iterator start, end;
+ Iterator first, last;
Argument &self;
};
- std::visit(action_apply{start, end, *this}, mAction);
+ std::visit(ActionApply{start, end, *this}, m_action);
return end;
- } else if (mDefaultValue.has_value()) {
- return start;
- } else {
- throw std::runtime_error("Too few arguments for '" +
- std::string(mUsedName) + "'.");
}
+ if (m_default_value.has_value()) {
+ return start;
+ }
+ throw std::runtime_error("Too few arguments for '" +
+ std::string(m_used_name) + "'.");
}
/*
* @throws std::runtime_error if argument values are not valid
*/
void validate() const {
- if (mIsOptional) {
- if (mIsUsed && !mNumArgsRange.contains(mValues.size()) && !mIsRepeatable &&
- !mDefaultValue.has_value()) {
+ if (m_is_optional) {
+ if (m_is_used && !m_num_args_range.contains(m_values.size()) && !m_is_repeatable &&
+ !m_default_value.has_value()) {
throw_nargs_range_validation_error();
} else {
// TODO: check if an implicit value was programmed for this argument
- if (!mIsUsed && !mDefaultValue.has_value() && mIsRequired) {
+ if (!m_is_used && !m_default_value.has_value() && m_is_required) {
throw_required_arg_not_used_error();
}
- if (mIsUsed && mIsRequired && mValues.size() == 0) {
+ if (m_is_used && m_is_required && m_values.size() == 0) {
throw_required_arg_no_value_provided_error();
}
}
} else {
- if (!mNumArgsRange.contains(mValues.size()) && !mDefaultValue.has_value()) {
+ if (!m_num_args_range.contains(m_values.size()) && !m_default_value.has_value()) {
throw_nargs_range_validation_error();
}
}
}
std::size_t get_arguments_length() const {
- return std::accumulate(std::begin(mNames), std::end(mNames), std::size_t(0),
- [](const auto &sum, const auto &s) {
+ return std::accumulate(std::begin(m_names), std::end(m_names),
+ std::size_t(0), [](const auto &sum, const auto &s) {
return sum + s.size() +
1; // +1 for space between names
});
@@ -580,39 +613,41 @@ public:
friend std::ostream &operator<<(std::ostream &stream,
const Argument &argument) {
- std::stringstream nameStream;
- std::copy(std::begin(argument.mNames), std::end(argument.mNames),
- std::ostream_iterator(nameStream, " "));
- stream << nameStream.str() << "\t" << argument.mHelp;
- if (argument.mDefaultValue.has_value()) {
- if (!argument.mHelp.empty())
+ std::stringstream name_stream;
+ std::copy(std::begin(argument.m_names), std::end(argument.m_names),
+ std::ostream_iterator(name_stream, " "));
+ stream << name_stream.str() << "\t" << argument.m_help;
+ if (argument.m_default_value.has_value()) {
+ if (!argument.m_help.empty()) {
stream << " ";
- stream << "[default: " << argument.mDefaultValueRepr << "]";
- } else if (argument.mIsRequired) {
- if (!argument.mHelp.empty())
+ }
+ stream << "[default: " << argument.m_default_value_repr << "]";
+ } else if (argument.m_is_required) {
+ if (!argument.m_help.empty()) {
stream << " ";
+ }
stream << "[required]";
}
stream << "\n";
return stream;
}
- template bool operator!=(const T &aRhs) const {
- return !(*this == aRhs);
+ template bool operator!=(const T &rhs) const {
+ return !(*this == rhs);
}
/*
* Compare to an argument value of known type
* @throws std::logic_error in case of incompatible types
*/
- template bool operator==(const T &aRhs) const {
- if constexpr (!details::is_container_v) {
- return get() == aRhs;
+ template bool operator==(const T &rhs) const {
+ if constexpr (!details::IsContainer) {
+ return get() == rhs;
} else {
using ValueType = typename T::value_type;
- auto tLhs = get();
- return std::equal(std::begin(tLhs), std::end(tLhs), std::begin(aRhs),
- std::end(aRhs), [](const auto &lhs, const auto &rhs) {
+ auto lhs = get();
+ return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
+ std::end(rhs), [](const auto &lhs, const auto &rhs) {
return std::any_cast(lhs) == rhs;
});
}
@@ -622,39 +657,39 @@ private:
void throw_nargs_range_validation_error() const {
std::stringstream stream;
- if (!mUsedName.empty())
- stream << mUsedName << ": ";
- if (mNumArgsRange.is_exact()) {
- stream << mNumArgsRange.get_min();
- } else if (mNumArgsRange.is_right_bounded()) {
- stream << mNumArgsRange.get_min() << " to " << mNumArgsRange.get_max();
+ if (!m_used_name.empty())
+ stream << m_used_name << ": ";
+ if (m_num_args_range.is_exact()) {
+ stream << m_num_args_range.get_min();
+ } else if (m_num_args_range.is_right_bounded()) {
+ stream << m_num_args_range.get_min() << " to " << m_num_args_range.get_max();
} else {
- stream << mNumArgsRange.get_min() << " or more";
+ stream << m_num_args_range.get_min() << " or more";
}
stream << " argument(s) expected. "
- << mValues.size() << " provided.";
+ << m_values.size() << " provided.";
throw std::runtime_error(stream.str());
}
void throw_required_arg_not_used_error() const {
std::stringstream stream;
- stream << mNames[0] << ": required.";
+ stream << m_names[0] << ": required.";
throw std::runtime_error(stream.str());
}
void throw_required_arg_no_value_provided_error() const {
std::stringstream stream;
- stream << mUsedName << ": no value provided.";
+ stream << m_used_name << ": no value provided.";
throw std::runtime_error(stream.str());
}
static constexpr int eof = std::char_traits::eof();
static auto lookahead(std::string_view s) -> int {
- if (s.empty())
+ if (s.empty()) {
return eof;
- else
- return static_cast(static_cast(s[0]));
+ }
+ return static_cast(static_cast(s[0]));
}
/*
@@ -706,6 +741,7 @@ private:
// precondition: we have consumed or will consume at least one digit
auto consume_digits = [=](std::string_view s) {
+ // NOLINTNEXTLINE(readability-qualified-auto)
auto it = std::find_if_not(std::begin(s), std::end(s), is_digit);
return s.substr(it - std::begin(s));
};
@@ -713,10 +749,10 @@ private:
switch (lookahead(s)) {
case '0': {
s.remove_prefix(1);
- if (s.empty())
+ if (s.empty()) {
return true;
- else
- goto integer_part;
+ }
+ goto integer_part;
}
case '1':
case '2':
@@ -728,10 +764,10 @@ private:
case '8':
case '9': {
s = consume_digits(s);
- if (s.empty())
+ if (s.empty()) {
return true;
- else
- goto integer_part_consumed;
+ }
+ goto integer_part_consumed;
}
case '.': {
s.remove_prefix(1);
@@ -747,10 +783,11 @@ private:
switch (lookahead(s)) {
case '.': {
s.remove_prefix(1);
- if (is_digit(lookahead(s)))
+ if (is_digit(lookahead(s))) {
goto post_decimal_point;
- else
+ } else {
goto exponent_part_opt;
+ }
}
case 'e':
case 'E': {
@@ -765,9 +802,8 @@ private:
if (is_digit(lookahead(s))) {
s = consume_digits(s);
goto exponent_part_opt;
- } else {
- return false;
}
+ return false;
exponent_part_opt:
switch (lookahead(s)) {
@@ -791,13 +827,12 @@ private:
if (is_digit(lookahead(s))) {
s = consume_digits(s);
return s.empty();
- } else {
- return false;
}
+ return false;
}
- static bool is_optional(std::string_view aName) {
- return !is_positional(aName);
+ static bool is_optional(std::string_view name) {
+ return !is_positional(name);
}
/*
@@ -807,16 +842,16 @@ private:
* '-' decimal-literal
* !'-' anything
*/
- static bool is_positional(std::string_view aName) {
- switch (lookahead(aName)) {
+ static bool is_positional(std::string_view name) {
+ switch (lookahead(name)) {
case eof:
return true;
case '-': {
- aName.remove_prefix(1);
- if (aName.empty())
+ name.remove_prefix(1);
+ if (name.empty()) {
return true;
- else
- return is_decimal_literal(aName);
+ }
+ return is_decimal_literal(name);
}
default:
return true;
@@ -828,20 +863,21 @@ private:
* @throws std::logic_error in case of incompatible types
*/
template T get() const {
- if (!mValues.empty()) {
- if constexpr (details::is_container_v)
- return any_cast_container(mValues);
- else
- return std::any_cast(mValues.front());
+ if (!m_values.empty()) {
+ if constexpr (details::IsContainer) {
+ return any_cast_container(m_values);
+ } else {
+ return std::any_cast(m_values.front());
+ }
}
- if (mDefaultValue.has_value()) {
- return std::any_cast(mDefaultValue);
+ if (m_default_value.has_value()) {
+ return std::any_cast(m_default_value);
} else {
- if constexpr (details::is_container_v)
- if (!mAcceptsOptionalLikeValue)
- return any_cast_container(mValues);
+ if constexpr (details::IsContainer)
+ if (!m_accepts_optional_like_value)
+ return any_cast_container(m_values);
}
- throw std::logic_error("No value provided for '" + mNames.back() + "'.");
+ throw std::logic_error("No value provided for '" + m_names.back() + "'.");
}
/*
@@ -850,78 +886,102 @@ private:
* @returns The stored value if any, std::nullopt otherwise.
*/
template auto present() const -> std::optional {
- if (mDefaultValue.has_value())
+ if (m_default_value.has_value()) {
throw std::logic_error("Argument with default value always presents");
-
- if (mValues.empty())
+ }
+ if (m_values.empty()) {
return std::nullopt;
- else if constexpr (details::is_container_v)
- return any_cast_container(mValues);
- else
- return std::any_cast(mValues.front());
+ }
+ if constexpr (details::IsContainer) {
+ return any_cast_container(m_values);
+ }
+ return std::any_cast(m_values.front());
}
template
- static auto any_cast_container(const std::vector &aOperand) -> T {
+ static auto any_cast_container(const std::vector &operand) -> T {
using ValueType = typename T::value_type;
- T tResult;
+ T result;
std::transform(
- std::begin(aOperand), std::end(aOperand), std::back_inserter(tResult),
+ std::begin(operand), std::end(operand), std::back_inserter(result),
[](const auto &value) { return std::any_cast(value); });
- return tResult;
+ return result;
}
- std::vector mNames;
- std::string_view mUsedName;
- std::string mHelp;
- std::any mDefaultValue;
- std::string mDefaultValueRepr;
- std::any mImplicitValue;
+ std::vector m_names;
+ std::string_view m_used_name;
+ std::string m_help;
+ std::any m_default_value;
+ std::string m_default_value_repr;
+ std::any m_implicit_value;
using valued_action = std::function;
using void_action = std::function;
- std::variant mAction{
+ std::variant m_action{
std::in_place_type,
- [](const std::string &aValue) { return aValue; }};
- std::vector mValues;
- SizeRange mNumArgsRange {1, 1};
- bool mAcceptsOptionalLikeValue = false;
- bool mIsOptional : true;
- bool mIsRequired : true;
- bool mIsRepeatable : true;
- bool mIsUsed : true; // True if the optional argument is used by user
+ [](const std::string &value) { return value; }};
+ std::vector m_values;
+ SizeRange m_num_args_range {1, 1};
+ bool m_accepts_optional_like_value = false;
+ bool m_is_optional : true;
+ bool m_is_required : true;
+ bool m_is_repeatable : true;
+ bool m_is_used : true; // True if the optional argument is used by user
};
class ArgumentParser {
public:
- explicit ArgumentParser(std::string aProgramName = {},
- std::string aVersion = "1.0")
- : mProgramName(std::move(aProgramName)), mVersion(std::move(aVersion)) {
- add_argument("-h", "--help").help("shows help message and exits").nargs(0);
-#ifndef ARGPARSE_LONG_VERSION_ARG_ONLY
- add_argument("-v", "--version")
-#else
- add_argument("--version")
-#endif
- .help("prints version information and exits")
- .nargs(0);
+ explicit ArgumentParser(std::string program_name = {},
+ std::string version = "1.0",
+ default_arguments add_args = default_arguments::all)
+ : m_program_name(std::move(program_name)), m_version(std::move(version)) {
+ if ((add_args & default_arguments::help) == default_arguments::help) {
+ add_argument("-h", "--help")
+ .action([&](const auto &/*unused*/) {
+ std::cout << help().str();
+ std::exit(0);
+ })
+ .default_value(false)
+ .help("shows help message and exits")
+ .implicit_value(true)
+ .nargs(0);
+ }
+ if ((add_args & default_arguments::version) == default_arguments::version) {
+ add_argument("-v", "--version")
+ .action([&](const auto &/*unused*/) {
+ std::cout << m_version << std::endl;
+ std::exit(0);
+ })
+ .default_value(false)
+ .help("prints version information and exits")
+ .implicit_value(true)
+ .nargs(0);
+ }
}
ArgumentParser(ArgumentParser &&) noexcept = default;
ArgumentParser &operator=(ArgumentParser &&) = default;
ArgumentParser(const ArgumentParser &other)
- : mProgramName(other.mProgramName),
- mPositionalArguments(other.mPositionalArguments),
- mOptionalArguments(other.mOptionalArguments) {
- for (auto it = std::begin(mPositionalArguments); it != std::end(mPositionalArguments);
- ++it)
+ : m_program_name(other.m_program_name),
+ m_version(other.m_version),
+ m_description(other.m_description),
+ m_epilog(other.m_epilog),
+ m_is_parsed(other.m_is_parsed),
+ m_positional_arguments(other.m_positional_arguments),
+ m_optional_arguments(other.m_optional_arguments) {
+ for (auto it = std::begin(m_positional_arguments);
+ it != std::end(m_positional_arguments); ++it) {
index_argument(it);
- for (auto it = std::begin(mOptionalArguments); it != std::end(mOptionalArguments);
- ++it)
+ }
+ for (auto it = std::begin(m_optional_arguments);
+ it != std::end(m_optional_arguments); ++it) {
index_argument(it);
+ }
}
+ ~ArgumentParser() = default;
+
ArgumentParser &operator=(const ArgumentParser &other) {
auto tmp = other;
std::swap(*this, tmp);
@@ -930,45 +990,46 @@ public:
// Parameter packing
// Call add_argument with variadic number of string arguments
- template Argument &add_argument(Targs... Fargs) {
- using array_of_sv = std::string_view[sizeof...(Targs)];
- auto tArgument = mOptionalArguments.emplace(cend(mOptionalArguments),
- array_of_sv{Fargs...});
+ template Argument &add_argument(Targs... f_args) {
+ using array_of_sv = std::array;
+ auto argument = m_optional_arguments.emplace(
+ std::cend(m_optional_arguments), array_of_sv{f_args...});
- if (!tArgument->mIsOptional)
- mPositionalArguments.splice(cend(mPositionalArguments),
- mOptionalArguments, tArgument);
+ if (!argument->m_is_optional) {
+ m_positional_arguments.splice(std::cend(m_positional_arguments),
+ m_optional_arguments, argument);
+ }
- index_argument(tArgument);
- return *tArgument;
+ index_argument(argument);
+ return *argument;
}
// Parameter packed add_parents method
// Accepts a variadic number of ArgumentParser objects
template
- ArgumentParser &add_parents(const Targs &... Fargs) {
- for (const ArgumentParser &tParentParser : {std::ref(Fargs)...}) {
- for (auto &tArgument : tParentParser.mPositionalArguments) {
- auto it =
- mPositionalArguments.insert(cend(mPositionalArguments), tArgument);
+ 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(
+ std::cend(m_positional_arguments), argument);
index_argument(it);
}
- for (auto &tArgument : tParentParser.mOptionalArguments) {
- auto it =
- mOptionalArguments.insert(cend(mOptionalArguments), tArgument);
+ for (const auto &argument : parent_parser.m_optional_arguments) {
+ auto it = m_optional_arguments.insert(std::cend(m_optional_arguments),
+ argument);
index_argument(it);
}
}
return *this;
}
- ArgumentParser &add_description(std::string aDescription) {
- mDescription = std::move(aDescription);
+ ArgumentParser &add_description(std::string description) {
+ m_description = std::move(description);
return *this;
}
- ArgumentParser &add_epilog(std::string aEpilog) {
- mEpilog = std::move(aEpilog);
+ ArgumentParser &add_epilog(std::string epilog) {
+ m_epilog = std::move(epilog);
return *this;
}
@@ -977,15 +1038,19 @@ public:
* This variant is used mainly for testing
* @throws std::runtime_error in case of any invalid argument
*/
- void parse_args(const std::vector &aArguments) {
- parse_args_internal(aArguments);
- parse_args_validate();
+ void parse_args(const std::vector &arguments) {
+ parse_args_internal(arguments);
+ // Check if all arguments are parsed
+ for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) {
+ argument->validate();
+ }
}
/* Main entry point for parsing command-line arguments using this
* ArgumentParser
* @throws std::runtime_error in case of any invalid argument
*/
+ // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)
void parse_args(int argc, const char *const argv[]) {
std::vector arguments;
std::copy(argv, argv + argc, std::back_inserter(arguments));
@@ -998,12 +1063,11 @@ public:
* @throws std::logic_error if the option has no value
* @throws std::bad_any_cast if the option is not of type T
*/
- template
- T get(std::string_view aArgumentName) const {
- if (!mIsParsed) {
+ template T get(std::string_view arg_name) const {
+ if (!m_is_parsed) {
throw std::logic_error("Nothing parsed, no arguments are available.");
}
- return (*this)[aArgumentName].get();
+ return (*this)[arg_name].get();
}
/* Getter for options without default values.
@@ -1012,79 +1076,81 @@ public:
* @throws std::bad_any_cast if the option is not of type T
*/
template
- auto present(std::string_view aArgumentName) const -> std::optional {
- return (*this)[aArgumentName].present();
+ auto present(std::string_view arg_name) const -> std::optional {
+ return (*this)[arg_name].present();
}
/* Getter that returns true for user-supplied options. Returns false if not
* user-supplied, even with a default value.
*/
- auto is_used(std::string_view aArgumentName) const {
- return (*this)[aArgumentName].mIsUsed;
+ auto is_used(std::string_view arg_name) const {
+ return (*this)[arg_name].m_is_used;
}
/* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name
*/
- Argument &operator[](std::string_view aArgumentName) const {
- auto tIterator = mArgumentMap.find(aArgumentName);
- if (tIterator != mArgumentMap.end()) {
- return *(tIterator->second);
+ Argument &operator[](std::string_view arg_name) const {
+ auto it = m_argument_map.find(arg_name);
+ if (it != m_argument_map.end()) {
+ return *(it->second);
}
- if (aArgumentName.front() != '-') {
- std::string nameStr(aArgumentName);
- // "-" + aArgumentName
- nameStr = "-" + nameStr;
- tIterator = mArgumentMap.find(nameStr);
- if (tIterator != mArgumentMap.end()) {
- return *(tIterator->second);
+ if (arg_name.front() != '-') {
+ std::string name(arg_name);
+ // "-" + arg_name
+ name = "-" + name;
+ it = m_argument_map.find(name);
+ if (it != m_argument_map.end()) {
+ return *(it->second);
}
- // "--" + aArgumentName
- nameStr = "-" + nameStr;
- tIterator = mArgumentMap.find(nameStr);
- if (tIterator != mArgumentMap.end()) {
- return *(tIterator->second);
+ // "--" + arg_name
+ name = "-" + name;
+ it = m_argument_map.find(name);
+ if (it != m_argument_map.end()) {
+ return *(it->second);
}
}
- throw std::logic_error("No such argument: " + std::string(aArgumentName));
+ throw std::logic_error("No such argument: " + std::string(arg_name));
}
// Print help message
friend auto operator<<(std::ostream &stream, const ArgumentParser &parser)
-> std::ostream & {
- if (auto sen = std::ostream::sentry(stream)) {
- stream.setf(std::ios_base::left);
- stream << "Usage: " << parser.mProgramName << " [options] ";
- std::size_t tLongestArgumentLength = parser.get_length_of_longest_argument();
+ stream.setf(std::ios_base::left);
+ stream << "Usage: " << parser.m_program_name << " [options] ";
+ std::size_t longest_arg_length = parser.get_length_of_longest_argument();
- for (const auto &argument : parser.mPositionalArguments) {
- stream << argument.mNames.front() << " ";
- }
- stream << "\n\n";
+ for (const auto &argument : parser.m_positional_arguments) {
+ stream << argument.m_names.front() << " ";
+ }
+ stream << "\n\n";
- if (!parser.mDescription.empty())
- stream << parser.mDescription << "\n\n";
+ if (!parser.m_description.empty()) {
+ stream << parser.m_description << "\n\n";
+ }
- if (!parser.mPositionalArguments.empty())
- stream << "Positional arguments:\n";
+ if (!parser.m_positional_arguments.empty()) {
+ stream << "Positional arguments:\n";
+ }
- for (const auto &mPositionalArgument : parser.mPositionalArguments) {
- stream.width(tLongestArgumentLength);
- stream << mPositionalArgument;
- }
+ for (const auto &argument : parser.m_positional_arguments) {
+ stream.width(longest_arg_length);
+ stream << argument;
+ }
- if (!parser.mOptionalArguments.empty())
- stream << (parser.mPositionalArguments.empty() ? "" : "\n")
- << "Optional arguments:\n";
+ if (!parser.m_optional_arguments.empty()) {
+ stream << (parser.m_positional_arguments.empty() ? "" : "\n")
+ << "Optional arguments:\n";
+ }
- for (const auto &mOptionalArgument : parser.mOptionalArguments) {
- stream.width(tLongestArgumentLength);
- stream << mOptionalArgument;
- }
+ for (const auto &argument : parser.m_optional_arguments) {
+ stream.width(longest_arg_length);
+ stream << argument;
+ }
- if (!parser.mEpilog.empty())
- stream << parser.mEpilog << "\n\n";
+ if (!parser.m_epilog.empty()) {
+ stream << parser.m_epilog << "\n\n";
}
return stream;
@@ -1100,7 +1166,7 @@ public:
// Printing the one and only help message
// I've stuck with a simple message format, nothing fancy.
[[deprecated("Use cout << program; instead. See also help().")]] std::string
- print_help() {
+ print_help() const {
auto out = help();
std::cout << out.rdbuf();
return out.str();
@@ -1110,102 +1176,77 @@ private:
/*
* @throws std::runtime_error in case of any invalid argument
*/
- void parse_args_internal(const std::vector &aArguments) {
- if (mProgramName.empty() && !aArguments.empty()) {
- mProgramName = aArguments.front();
+ void parse_args_internal(const std::vector &arguments) {
+ if (m_program_name.empty() && !arguments.empty()) {
+ m_program_name = arguments.front();
}
- auto end = std::end(aArguments);
- auto positionalArgumentIt = std::begin(mPositionalArguments);
- for (auto it = std::next(std::begin(aArguments)); it != end;) {
- const auto &tCurrentArgument = *it;
- if (Argument::is_positional(tCurrentArgument)) {
- if (positionalArgumentIt == std::end(mPositionalArguments)) {
+ auto end = std::end(arguments);
+ auto positional_argument_it = std::begin(m_positional_arguments);
+ for (auto it = std::next(std::begin(arguments)); it != end;) {
+ const auto ¤t_argument = *it;
+ if (Argument::is_positional(current_argument)) {
+ if (positional_argument_it == std::end(m_positional_arguments)) {
throw std::runtime_error(
"Maximum number of positional arguments exceeded");
}
- auto tArgument = positionalArgumentIt++;
- it = tArgument->consume(it, end);
+ auto argument = positional_argument_it++;
+ it = argument->consume(it, end);
continue;
}
- auto tIterator = mArgumentMap.find(tCurrentArgument);
- if (tIterator != mArgumentMap.end()) {
- auto tArgument = tIterator->second;
-
- // the first optional argument is --help
- if (tArgument == mOptionalArguments.begin()) {
- std::cout << *this;
- std::exit(0);
- }
- // the second optional argument is --version
- else if (tArgument == std::next(mOptionalArguments.begin(), 1)) {
- std::cout << mVersion << "\n";
- std::exit(0);
- }
-
- it = tArgument->consume(std::next(it), end, tIterator->first);
- } else if (const auto &tCompoundArgument = tCurrentArgument;
- tCompoundArgument.size() > 1 && tCompoundArgument[0] == '-' &&
- tCompoundArgument[1] != '-') {
+ auto arg_map_it = m_argument_map.find(current_argument);
+ if (arg_map_it != m_argument_map.end()) {
+ auto argument = arg_map_it->second;
+ it = argument->consume(std::next(it), end, arg_map_it->first);
+ } else if (const auto &compound_arg = current_argument;
+ compound_arg.size() > 1 && compound_arg[0] == '-' &&
+ compound_arg[1] != '-') {
++it;
- for (std::size_t j = 1; j < tCompoundArgument.size(); j++) {
- auto tHypotheticalArgument = std::string{'-', tCompoundArgument[j]};
- auto tIterator2 = mArgumentMap.find(tHypotheticalArgument);
- if (tIterator2 != mArgumentMap.end()) {
- auto tArgument = tIterator2->second;
- it = tArgument->consume(it, end, tIterator2->first);
+ for (std::size_t j = 1; j < compound_arg.size(); j++) {
+ auto hypothetical_arg = std::string{'-', compound_arg[j]};
+ auto arg_map_it2 = m_argument_map.find(hypothetical_arg);
+ if (arg_map_it2 != m_argument_map.end()) {
+ auto argument = arg_map_it2->second;
+ it = argument->consume(it, end, arg_map_it2->first);
} else {
- throw std::runtime_error("Unknown argument");
+ throw std::runtime_error("Unknown argument: " + current_argument);
}
}
} else {
- throw std::runtime_error("Unknown argument");
+ throw std::runtime_error("Unknown argument: " + current_argument);
}
}
- mIsParsed = true;
- }
-
- /*
- * @throws std::runtime_error in case of any invalid argument
- */
- void parse_args_validate() {
- // Check if all arguments are parsed
- std::for_each(std::begin(mArgumentMap), std::end(mArgumentMap),
- [](const auto &argPair) {
- const auto &tArgument = argPair.second;
- tArgument->validate();
- });
+ m_is_parsed = true;
}
// Used by print_help.
std::size_t get_length_of_longest_argument() const {
- if (mArgumentMap.empty())
+ if (m_argument_map.empty()) {
return 0;
- std::vector argumentLengths(mArgumentMap.size());
- std::transform(std::begin(mArgumentMap), std::end(mArgumentMap),
- std::begin(argumentLengths), [](const auto &argPair) {
- const auto &tArgument = argPair.second;
- return tArgument->get_arguments_length();
- });
- return *std::max_element(std::begin(argumentLengths),
- std::end(argumentLengths));
+ }
+ std::size_t max_size = 0;
+ for ([[maybe_unused]] const auto& [unused, argument] : m_argument_map) {
+ max_size = std::max(max_size, argument->get_arguments_length());
+ }
+ return max_size;
}
using list_iterator = std::list::iterator;
- void index_argument(list_iterator argIt) {
- for (auto &mName : std::as_const(argIt->mNames))
- mArgumentMap.insert_or_assign(mName, argIt);
+ void index_argument(list_iterator it) {
+ for (const auto &name : std::as_const(it->m_names)) {
+ m_argument_map.insert_or_assign(name, it);
+ }
}
- std::string mProgramName;
- std::string mVersion;
- std::string mDescription;
- std::string mEpilog;
- bool mIsParsed = false;
- std::list