1 Commits

Author SHA1 Message Date
fc847c4cd1 fix: use correct ref to parsed args on linux 2026-04-20 21:25:36 +04:00
9 changed files with 72 additions and 888 deletions

219
README.md
View File

@@ -1,21 +1,17 @@
# argument-parser # argument-parser
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. A lightweight, modern, expressively typed, and highly customizable C++17 argument parser library.
> `v1` is deprecated and mainly kept as implementation history. For new projects, use `argument_parser::v2` together with `argument_parser::builder`.
## Features ## Features
- Native platform parser alias: `argument_parser::v2::parser` resolves to the current platform parser and reads arguments directly from OS APIs. - **Type-safe Argument Extraction**: Use type traits to automatically parse fundamental types and custom structures (e.g. `std::vector<int>`, `std::regex`, `Point`).
- Fluent builder API with compile-time builder constraints that prevent invalid combinations after a terminal/mutually exclusive mode has been selected. - **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`).
- Type-safe parsing and extraction. Just extend `parser_trait<T>` for your types and if just want to store use `get_optional<T>()`! - **Automated Help Text Formatting**: Call `parser.display_help(conventions)` to easily generate beautifully formatted usage instructions.
- Positional arguments with optional explicit ordering and support for `--` as a positional separator. - **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.
- Trait-driven `format_hint` and `purpose_hint` metadata used in generated help text and parse errors. - **Fluid setup**: Enjoy fluid setup routines with maps and initializer lists.
- Automatic help flag on `argument_parser::v2::parser` (`-h`, `--help`) with configurable exit behavior.
- Auto-formatted help output.. ### Important Note:
- Completion hooks via `parser.on_complete(...)`. 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!
- 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
@@ -24,193 +20,104 @@ A lightweight, modern, and highly customizable C++17 argument parser with native
## 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>
using argument = argument_parser::builder::argument<>; #include <argparse> // Provides the native parser for your compiling platform
int main() { int main() {
argument_parser::v2::parser parser(false); // --help prints without exiting immediately using namespace argument_parser::v2::flags;
// Automatically uses the platform-native parser!
// It will fetch arguments directly from OS APIs (e.g., GetCommandLineW on Windows)
argument_parser::v2::parser parser;
int threshold = 0; // A flag with an action
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"}
});
argument::start() // A flag that just stores the value to extract later
.short_argument("e") parser.add_argument<std::regex>({
.long_argument("echo") {ShortArgument, "g"},
.action<std::string>([](std::string const& text) { {LongArgument, "grep"},
std::cout << text << '\n'; {HelpText, "Grep pattern"}
}) });
.build(parser);
argument::start() // A required flag
.long_argument("file") parser.add_argument<std::string>({
.store<std::string>() {LongArgument, "file"},
.required() {Required, true},
.help_text("Input file to process.") {HelpText, "File to grep"}
.build(parser); });
argument::start() // Run action callback on complete
.long_argument("threshold") parser.on_complete([](argument_parser::base_parser const &p) {
.reference(threshold) auto filename = p.get_optional<std::string>("file");
.build(parser); auto pattern = p.get_optional<std::regex>("grep");
argument::start() if (filename && pattern) {
.short_argument("v") std::cout << "Grepping " << filename.value() << " with pattern." << std::endl;
.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::gnu_equal_argument_convention, &argument_parser::conventions::windows_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);
if (auto file = parser.get_optional<std::string>("file")) { return 0;
std::cout << "file: " << *file << '\n';
}
std::cout << "threshold: " << threshold << '\n';
} }
``` ```
## Trait-Driven Parsing and Hints ### 2. Custom Type Parsing
Specialize `argument_parser::parsing_traits::parser_trait<T>` to add support for your own types and to describe their expected format. You can natively parse your custom structs, objects, or arrays by specializing `argument_parser::parsing_traits::parser_trait<T>`.
```cpp ```cpp
#include <macros.h>
#include <traits.hpp>
#include <stdexcept>
#include <string>
struct Point { struct Point {
int x; int x, y;
int y;
}; };
template <> template <> struct argument_parser::parsing_traits::parser_trait<Point> {
struct argument_parser::parsing_traits::parser_trait<Point> { static Point parse(const std::string &input) {
static Point parse(std::string const& input) { auto comma_pos = input.find(',');
auto comma = input.find(','); int x = std::stoi(input.substr(0, comma_pos));
if (comma == std::string::npos) { int y = std::stoi(input.substr(comma_pos + 1));
throw std::runtime_error("Expected x,y"); return {x, y};
}
return {
std::stoi(input.substr(0, comma)),
std::stoi(input.substr(comma + 1))
};
} }
ARGPARSE_TRAIT_FORMAT_HINT = "x,y";
ARGPARSE_TRAIT_PURPOSE_HINT = "point coordinates";
}; };
```
Then use the type directly from the builder: // Now you can directly use your type:
// parser.add_argument<Point>({ {LongArgument, "point"} });
```cpp // auto point = parser.get_optional<Point>("point");
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
Use the project directly: The library can be installed globally via CMake or incorporated into your project.
```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
View File

@@ -46,38 +46,12 @@ 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 | DONE # TODO 8: Validators
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( parser.add_argument<std::string>(
{{ShortArgument, "g"}, {LongArgument, "get"}, {Action, get_}, {HelpText, "Gets <files, system_info, status>"}} {{ShortArgument, "l"}, {LongArgument, "list"}, {Action, list_files}, {HelpText, "Lists files in the directory"}});
);
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);
``` ```

View File

@@ -1,11 +0,0 @@
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)

View File

@@ -1,110 +0,0 @@
#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;
}

View File

@@ -2,8 +2,6 @@
#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__

View File

@@ -1,521 +0,0 @@
#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

View File

@@ -9,13 +9,12 @@
#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, Reference }; enum class add_argument_flags { ShortArgument, LongArgument, Positional, Position, HelpText, Action, Required };
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;
@@ -25,12 +24,11 @@ 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, T*>; template <typename T> using typed_flag_value = std::variant<std::string, parametered_action<T>, bool, int>;
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>>;
@@ -146,22 +144,6 @@ 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 "
@@ -183,7 +165,8 @@ namespace argument_parser::v2 {
} }
} }
base::add_argument(short_arg, long_arg, help_text, *static_cast<ActionType *>(&(*action)), required); base::add_argument(short_arg, long_arg, help_text, *static_cast<ActionType *>(&(*action)),
required);
break; break;
case candidate_type::store_other: case candidate_type::store_other:
if (help_text.empty()) { if (help_text.empty()) {

View File

@@ -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,42 +50,6 @@ 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

View File

@@ -22,7 +22,7 @@ namespace argument_parser {
set_program_name(program_name); set_program_name(program_name);
for (std::string line; std::getline(command_line_file, line, '\0');) { for (std::string line; std::getline(command_line_file, line, '\0');) {
parsed_arguments.emplace_back(line); ref_parsed_args().emplace_back(line);
} }
prepare_help_flag(should_exit); prepare_help_flag(should_exit);