mirror of
https://github.com/sametersoylu/argument-parser.git
synced 2026-05-28 20:08:10 +00:00
Compare commits
8 Commits
fc847c4cd1
...
v3
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c63622fd8 | |||
| 1479892e7b | |||
| 708f63a00d | |||
| faf1715ee3 | |||
| 05c2f782b1 | |||
| 7caeb20dfa | |||
| e1d72aaea7 | |||
| 81a85149b4 |
221
README.md
221
README.md
@@ -1,17 +1,21 @@
|
|||||||
# argument-parser
|
# argument-parser
|
||||||
|
|
||||||
A lightweight, modern, expressively typed, and highly customizable C++17 argument parser library.
|
A lightweight, modern, and highly customizable C++17 argument parser with native platform argument collection, trait-driven typed parsing, pluggable option conventions, and a fluent `v2` builder API.
|
||||||
|
|
||||||
|
> `v1` is deprecated and mainly kept as implementation history. For new projects, use `argument_parser::v2` together with `argument_parser::builder`.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Type-safe Argument Extraction**: Use type traits to automatically parse fundamental types and custom structures (e.g. `std::vector<int>`, `std::regex`, `Point`).
|
- Native platform parser alias: `argument_parser::v2::parser` resolves to the current platform parser and reads arguments directly from OS APIs.
|
||||||
- **Support for Multiple Parsing Conventions**: Pluggable convention system out of the box, offering GNU-style (`-a`, `--arg`), GNU-equal-style (`--arg=value`), Windows-style (`/arg`), and Windows-equal-style (`/arg:value`).
|
- Fluent builder API with compile-time builder constraints that prevent invalid combinations after a terminal/mutually exclusive mode has been selected.
|
||||||
- **Automated Help Text Formatting**: Call `parser.display_help(conventions)` to easily generate beautifully formatted usage instructions.
|
- Type-safe parsing and extraction. Just extend `parser_trait<T>` for your types and if just want to store use `get_optional<T>()`!
|
||||||
- **Cross-Platform Native Parsers**: Dedicated parsers that automatically fetch command-line arguments using OS-specific APIs (`windows_parser`, `linux_parser`, `macos_parser`), so you don't need to manually pass `argc` and `argv` on most platforms.
|
- Positional arguments with optional explicit ordering and support for `--` as a positional separator.
|
||||||
- **Fluid setup**: Enjoy fluid setup routines with maps and initializer lists.
|
- Trait-driven `format_hint` and `purpose_hint` metadata used in generated help text and parse errors.
|
||||||
|
- Automatic help flag on `argument_parser::v2::parser` (`-h`, `--help`) with configurable exit behavior.
|
||||||
### Important Note:
|
- Auto-formatted help output..
|
||||||
V1 is deprecated and is mainly kept as a base implementation for the V2. You should use V2 for your projects. If any features are missing compared to V1, please let me know so I can introduce them!
|
- Completion hooks via `parser.on_complete(...)`.
|
||||||
|
- Pluggable conventions for GNU next-token, GNU equal-style, Windows next-token, and Windows inline `=` / `:` parsing, or bring your own!
|
||||||
|
- Testing helper + pseudo command handler `argument_parser::v2::fake_parser`.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -20,104 +24,193 @@ V1 is deprecated and is mainly kept as a base implementation for the V2. You sho
|
|||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. Create your Parser and Define Arguments
|
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
#include <argparse>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <regex>
|
|
||||||
#include <argparse> // Provides the native parser for your compiling platform
|
using argument = argument_parser::builder::argument<>;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
using namespace argument_parser::v2::flags;
|
argument_parser::v2::parser parser(false); // --help prints without exiting immediately
|
||||||
|
|
||||||
// Automatically uses the platform-native parser!
|
|
||||||
// It will fetch arguments directly from OS APIs (e.g., GetCommandLineW on Windows)
|
|
||||||
argument_parser::v2::parser parser;
|
|
||||||
|
|
||||||
// A flag with an action
|
int threshold = 0;
|
||||||
parser.add_argument<std::string>({
|
|
||||||
{ShortArgument, "e"},
|
|
||||||
{LongArgument, "echo"},
|
|
||||||
{Action, argument_parser::helpers::make_parametered_action<std::string>(
|
|
||||||
[](std::string const &text) { std::cout << text << std::endl; }
|
|
||||||
)},
|
|
||||||
{HelpText, "echoes given variable"}
|
|
||||||
});
|
|
||||||
|
|
||||||
// A flag that just stores the value to extract later
|
argument::start()
|
||||||
parser.add_argument<std::regex>({
|
.short_argument("e")
|
||||||
{ShortArgument, "g"},
|
.long_argument("echo")
|
||||||
{LongArgument, "grep"},
|
.action<std::string>([](std::string const& text) {
|
||||||
{HelpText, "Grep pattern"}
|
std::cout << text << '\n';
|
||||||
});
|
})
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
// A required flag
|
argument::start()
|
||||||
parser.add_argument<std::string>({
|
.long_argument("file")
|
||||||
{LongArgument, "file"},
|
.store<std::string>()
|
||||||
{Required, true},
|
.required()
|
||||||
{HelpText, "File to grep"}
|
.help_text("Input file to process.")
|
||||||
});
|
.build(parser);
|
||||||
|
|
||||||
// Run action callback on complete
|
argument::start()
|
||||||
parser.on_complete([](argument_parser::base_parser const &p) {
|
.long_argument("threshold")
|
||||||
auto filename = p.get_optional<std::string>("file");
|
.reference(threshold)
|
||||||
auto pattern = p.get_optional<std::regex>("grep");
|
.build(parser);
|
||||||
|
|
||||||
if (filename && pattern) {
|
argument::start()
|
||||||
std::cout << "Grepping " << filename.value() << " with pattern." << std::endl;
|
.short_argument("v")
|
||||||
|
.long_argument("verbose")
|
||||||
|
.flag()
|
||||||
|
.help_text("Enable verbose output.")
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
argument::start()
|
||||||
|
.positional("output")
|
||||||
|
.position(0)
|
||||||
|
.help_text("Output file.")
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
parser.on_complete([](auto const& state) {
|
||||||
|
if (auto file = state.template get_optional<std::string>("file")) {
|
||||||
|
std::cout << "completed for: " << *file << '\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register Conventions
|
|
||||||
const std::initializer_list<argument_parser::conventions::convention const *const> conventions = {
|
const std::initializer_list<argument_parser::conventions::convention const *const> conventions = {
|
||||||
&argument_parser::conventions::gnu_argument_convention,
|
&argument_parser::conventions::gnu_argument_convention,
|
||||||
&argument_parser::conventions::windows_argument_convention
|
&argument_parser::conventions::gnu_equal_argument_convention,
|
||||||
|
&argument_parser::conventions::windows_argument_convention,
|
||||||
|
&argument_parser::conventions::windows_equal_argument_convention,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute logic!
|
|
||||||
parser.handle_arguments(conventions);
|
parser.handle_arguments(conventions);
|
||||||
|
|
||||||
return 0;
|
if (auto file = parser.get_optional<std::string>("file")) {
|
||||||
|
std::cout << "file: " << *file << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "threshold: " << threshold << '\n';
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Custom Type Parsing
|
## Trait-Driven Parsing and Hints
|
||||||
|
|
||||||
You can natively parse your custom structs, objects, or arrays by specializing `argument_parser::parsing_traits::parser_trait<T>`.
|
Specialize `argument_parser::parsing_traits::parser_trait<T>` to add support for your own types and to describe their expected format.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
#include <macros.h>
|
||||||
|
#include <traits.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
struct Point {
|
struct Point {
|
||||||
int x, y;
|
int x;
|
||||||
|
int y;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <> struct argument_parser::parsing_traits::parser_trait<Point> {
|
template <>
|
||||||
static Point parse(const std::string &input) {
|
struct argument_parser::parsing_traits::parser_trait<Point> {
|
||||||
auto comma_pos = input.find(',');
|
static Point parse(std::string const& input) {
|
||||||
int x = std::stoi(input.substr(0, comma_pos));
|
auto comma = input.find(',');
|
||||||
int y = std::stoi(input.substr(comma_pos + 1));
|
if (comma == std::string::npos) {
|
||||||
return {x, y};
|
throw std::runtime_error("Expected x,y");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
std::stoi(input.substr(0, comma)),
|
||||||
|
std::stoi(input.substr(comma + 1))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Now you can directly use your type:
|
ARGPARSE_TRAIT_FORMAT_HINT = "x,y";
|
||||||
// parser.add_argument<Point>({ {LongArgument, "point"} });
|
ARGPARSE_TRAIT_PURPOSE_HINT = "point coordinates";
|
||||||
// auto point = parser.get_optional<Point>("point");
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the type directly from the builder:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
argument::start()
|
||||||
|
.long_argument("point")
|
||||||
|
.store<Point>()
|
||||||
|
.build(parser);
|
||||||
|
```
|
||||||
|
|
||||||
|
If you omit `help_text()`, `v2` uses the trait hints to generate help such as `Accepts point coordinates in x,y format.` The same hints are also included in type conversion errors.
|
||||||
|
|
||||||
|
## Help Behavior
|
||||||
|
|
||||||
|
`argument_parser::v2::parser` automatically registers `-h` and `--help`.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
argument_parser::v2::parser parser; // help prints and exits
|
||||||
|
argument_parser::v2::parser parser(false); // help prints without immediate exit
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also display help manually:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
parser.display_help(conventions);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Conventions
|
||||||
|
|
||||||
|
- GNU next-token: `-o value`, `--output value`
|
||||||
|
- GNU equal-style: `-o=value`, `--output=value`
|
||||||
|
- Windows next-token: `/output value`
|
||||||
|
- Windows inline value: `/output=value`, `/output:value`
|
||||||
|
|
||||||
|
Mix any of them in the same parser by passing the conventions you want to `handle_arguments()`.
|
||||||
|
|
||||||
|
## Builder Modes
|
||||||
|
|
||||||
|
`argument_parser::builder::argument<>` is a staged builder. `build(parser)` is the terminal call.
|
||||||
|
|
||||||
|
Before `build(...)`, you compose an argument from three kinds of steps:
|
||||||
|
|
||||||
|
- Identifier selection: `short_argument(...)`, `long_argument(...)`, or `positional(...)`
|
||||||
|
- Optional metadata: `position(...)` for positional arguments, `help_text(...)`, and `required(...)`
|
||||||
|
- One mutually exclusive value behavior:
|
||||||
|
- `store<T>()` to parse and retain a value for later `get_optional<T>()`
|
||||||
|
- `flag()` to store a boolean presence flag
|
||||||
|
- `reference(value)` to write the parsed result directly into an existing variable
|
||||||
|
- `action([] { ... })` for no-value callbacks
|
||||||
|
- `action<T>([](T const&) { ... })` for typed value callbacks
|
||||||
|
|
||||||
|
Once you select one value behavior, the other value behavior methods are disabled at compile time, so combinations like `store<T>().action(...)` or `flag().reference(value)` are rejected by the type system. Also you cannot use the same method repeatedly as it is also disabled at compile time by the type system.
|
||||||
|
|
||||||
|
If you do not select a value behavior explicitly, `build(parser)` uses the default for the argument kind: named arguments become boolean flags, while positional arguments store a `std::string`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
For unit tests or synthetic argument lists, use `argument_parser::v2::fake_parser` instead of the native platform parser:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <fake_parser.hpp>
|
||||||
|
|
||||||
|
argument_parser::v2::fake_parser parser("tool", {"--count", "3", "input.txt"});
|
||||||
```
|
```
|
||||||
|
|
||||||
## CMake Integration
|
## CMake Integration
|
||||||
|
|
||||||
The library can be installed globally via CMake or incorporated into your project.
|
Use the project directly:
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
add_subdirectory(argument-parser)
|
add_subdirectory(argument-parser)
|
||||||
target_link_libraries(your_target PRIVATE argument_parser)
|
target_link_libraries(your_target PRIVATE argument_parser)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or install and consume it as a package:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
find_package(argument_parser CONFIG REQUIRED)
|
||||||
|
target_link_libraries(your_target PRIVATE argument_parser::argument_parser)
|
||||||
|
```
|
||||||
|
|
||||||
## Building & Installing
|
## Building & Installing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake ..
|
cmake ..
|
||||||
cmake --build .
|
cmake --build .
|
||||||
|
cmake --install .
|
||||||
```
|
```
|
||||||
|
|||||||
32
TODO.md
32
TODO.md
@@ -46,12 +46,38 @@ instead of an action doing it.
|
|||||||
# TODO 7: Defaults/Implicits
|
# TODO 7: Defaults/Implicits
|
||||||
If given, an arguments default store value could be changed. If nothing was given use that value instead.
|
If given, an arguments default store value could be changed. If nothing was given use that value instead.
|
||||||
|
|
||||||
# TODO 8: Validators
|
# TODO 8: Validators | DONE
|
||||||
If given, validate the argument before passing to the storage or action. If fail, let user decide fail loud or fail skip.
|
If given, validate the argument before passing to the storage or action. If fail, let user decide fail loud or fail skip.
|
||||||
|
|
||||||
# TODO 9: Subcommand/Subactions
|
# TODO 9: Subcommand/Subactions
|
||||||
Implement subcommand support. Users should be able to define subactions to the higher level action. For example,
|
Implement subcommand support. Users should be able to define subactions to the higher level action. For example,
|
||||||
```cpp
|
```cpp
|
||||||
parser.add_argument<std::string>(
|
parser.add_argument(
|
||||||
{{ShortArgument, "l"}, {LongArgument, "list"}, {Action, list_files}, {HelpText, "Lists files in the directory"}});
|
{{ShortArgument, "g"}, {LongArgument, "get"}, {Action, get_}, {HelpText, "Gets <files, system_info, status>"}}
|
||||||
|
);
|
||||||
|
parser.add_argument(
|
||||||
|
{{BaseArgument, "g"}, {ShortArgument, "f"}, {LongArgument, "files"}, {Action, get_files}, {HelpText, "Gets files"}}
|
||||||
|
);
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
# TODO 10: Reference capture | DONE
|
||||||
|
Reference capturing.
|
||||||
|
```cpp
|
||||||
|
parser.add_argument<int>({
|
||||||
|
{ShortArgument, "c"},
|
||||||
|
{HelpText, "capture value"},
|
||||||
|
{Reference, &captured_value},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
# TODO 11: Builder | DONE
|
||||||
|
Implement type safe logic enforcing argument builder;
|
||||||
|
```cpp
|
||||||
|
argument::start()
|
||||||
|
.short_argument("e")
|
||||||
|
.long_argument("echo")
|
||||||
|
.help_text("Echo the parsed value.")
|
||||||
|
.action(echo)
|
||||||
|
.build(parser);
|
||||||
```
|
```
|
||||||
|
|||||||
11
examples/test/CMakeLists.txt
Normal file
11
examples/test/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(test)
|
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Debug)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
find_package(argument_parser REQUIRED)
|
||||||
|
add_executable(main main.cpp)
|
||||||
|
target_link_libraries(main argument_parser)
|
||||||
110
examples/test/main.cpp
Normal file
110
examples/test/main.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
|
||||||
|
#include <argparse>
|
||||||
|
#include <gnu_argument_convention.hpp>
|
||||||
|
#include <macros.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <parser_v2.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <traits.hpp>
|
||||||
|
|
||||||
|
using argument = argument_parser::builder::argument<>;
|
||||||
|
|
||||||
|
using argument_parser::parsing_traits::hint_type;
|
||||||
|
|
||||||
|
auto echo(std::string const& s) -> void {
|
||||||
|
std::cout << s << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace argument_parser::parsing_traits;
|
||||||
|
|
||||||
|
constexpr hint_type vector_purpose_hint = "vector of ";
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class parser_trait<std::vector<T>> {
|
||||||
|
public:
|
||||||
|
static std::vector<T> parse(std::string const& s) {
|
||||||
|
std::vector<T> result;
|
||||||
|
std::stringstream ss(s);
|
||||||
|
|
||||||
|
for (std::string line; std::getline(ss, line, ',');) {
|
||||||
|
T item = parser_trait<T>::parse(line);
|
||||||
|
result.push_back(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ARGPARSE_TRAIT_FORMAT_HINT = concat<
|
||||||
|
hint_provider<&parser_trait<T>::format_hint>,
|
||||||
|
hint_provider<&comma>,
|
||||||
|
hint_provider<&parser_trait<T>::format_hint>
|
||||||
|
>;
|
||||||
|
|
||||||
|
ARGPARSE_TRAIT_PURPOSE_HINT = concat<
|
||||||
|
hint_provider<&vector_purpose_hint>,
|
||||||
|
hint_provider<&parser_trait<T>::purpose_hint>
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
|
||||||
|
auto main() -> int {
|
||||||
|
argument_parser::v2::parser parser(false);
|
||||||
|
|
||||||
|
argument::start()
|
||||||
|
.positional("count")
|
||||||
|
.position(0)
|
||||||
|
.help_text("How many times to repeat the action.")
|
||||||
|
.action<int>([](int const& count) {
|
||||||
|
std::cout << "count action configured for " << count << '\n';
|
||||||
|
})
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
int captured_value = 0;
|
||||||
|
argument::start()
|
||||||
|
.long_argument("threshold")
|
||||||
|
.help_text("Store the parsed value through a reference.")
|
||||||
|
.reference(captured_value)
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
// parser.add_argument<int>({
|
||||||
|
// {ShortArgument, "c"},
|
||||||
|
// {HelpText, "capture count"},
|
||||||
|
// {Reference, &captured_value},
|
||||||
|
// });
|
||||||
|
|
||||||
|
argument::start()
|
||||||
|
.short_argument("q")
|
||||||
|
.help_text("Store a boolean flag.")
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
// argument::start()
|
||||||
|
// .long_argument("regex")
|
||||||
|
// .help_text("Store a regex value.")
|
||||||
|
// .store<std::optional<std::regex>>()
|
||||||
|
// .build(parser);
|
||||||
|
|
||||||
|
argument::start()
|
||||||
|
.short_argument("e")
|
||||||
|
.long_argument("echo")
|
||||||
|
.help_text("Echo the parsed value.")
|
||||||
|
.action(echo)
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
argument::start()
|
||||||
|
.long_argument("vecstr")
|
||||||
|
.short_argument("vs")
|
||||||
|
.action<std::vector<int>>([](std::vector<int> const& vecstr) {
|
||||||
|
for (auto const& str : vecstr) {
|
||||||
|
std::cout << str << '\n';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build(parser);
|
||||||
|
|
||||||
|
parser.handle_arguments({
|
||||||
|
&argument_parser::conventions::gnu_argument_convention
|
||||||
|
});
|
||||||
|
|
||||||
|
std::cout << "captured value: " << captured_value << '\n';
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
#ifndef ARGPARSE_HPP
|
#ifndef ARGPARSE_HPP
|
||||||
#define ARGPARSE_HPP
|
#define ARGPARSE_HPP
|
||||||
#include <argument_parser.hpp>
|
#include <argument_parser.hpp>
|
||||||
|
#include <parser_v2.hpp>
|
||||||
|
#include <argument_builder.hpp>
|
||||||
#include "macros.h"
|
#include "macros.h"
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
|||||||
521
src/headers/parser/argument_builder.hpp
Normal file
521
src/headers/parser/argument_builder.hpp
Normal file
@@ -0,0 +1,521 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "argument_parser.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <parser_v2.hpp>
|
||||||
|
|
||||||
|
#ifndef ARGUMENT_PARSER_PARSER_V3_HPP
|
||||||
|
#define ARGUMENT_PARSER_PARSER_V3_HPP
|
||||||
|
|
||||||
|
namespace argument_parser::builder {
|
||||||
|
|
||||||
|
class non_type {};
|
||||||
|
namespace builder_mask {
|
||||||
|
using v2_flag = argument_parser::v2::add_argument_flags;
|
||||||
|
using mask_type = std::uint64_t;
|
||||||
|
enum class value_mode {
|
||||||
|
unresolved,
|
||||||
|
store,
|
||||||
|
flag,
|
||||||
|
reference,
|
||||||
|
nonparametered_action,
|
||||||
|
parametered_action
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class extra_capability : unsigned {
|
||||||
|
Store = static_cast<unsigned>(v2_flag::Reference) + 1,
|
||||||
|
Flag
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto bit(v2_flag flag) -> mask_type {
|
||||||
|
return mask_type{1} << static_cast<unsigned>(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto bit(extra_capability capability) -> mask_type {
|
||||||
|
return mask_type{1} << static_cast<unsigned>(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr mask_type short_argument = bit(v2_flag::ShortArgument);
|
||||||
|
constexpr mask_type long_argument = bit(v2_flag::LongArgument);
|
||||||
|
constexpr mask_type positional = bit(v2_flag::Positional);
|
||||||
|
constexpr mask_type position = bit(v2_flag::Position);
|
||||||
|
constexpr mask_type help_text = bit(v2_flag::HelpText);
|
||||||
|
constexpr mask_type action = bit(v2_flag::Action);
|
||||||
|
constexpr mask_type required = bit(v2_flag::Required);
|
||||||
|
constexpr mask_type reference = bit(v2_flag::Reference);
|
||||||
|
constexpr mask_type store = bit(extra_capability::Store);
|
||||||
|
constexpr mask_type flag = bit(extra_capability::Flag);
|
||||||
|
|
||||||
|
constexpr mask_type value_mode_group = action | reference | store | flag;
|
||||||
|
constexpr mask_type initial = short_argument | long_argument | positional | help_text | action | required |
|
||||||
|
reference | store | flag;
|
||||||
|
|
||||||
|
constexpr auto has(mask_type mask, mask_type capability) -> bool {
|
||||||
|
return (mask & capability) == capability;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto remove(mask_type mask, mask_type capability) -> mask_type {
|
||||||
|
return mask & ~capability;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto replace(mask_type mask, mask_type remove_bits, mask_type add_bits = 0) -> mask_type {
|
||||||
|
return (mask & ~remove_bits) | add_bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto has_selected_identifier(mask_type mask) -> bool {
|
||||||
|
return !has(mask, short_argument) || !has(mask, long_argument) || !has(mask, positional);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr auto is_buildable(mask_type mask) -> bool {
|
||||||
|
return has_selected_identifier(mask);
|
||||||
|
}
|
||||||
|
} // namespace builder_mask
|
||||||
|
|
||||||
|
template<builder_mask::mask_type mask = builder_mask::initial, typename store_type = non_type>
|
||||||
|
class argument {
|
||||||
|
public:
|
||||||
|
using mask_type = builder_mask::mask_type;
|
||||||
|
using v2_flag = argument_parser::v2::add_argument_flags;
|
||||||
|
using value_mode = builder_mask::value_mode;
|
||||||
|
|
||||||
|
static auto start() -> argument<builder_mask::initial> {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::short_argument), int> = 0>
|
||||||
|
auto short_argument(std::string short_name) const
|
||||||
|
-> argument<builder_mask::replace(current_mask, builder_mask::short_argument | builder_mask::positional |
|
||||||
|
builder_mask::position),
|
||||||
|
store_type> {
|
||||||
|
using next_argument = argument<builder_mask::replace(current_mask,
|
||||||
|
builder_mask::short_argument | builder_mask::positional |
|
||||||
|
builder_mask::position),
|
||||||
|
store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_short_argument = std::move(short_name);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::long_argument), int> = 0>
|
||||||
|
auto long_argument(std::string long_name) const
|
||||||
|
-> argument<builder_mask::replace(current_mask, builder_mask::long_argument | builder_mask::positional |
|
||||||
|
builder_mask::position),
|
||||||
|
store_type> {
|
||||||
|
using next_argument = argument<builder_mask::replace(current_mask,
|
||||||
|
builder_mask::long_argument | builder_mask::positional |
|
||||||
|
builder_mask::position),
|
||||||
|
store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_long_argument = std::move(long_name);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::positional), int> = 0>
|
||||||
|
auto positional(std::string positional_name) const
|
||||||
|
-> argument<builder_mask::replace(current_mask,
|
||||||
|
builder_mask::short_argument | builder_mask::long_argument |
|
||||||
|
builder_mask::positional | builder_mask::flag,
|
||||||
|
builder_mask::position),
|
||||||
|
store_type> {
|
||||||
|
using next_argument =
|
||||||
|
argument<builder_mask::replace(current_mask,
|
||||||
|
builder_mask::short_argument | builder_mask::long_argument |
|
||||||
|
builder_mask::positional | builder_mask::flag,
|
||||||
|
builder_mask::position),
|
||||||
|
store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_positional_name = std::move(positional_name);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::position), int> = 0>
|
||||||
|
auto position(int index) const -> argument<builder_mask::remove(current_mask, builder_mask::position), store_type> {
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::position), store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_position = index;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::help_text), int> = 0>
|
||||||
|
auto help_text(std::string help) const -> argument<builder_mask::remove(current_mask, builder_mask::help_text), store_type> {
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::help_text), store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_help_text = std::move(help);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::required), int> = 0>
|
||||||
|
auto required(bool value = true) const -> argument<builder_mask::remove(current_mask, builder_mask::required), store_type> {
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::required), store_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_required = value;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = std::string, mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::store), int> = 0>
|
||||||
|
auto store() const -> argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T> {
|
||||||
|
static_assert(!std::is_same_v<T, void>, "store<void>() is not supported. Use flag() for boolean-style arguments.");
|
||||||
|
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T>;
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_value_mode = value_mode::store;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::flag), int> = 0>
|
||||||
|
auto flag() const -> argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), bool> {
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), bool>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_value_mode = value_mode::flag;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::reference), int> = 0,
|
||||||
|
typename T>
|
||||||
|
auto reference(T& value) const -> argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T> {
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_reference = std::addressof(value);
|
||||||
|
next.m_value_mode = value_mode::reference;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::has(current_mask, builder_mask::action), int> = 0,
|
||||||
|
typename Callable>
|
||||||
|
auto action(Callable&& handler) const
|
||||||
|
-> std::enable_if_t<std::is_invocable_r_v<void, Callable>,
|
||||||
|
argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), non_type>> {
|
||||||
|
using next_argument =
|
||||||
|
argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), non_type>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_action = std::make_shared<argument_parser::non_parametered_action>(
|
||||||
|
std::function<void()>(std::forward<Callable>(handler)));
|
||||||
|
next.m_value_mode = value_mode::nonparametered_action;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T = std::string, mask_type current_mask = mask,
|
||||||
|
std::enable_if_t<builder_mask::has(current_mask, builder_mask::action), int> = 0, typename Callable>
|
||||||
|
auto action(Callable&& handler) const
|
||||||
|
-> std::enable_if_t<std::is_invocable_r_v<void, Callable, const T&>,
|
||||||
|
argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T>> {
|
||||||
|
static_assert(!std::is_same_v<T, void>, "action<void>(...) is not supported. Use action([] { ... }) instead.");
|
||||||
|
|
||||||
|
using next_argument = argument<builder_mask::remove(current_mask, builder_mask::value_mode_group), T>;
|
||||||
|
|
||||||
|
next_argument next{*this};
|
||||||
|
next.m_action = std::make_shared<argument_parser::parametered_action<T>>(
|
||||||
|
std::function<void(const T&)>(std::forward<Callable>(handler)));
|
||||||
|
next.m_value_mode = value_mode::parametered_action;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type current_mask = mask, std::enable_if_t<builder_mask::is_buildable(current_mask), int> = 0>
|
||||||
|
auto build(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
assert_has_identifier();
|
||||||
|
|
||||||
|
switch (m_value_mode) {
|
||||||
|
case value_mode::flag:
|
||||||
|
build_flag(parser);
|
||||||
|
return;
|
||||||
|
case value_mode::nonparametered_action:
|
||||||
|
build_nonparametered_action(parser);
|
||||||
|
return;
|
||||||
|
case value_mode::store:
|
||||||
|
if constexpr (!std::is_same_v<store_type, non_type>) {
|
||||||
|
build_store(parser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_mode::reference:
|
||||||
|
if constexpr (!std::is_same_v<store_type, non_type>) {
|
||||||
|
build_reference(parser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_mode::parametered_action:
|
||||||
|
if constexpr (!std::is_same_v<store_type, non_type>) {
|
||||||
|
build_parametered_action(parser);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case value_mode::unresolved:
|
||||||
|
if (is_positional()) {
|
||||||
|
build_default_positional(parser);
|
||||||
|
} else {
|
||||||
|
build_flag(parser);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::logic_error("The builder reached build() without a supported terminal value mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
argument() = default;
|
||||||
|
|
||||||
|
template<mask_type other_mask, typename other_store_type>
|
||||||
|
argument(argument<other_mask, other_store_type> const& other)
|
||||||
|
: m_short_argument(other.m_short_argument),
|
||||||
|
m_long_argument(other.m_long_argument),
|
||||||
|
m_positional_name(other.m_positional_name),
|
||||||
|
m_position(other.m_position),
|
||||||
|
m_help_text(other.m_help_text),
|
||||||
|
m_required(other.m_required),
|
||||||
|
m_action(other.m_action),
|
||||||
|
m_reference(copy_reference(other.m_reference)),
|
||||||
|
m_value_mode(other.m_value_mode) {}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using typed_map = std::unordered_map<v2_flag, typename argument_parser::v2::base_parser::template typed_flag_value<T>>;
|
||||||
|
|
||||||
|
using non_typed_map = std::unordered_map<v2_flag, argument_parser::v2::base_parser::non_typed_flag_value>;
|
||||||
|
|
||||||
|
auto is_positional() const -> bool {
|
||||||
|
return !m_positional_name.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lookup_key() const -> std::string {
|
||||||
|
if (is_positional()) {
|
||||||
|
return m_positional_name;
|
||||||
|
}
|
||||||
|
if (!m_long_argument.empty()) {
|
||||||
|
return m_long_argument;
|
||||||
|
}
|
||||||
|
if (!m_short_argument.empty()) {
|
||||||
|
return m_short_argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw std::logic_error("No argument identifier is available for lookup.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto assert_has_identifier() const -> void {
|
||||||
|
if (!is_positional() && m_short_argument.empty() && m_long_argument.empty()) {
|
||||||
|
throw std::logic_error("build() requires short_argument(), long_argument(), or positional().");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Map>
|
||||||
|
auto add_common_pairs(Map& pairs) const -> void {
|
||||||
|
using namespace argument_parser::v2::flags;
|
||||||
|
|
||||||
|
if (is_positional()) {
|
||||||
|
pairs[Positional] = m_positional_name;
|
||||||
|
if (m_position.has_value()) {
|
||||||
|
pairs[Position] = m_position.value();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!m_short_argument.empty()) {
|
||||||
|
pairs[ShortArgument] = m_short_argument;
|
||||||
|
}
|
||||||
|
if (!m_long_argument.empty()) {
|
||||||
|
pairs[LongArgument] = m_long_argument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_help_text.empty()) {
|
||||||
|
pairs[HelpText] = m_help_text;
|
||||||
|
}
|
||||||
|
if (m_required) {
|
||||||
|
pairs[Required] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
auto make_typed_pairs() const -> typed_map<T> {
|
||||||
|
typed_map<T> pairs;
|
||||||
|
add_common_pairs(pairs);
|
||||||
|
return pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto make_non_typed_pairs() const -> non_typed_map {
|
||||||
|
non_typed_map pairs;
|
||||||
|
add_common_pairs(pairs);
|
||||||
|
return pairs;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_flag(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto pairs = make_non_typed_pairs();
|
||||||
|
parser.add_argument(pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_default_positional(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto pairs = make_non_typed_pairs();
|
||||||
|
parser.add_argument(pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_store(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto pairs = make_typed_pairs<store_type>();
|
||||||
|
parser.template add_argument<store_type>(pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_reference(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto pairs = make_typed_pairs<store_type>();
|
||||||
|
auto* target = m_reference;
|
||||||
|
auto key = lookup_key();
|
||||||
|
|
||||||
|
if (target == nullptr) {
|
||||||
|
throw std::logic_error("reference() was selected without a target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.template add_argument<store_type>(pairs);
|
||||||
|
parser.on_complete([target, key](argument_parser::base_parser const& completed_parser) {
|
||||||
|
if (auto value = completed_parser.template get_optional<store_type>(key)) {
|
||||||
|
*target = value.value();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_parametered_action(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto const* typed_action = dynamic_cast<argument_parser::parametered_action<store_type> const*>(m_action.get());
|
||||||
|
if (typed_action == nullptr) {
|
||||||
|
throw std::logic_error("Stored action is not compatible with the requested parameter type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pairs = make_typed_pairs<store_type>();
|
||||||
|
pairs[argument_parser::v2::flags::Action] = *typed_action;
|
||||||
|
parser.template add_argument<store_type>(pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto build_nonparametered_action(argument_parser::v2::base_parser& parser) const -> void {
|
||||||
|
auto const* nonparametered_action = dynamic_cast<argument_parser::non_parametered_action const*>(m_action.get());
|
||||||
|
if (nonparametered_action == nullptr) {
|
||||||
|
throw std::logic_error("Stored action is not a non-parametered action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_positional()) {
|
||||||
|
auto wrapped_action = argument_parser::helpers::make_parametered_action<std::string>(
|
||||||
|
[action = *nonparametered_action](std::string const&) { action.invoke(); });
|
||||||
|
auto pairs = make_typed_pairs<std::string>();
|
||||||
|
pairs[argument_parser::v2::flags::Action] = wrapped_action;
|
||||||
|
parser.template add_argument<std::string>(pairs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pairs = make_non_typed_pairs();
|
||||||
|
pairs[argument_parser::v2::flags::Action] = *nonparametered_action;
|
||||||
|
parser.add_argument(pairs);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string m_short_argument{};
|
||||||
|
std::string m_long_argument{};
|
||||||
|
std::string m_positional_name{};
|
||||||
|
std::optional<int> m_position{};
|
||||||
|
std::string m_help_text{};
|
||||||
|
bool m_required = false;
|
||||||
|
std::shared_ptr<argument_parser::action_base const> m_action{};
|
||||||
|
store_type* m_reference = nullptr;
|
||||||
|
value_mode m_value_mode = value_mode::unresolved;
|
||||||
|
|
||||||
|
template<typename other_store_type>
|
||||||
|
static auto copy_reference(other_store_type* reference) -> store_type* {
|
||||||
|
if constexpr (std::is_same_v<store_type, other_store_type>) {
|
||||||
|
return reference;
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<mask_type other_mask, typename other_store_type>
|
||||||
|
friend class argument;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace assertions {
|
||||||
|
struct noop_handler {
|
||||||
|
void operator()() const {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct parameter_sink {
|
||||||
|
void operator()(T const&) const {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct can_use_help_text : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct can_use_help_text<T, std::void_t<decltype(std::declval<T>().help_text(std::declval<std::string>()))>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct can_use_position : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct can_use_position<T, std::void_t<decltype(std::declval<T>().position(0))>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct can_use_nonparametered_action : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct can_use_nonparametered_action<T, std::void_t<decltype(std::declval<T>().action(noop_handler{}))>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U, typename = void>
|
||||||
|
struct can_use_parametered_action : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
struct can_use_parametered_action<T, U, std::void_t<decltype(std::declval<T>().template action<U>(parameter_sink<U>{}))>>
|
||||||
|
: std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U, typename = void>
|
||||||
|
struct can_use_store : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
struct can_use_store<T, U, std::void_t<decltype(std::declval<T>().template store<U>())>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename = void>
|
||||||
|
struct can_use_flag : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct can_use_flag<T, std::void_t<decltype(std::declval<T>().flag())>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U, typename = void>
|
||||||
|
struct can_use_reference : std::false_type {};
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
struct can_use_reference<T, U, std::void_t<decltype(std::declval<T>().reference(std::declval<U&>()))>> : std::true_type {};
|
||||||
|
|
||||||
|
using after_help_text = decltype(argument<>::start().help_text("help"));
|
||||||
|
static_assert(!can_use_help_text<after_help_text>::value, "help_text() should be single-use.");
|
||||||
|
|
||||||
|
using after_positional = decltype(argument<>::start().positional("path"));
|
||||||
|
static_assert(can_use_position<after_positional>::value, "positional() should unlock position().");
|
||||||
|
|
||||||
|
using after_position = decltype(argument<>::start().positional("path").position(0));
|
||||||
|
static_assert(!can_use_position<after_position>::value, "position() should be single-use.");
|
||||||
|
|
||||||
|
using after_positional_mode_selection = decltype(argument<>::start().positional("path").store<>());
|
||||||
|
static_assert(!can_use_flag<after_positional_mode_selection>::value, "flag() should not be available for positional arguments.");
|
||||||
|
|
||||||
|
using after_nonparametered_action =
|
||||||
|
decltype(argument<>::start().short_argument("v").help_text("verbose").action(noop_handler{}));
|
||||||
|
static_assert(!can_use_nonparametered_action<after_nonparametered_action>::value,
|
||||||
|
"action() should not remain callable after selecting a non-parametered action.");
|
||||||
|
static_assert(!can_use_parametered_action<after_nonparametered_action, int>::value,
|
||||||
|
"typed action() should also be disabled after selecting a non-parametered action.");
|
||||||
|
static_assert(!can_use_store<after_nonparametered_action, int>::value,
|
||||||
|
"store() should be mutually exclusive with action().");
|
||||||
|
static_assert(!can_use_flag<after_nonparametered_action>::value,
|
||||||
|
"flag() should be mutually exclusive with action().");
|
||||||
|
static_assert(!can_use_reference<after_nonparametered_action, int>::value,
|
||||||
|
"reference() should be mutually exclusive with action().");
|
||||||
|
|
||||||
|
using after_parametered_action =
|
||||||
|
decltype(argument<>::start().positional("count").position(0).action<int>(parameter_sink<int>{}));
|
||||||
|
static_assert(!can_use_nonparametered_action<after_parametered_action>::value,
|
||||||
|
"non-parametered action() should be disabled after selecting a typed action.");
|
||||||
|
static_assert(!can_use_parametered_action<after_parametered_action, std::string>::value,
|
||||||
|
"typed action() should be single-use regardless of the parameter type.");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -9,12 +9,13 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace argument_parser::v2 {
|
namespace argument_parser::v2 {
|
||||||
enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required };
|
enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required, Reference };
|
||||||
|
|
||||||
namespace flags {
|
namespace flags {
|
||||||
constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument;
|
constexpr static inline add_argument_flags ShortArgument = add_argument_flags::ShortArgument;
|
||||||
@@ -24,11 +25,12 @@ namespace argument_parser::v2 {
|
|||||||
constexpr static inline add_argument_flags Required = add_argument_flags::Required;
|
constexpr static inline add_argument_flags Required = add_argument_flags::Required;
|
||||||
constexpr static inline add_argument_flags Positional = add_argument_flags::Positional;
|
constexpr static inline add_argument_flags Positional = add_argument_flags::Positional;
|
||||||
constexpr static inline add_argument_flags Position = add_argument_flags::Position;
|
constexpr static inline add_argument_flags Position = add_argument_flags::Position;
|
||||||
|
constexpr static inline add_argument_flags Reference = add_argument_flags::Reference;
|
||||||
} // namespace flags
|
} // namespace flags
|
||||||
|
|
||||||
class base_parser : private argument_parser::base_parser {
|
class base_parser : private argument_parser::base_parser {
|
||||||
public:
|
public:
|
||||||
template <typename T> using typed_flag_value = std::variant<std::string, parametered_action<T>, bool, int>;
|
template <typename T> using typed_flag_value = std::variant<std::string, parametered_action<T>, bool, int, T*>;
|
||||||
using non_typed_flag_value = std::variant<std::string, non_parametered_action, bool, int>;
|
using non_typed_flag_value = std::variant<std::string, non_parametered_action, bool, int>;
|
||||||
|
|
||||||
template <typename T> using typed_argument_pair = std::pair<add_argument_flags, typed_flag_value<T>>;
|
template <typename T> using typed_argument_pair = std::pair<add_argument_flags, typed_flag_value<T>>;
|
||||||
@@ -144,6 +146,22 @@ namespace argument_parser::v2 {
|
|||||||
required = true;
|
required = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (argument_pairs.find(add_argument_flags::Reference) != argument_pairs.end()) {
|
||||||
|
if (!IsTyped) {
|
||||||
|
throw std::logic_error("Reference argument must be typed");
|
||||||
|
}
|
||||||
|
|
||||||
|
found_params[extended_add_argument_flags::Action] = true;
|
||||||
|
if constexpr (!std::is_same_v<T, void>) {
|
||||||
|
auto ref = get_or_throw<T*>(argument_pairs.at(add_argument_flags::Reference), "reference");
|
||||||
|
action = helpers::make_parametered_action<T>([ref](T const& t) {
|
||||||
|
*ref = t;
|
||||||
|
}).clone();
|
||||||
|
} else {
|
||||||
|
throw std::logic_error("Reference argument must not be void");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto suggested_add = suggest_candidate(found_params);
|
auto suggested_add = suggest_candidate(found_params);
|
||||||
if (suggested_add == candidate_type::unknown) {
|
if (suggested_add == candidate_type::unknown) {
|
||||||
throw std::runtime_error("Could not match any add argument overload to given parameters. Are you "
|
throw std::runtime_error("Could not match any add argument overload to given parameters. Are you "
|
||||||
@@ -165,8 +183,7 @@ namespace argument_parser::v2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
base::add_argument(short_arg, long_arg, help_text, *static_cast<ActionType *>(&(*action)),
|
base::add_argument(short_arg, long_arg, help_text, *static_cast<ActionType *>(&(*action)), required);
|
||||||
required);
|
|
||||||
break;
|
break;
|
||||||
case candidate_type::store_other:
|
case candidate_type::store_other:
|
||||||
if (help_text.empty()) {
|
if (help_text.empty()) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace argument_parser::parsing_traits {
|
namespace argument_parser::parsing_traits {
|
||||||
using hint_type = const char *;
|
using hint_type = const char*;
|
||||||
|
|
||||||
template <typename T_> struct parser_trait {
|
template <typename T_> struct parser_trait {
|
||||||
using type = T_;
|
using type = T_;
|
||||||
@@ -50,6 +50,42 @@ namespace argument_parser::parsing_traits {
|
|||||||
static constexpr hint_type format_hint = "3.14";
|
static constexpr hint_type format_hint = "3.14";
|
||||||
static constexpr hint_type purpose_hint = "double precision floating point number";
|
static constexpr hint_type purpose_hint = "double precision floating point number";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
constexpr hint_type comma = ",";
|
||||||
|
template <const hint_type* PtrAddr>
|
||||||
|
struct hint_provider {
|
||||||
|
static constexpr hint_type value = *PtrAddr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Providers>
|
||||||
|
struct joiner {
|
||||||
|
static constexpr auto get_combined() {
|
||||||
|
constexpr size_t total_len = (std::string_view{Providers::value}.length() + ... + 0);
|
||||||
|
|
||||||
|
std::array<char, total_len + 1> arr{};
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
auto append = [&](hint_type s) {
|
||||||
|
std::string_view sv{s};
|
||||||
|
for (char c : sv) arr[offset++] = c;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
(append(Providers::value), ...);
|
||||||
|
|
||||||
|
arr[total_len] = '\0';
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr auto storage = get_combined();
|
||||||
|
static constexpr hint_type value = storage.data();
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Providers>
|
||||||
|
constexpr hint_type concat = joiner<Providers...>::value;
|
||||||
|
|
||||||
} // namespace argument_parser::parsing_traits
|
} // namespace argument_parser::parsing_traits
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user