argparse update
This commit is contained in:
parent
4a9994931a
commit
de2f5f6547
|
@ -0,0 +1 @@
|
|||
indent_type = "Spaces"
|
|
@ -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::
|
||||
|
|
|
@ -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 isn’t 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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
#include <test_utility.hpp>
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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"}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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"}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <cmath>
|
||||
#include <doctest.hpp>
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
|
||||
using doctest::test_suite;
|
||||
|
|
|
@ -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>) \
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
#ifdef WITH_MODULE
|
||||
import argparse;
|
||||
#else
|
||||
#include <argparse/argparse.hpp>
|
||||
#endif
|
||||
#include <doctest.hpp>
|
||||
#include <sstream>
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue