Merge pull request #5 from sametersoylu/v3/betterhelp

V3/betterhelp
This commit is contained in:
Abdüssamet ERSOYLU
2026-03-16 21:54:06 +04:00
committed by GitHub
11 changed files with 450 additions and 31 deletions

123
README.md Normal file
View File

@@ -0,0 +1,123 @@
# argument-parser
A lightweight, modern, expressively typed, and highly customizable C++17 argument parser library.
## Features
- **Type-safe Argument Extraction**: Use type traits to automatically parse fundamental types and custom structures (e.g. `std::vector<int>`, `std::regex`, `Point`).
- **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`).
- **Automated Help Text Formatting**: Call `parser.display_help(conventions)` to easily generate beautifully formatted usage instructions.
- **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.
- **Fluid setup**: Enjoy fluid setup routines with maps and initializer lists.
### Important Note:
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!
## Requirements
- C++17 or later
- CMake 3.15 or later
## Quick Start
### 1. Create your Parser and Define Arguments
```cpp
#include <iostream>
#include <string>
#include <regex>
#include <argparse> // Provides the native parser for your compiling platform
int main() {
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;
// 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"}
});
// A flag that just stores the value to extract later
parser.add_argument<std::regex>({
{ShortArgument, "g"},
{LongArgument, "grep"},
{HelpText, "Grep pattern"}
});
// A required flag
parser.add_argument<std::string>({
{LongArgument, "file"},
{Required, true},
{HelpText, "File to grep"}
});
// Run action callback on complete
parser.on_complete([](argument_parser::base_parser const &p) {
auto filename = p.get_optional<std::string>("file");
auto pattern = p.get_optional<std::regex>("grep");
if (filename && pattern) {
std::cout << "Grepping " << filename.value() << " with pattern." << std::endl;
}
});
// Register Conventions
const std::initializer_list<argument_parser::conventions::convention const *const> conventions = {
&argument_parser::conventions::gnu_argument_convention,
&argument_parser::conventions::windows_argument_convention
};
// Execute logic!
parser.handle_arguments(conventions);
return 0;
}
```
### 2. Custom Type Parsing
You can natively parse your custom structs, objects, or arrays by specializing `argument_parser::parsing_traits::parser_trait<T>`.
```cpp
struct Point {
int x, y;
};
template <> struct argument_parser::parsing_traits::parser_trait<Point> {
static Point parse(const std::string &input) {
auto comma_pos = input.find(',');
int x = std::stoi(input.substr(0, comma_pos));
int y = std::stoi(input.substr(comma_pos + 1));
return {x, y};
}
};
// Now you can directly use your type:
// parser.add_argument<Point>({ {LongArgument, "point"} });
// auto point = parser.get_optional<Point>("point");
```
## CMake Integration
The library can be installed globally via CMake or incorporated into your project.
```cmake
add_subdirectory(argument-parser)
target_link_libraries(your_target PRIVATE argument_parser)
```
## Building & Installing
```bash
mkdir build && cd build
cmake ..
cmake --build .
```

View File

@@ -1,11 +1,13 @@
#pragma once #pragma once
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#ifndef BASE_CONVENTION_HPP #ifndef BASE_CONVENTION_HPP
#define BASE_CONVENTION_HPP #define BASE_CONVENTION_HPP
namespace argument_parser::conventions { namespace argument_parser::conventions {
enum class convention_features { ALLOW_SHORT_TO_LONG_FALLBACK, ALLOW_LONG_TO_SHORT_FALLBACK };
enum class argument_type { SHORT, LONG, POSITIONAL, INTERCHANGABLE, ERROR }; enum class argument_type { SHORT, LONG, POSITIONAL, INTERCHANGABLE, ERROR };
using parsed_argument = std::pair<argument_type, std::string>; using parsed_argument = std::pair<argument_type, std::string>;
@@ -18,6 +20,9 @@ namespace argument_parser::conventions {
virtual std::string name() const = 0; virtual std::string name() const = 0;
virtual std::string short_prec() const = 0; virtual std::string short_prec() const = 0;
virtual std::string long_prec() const = 0; virtual std::string long_prec() const = 0;
virtual std::pair<std::string, std::string> make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const = 0;
virtual std::vector<convention_features> get_features() const = 0;
protected: protected:
base_convention() = default; base_convention() = default;

View File

@@ -14,6 +14,10 @@ namespace argument_parser::conventions::implementations {
std::string name() const override; std::string name() const override;
std::string short_prec() const override; std::string short_prec() const override;
std::string long_prec() const override; std::string long_prec() const override;
std::pair<std::string, std::string> make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const override;
std::vector<convention_features> get_features() const override;
static gnu_argument_convention instance; static gnu_argument_convention instance;
private: private:
@@ -28,6 +32,10 @@ namespace argument_parser::conventions::implementations {
std::string name() const override; std::string name() const override;
std::string short_prec() const override; std::string short_prec() const override;
std::string long_prec() const override; std::string long_prec() const override;
std::pair<std::string, std::string> make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const override;
std::vector<convention_features> get_features() const override;
static gnu_equal_argument_convention instance; static gnu_equal_argument_convention instance;
private: private:

View File

@@ -18,6 +18,10 @@ namespace argument_parser::conventions::implementations {
std::string name() const override; std::string name() const override;
std::string short_prec() const override; std::string short_prec() const override;
std::string long_prec() const override; std::string long_prec() const override;
std::pair<std::string, std::string> make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const override;
std::vector<convention_features> get_features() const override;
static windows_argument_convention instance; static windows_argument_convention instance;
private: private:
@@ -33,6 +37,10 @@ namespace argument_parser::conventions::implementations {
std::string name() const override; std::string name() const override;
std::string short_prec() const override; std::string short_prec() const override;
std::string long_prec() const override; std::string long_prec() const override;
std::pair<std::string, std::string> make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const override;
std::vector<convention_features> get_features() const override;
static windows_kv_argument_convention instance; static windows_kv_argument_convention instance;
private: private:

View File

@@ -256,7 +256,7 @@ namespace argument_parser {
std::optional<argument> &found_help); std::optional<argument> &found_help);
void invoke_arguments(std::unordered_map<std::string, std::string> const &values_for_arguments, void invoke_arguments(std::unordered_map<std::string, std::string> const &values_for_arguments,
std::vector<std::pair<std::string, argument>> const &found_arguments, std::vector<std::pair<std::string, argument>> &found_arguments,
std::optional<argument> const &found_help); std::optional<argument> const &found_help);
void enforce_creation_thread(); void enforce_creation_thread();

View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "traits.hpp"
#include <argument_parser.hpp> #include <argument_parser.hpp>
#include <array> #include <array>
#include <cstdlib> #include <cstdlib>
@@ -103,6 +104,30 @@ namespace argument_parser::v2 {
} }
private: private:
template <typename T> struct has_format_hint {
private:
typedef char YesType[1];
typedef char NoType[2];
template <typename C> static YesType &test(decltype(&C::format_hint));
template <typename> static NoType &test(...);
public:
static constexpr bool value = sizeof(test<T>(0)) == sizeof(YesType);
};
template <typename T> struct has_purpose_hint {
private:
typedef char YesType[1];
typedef char NoType[2];
template <typename C> static YesType &test(decltype(&C::purpose_hint));
template <typename> static NoType &test(...);
public:
static constexpr bool value = sizeof(test<T>(0)) == sizeof(YesType);
};
template <bool IsTyped, typename ActionType, typename T, typename ArgsMap> template <bool IsTyped, typename ActionType, typename T, typename ArgsMap>
void add_argument_impl(ArgsMap const &argument_pairs) { void add_argument_impl(ArgsMap const &argument_pairs) {
std::unordered_map<extended_add_argument_flags, bool> found_params{ std::unordered_map<extended_add_argument_flags, bool> found_params{
@@ -120,10 +145,10 @@ namespace argument_parser::v2 {
found_params[extended_add_argument_flags::LongArgument] = true; found_params[extended_add_argument_flags::LongArgument] = true;
long_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::LongArgument), "long"); long_arg = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::LongArgument), "long");
if (short_arg.empty()) if (short_arg.empty())
short_arg = long_arg; short_arg = "-";
} else { } else {
if (!short_arg.empty()) if (!short_arg.empty())
long_arg = short_arg; long_arg = "-";
} }
if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) {
@@ -132,8 +157,6 @@ namespace argument_parser::v2 {
} }
if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) {
help_text = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::HelpText), "help"); help_text = get_or_throw<std::string>(argument_pairs.at(add_argument_flags::HelpText), "help");
} else {
help_text = short_arg + ", " + long_arg;
} }
if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() &&
@@ -150,10 +173,34 @@ namespace argument_parser::v2 {
if constexpr (IsTyped) { if constexpr (IsTyped) {
switch (suggested_add) { switch (suggested_add) {
case candidate_type::typed_action: case candidate_type::typed_action:
if (help_text.empty()) {
if constexpr (has_format_hint<parsing_traits::parser_trait<T>>::value &&
has_purpose_hint<parsing_traits::parser_trait<T>>::value) {
auto format_hint = parsing_traits::parser_trait<T>::format_hint;
auto purpose_hint = parsing_traits::parser_trait<T>::purpose_hint;
help_text = "Triggers action with " + std::string(purpose_hint) + " (" +
std::string(format_hint) + ")";
} else {
help_text = "Triggers action with value.";
}
}
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 constexpr (has_format_hint<parsing_traits::parser_trait<T>>::value &&
has_purpose_hint<parsing_traits::parser_trait<T>>::value) {
auto format_hint = parsing_traits::parser_trait<T>::format_hint;
auto purpose_hint = parsing_traits::parser_trait<T>::purpose_hint;
help_text =
"Accepts " + std::string(purpose_hint) + " in " + std::string(format_hint) + " format.";
} else {
help_text = "Accepts value.";
}
}
base::add_argument<T>(short_arg, long_arg, help_text, required); base::add_argument<T>(short_arg, long_arg, help_text, required);
break; break;
default: default:
@@ -162,10 +209,21 @@ namespace argument_parser::v2 {
} else { } else {
switch (suggested_add) { switch (suggested_add) {
case candidate_type::non_typed_action: case candidate_type::non_typed_action:
if (help_text.empty()) {
help_text = "Triggers action with no value.";
}
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_boolean: case candidate_type::store_boolean:
if (help_text.empty()) {
auto boolPurpose = parsing_traits::parser_trait<bool>::purpose_hint;
auto boolFormat = parsing_traits::parser_trait<bool>::format_hint;
help_text =
"Accepts " + std::string(boolPurpose) + " in " + std::string(boolFormat) + " format.";
}
base::add_argument(short_arg, long_arg, help_text, required); base::add_argument(short_arg, long_arg, help_text, required);
break; break;
default: default:

View File

@@ -5,29 +5,43 @@
#include <string> #include <string>
namespace argument_parser::parsing_traits { namespace argument_parser::parsing_traits {
using hint_type = const char *;
template <typename T_> struct parser_trait { template <typename T_> struct parser_trait {
using type = T_; using type = T_;
static T_ parse(const std::string &input); static T_ parse(const std::string &input);
static constexpr hint_type format_hint = "value";
static constexpr hint_type purpose_hint = "value";
}; };
template <> struct parser_trait<std::string> { template <> struct parser_trait<std::string> {
static std::string parse(const std::string &input); static std::string parse(const std::string &input);
static constexpr hint_type format_hint = "string";
static constexpr hint_type purpose_hint = "string value";
}; };
template <> struct parser_trait<bool> { template <> struct parser_trait<bool> {
static bool parse(const std::string &input); static bool parse(const std::string &input);
static constexpr hint_type format_hint = "true/false";
static constexpr hint_type purpose_hint = "boolean value";
}; };
template <> struct parser_trait<int> { template <> struct parser_trait<int> {
static int parse(const std::string &input); static int parse(const std::string &input);
static constexpr hint_type format_hint = "123";
static constexpr hint_type purpose_hint = "integer value";
}; };
template <> struct parser_trait<float> { template <> struct parser_trait<float> {
static float parse(const std::string &input); static float parse(const std::string &input);
static constexpr hint_type format_hint = "3.14";
static constexpr hint_type purpose_hint = "flaoting point number";
}; };
template <> struct parser_trait<double> { template <> struct parser_trait<double> {
static double parse(const std::string &input); static double parse(const std::string &input);
static constexpr hint_type format_hint = "3.14";
static constexpr hint_type purpose_hint = "double precision floating point number";
}; };
} // namespace argument_parser::parsing_traits } // namespace argument_parser::parsing_traits

View File

@@ -1,10 +1,10 @@
#include "headers/parser/parsing_traits/traits.hpp"
#include <string> #include <string>
#define ALLOW_DASH_FOR_WINDOWS 0 #define ALLOW_DASH_FOR_WINDOWS 0
#include <argparse> #include <argparse>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <parser_v2.hpp>
#include <regex> #include <regex>
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@@ -23,12 +23,16 @@ template <> struct argument_parser::parsing_traits::parser_trait<Point> {
int y = std::stoi(input.substr(comma_pos + 1)); int y = std::stoi(input.substr(comma_pos + 1));
return {x, y}; return {x, y};
} }
static constexpr argument_parser::parsing_traits::hint_type format_hint = "x,y";
static constexpr argument_parser::parsing_traits::hint_type purpose_hint = "point coordinates";
}; };
template <> struct argument_parser::parsing_traits::parser_trait<std::regex> { template <> struct argument_parser::parsing_traits::parser_trait<std::regex> {
static std::regex parse(const std::string &input) { static std::regex parse(const std::string &input) {
return std::regex(input); return std::regex(input);
} }
static constexpr argument_parser::parsing_traits::hint_type format_hint = "regex";
static constexpr argument_parser::parsing_traits::hint_type purpose_hint = "regular expression";
}; };
template <> struct argument_parser::parsing_traits::parser_trait<std::vector<int>> { template <> struct argument_parser::parsing_traits::parser_trait<std::vector<int>> {
@@ -41,6 +45,8 @@ template <> struct argument_parser::parsing_traits::parser_trait<std::vector<int
} }
return result; return result;
} }
static constexpr argument_parser::parsing_traits::hint_type format_hint = "int,int,int";
static constexpr argument_parser::parsing_traits::hint_type purpose_hint = "list of integers";
}; };
template <> struct argument_parser::parsing_traits::parser_trait<std::vector<std::string>> { template <> struct argument_parser::parsing_traits::parser_trait<std::vector<std::string>> {
@@ -53,13 +59,16 @@ template <> struct argument_parser::parsing_traits::parser_trait<std::vector<std
} }
return result; return result;
} }
static constexpr argument_parser::parsing_traits::hint_type format_hint = "string,string,string";
static constexpr argument_parser::parsing_traits::hint_type purpose_hint = "list of strings";
}; };
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::gnu_equal_argument_convention,
&argument_parser::conventions::windows_argument_convention, // &argument_parser::conventions::windows_argument_convention,
&argument_parser::conventions::windows_equal_argument_convention}; // &argument_parser::conventions::windows_equal_argument_convention
};
const auto echo = argument_parser::helpers::make_parametered_action<std::string>( const auto echo = argument_parser::helpers::make_parametered_action<std::string>(
[](std::string const &text) { std::cout << text << std::endl; }); [](std::string const &text) { std::cout << text << std::endl; });
@@ -132,8 +141,7 @@ int v2Examples() {
parser.add_argument<std::string>( parser.add_argument<std::string>(
{{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}, {HelpText, "echoes given variable"}}); {{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}, {HelpText, "echoes given variable"}});
parser.add_argument<Point>( parser.add_argument<Point>({{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_point}});
{{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_point}, {HelpText, "echoes given point"}});
parser.add_argument<std::string>({ parser.add_argument<std::string>({
// stores string for f/file flag // stores string for f/file flag
@@ -160,6 +168,8 @@ int v2Examples() {
{Required, true} // makes this flag required {Required, true} // makes this flag required
}); });
parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}});
parser.on_complete(::run_grep); parser.on_complete(::run_grep);
parser.on_complete(::run_store_point); parser.on_complete(::run_store_point);
@@ -168,8 +178,9 @@ int v2Examples() {
} }
int main() { int main() {
return v2Examples();
try { try {
return v2Examples();
} catch (std::exception const &e) { } catch (std::exception const &e) {
std::cerr << "Error: " << e.what() << std::endl; std::cerr << "Error: " << e.what() << std::endl;
return -1; return -1;

View File

@@ -35,6 +35,32 @@ namespace argument_parser::conventions::implementations {
std::string gnu_argument_convention::long_prec() const { std::string gnu_argument_convention::long_prec() const {
return "--"; return "--";
} }
std::vector<convention_features> gnu_argument_convention::get_features() const {
return {}; // no fallback allowed
}
std::pair<std::string, std::string> gnu_argument_convention::make_help_text(std::string const &short_arg,
std::string const &long_arg,
bool requires_value) const {
std::string s_part = "";
if (short_arg != "-" && short_arg != "") {
s_part += short_prec() + short_arg;
if (requires_value) {
s_part += " <value>";
}
}
std::string l_part = "";
if (long_arg != "-" && long_arg != "") {
l_part += long_prec() + long_arg;
if (requires_value) {
l_part += " <value>";
}
}
return {s_part, l_part};
}
} // namespace argument_parser::conventions::implementations } // namespace argument_parser::conventions::implementations
namespace argument_parser::conventions::implementations { namespace argument_parser::conventions::implementations {
@@ -72,4 +98,29 @@ namespace argument_parser::conventions::implementations {
return "--"; return "--";
} }
std::pair<std::string, std::string> gnu_equal_argument_convention::make_help_text(std::string const &short_arg,
std::string const &long_arg,
bool requires_value) const {
std::string s_part = "";
if (short_arg != "-" && short_arg != "") {
s_part += short_prec() + short_arg;
if (requires_value) {
s_part += "=<value>";
}
}
std::string l_part = "";
if (long_arg != "-" && long_arg != "") {
l_part += long_prec() + long_arg;
if (requires_value) {
l_part += "=<value>";
}
}
return {s_part, l_part};
}
std::vector<convention_features> gnu_equal_argument_convention::get_features() const {
return {}; // no fallback allowed
}
} // namespace argument_parser::conventions::implementations } // namespace argument_parser::conventions::implementations

View File

@@ -1,4 +1,5 @@
#include "windows_argument_convention.hpp" #include "windows_argument_convention.hpp"
#include "base_convention.hpp"
#include <stdexcept> #include <stdexcept>
namespace argument_parser::conventions::implementations { namespace argument_parser::conventions::implementations {
@@ -49,6 +50,31 @@ namespace argument_parser::conventions::implementations {
return "/"; return "/";
} }
std::pair<std::string, std::string> windows_argument_convention::make_help_text(std::string const &short_arg, std::string const &long_arg,
bool requires_value) const {
std::string s_part = "";
if (short_arg != "-" && short_arg != "") {
s_part += short_prec() + short_arg;
if (requires_value) {
s_part += " <value>";
}
}
std::string l_part = "";
if (long_arg != "-" && long_arg != "") {
l_part += long_prec() + long_arg;
if (requires_value) {
l_part += " <value>";
}
}
return {s_part, l_part};
}
std::vector<convention_features> windows_argument_convention::get_features() const {
return {convention_features::ALLOW_LONG_TO_SHORT_FALLBACK,
convention_features::ALLOW_SHORT_TO_LONG_FALLBACK}; // interchangable
}
} // namespace argument_parser::conventions::implementations } // namespace argument_parser::conventions::implementations
namespace argument_parser::conventions::implementations { namespace argument_parser::conventions::implementations {
@@ -101,4 +127,30 @@ namespace argument_parser::conventions::implementations {
std::string windows_kv_argument_convention::long_prec() const { std::string windows_kv_argument_convention::long_prec() const {
return "/"; return "/";
} }
std::pair<std::string, std::string> windows_kv_argument_convention::make_help_text(std::string const &short_arg,
std::string const &long_arg, bool requires_value) const {
std::string s_part = "";
if (short_arg != "-" && short_arg != "") {
s_part += short_prec() + short_arg;
if (requires_value) {
s_part += "=<value>, " + short_prec() + short_arg + ":<value>";
}
}
std::string l_part = "";
if (long_arg != "-" && long_arg != "") {
l_part += long_prec() + long_arg;
if (requires_value) {
l_part += "=<value>, " + long_prec() + long_arg + ":<value>";
}
}
return {s_part, l_part};
}
std::vector<convention_features> windows_kv_argument_convention::get_features() const {
return {convention_features::ALLOW_LONG_TO_SHORT_FALLBACK,
convention_features::ALLOW_SHORT_TO_LONG_FALLBACK}; // interchangable
}
} // namespace argument_parser::conventions::implementations } // namespace argument_parser::conventions::implementations

View File

@@ -1,11 +1,14 @@
#include "argument_parser.hpp" #include "argument_parser.hpp"
#include <functional> #include <functional>
#include <iomanip>
#include <iostream> #include <iostream>
#include <optional> #include <optional>
#include <sstream> #include <sstream>
#include <string>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
class deferred_exec { class deferred_exec {
@@ -84,15 +87,56 @@ namespace argument_parser {
std::stringstream ss; std::stringstream ss;
ss << "Usage: " << program_name << " [OPTIONS]...\n"; ss << "Usage: " << program_name << " [OPTIONS]...\n";
size_t max_short_len = 0;
size_t max_long_len = 0;
struct arg_help_info_t {
std::vector<std::pair<std::string, std::string>> convention_parts;
std::string desc;
};
std::vector<arg_help_info_t> help_lines;
for (auto const &[id, arg] : argument_map) { for (auto const &[id, arg] : argument_map) {
auto short_arg = reverse_short_arguments.at(id); auto short_arg =
auto long_arg = reverse_long_arguments.at(id); reverse_short_arguments.find(id) != reverse_short_arguments.end() ? reverse_short_arguments.at(id) : "";
ss << "\t"; auto long_arg =
reverse_long_arguments.find(id) != reverse_long_arguments.end() ? reverse_long_arguments.at(id) : "";
std::vector<std::pair<std::string, std::string>> parts;
std::unordered_set<std::string> hasOnce;
for (auto const &convention : convention_types) { for (auto const &convention : convention_types) {
ss << convention->short_prec() << short_arg << ", " << convention->long_prec() << long_arg << "\t"; auto generatedParts = convention->make_help_text(short_arg, long_arg, arg.expects_parameter());
std::string combined = generatedParts.first + "|" + generatedParts.second;
if (hasOnce.find(combined) == hasOnce.end()) {
parts.push_back(generatedParts);
hasOnce.insert(combined);
if (generatedParts.first.length() > max_short_len) {
max_short_len = generatedParts.first.length();
}
if (generatedParts.second.length() > max_long_len) {
max_long_len = generatedParts.second.length();
}
} else {
parts.push_back({"", ""}); // trigger empty space in the help text
}
} }
ss << arg.help_text << "\n"; help_lines.push_back({parts, arg.help_text});
} }
for (auto const &line : help_lines) {
ss << "\t";
for (size_t i = 0; i < line.convention_parts.size(); ++i) {
auto const &parts = line.convention_parts[i];
if (i > 0) {
ss << " ";
}
ss << std::left << std::setw(static_cast<int>(max_short_len)) << parts.first << " "
<< std::setw(static_cast<int>(max_long_len)) << parts.second;
}
ss << "\t" << line.desc << "\n";
}
return ss.str(); return ss.str();
} }
@@ -143,20 +187,19 @@ namespace argument_parser {
if (extracted.second == "h" || extracted.second == "help") { if (extracted.second == "h" || extracted.second == "help") {
found_help = corresponding_argument; found_help = corresponding_argument;
continue; return true;
} }
found_arguments.emplace_back(extracted.second, corresponding_argument); found_arguments.emplace_back(extracted.second, corresponding_argument);
if (corresponding_argument.expects_parameter()) { if (corresponding_argument.expects_parameter()) {
if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) { if (convention_type->requires_next_token() && (it + 1) == parsed_arguments.end()) {
throw std::runtime_error("expected value for argument " + extracted.second); throw std::runtime_error("Expected value for argument " + extracted.second);
} }
values_for_arguments[extracted.second] = values_for_arguments[extracted.second] =
convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it);
} }
corresponding_argument.set_invoked(true);
return true; return true;
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n";
@@ -183,7 +226,7 @@ namespace argument_parser {
} }
void base_parser::invoke_arguments(std::unordered_map<std::string, std::string> const &values_for_arguments, void base_parser::invoke_arguments(std::unordered_map<std::string, std::string> const &values_for_arguments,
std::vector<std::pair<std::string, argument>> const &found_arguments, std::vector<std::pair<std::string, argument>> &found_arguments,
std::optional<argument> const &found_help) { std::optional<argument> const &found_help) {
if (found_help) { if (found_help) {
@@ -192,13 +235,14 @@ namespace argument_parser {
} }
std::stringstream error_stream; std::stringstream error_stream;
for (auto const &[key, value] : found_arguments) { for (auto &[key, value] : found_arguments) {
try { try {
if (value.expects_parameter()) { if (value.expects_parameter()) {
value.action->invoke_with_parameter(values_for_arguments.at(key)); value.action->invoke_with_parameter(values_for_arguments.at(key));
} else { } else {
value.action->invoke(); value.action->invoke();
} }
value.set_invoked(true);
} catch (const std::runtime_error &e) { } catch (const std::runtime_error &e) {
error_stream << "Argument " << key << " failed with: " << e.what() << "\n"; error_stream << "Argument " << key << " failed with: " << e.what() << "\n";
} }
@@ -255,26 +299,71 @@ namespace argument_parser {
void base_parser::place_argument(int id, argument const &arg, std::string const &short_arg, void base_parser::place_argument(int id, argument const &arg, std::string const &short_arg,
std::string const &long_arg) { std::string const &long_arg) {
argument_map[id] = arg; argument_map[id] = arg;
short_arguments[short_arg] = id; if (short_arg != "-") {
reverse_short_arguments[id] = short_arg; short_arguments[short_arg] = id;
long_arguments[long_arg] = id; reverse_short_arguments[id] = short_arg;
reverse_long_arguments[id] = long_arg; }
if (long_arg != "-") {
long_arguments[long_arg] = id;
reverse_long_arguments[id] = long_arg;
}
}
std::string get_one_name(std::string const &short_name, std::string const &long_name) {
std::string res{};
if (short_name != "-") {
res += short_name;
}
if (long_name != "-") {
if (!res.empty()) {
res += ", ";
}
res += long_name;
}
return res;
} }
void base_parser::check_for_required_arguments( void base_parser::check_for_required_arguments(
std::initializer_list<conventions::convention const *const> convention_types) { std::initializer_list<conventions::convention const *const> convention_types) {
std::vector<std::pair<std::string, std::string>> required_args; std::vector<std::tuple<std::string, std::string, bool>> required_args;
for (auto const &[key, arg] : argument_map) { for (auto const &[key, arg] : argument_map) {
if (arg.is_required() && !arg.is_invoked()) { if (arg.is_required() && !arg.is_invoked()) {
required_args.emplace_back<std::pair<std::string, std::string>>( auto short_arg = reverse_short_arguments.find(key) != reverse_short_arguments.end()
{reverse_short_arguments[key], reverse_long_arguments[key]}); ? reverse_short_arguments.at(key)
: "-";
auto long_arg = reverse_long_arguments.find(key) != reverse_long_arguments.end()
? reverse_long_arguments.at(key)
: "-";
required_args.emplace_back<std::tuple<std::string, std::string, bool>>(
{short_arg, long_arg, arg.expects_parameter()});
} }
} }
if (!required_args.empty()) { if (!required_args.empty()) {
std::cerr << "These arguments were expected but not provided: "; std::cerr << "These arguments were expected but not provided: \n";
for (auto const &[s, l] : required_args) { for (auto const &[s, l, p] : required_args) {
std::cerr << "[-" << s << ", --" << l << "] "; std::cerr << "\t" << get_one_name(s, l) << ": must be provided as one of [";
for (auto it = convention_types.begin(); it != convention_types.end(); ++it) {
auto generatedParts = (*it)->make_help_text(s, l, p);
std::string help_str = generatedParts.first;
if (!generatedParts.first.empty() && !generatedParts.second.empty()) {
help_str += " ";
}
help_str += generatedParts.second;
size_t last_not_space = help_str.find_last_not_of(" \t");
if (last_not_space != std::string::npos) {
help_str.erase(last_not_space + 1);
}
std::cerr << help_str;
if (it + 1 != convention_types.end()) {
std::cerr << ", ";
}
}
std::cerr << "]\n";
} }
std::cerr << "\n"; std::cerr << "\n";
display_help(convention_types); display_help(convention_types);