argparse update

This commit is contained in:
Marek Roszko 2023-12-17 21:29:05 -05:00
parent 4a9994931a
commit de2f5f6547
55 changed files with 2826 additions and 1180 deletions

1
thirdparty/argparse/.stylua.toml vendored Normal file
View File

@ -0,0 +1 @@
indent_type = "Spaces"

View File

@ -1,14 +1,21 @@
cmake_minimum_required(VERSION 3.12.4)
if(NOT DEFINED PROJECT_NAME)
set(ARGPARSE_IS_TOP_LEVEL ON)
else()
set(ARGPARSE_IS_TOP_LEVEL OFF)
endif()
project(argparse
VERSION 2.9.0
VERSION 3.0.0
DESCRIPTION "A single header argument parser for C++17"
HOMEPAGE_URL "https://github.com/p-ranav/argparse"
LANGUAGES CXX
)
option(ARGPARSE_INSTALL "Include an install target" ON)
option(ARGPARSE_BUILD_TESTS "Build tests" OFF)
option(ARGPARSE_BUILD_TESTS "Build tests" ON)
option(ARGPARSE_BUILD_SAMPLES "Build samples" OFF)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
@ -24,12 +31,12 @@ target_include_directories(argparse INTERFACE
if(ARGPARSE_BUILD_SAMPLES)
add_subdirectory(samples)
endif()
if(ARGPARSE_BUILD_TESTS)
if(ARGPARSE_BUILD_TESTS AND ARGPARSE_IS_TOP_LEVEL)
add_subdirectory(test)
endif()
if(ARGPARSE_INSTALL)
if(ARGPARSE_INSTALL AND ARGPARSE_IS_TOP_LEVEL)
install(TARGETS argparse EXPORT argparseConfig)
install(EXPORT argparseConfig
NAMESPACE argparse::

View File

@ -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-2.9-blue.svg?cacheSeconds=2592000" alt="version"/>
<img src="https://img.shields.io/badge/version-3.0-blue.svg?cacheSeconds=2592000" alt="version"/>
</p>
## Highlights
@ -25,6 +25,7 @@
* [Deciding if the value was given by the user](#deciding-if-the-value-was-given-by-the-user)
* [Joining values of repeated optional arguments](#joining-values-of-repeated-optional-arguments)
* [Repeating an argument to increase a value](#repeating-an-argument-to-increase-a-value)
* [Mutually Exclusive Group](#mutually-exclusive-group)
* [Negative Numbers](#negative-numbers)
* [Combining Positional and Optional Arguments](#combining-positional-and-optional-arguments)
* [Printing Help](#printing-help)
@ -45,6 +46,8 @@
* [Positional Arguments with Compound Toggle Arguments](#positional-arguments-with-compound-toggle-arguments)
* [Restricting the set of values for an argument](#restricting-the-set-of-values-for-an-argument)
* [Using `option=value` syntax](#using-optionvalue-syntax)
* [Developer Notes](#developer-notes)
* [Copying and Moving](#copying-and-moving)
* [CMake Integration](#cmake-integration)
* [Building, Installing, and Testing](#building-installing-and-testing)
* [Supported Toolchains](#supported-toolchains)
@ -95,7 +98,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
@ -137,7 +140,7 @@ program.add_argument("--verbose")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -158,6 +161,31 @@ Here's what's happening:
* Since the argument is actually optional, no error is thrown when running the program without ```--verbose```. Note that by using ```.default_value(false)```, if the optional argument isnt used, it's value is automatically set to false.
* By using ```.implicit_value(true)```, the user specifies that this option is more of a flag than something that requires a value. When the user provides the --verbose option, it's value is set to true.
#### Flag
When defining flag arguments, you can use the shorthand `flag()` which is the same as `default_value(false).implicit_value(true)`.
```cpp
argparse::ArgumentParser program("test");
program.add_argument("--verbose")
.help("increase output verbosity")
.flag();
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
if (program["--verbose"] == true) {
std::cout << "Verbosity enabled" << std::endl;
}
```
#### Requiring optional arguments
There are scenarios where you would like to make an optional argument ***required***. As discussed above, optional arguments either begin with `-` or `--`. You can make these types of arguments required like so:
@ -203,7 +231,7 @@ program.add_argument("--color")
try {
program.parse_args(argc, argv); // Example: ./main --color orange
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -226,7 +254,7 @@ program.add_argument("--color")
try {
program.parse_args(argc, argv); // Example: ./main --color red --color green --color blue
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -255,6 +283,38 @@ program.parse_args(argc, argv); // Example: ./main -VVVV
std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4
```
#### Mutually Exclusive Group
Create a mutually exclusive group using `program.add_mutually_exclusive_group(required = false)`. `argparse`` will make sure that only one of the arguments in the mutually exclusive group was present on the command line:
```cpp
auto &group = program.add_mutually_exclusive_group();
group.add_argument("--first");
group.add_argument("--second");
```
with the following usage will yield an error:
```console
foo@bar:/home/dev/$ ./main --first 1 --second 2
Argument '--second VAR' not allowed with '--first VAR'
```
The `add_mutually_exclusive_group()` function also accepts a `required` argument, to indicate that at least one of the mutually exclusive arguments is required:
```cpp
auto &group = program.add_mutually_exclusive_group(true);
group.add_argument("--first");
group.add_argument("--second");
```
with the following usage will yield an error:
```console
foo@bar:/home/dev/$ ./main
One of the arguments '--first VAR' or '--second VAR' is required
```
### Negative Numbers
Optional arguments start with ```-```. Can ```argparse``` handle negative numbers? The answer is yes!
@ -274,7 +334,7 @@ program.add_argument("floats")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -307,7 +367,7 @@ program.add_argument("--verbose")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -388,7 +448,7 @@ Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
--member ALIAS The alias for the member to pass to.
--verbose
--verbose
Possible things include betingalw, chiz, and res.
```
@ -407,7 +467,7 @@ program.add_argument("--input_files")
try {
program.parse_args(argc, argv); // Example: ./main --input_files config.yml System.xml
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -436,7 +496,7 @@ program.add_argument("--query_point")
try {
program.parse_args(argc, argv); // Example: ./main --query_point 3.5 4.7 9.2
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -491,7 +551,7 @@ program.add_argument("-c")
try {
program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -551,7 +611,7 @@ The grammar follows `std::from_chars`, but does not exactly duplicate it. For ex
| 'g' or 'G' | general form (either fixed or scientific) |
| | |
| 'd' | decimal |
| 'i' | `std::from_chars` grammar with base == 0 |
| 'i' | `std::from_chars` grammar with base == 10 |
| 'o' | octal (unsigned) |
| 'u' | decimal (unsigned) |
| 'x' or 'X' | hexadecimal (unsigned) |
@ -604,7 +664,7 @@ program.add_argument("files")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -651,7 +711,7 @@ program.add_argument("files")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -777,7 +837,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
@ -841,6 +901,39 @@ When a help message is requested from a subparser, only the help for that partic
Additionally, every parser has the `.is_subcommand_used("<command_name>")` and `.is_subcommand_used(subparser)` member functions to check if a subcommand was used.
Sometimes there may be a need to hide part of the subcommands from the user
by suppressing information about them in an help message. To do this,
```ArgumentParser``` contains the method ```.set_suppress(bool suppress)```:
```cpp
argparse::ArgumentParser program("test");
argparse::ArgumentParser hidden_cmd("hidden");
hidden_cmd.add_argument("files").remaining();
hidden_cmd.set_suppress(true);
program.add_subparser(hidden_cmd);
```
```console
foo@bar:/home/dev/$ ./main -h
Usage: test [--help] [--version] {}
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
foo@bar:/home/dev/$ ./main hidden -h
Usage: hidden [--help] [--version] files
Positional arguments:
files [nargs: 0 or more]
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
```
### Getting Argument and Subparser Instances
```Argument``` and ```ArgumentParser``` instances added to an ```ArgumentParser``` can be retrieved with ```.at<T>()```. The default return type is ```Argument```.
@ -904,7 +997,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
@ -952,7 +1045,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
@ -993,7 +1086,7 @@ program.add_argument("config")
try {
program.parse_args({"./test", "config.json"});
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -1029,7 +1122,7 @@ program.add_argument("--files")
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -1060,18 +1153,12 @@ argparse::ArgumentParser program("test");
program.add_argument("input")
.default_value(std::string{"baz"})
.action([](const std::string& value) {
static const std::vector<std::string> choices = { "foo", "bar", "baz" };
if (std::find(choices.begin(), choices.end(), value) != choices.end()) {
return value;
}
return std::string{ "baz" };
});
.choices("foo", "bar", "baz");
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
@ -1083,10 +1170,37 @@ std::cout << input << std::endl;
```console
foo@bar:/home/dev/$ ./main fex
baz
Invalid argument "fex" - allowed options: {foo, bar, baz}
```
## Using `option=value` syntax
Using choices also works with integer types, e.g.,
```cpp
argparse::ArgumentParser program("test");
program.add_argument("input")
.default_value(0)
.choices(0, 1, 2, 3, 4, 5);
try {
program.parse_args(argc, argv);
}
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
std::exit(1);
}
auto input = program.get("input");
std::cout << input << std::endl;
```
```console
foo@bar:/home/dev/$ ./main 6
Invalid argument "6" - allowed options: {0, 1, 2, 3, 4, 5}
```
### Using `option=value` syntax
```cpp
#include "argparse.hpp"
@ -1100,7 +1214,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
}
catch (const std::runtime_error& err) {
catch (const std::exception& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;
@ -1122,6 +1236,12 @@ foo@bar:/home/dev/$ ./test --bar=BAR --foo
--bar: BAR
```
## Developer Notes
### Copying and Moving
`argparse::ArgumentParser` is intended to be used in a single function - setup everything and parse arguments in one place. Attempting to move or copy invalidates internal references (issue #260). Thus, starting with v3.0, `argparse::ArgumentParser` copy and move constructors are marked as `delete`.
## CMake Integration
Use the latest argparse in your CMake project without copying any content.

View File

@ -2,7 +2,7 @@ from conans import ConanFile
class ArgparseConan(ConanFile):
name = "argparse"
version = "2.9"
version = "3.0"
exports_sources = "include/argparse.hpp"
no_copy_source = True

View File

@ -29,6 +29,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#pragma once
#ifndef ARGPARSE_MODULE_USE_STD_MODULE
#include <algorithm>
#include <any>
#include <array>
@ -53,6 +55,7 @@ SOFTWARE.
#include <utility>
#include <variant>
#include <vector>
#endif
namespace argparse {
@ -72,7 +75,7 @@ struct HasContainerTraits<
decltype(std::declval<T>().size())>> : std::true_type {};
template <typename T>
static constexpr bool IsContainer = HasContainerTraits<T>::value;
inline constexpr bool IsContainer = HasContainerTraits<T>::value;
template <typename T, typename = void>
struct HasStreamableTraits : std::false_type {};
@ -84,7 +87,7 @@ struct HasStreamableTraits<
: std::true_type {};
template <typename T>
static constexpr bool IsStreamable = HasStreamableTraits<T>::value;
inline constexpr bool IsStreamable = HasStreamableTraits<T>::value;
constexpr std::size_t repr_max_container_size = 5;
@ -145,6 +148,7 @@ constexpr bool standard_unsigned_integer<unsigned long long int> = true;
} // namespace
constexpr int radix_2 = 2;
constexpr int radix_8 = 8;
constexpr int radix_10 = 10;
constexpr int radix_16 = 16;
@ -180,12 +184,28 @@ constexpr bool starts_with(std::basic_string_view<CharT, Traits> prefix,
}
enum class chars_format {
scientific = 0x1,
fixed = 0x2,
hex = 0x4,
scientific = 0xf1,
fixed = 0xf2,
hex = 0xf4,
binary = 0xf8,
general = fixed | scientific
};
struct ConsumeBinaryPrefixResult {
bool is_binary;
std::string_view rest;
};
constexpr auto consume_binary_prefix(std::string_view s)
-> ConsumeBinaryPrefixResult {
if (starts_with(std::string_view{"0b"}, s) ||
starts_with(std::string_view{"0B"}, s)) {
s.remove_prefix(2);
return {true, s};
}
return {false, s};
}
struct ConsumeHexPrefixResult {
bool is_hexadecimal;
std::string_view rest;
@ -211,13 +231,14 @@ inline auto do_from_chars(std::string_view s) -> T {
if (ptr == last) {
return x;
}
throw std::invalid_argument{"pattern does not match to the end"};
throw std::invalid_argument{"pattern '" + std::string(s) +
"' does not match to the end"};
}
if (ec == std::errc::invalid_argument) {
throw std::invalid_argument{"pattern not found"};
throw std::invalid_argument{"pattern '" + std::string(s) + "' not found"};
}
if (ec == std::errc::result_out_of_range) {
throw std::range_error{"not representable"};
throw std::range_error{"'" + std::string(s) + "' not representable"};
}
return x; // unreachable
}
@ -228,25 +249,97 @@ template <class T, auto Param = 0> struct parse_number {
}
};
template <class T> struct parse_number<T, radix_16> {
template <class T> struct parse_number<T, radix_2> {
auto operator()(std::string_view s) -> T {
if (auto [ok, rest] = consume_hex_prefix(s); ok) {
return do_from_chars<T, radix_16>(rest);
if (auto [ok, rest] = consume_binary_prefix(s); ok) {
return do_from_chars<T, radix_2>(rest);
}
throw std::invalid_argument{"pattern not found"};
}
};
template <class T> struct parse_number<T, radix_16> {
auto operator()(std::string_view s) -> T {
if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) {
if (auto [ok, rest] = consume_hex_prefix(s); ok) {
try {
return do_from_chars<T, radix_16>(rest);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
}
}
} else {
// Allow passing hex numbers without prefix
// Shape 'x' already has to be specified
try {
return do_from_chars<T, radix_16>(s);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
}
}
throw std::invalid_argument{"pattern '" + std::string(s) +
"' not identified as hexadecimal"};
}
};
template <class T> struct parse_number<T> {
auto operator()(std::string_view s) -> T {
auto [ok, rest] = consume_hex_prefix(s);
if (ok) {
return do_from_chars<T, radix_16>(rest);
try {
return do_from_chars<T, radix_16>(rest);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as hexadecimal: " + err.what());
}
}
auto [ok_binary, rest_binary] = consume_binary_prefix(s);
if (ok_binary) {
try {
return do_from_chars<T, radix_2>(rest_binary);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as binary: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as binary: " + err.what());
}
}
if (starts_with("0"sv, s)) {
return do_from_chars<T, radix_8>(rest);
try {
return do_from_chars<T, radix_8>(rest);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as octal: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as octal: " + err.what());
}
}
try {
return do_from_chars<T, radix_10>(rest);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + std::string(s) +
"' as decimal integer: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + std::string(s) +
"' as decimal integer: " + err.what());
}
return do_from_chars<T, radix_10>(rest);
}
};
@ -261,7 +354,7 @@ template <> inline const auto generic_strtod<long double> = strtold;
template <class T> inline auto do_strtod(std::string const &s) -> T {
if (isspace(static_cast<unsigned char>(s[0])) || s[0] == '+') {
throw std::invalid_argument{"pattern not found"};
throw std::invalid_argument{"pattern '" + s + "' not found"};
}
auto [first, last] = pointer_range(s);
@ -273,10 +366,11 @@ template <class T> inline auto do_strtod(std::string const &s) -> T {
if (ptr == last) {
return x;
}
throw std::invalid_argument{"pattern does not match to the end"};
throw std::invalid_argument{"pattern '" + s +
"' does not match to the end"};
}
if (errno == ERANGE) {
throw std::range_error{"not representable"};
throw std::range_error{"'" + s + "' not representable"};
}
return x; // unreachable
}
@ -287,8 +381,20 @@ template <class T> struct parse_number<T, chars_format::general> {
throw std::invalid_argument{
"chars_format::general does not parse hexfloat"};
}
if (auto r = consume_binary_prefix(s); r.is_binary) {
throw std::invalid_argument{
"chars_format::general does not parse binfloat"};
}
return do_strtod<T>(s);
try {
return do_strtod<T>(s);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + s +
"' as number: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + s +
"' as number: " + err.what());
}
}
};
@ -297,6 +403,31 @@ template <class T> struct parse_number<T, chars_format::hex> {
if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) {
throw std::invalid_argument{"chars_format::hex parses hexfloat"};
}
if (auto r = consume_binary_prefix(s); r.is_binary) {
throw std::invalid_argument{"chars_format::hex does not parse binfloat"};
}
try {
return do_strtod<T>(s);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + s +
"' as hexadecimal: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + s +
"' as hexadecimal: " + err.what());
}
}
};
template <class T> struct parse_number<T, chars_format::binary> {
auto operator()(std::string const &s) -> T {
if (auto r = consume_hex_prefix(s); r.is_hexadecimal) {
throw std::invalid_argument{
"chars_format::binary does not parse hexfloat"};
}
if (auto r = consume_binary_prefix(s); !r.is_binary) {
throw std::invalid_argument{"chars_format::binary parses binfloat"};
}
return do_strtod<T>(s);
}
@ -308,12 +439,24 @@ template <class T> struct parse_number<T, chars_format::scientific> {
throw std::invalid_argument{
"chars_format::scientific does not parse hexfloat"};
}
if (auto r = consume_binary_prefix(s); r.is_binary) {
throw std::invalid_argument{
"chars_format::scientific does not parse binfloat"};
}
if (s.find_first_of("eE") == std::string::npos) {
throw std::invalid_argument{
"chars_format::scientific requires exponent part"};
}
return do_strtod<T>(s);
try {
return do_strtod<T>(s);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + s +
"' as scientific notation: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + s +
"' as scientific notation: " + err.what());
}
}
};
@ -323,12 +466,24 @@ template <class T> struct parse_number<T, chars_format::fixed> {
throw std::invalid_argument{
"chars_format::fixed does not parse hexfloat"};
}
if (auto r = consume_binary_prefix(s); r.is_binary) {
throw std::invalid_argument{
"chars_format::fixed does not parse binfloat"};
}
if (s.find_first_of("eE") != std::string::npos) {
throw std::invalid_argument{
"chars_format::fixed does not parse exponent part"};
}
return do_strtod<T>(s);
try {
return do_strtod<T>(s);
} catch (const std::invalid_argument &err) {
throw std::invalid_argument("Failed to parse '" + s +
"' as fixed notation: " + err.what());
} catch (const std::range_error &err) {
throw std::range_error("Failed to parse '" + s +
"' as fixed notation: " + err.what());
}
}
};
@ -347,6 +502,65 @@ std::string join(StrIt first, StrIt last, const std::string &separator) {
return value.str();
}
template <typename T> struct can_invoke_to_string {
template <typename U>
static auto test(int)
-> decltype(std::to_string(std::declval<U>()), std::true_type{});
template <typename U> static auto test(...) -> std::false_type;
static constexpr bool value = decltype(test<T>(0))::value;
};
template <typename T> struct IsChoiceTypeSupported {
using CleanType = typename std::decay<T>::type;
static const bool value = std::is_integral<CleanType>::value ||
std::is_same<CleanType, std::string>::value ||
std::is_same<CleanType, std::string_view>::value ||
std::is_same<CleanType, const char *>::value;
};
template <typename StringType>
std::size_t get_levenshtein_distance(const StringType &s1,
const StringType &s2) {
std::vector<std::vector<std::size_t>> dp(
s1.size() + 1, std::vector<std::size_t>(s2.size() + 1, 0));
for (std::size_t i = 0; i <= s1.size(); ++i) {
for (std::size_t j = 0; j <= s2.size(); ++j) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} 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]});
}
}
}
return dp[s1.size()][s2.size()];
}
template <typename ValueType>
std::string_view
get_most_similar_string(const std::map<std::string_view, ValueType> &map,
const std::string_view input) {
std::string_view most_similar{};
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);
if (distance < min_distance) {
min_distance = distance;
most_similar = entry.first;
}
}
return most_similar;
}
} // namespace details
enum class nargs_pattern { optional, any, at_least_one };
@ -404,7 +618,15 @@ public:
}
template <typename T> Argument &default_value(T &&value) {
m_num_args_range = NArgsRange{0, m_num_args_range.get_max()};
m_default_value_repr = details::repr(value);
if constexpr (std::is_convertible_v<T, std::string_view>) {
m_default_value_str = std::string{std::string_view{value}};
} else if constexpr (details::can_invoke_to_string<T>::value) {
m_default_value_str = std::to_string(value);
}
m_default_value = std::forward<T>(value);
return *this;
}
@ -424,8 +646,18 @@ public:
return *this;
}
// This is shorthand for:
// program.add_argument("foo")
// .default_value(false)
// .implicit_value(true)
Argument &flag() {
default_value(false);
implicit_value(true);
return *this;
}
template <class F, class... Args>
auto action(F &&callable, Args &&... bound_args)
auto action(F &&callable, Args &&...bound_args)
-> std::enable_if_t<std::is_invocable_v<F, Args..., std::string const>,
Argument &> {
using action_type = std::conditional_t<
@ -465,6 +697,9 @@ public:
} else if constexpr (is_one_of(Shape, 'u') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_10>());
} else if constexpr (is_one_of(Shape, 'b') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_2>());
} else if constexpr (is_one_of(Shape, 'o') &&
details::standard_unsigned_integer<T>) {
action(details::parse_number<T, details::radix_8>());
@ -506,10 +741,12 @@ public:
m_num_args_range = NArgsRange{0, 1};
break;
case nargs_pattern::any:
m_num_args_range = NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
m_num_args_range =
NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
break;
case nargs_pattern::at_least_one:
m_num_args_range = NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
m_num_args_range =
NArgsRange{1, (std::numeric_limits<std::size_t>::max)()};
break;
}
return *this;
@ -520,6 +757,82 @@ public:
return nargs(nargs_pattern::any);
}
template <typename T> void add_choice(T &&choice) {
static_assert(details::IsChoiceTypeSupported<T>::value,
"Only string or integer type supported for choice");
static_assert(std::is_convertible_v<T, std::string_view> ||
details::can_invoke_to_string<T>::value,
"Choice is not convertible to string_type");
if (!m_choices.has_value()) {
m_choices = std::vector<std::string>{};
}
if constexpr (std::is_convertible_v<T, std::string_view>) {
m_choices.value().push_back(
std::string{std::string_view{std::forward<T>(choice)}});
} else if constexpr (details::can_invoke_to_string<T>::value) {
m_choices.value().push_back(std::to_string(std::forward<T>(choice)));
}
}
Argument &choices() {
if (!m_choices.has_value()) {
throw std::runtime_error("Zero choices provided");
}
return *this;
}
template <typename T, typename... U>
Argument &choices(T &&first, U &&...rest) {
add_choice(std::forward<T>(first));
choices(std::forward<U>(rest)...);
return *this;
}
void find_default_value_in_choices_or_throw() const {
const auto &choices = m_choices.value();
if (m_default_value.has_value()) {
if (std::find(choices.begin(), choices.end(), m_default_value_str) ==
choices.end()) {
// provided arg not in list of allowed choices
// report error
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;
});
throw std::runtime_error(
std::string{"Invalid default value "} + m_default_value_repr +
" - allowed options: {" + choices_as_csv + "}");
}
}
}
template <typename Iterator>
void find_value_in_choices_or_throw(Iterator 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
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;
});
throw std::runtime_error(std::string{"Invalid argument "} +
details::repr(*it) + " - allowed options: {" +
choices_as_csv + "}");
}
}
template <typename Iterator>
Iterator consume(Iterator start, Iterator end,
std::string_view used_name = {}) {
@ -529,6 +842,14 @@ public:
m_is_used = true;
m_used_name = used_name;
if (m_choices.has_value()) {
// Check each value in (start, end) and make sure
// it is in the list of allowed choices/options
for (auto it = start; it != end; ++it) {
find_value_in_choices_or_throw(it);
}
}
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;
@ -599,6 +920,34 @@ public:
throw_nargs_range_validation_error();
}
}
if (m_choices.has_value()) {
// Make sure the default value (if provided)
// is in the list of choices
find_default_value_in_choices_or_throw();
}
}
std::string get_names_csv(char separator = ',') const {
return std::accumulate(
m_names.begin(), m_names.end(), std::string{""},
[&](const std::string &result, const std::string &name) {
return result.empty() ? name : result + separator + name;
});
}
std::string get_usage_full() const {
std::stringstream usage;
usage << get_names_csv('/');
const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR";
if (m_num_args_range.get_max() > 0) {
usage << " " << metavar;
if (m_num_args_range.get_max() > 1) {
usage << "...";
}
}
return usage.str();
}
std::string get_inline_usage() const {
@ -677,13 +1026,13 @@ public:
// align multiline help message
auto stream_width = stream.width();
auto name_padding = std::string(name_stream.str().size(), ' ');
auto pos = 0;
auto prev = 0;
auto pos = std::string::size_type{};
auto prev = std::string::size_type{};
auto first_line = true;
auto hspace = " "; // minimal space between name and help message
stream << name_stream.str();
std::string_view help_view(argument.m_help);
while ((pos = argument.m_help.find('\n', prev)) != (int)std::string::npos) {
while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
auto line = help_view.substr(prev, pos - prev + 1);
if (first_line) {
stream << hspace << line;
@ -735,8 +1084,7 @@ public:
using ValueType = typename T::value_type;
auto lhs = get<T>();
return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs),
std::end(rhs),
[](const auto &a, const auto &b) {
std::end(rhs), [](const auto &a, const auto &b) {
return std::any_cast<const ValueType &>(a) == b;
});
}
@ -1060,7 +1408,10 @@ private:
std::string m_metavar;
std::any m_default_value;
std::string m_default_value_repr;
std::optional<std::string>
m_default_value_str; // used for checking default_value against choices
std::any m_implicit_value;
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{
@ -1082,14 +1433,15 @@ public:
explicit ArgumentParser(std::string program_name = {},
std::string version = "1.0",
default_arguments add_args = default_arguments::all,
bool exit_on_default_arguments = true)
bool exit_on_default_arguments = true,
std::ostream &os = std::cout)
: m_program_name(std::move(program_name)), m_version(std::move(version)),
m_exit_on_default_arguments(exit_on_default_arguments),
m_parser_path(m_program_name) {
if ((add_args & default_arguments::help) == default_arguments::help) {
add_argument("-h", "--help")
.action([&](const auto & /*unused*/) {
std::cout << help().str();
os << help().str();
if (m_exit_on_default_arguments) {
std::exit(0);
}
@ -1102,7 +1454,7 @@ public:
if ((add_args & default_arguments::version) == default_arguments::version) {
add_argument("-v", "--version")
.action([&](const auto & /*unused*/) {
std::cout << m_version << std::endl;
os << m_version << std::endl;
if (m_exit_on_default_arguments) {
std::exit(0);
}
@ -1114,51 +1466,25 @@ public:
}
}
ArgumentParser(ArgumentParser &&) noexcept = default;
ArgumentParser &operator=(ArgumentParser &&) = default;
ArgumentParser(const ArgumentParser &other)
: m_program_name(other.m_program_name), m_version(other.m_version),
m_description(other.m_description), m_epilog(other.m_epilog),
m_prefix_chars(other.m_prefix_chars),
m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed),
m_positional_arguments(other.m_positional_arguments),
m_optional_arguments(other.m_optional_arguments),
m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) {
for (auto it = std::begin(m_positional_arguments);
it != std::end(m_positional_arguments); ++it) {
index_argument(it);
}
for (auto it = std::begin(m_optional_arguments);
it != std::end(m_optional_arguments); ++it) {
index_argument(it);
}
for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers);
++it) {
m_subparser_map.insert_or_assign(it->get().m_program_name, it);
m_subparser_used.insert_or_assign(it->get().m_program_name, false);
}
}
~ArgumentParser() = default;
ArgumentParser &operator=(const ArgumentParser &other) {
auto tmp = other;
std::swap(*this, tmp);
return *this;
}
// ArgumentParser is meant to be used in a single function.
// Setup everything and parse arguments in one place.
//
// ArgumentParser internally uses std::string_views,
// references, iterators, etc.
// Many of these elements become invalidated after a copy or move.
ArgumentParser(const ArgumentParser &other) = delete;
ArgumentParser &operator=(const ArgumentParser &other) = delete;
ArgumentParser(ArgumentParser &&) noexcept = delete;
ArgumentParser &operator=(ArgumentParser &&) = delete;
explicit operator bool() const {
auto arg_used = std::any_of(m_argument_map.cbegin(),
m_argument_map.cend(),
[](auto &it) {
return it.second->m_is_used;
});
auto subparser_used = std::any_of(m_subparser_used.cbegin(),
m_subparser_used.cend(),
[](auto &it) {
return it.second;
});
auto arg_used = std::any_of(m_argument_map.cbegin(), m_argument_map.cend(),
[](auto &it) { return it.second->m_is_used; });
auto subparser_used =
std::any_of(m_subparser_used.cbegin(), m_subparser_used.cend(),
[](auto &it) { return it.second; });
return m_is_parsed && (arg_used || subparser_used);
}
@ -1180,10 +1506,47 @@ public:
return *argument;
}
class MutuallyExclusiveGroup {
friend class ArgumentParser;
public:
MutuallyExclusiveGroup() = delete;
explicit MutuallyExclusiveGroup(ArgumentParser &parent,
bool required = false)
: m_parent(parent), m_required(required), m_elements({}) {}
MutuallyExclusiveGroup(const MutuallyExclusiveGroup &other) = delete;
MutuallyExclusiveGroup &
operator=(const MutuallyExclusiveGroup &other) = delete;
MutuallyExclusiveGroup(MutuallyExclusiveGroup &&other) noexcept
: m_parent(other.m_parent), m_required(other.m_required),
m_elements(std::move(other.m_elements)) {
other.m_elements.clear();
}
template <typename... Targs> Argument &add_argument(Targs... f_args) {
auto &argument = m_parent.add_argument(std::forward<Targs>(f_args)...);
m_elements.push_back(&argument);
return argument;
}
private:
ArgumentParser &m_parent;
bool m_required{false};
std::vector<Argument *> m_elements{};
};
MutuallyExclusiveGroup &add_mutually_exclusive_group(bool required = false) {
m_mutually_exclusive_groups.emplace_back(*this, required);
return m_mutually_exclusive_groups.back();
}
// Parameter packed add_parents method
// Accepts a variadic number of ArgumentParser objects
template <typename... Targs>
ArgumentParser &add_parents(const Targs &... f_args) {
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(
@ -1212,8 +1575,7 @@ public:
/* Getter for arguments and subparsers.
* @throws std::logic_error in case of an invalid argument or subparser name
*/
template <typename T = Argument>
T& at(std::string_view name) {
template <typename T = Argument> T &at(std::string_view name) {
if constexpr (std::is_same_v<T, Argument>) {
return (*this)[name];
} else {
@ -1246,6 +1608,43 @@ public:
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
argument->validate();
}
// Check each mutually exclusive group and make sure
// there are no constraint violations
for (const auto &group : m_mutually_exclusive_groups) {
auto mutex_argument_used{false};
Argument *mutex_argument_it{nullptr};
for (Argument *arg : group.m_elements) {
if (!mutex_argument_used && arg->m_is_used) {
mutex_argument_used = true;
mutex_argument_it = arg;
} else if (mutex_argument_used && arg->m_is_used) {
// Violation
throw std::runtime_error("Argument '" + arg->get_usage_full() +
"' not allowed with '" +
mutex_argument_it->get_usage_full() + "'");
}
}
if (!mutex_argument_used && group.m_required) {
// at least one argument from the group is
// required
std::string argument_names{};
std::size_t i = 0;
std::size_t size = group.m_elements.size();
for (Argument *arg : group.m_elements) {
if (i + 1 == size) {
// last
argument_names += "'" + arg->get_usage_full() + "' ";
} else {
argument_names += "'" + arg->get_usage_full() + "' or ";
}
i += 1;
}
throw std::runtime_error("One of the arguments " + argument_names +
"is required");
}
}
}
/* Call parse_known_args_internal - which does all the work
@ -1324,7 +1723,7 @@ public:
}
/* Indexing operator. Return a reference to an Argument object
* Used in conjuction with Argument.operator== e.g., parser["foo"] == true
* Used in conjunction with Argument.operator== e.g., parser["foo"] == true
* @throws std::logic_error in case of an invalid argument name
*/
Argument &operator[](std::string_view arg_name) const {
@ -1385,12 +1784,20 @@ public:
stream << argument;
}
if (!parser.m_subparser_map.empty()) {
bool has_visible_subcommands = std::any_of(
parser.m_subparser_map.begin(), parser.m_subparser_map.end(),
[](auto &p) { return !p.second->get().m_suppress; });
if (has_visible_subcommands) {
stream << (parser.m_positional_arguments.empty()
? (parser.m_optional_arguments.empty() ? "" : "\n")
: "\n")
<< "Subcommands:\n";
for (const auto &[command, subparser] : parser.m_subparser_map) {
if (subparser->get().m_suppress) {
continue;
}
stream << std::setw(2) << " ";
stream << std::setw(static_cast<int>(longest_arg_length - 2))
<< command;
@ -1435,7 +1842,11 @@ public:
if (!m_subparser_map.empty()) {
stream << " {";
std::size_t i{0};
for (const auto &[command, unused] : m_subparser_map) {
for (const auto &[command, subparser] : m_subparser_map) {
if (subparser->get().m_suppress) {
continue;
}
if (i == 0) {
stream << command;
} else {
@ -1465,6 +1876,8 @@ public:
m_subparser_used.insert_or_assign(parser.m_program_name, false);
}
void set_suppress(bool suppress) { m_suppress = suppress; }
private:
bool is_valid_prefix_char(char c) const {
return m_prefix_chars.find(c) != std::string::npos;
@ -1569,8 +1982,41 @@ private:
unprocessed_arguments);
}
throw std::runtime_error(
"Maximum number of positional arguments exceeded");
if (m_positional_arguments.empty()) {
// Ask the user if they argument they provided was a typo
// for some sub-parser,
// e.g., user provided `git totes` instead of `git notes`
if (!m_subparser_map.empty()) {
throw std::runtime_error(
"Failed to parse '" + current_argument + "', did you mean '" +
std::string{details::get_most_similar_string(
m_subparser_map, current_argument)} +
"'");
}
// Ask the user if they meant to use a specific optional argument
if (!m_optional_arguments.empty()) {
for (const auto &opt : m_optional_arguments) {
if (!opt.m_implicit_value.has_value()) {
// not a flag, requires a value
if (!opt.m_is_used) {
throw std::runtime_error(
"Zero positional arguments expected, did you mean " +
opt.get_usage_full());
}
}
}
throw std::runtime_error("Zero positional arguments expected");
} else {
throw std::runtime_error("Zero positional arguments expected");
}
} else {
throw std::runtime_error("Maximum number of positional arguments "
"exceeded, failed to parse '" +
current_argument + "'");
}
}
auto argument = positional_argument_it++;
it = argument->consume(it, end);
@ -1689,7 +2135,8 @@ private:
}
std::size_t max_size = 0;
for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) {
max_size = std::max<std::size_t>(max_size, argument->get_arguments_length());
max_size =
std::max<std::size_t>(max_size, argument->get_arguments_length());
}
for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) {
max_size = std::max<std::size_t>(max_size, command.size());
@ -1698,6 +2145,7 @@ private:
}
using argument_it = std::list<Argument>::iterator;
using mutex_group_it = std::vector<MutuallyExclusiveGroup>::iterator;
using argument_parser_it =
std::list<std::reference_wrapper<ArgumentParser>>::iterator;
@ -1722,6 +2170,8 @@ private:
std::list<std::reference_wrapper<ArgumentParser>> m_subparsers;
std::map<std::string_view, argument_parser_it> m_subparser_map;
std::map<std::string_view, bool> m_subparser_used;
std::vector<MutuallyExclusiveGroup> m_mutually_exclusive_groups;
bool m_suppress = false;
};
} // namespace argparse

View File

@ -0,0 +1,56 @@
/*
__ _ _ __ __ _ _ __ __ _ _ __ ___ ___
/ _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++
| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse
\__,_|_| \__, | .__/ \__,_|_| |___/\___|
|___/|_|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
SPDX-License-Identifier: MIT
Copyright (c) 2019-2022 Pranav Srinivas Kumar <pranav.srinivas.kumar@gmail.com>
and other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
module;
#ifndef ARGPARSE_MODULE_USE_STD_MODULE
#include <argparse/argparse.hpp>
#endif
export module argparse;
#ifdef ARGPARSE_MODULE_USE_STD_MODULE
import std;
extern "C++" {
#include <argparse/argparse.hpp>
}
#endif
export namespace argparse {
using argparse::nargs_pattern;
using argparse::default_arguments;
using argparse::operator&;
using argparse::Argument;
using argparse::ArgumentParser;
}

View File

@ -26,6 +26,7 @@ function(add_sample NAME)
ADD_EXECUTABLE(ARGPARSE_SAMPLE_${NAME} ${NAME}.cpp)
INCLUDE_DIRECTORIES("../include" ".")
set_target_properties(ARGPARSE_SAMPLE_${NAME} PROPERTIES OUTPUT_NAME ${NAME})
set_property(TARGET ARGPARSE_SAMPLE_${NAME} PROPERTY CXX_STANDARD 17)
endfunction()
add_sample(positional_argument)

View File

@ -5,9 +5,9 @@
int main(int argc, char *argv[]) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-b").default_value(false).implicit_value(true);
program.add_argument("-b").flag();
program.add_argument("-c")
.nargs(2)
@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -13,7 +13,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -13,7 +13,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -8,7 +8,7 @@ int main(int argc, char *argv[]) {
program.add_argument("--member")
.help("The alias for the member to pass to.")
.metavar("ALIAS");
program.add_argument("--verbose").default_value(false).implicit_value(true);
program.add_argument("--verbose").flag();
program.add_description("Forward a thing to the next member.");
program.add_epilog("Possible things include betingalw, chiz, and res.");

View File

@ -9,7 +9,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -13,7 +13,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv); // Example: ./main --color orange
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -13,7 +13,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(
argc, argv); // Example: ./main --color red --color green --color blue
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -13,7 +13,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(
argc, argv); // Example: ./main --input_files config.yml System.xml
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -14,7 +14,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -12,7 +12,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -9,11 +9,11 @@ int main(int argc, char *argv[]) {
.help("display the square of a given number")
.scan<'i', int>();
program.add_argument("--verbose").default_value(false).implicit_value(true);
program.add_argument("--verbose").flag();
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -11,7 +11,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -57,7 +57,7 @@ int main(int argc, char *argv[]) {
try {
program.parse_args(argc, argv);
} catch (const std::runtime_error &err) {
} catch (const std::exception &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
return 1;

View File

@ -10,7 +10,7 @@ if(MSVC)
endif()
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
# Update if necessary
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wpedantic -Wsign-conversion -Wshadow -Wconversion -Werror -Wextra")
endif()
if(NOT CMAKE_BUILD_TYPE)
@ -29,16 +29,18 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_append.cpp
test_as_container.cpp
test_bool_operator.cpp
test_choices.cpp
test_compound_arguments.cpp
test_container_arguments.cpp
test_const_correct.cpp
test_default_args.cpp
test_default_value.cpp
test_error_reporting.cpp
test_get.cpp
test_help.cpp
test_invalid_arguments.cpp
test_is_used.cpp
test_issue_37.cpp
test_mutually_exclusive_group.cpp
test_negative_numbers.cpp
test_optional_arguments.cpp
test_parent_parsers.cpp
@ -47,7 +49,7 @@ file(GLOB ARGPARSE_TEST_SOURCES
test_repr.cpp
test_required_arguments.cpp
test_scan.cpp
test_value_semantics.cpp
test_stringstream.cpp
test_version.cpp
test_subparsers.cpp
test_parse_known_args.cpp

View File

@ -0,0 +1,10 @@
module;
#include <argparse/argparse.hpp>
export module argparse.details;
export namespace argparse::details {
using argparse::details::repr;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,6 +1,13 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Simplest .append" * test_suite("append")) {

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
@ -24,8 +28,8 @@ TEST_CASE("Get argument with .at()" * test_suite("as_container")) {
SUBCASE("with unknown argument") {
program.parse_args({"test"});
REQUIRE_THROWS_WITH_AS(program.at("--folder"),
"No such argument: --folder", std::logic_error);
REQUIRE_THROWS_WITH_AS(program.at("--folder"), "No such argument: --folder",
std::logic_error);
}
}
@ -40,7 +44,8 @@ TEST_CASE("Get subparser with .at()" * test_suite("as_container")) {
SUBCASE("and its argument") {
program.parse_args({"test", "walk", "4km/h"});
REQUIRE(&(program.at<argparse::ArgumentParser>("walk")) == &walk_cmd);
REQUIRE(&(program.at<argparse::ArgumentParser>("walk").at("speed")) == &speed);
REQUIRE(&(program.at<argparse::ArgumentParser>("walk").at("speed")) ==
&speed);
REQUIRE(program.at<argparse::ArgumentParser>("walk").is_used("speed"));
}

View File

@ -1,10 +1,14 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
TEST_CASE("ArgumentParser in bool context" *
test_suite("argument_parser")) {
TEST_CASE("ArgumentParser in bool context" * test_suite("argument_parser")) {
argparse::ArgumentParser program("test");
program.add_argument("cases").remaining();
@ -34,7 +38,7 @@ TEST_CASE("With subparsers in bool context" * test_suite("argument_parser")) {
}
TEST_CASE("Parsers remain false with unknown arguments" *
test_suite("argument_parser")) {
test_suite("argument_parser")) {
argparse::ArgumentParser program("test");
argparse::ArgumentParser cmd_build("build");
@ -54,7 +58,7 @@ TEST_CASE("Parsers remain false with unknown arguments" *
}
TEST_CASE("Multi-level parsers match subparser bool" *
test_suite("argument_parser")) {
test_suite("argument_parser")) {
argparse::ArgumentParser program("test");
argparse::ArgumentParser cmd_cook("cook");

View File

@ -0,0 +1,91 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
TEST_CASE("Parse argument that is provided zero choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
REQUIRE_THROWS_WITH_AS(program.add_argument("color").choices(),
"Zero choices provided", std::runtime_error);
}
TEST_CASE("Parse argument that is in the fixed number of allowed choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("color").choices("red", "green", "blue");
program.parse_args({"test", "red"});
}
TEST_CASE("Parse argument that is in the fixed number of allowed choices, with "
"invalid default" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("color").default_value("yellow").choices("red", "green",
"blue");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test"}),
"Invalid default value \"yellow\" - allowed options: {red, green, blue}",
std::runtime_error);
}
TEST_CASE("Parse invalid argument that is not in the fixed number of allowed "
"choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("color").choices("red", "green", "blue");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "red2"}),
"Invalid argument \"red2\" - allowed options: {red, green, blue}",
std::runtime_error);
}
TEST_CASE(
"Parse multiple arguments that are in the fixed number of allowed choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("color").nargs(2).choices("red", "green", "blue");
program.parse_args({"test", "red", "green"});
}
TEST_CASE("Parse multiple arguments one of which is not in the fixed number of "
"allowed choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("color").nargs(2).choices("red", "green", "blue");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "red", "green2"}),
"Invalid argument \"green2\" - allowed options: {red, green, blue}",
std::runtime_error);
}
TEST_CASE("Parse multiple arguments that are in the fixed number of allowed "
"INTEGER choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5);
program.parse_args({"test", "1", "2"});
}
TEST_CASE("Parse multiple arguments that are not in fixed number of allowed "
"INTEGER choices" *
test_suite("choices")) {
argparse::ArgumentParser program("test");
program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5);
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "6", "7"}),
"Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}",
std::runtime_error);
}

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <test_utility.hpp>
@ -7,11 +11,11 @@ using doctest::test_suite;
TEST_CASE("Parse compound toggle arguments with implicit values" *
test_suite("compound_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-u").default_value(false).implicit_value(true);
program.add_argument("-u").flag();
program.add_argument("-x").default_value(false).implicit_value(true);
program.add_argument("-x").flag();
program.parse_args({"./test.exe", "-aux"});
REQUIRE(program.get<bool>("-a") == true);
@ -22,9 +26,9 @@ TEST_CASE("Parse compound toggle arguments with implicit values" *
TEST_CASE("Parse compound toggle arguments with implicit values and nargs" *
test_suite("compound_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-b").default_value(false).implicit_value(true);
program.add_argument("-b").flag();
program.add_argument("-c").nargs(2).scan<'g', float>();
@ -52,9 +56,9 @@ TEST_CASE("Parse compound toggle arguments with implicit values and nargs and "
program.add_argument("numbers").nargs(3).scan<'i', int>();
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-b").default_value(false).implicit_value(true);
program.add_argument("-b").flag();
program.add_argument("-c").nargs(2).scan<'g', float>();
@ -69,9 +73,9 @@ TEST_CASE("Parse out-of-order compound arguments" *
test_suite("compound_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-b").default_value(false).implicit_value(true);
program.add_argument("-b").flag();
program.add_argument("-c").nargs(2).scan<'g', float>();
@ -89,9 +93,9 @@ TEST_CASE("Parse out-of-order compound arguments. Second variation" *
test_suite("compound_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-b").default_value(false).implicit_value(true);
program.add_argument("-b").flag();
program.add_argument("-c")
.nargs(2)

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <test_utility.hpp>

View File

@ -1,5 +1,11 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <iostream>
#include <sstream>
#include <streambuf>
@ -24,7 +30,7 @@ TEST_CASE("Do not exit on default arguments" * test_suite("default_args")) {
argparse::ArgumentParser parser("test", "1.0",
argparse::default_arguments::all, false);
std::stringstream buf;
std::streambuf* saved_cout_buf = std::cout.rdbuf(buf.rdbuf());
std::streambuf *saved_cout_buf = std::cout.rdbuf(buf.rdbuf());
parser.parse_args({"test", "--help"});
std::cout.rdbuf(saved_cout_buf);
REQUIRE(parser.is_used("--help"));

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <string>
@ -19,3 +23,61 @@ TEST_CASE("Use a 'string' default value" * test_suite("default_value")) {
REQUIRE(program.get("--arg") == std::string("string object"));
}
}
TEST_CASE("Use a default value with flag arguments" *
test_suite("default_value")) {
argparse::ArgumentParser program("test");
program.add_argument("-inc_chr", "--include_chromes")
.help(std::string{"only process the anchor whose one of the end is "
"contained on the specified "
"chromatin, used ',' to split."})
.default_value("all");
program.add_argument("-l").flag();
program.add_argument("-o").flag();
program.add_argument("filename");
SUBCASE("Leading optional argument with default_value") {
REQUIRE_NOTHROW(program.parse_args({"test", "-inc_chr", "-lo", "my.log"}));
REQUIRE(program.get("-inc_chr") == std::string{"all"});
}
SUBCASE("Trailing optional argument with default_value") {
REQUIRE_NOTHROW(program.parse_args({"test", "-lo", "my.log", "-inc_chr"}));
REQUIRE(program.get("-inc_chr") == std::string{"all"});
}
}
TEST_CASE("Position of the argument with default value") {
argparse::ArgumentParser program("test");
program.add_argument("-g").default_value("the_default_value");
program.add_argument("-s");
SUBCASE("Arg with default value not passed") {
REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"}));
REQUIRE(program.get("-g") == std::string("the_default_value"));
REQUIRE(program.get("-s") == std::string("./src"));
}
SUBCASE("Arg with default value passed last") {
REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src", "-g"}));
REQUIRE(program.get("-g") == std::string("the_default_value"));
REQUIRE(program.get("-s") == std::string("./src"));
}
SUBCASE("Arg with default value passed before last") {
REQUIRE_NOTHROW(program.parse_args({"test", "-g", "-s", "./src"}));
REQUIRE(program.get("-g") == std::string("the_default_value"));
REQUIRE(program.get("-s") == std::string("./src"));
}
SUBCASE("Arg with default value replaces the value if given") {
REQUIRE_NOTHROW(
program.parse_args({"test", "-g", "a_different_value", "-s", "./src"}));
REQUIRE(program.get("-g") == std::string("a_different_value"));
REQUIRE(program.get("-s") == std::string("./src"));
}
}

View File

@ -1,6 +1,14 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <iostream>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Basic --value=value" * test_suite("equals_form")) {
@ -36,4 +44,4 @@ TEST_CASE("Basic --value=value with nargs(2)" * test_suite("equals_form")) {
parser.parse_args({"test", "--long=value1", "value2"});
REQUIRE((parser.get<std::vector<std::string>>("--long") ==
std::vector<std::string>{"value1", "value2"}));
}
}

View File

@ -0,0 +1,124 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <iostream>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Missing optional argument name" * test_suite("error_reporting")) {
argparse::ArgumentParser parser("test");
parser.add_argument("-a");
parser.add_argument("-b");
SUBCASE("Good case") {
REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2"}));
}
SUBCASE("Bad case") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-a", "1", "2"}),
"Zero positional arguments expected, did you mean -b VAR",
std::runtime_error);
}
SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "1", "2"}),
"Zero positional arguments expected, did you mean -a VAR",
std::runtime_error);
}
}
TEST_CASE("Missing optional argument name (some flag arguments)" *
test_suite("error_reporting")) {
argparse::ArgumentParser parser("test");
parser.add_argument("-a").flag();
parser.add_argument("-b").flag();
parser.add_argument("-c");
parser.add_argument("-d");
SUBCASE("Good case") {
REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "-b", "-c", "2"}));
}
SUBCASE("Bad case") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-a", "-b", "2"}),
"Zero positional arguments expected, did you mean -c VAR",
std::runtime_error);
}
SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-abc", "1", "2"}),
"Zero positional arguments expected, did you mean -d VAR",
std::runtime_error);
}
}
TEST_CASE("Missing optional argument name (multiple names)" *
test_suite("error_reporting")) {
argparse::ArgumentParser parser("test");
parser.add_argument("-a", "--number-of-apples");
parser.add_argument("-b");
SUBCASE("Bad case 2") {
REQUIRE_THROWS_WITH_AS(parser.parse_args({"test", "1", "2"}),
"Zero positional arguments expected, did you mean "
"-a/--number-of-apples VAR",
std::runtime_error);
}
}
TEST_CASE("Missing optional argument name with other positional arguments" *
test_suite("error_reporting")) {
argparse::ArgumentParser parser("test");
parser.add_argument("-a");
parser.add_argument("-b");
parser.add_argument("c");
SUBCASE("Good case") {
REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2", "3"}));
}
SUBCASE("Bad case") {
REQUIRE_THROWS_WITH_AS(
parser.parse_args({"test", "-a", "1", "2", "3", "4"}),
"Maximum number of positional arguments exceeded, failed to parse '3'",
std::runtime_error);
}
}
TEST_CASE("Detect unknown subcommand" * test_suite("error_reporting")) {
argparse::ArgumentParser program("git");
argparse::ArgumentParser log_command("log");
argparse::ArgumentParser notes_command("notes");
argparse::ArgumentParser add_command("add");
program.add_subparser(log_command);
program.add_subparser(notes_command);
program.add_subparser(add_command);
SUBCASE("Typo for 'notes'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "tote"}),
"Failed to parse 'tote', did you mean 'notes'",
std::runtime_error);
}
SUBCASE("Typo for 'add'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "bad"}),
"Failed to parse 'bad', did you mean 'add'",
std::runtime_error);
}
SUBCASE("Typo for 'log'") {
REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "logic"}),
"Failed to parse 'logic', did you mean 'log'",
std::runtime_error);
}
}

View File

@ -1,6 +1,12 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <any>
using doctest::test_suite;
TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) {
@ -36,7 +42,7 @@ TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) {
TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) {
argparse::ArgumentParser program("test");
program.add_argument("-s", "--stuff"); // as default type, a std::string
program.add_argument("-s", "--stuff"); // as default type, a std::string
REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"}));
REQUIRE_THROWS_AS(program.get<int>("--stuff"), std::bad_any_cast);
}

View File

@ -1,5 +1,11 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <optional>
#include <sstream>
using doctest::test_suite;
@ -76,39 +82,37 @@ TEST_CASE("Users can replace default -h/--help" * test_suite("help")) {
TEST_CASE("Multiline help message alignment") {
// '#' is used at the beginning of each help message line to simplify testing.
// It is important to ensure that this character doesn't appear elsewhere in the test case.
// Default arguments (e.g., -h/--help, -v/--version) are not included in this test.
// It is important to ensure that this character doesn't appear elsewhere in
// the test case. Default arguments (e.g., -h/--help, -v/--version) are not
// included in this test.
argparse::ArgumentParser program("program");
program.add_argument("INPUT1")
.help(
"#This is the first line of help message.\n"
"#And this is the second line of help message."
);
program.add_argument("program_input2")
.help("#There is only one line.");
program.add_argument("INPUT1").help(
"#This is the first line of help message.\n"
"#And this is the second line of help message.");
program.add_argument("program_input2").help("#There is only one line.");
program.add_argument("-p", "--prog_input3")
.help(
R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
#Sed ut perspiciatis unde omnis iste natus error sit voluptatem
#accusantium doloremque laudantium, totam rem aperiam...)"
);
program.add_argument("--verbose").default_value(false).implicit_value(true);
#accusantium doloremque laudantium, totam rem aperiam...)");
program.add_argument("--verbose").flag();
std::ostringstream stream;
stream << program;
std::istringstream iss(stream.str());
int help_message_start = -1;
auto help_message_start = std::string::npos;
std::string line;
while (std::getline(iss, line)) {
// Find the position of '#', which indicates the start of the help message line
// Find the position of '#', which indicates the start of the help message
// line
auto pos = line.find('#');
if (pos == std::string::npos) {
continue;
}
if (help_message_start == -1) {
if (help_message_start == std::string::npos) {
help_message_start = pos;
} else {
REQUIRE(pos == help_message_start);

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -0,0 +1,85 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
TEST_CASE("Create mutually exclusive group with 2 arguments" *
test_suite("mutex_args")) {
argparse::ArgumentParser program("test");
auto &group = program.add_mutually_exclusive_group();
group.add_argument("--first");
group.add_argument("--second");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "--first", "1", "--second", "2"}),
"Argument '--second VAR' not allowed with '--first VAR'",
std::runtime_error);
}
TEST_CASE(
"Create mutually exclusive group with 2 arguments with required flag" *
test_suite("mutex_args")) {
argparse::ArgumentParser program("test");
auto &group = program.add_mutually_exclusive_group(true);
group.add_argument("--first");
group.add_argument("--second");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test"}),
"One of the arguments '--first VAR' or '--second VAR' is required",
std::runtime_error);
}
TEST_CASE(
"Create mutually exclusive group with 3 arguments with required flag" *
test_suite("mutex_args")) {
argparse::ArgumentParser program("test");
auto &group = program.add_mutually_exclusive_group(true);
group.add_argument("--first");
group.add_argument("--second");
group.add_argument("--third");
REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}),
"One of the arguments '--first VAR' or '--second VAR' "
"or '--third VAR' is required",
std::runtime_error);
}
TEST_CASE("Create mutually exclusive group with 3 arguments" *
test_suite("mutex_args")) {
argparse::ArgumentParser program("test");
auto &group = program.add_mutually_exclusive_group();
group.add_argument("--first");
group.add_argument("--second");
group.add_argument("--third");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "--first", "1", "--third", "2"}),
"Argument '--third VAR' not allowed with '--first VAR'",
std::runtime_error);
}
TEST_CASE("Create two mutually exclusive groups" * test_suite("mutex_args")) {
argparse::ArgumentParser program("test");
auto &group_1 = program.add_mutually_exclusive_group();
group_1.add_argument("--first");
group_1.add_argument("--second");
group_1.add_argument("--third");
auto &group_2 = program.add_mutually_exclusive_group();
group_2.add_argument("-a");
group_2.add_argument("-b");
REQUIRE_THROWS_WITH_AS(
program.parse_args({"test", "--first", "1", "-a", "2", "-b", "3"}),
"Argument '-b VAR' not allowed with '-a VAR'", std::runtime_error);
}

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;
@ -26,7 +30,7 @@ TEST_CASE("Argument '-' is not an optional argument" *
TEST_CASE("Argument '-' is not an optional argument but '-l' is" *
test_suite("optional_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-l").default_value(false).implicit_value(true);
program.add_argument("-l").flag();
program.add_argument("input");
program.parse_args({"./test.exe", "-l", "-"});
REQUIRE(program.get<bool>("-l") == true);
@ -36,7 +40,7 @@ TEST_CASE("Argument '-' is not an optional argument but '-l' is" *
TEST_CASE("Argument '-l' is an optional argument but '-' is not" *
test_suite("optional_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-l").default_value(false).implicit_value(true);
program.add_argument("-l").flag();
program.add_argument("input");
program.parse_args({"./test.exe", "-", "-l"});
REQUIRE(program.get<bool>("-l") == true);
@ -46,7 +50,7 @@ TEST_CASE("Argument '-l' is an optional argument but '-' is not" *
TEST_CASE("Parse toggle arguments with implicit value" *
test_suite("optional_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("--verbose").default_value(false).implicit_value(true);
program.add_argument("--verbose").flag();
program.parse_args({"./test.exe", "--verbose"});
REQUIRE(program.get<bool>("--verbose") == true);
@ -57,11 +61,11 @@ TEST_CASE("Parse toggle arguments with implicit value" *
TEST_CASE("Parse multiple toggle arguments with implicit values" *
test_suite("optional_arguments")) {
argparse::ArgumentParser program("test");
program.add_argument("-a").default_value(false).implicit_value(true);
program.add_argument("-a").flag();
program.add_argument("-u").default_value(false).implicit_value(true);
program.add_argument("-u").flag();
program.add_argument("-x").default_value(false).implicit_value(true);
program.add_argument("-x").flag();
program.parse_args({"./test.exe", "-a", "-x"});
REQUIRE(program.get<bool>("-a") == true);

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,6 +1,12 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <optional>
using doctest::test_suite;
TEST_CASE("Missing argument" * test_suite("parse_args")) {

View File

@ -1,6 +1,13 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Parse unknown optional and positional arguments without exceptions" *
@ -79,4 +86,4 @@ TEST_CASE("Parse unknown optional and positional arguments in subparsers "
REQUIRE((unknown_args == std::vector<std::string>{"--verbose", "FOO", "5",
"BAR", "-jn", "spam"}));
}
}
}

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <cmath>
#include <doctest.hpp>

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <cmath>
#include <doctest.hpp>
@ -19,8 +23,8 @@ TEST_CASE("Parse with custom Windows-style prefix chars" *
argparse::ArgumentParser program("dir");
program.set_prefix_chars("/");
program.add_argument("/A").nargs(1);
program.add_argument("/B").default_value(false).implicit_value(true);
program.add_argument("/C").default_value(false).implicit_value(true);
program.add_argument("/B").flag();
program.add_argument("/C").flag();
program.parse_args({"dir", "/A", "D", "/B", "/C"});
REQUIRE(program.get("/A") == "D");
REQUIRE(program.get<bool>("/B") == true);
@ -33,7 +37,7 @@ TEST_CASE("Parse with custom Windows-style prefix chars and assign chars" *
program.set_assign_chars(":=");
program.add_argument("/A").nargs(1);
program.add_argument("/B").nargs(1);
program.add_argument("/C").default_value(false).implicit_value(true);
program.add_argument("/C").flag();
program.parse_args({"dir", "/A:D", "/B=Boo", "/C"});
REQUIRE(program.get("/A") == "D");
REQUIRE(program.get("/B") == "Boo");

View File

@ -1,6 +1,14 @@
#ifdef WITH_MODULE
import argparse;
import argparse.details;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <list>
#include <set>
#include <sstream>
using doctest::test_suite;

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
using doctest::test_suite;

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <stdint.h>
@ -108,6 +112,39 @@ TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"),
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}),
std::range_error);
}
SUBCASE("with hex digit without prefix") {
program.parse_args({"test", "-n", "1a"});
REQUIRE(program.get<T>("-n") == 0x1a);
}
SUBCASE("minus sign without prefix produces an optional argument") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-1"}),
std::invalid_argument);
}
SUBCASE("plus sign without prefix is not allowed") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+1a"}),
std::invalid_argument);
}
SUBCASE("without prefix does not fit") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "FFFFFFFFFFFFFFFF1"}),
std::range_error);
}
}
TEST_CASE("Parse multiple hex numbers without prefix" * test_suite("scan")) {
argparse::ArgumentParser program("test");
program.add_argument("-x", "--hex")
.help("bytes in hex separated by spaces")
.nargs(1, std::numeric_limits<std::size_t>::max())
.scan<'x', uint8_t>();
REQUIRE_NOTHROW(
program.parse_args({"test", "-x", "f2", "b2", "10", "80", "64"}));
const auto &input_bytes = program.get<std::vector<uint8_t>>("-x");
REQUIRE((input_bytes == std::vector<uint8_t>{0xf2, 0xb2, 0x10, 0x80, 0x64}));
}
TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),
@ -172,6 +209,49 @@ TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"),
}
}
TEST_CASE_TEMPLATE("Parse a binary argument" * test_suite("scan"), T, uint8_t,
uint16_t, uint32_t, uint64_t) {
argparse::ArgumentParser program("test");
program.add_argument("-n").scan<'b', T>();
SUBCASE("with binary digit") {
program.parse_args({"test", "-n", "0b101"});
REQUIRE(program.get<T>("-n") == 0b101);
}
SUBCASE("minus sign produces an optional argument") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0b101"}),
std::runtime_error);
}
SUBCASE("plus sign is not allowed") {
REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0b101"}),
std::invalid_argument);
}
SUBCASE("does not fit") {
REQUIRE_THROWS_AS(
program.parse_args(
{"test", "-n",
"0b111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"11111111111111111111111111111111111111111111111111111111111111111"
"1111111111111111111111111111111111111111111111111111111111111111"
"1"}),
std::range_error);
}
}
#define FLOAT_G(t, literal) \
([] { \
if constexpr (std::is_same_v<t, float>) \

View File

@ -0,0 +1,20 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <cmath>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Get Version String" * test_suite("stringstream")) {
std::stringstream os;
argparse::ArgumentParser program("test", "1.0",
argparse::default_arguments::all, false, os);
program.parse_args({"test", "--version"});
REQUIRE(os.str() == "1.0\n");
}

View File

@ -1,7 +1,14 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#include <cmath>
#endif
#include <doctest.hpp>
#include <cmath>
#include <string>
#include <vector>
using doctest::test_suite;
TEST_CASE("Add subparsers" * test_suite("subparsers")) {
@ -54,7 +61,7 @@ TEST_CASE("Parse subparser command" * test_suite("subparsers")) {
TEST_CASE("Parse subparser command with optional argument" *
test_suite("subparsers")) {
argparse::ArgumentParser program("test");
program.add_argument("--verbose").default_value(false).implicit_value(true);
program.add_argument("--verbose").flag();
argparse::ArgumentParser command_1("add");
command_1.add_argument("file");
@ -86,7 +93,7 @@ TEST_CASE("Parse subparser command with parent parser" *
argparse::ArgumentParser program("test");
argparse::ArgumentParser parent("parent");
parent.add_argument("--verbose").default_value(false).implicit_value(true);
parent.add_argument("--verbose").flag();
program.add_parents(parent);
argparse::ArgumentParser command_1("add");
@ -121,7 +128,7 @@ TEST_CASE("Parse git commands" * test_suite("subparsers")) {
add_command.add_argument("files").remaining();
argparse::ArgumentParser commit_command("commit");
commit_command.add_argument("-a").default_value(false).implicit_value(true);
commit_command.add_argument("-a").flag();
commit_command.add_argument("-m");
@ -206,8 +213,8 @@ TEST_CASE("Check is_subcommand_used after parse" * test_suite("subparsers")) {
argparse::ArgumentParser command_2("clean");
command_2.add_argument("--fullclean")
.default_value(false)
.implicit_value(true);
.default_value(false)
.implicit_value(true);
argparse::ArgumentParser program("test");
program.add_subparser(command_1);
@ -236,4 +243,40 @@ TEST_CASE("Check is_subcommand_used after parse" * test_suite("subparsers")) {
REQUIRE(program.is_subcommand_used("clean") == false);
REQUIRE(program.is_subcommand_used(command_2) == false);
}
}
}
static bool contains(const std::string &haystack, const std::string &needle) {
return haystack.find(needle) != std::string::npos;
}
TEST_CASE("Check set_suppress" * test_suite("subparsers")) {
argparse::ArgumentParser command("cmd");
command.add_argument("arg").remaining();
argparse::ArgumentParser program("test");
program.add_subparser(command);
SUBCASE("help message contain info if subcommand not suppressed") {
command.set_suppress(false);
REQUIRE(contains(program.help().str(), "Subcommands") == true);
REQUIRE(contains(program.help().str(), "cmd") == true);
}
SUBCASE("help message does not contain info if subcommand suppressed") {
command.set_suppress(true);
REQUIRE(contains(program.help().str(), "Subcommands") == false);
REQUIRE(contains(program.help().str(), "cmd") == false);
}
SUBCASE("help message contain info if not all subcommands suppressed") {
argparse::ArgumentParser command_2("command_2");
program.add_subparser(command_2);
command.set_suppress(true);
command_2.set_suppress(false);
REQUIRE(contains(program.help().str(), "Subcommands") == true);
REQUIRE(contains(program.help().str(), "cmd") == false);
REQUIRE(contains(program.help().str(), "command_2") == true);
}
}

View File

@ -1,6 +1,8 @@
#ifndef ARGPARSE_TEST_UTILITY_HPP
#define ARGPARSE_TEST_UTILITY_HPP
#include <list>
namespace testutility {
// Get value at index from std::list
template <typename T>

View File

@ -1,4 +1,8 @@
#ifdef WITH_MODULE
import argparse;
#else
#include <argparse/argparse.hpp>
#endif
#include <doctest.hpp>
#include <sstream>

89
thirdparty/argparse/xmake.lua vendored Normal file
View File

@ -0,0 +1,89 @@
set_xmakever("2.8.2")
set_project("argparse")
set_version("3.0.0", { build = "%Y%m%d%H%M" })
option("enable_module")
option("enable_std_import", { defines = "ARGPARSE_MODULE_USE_STD_MODULE" })
option("enable_tests")
option("enable_samples")
add_cxxflags(
"-Wall",
"-Wno-long-long",
"-pedantic",
"-Wsign-conversion",
"-Wshadow",
"-Wconversion",
{ toolsets = { "clang", "gcc" } }
)
add_cxxflags("cl::/W4")
if is_plat("windows") then
add_defines("_CRT_SECURE_NO_WARNINGS")
end
target("argparse", function()
set_languages("c++17")
set_kind("headeronly")
if get_config("enable_module") then
set_languages("c++20")
set_kind("static") -- static atm because of a XMake bug, headeronly doesn't generate package module metadata
end
add_options("enable_std_import")
add_includedirs("include", { public = true })
add_headerfiles("include/argparse/argparse.hpp")
if get_config("enable_module") then
add_files("module/argparse.cppm", { install = true })
end
end)
if get_config("enable_tests") then
target("argparse_tests", function()
set_kind("binary")
set_languages("c++17")
set_basename("tests")
add_includedirs("test")
add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } })
add_files("test/**.cpp")
add_deps("argparse")
end)
if get_config("enable_module") then
target("argparse_module_tests", function()
set_kind("binary")
set_languages("c++20")
set_basename("module_tests")
add_defines("WITH_MODULE")
add_includedirs("test")
add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } })
add_files("test/**.cpp")
add_files("test/argparse_details.cppm")
add_deps("argparse")
end)
end
end
if get_config("enable_samples") then
for _, sample_file in ipairs(os.files("samples/*.cpp")) do
target(path.basename(sample_file), function()
set_kind("binary")
set_languages("c++17")
add_files(sample_file)
set_policy("build.c++.modules", false)
add_deps("argparse")
end)
end
end