diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e21e8d --- /dev/null +++ b/README.md @@ -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`, `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 +#include +#include +#include // 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({ + {ShortArgument, "e"}, + {LongArgument, "echo"}, + {Action, argument_parser::helpers::make_parametered_action( + [](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({ + {ShortArgument, "g"}, + {LongArgument, "grep"}, + {HelpText, "Grep pattern"} + }); + + // A required flag + parser.add_argument({ + {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("file"); + auto pattern = p.get_optional("grep"); + + if (filename && pattern) { + std::cout << "Grepping " << filename.value() << " with pattern." << std::endl; + } + }); + + // Register Conventions + const std::initializer_list 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`. + +```cpp +struct Point { + int x, y; +}; + +template <> struct argument_parser::parsing_traits::parser_trait { + 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({ {LongArgument, "point"} }); +// auto point = parser.get_optional("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 . +``` diff --git a/src/headers/conventions/base_convention.hpp b/src/headers/conventions/base_convention.hpp index 96dc59e..3c9463c 100644 --- a/src/headers/conventions/base_convention.hpp +++ b/src/headers/conventions/base_convention.hpp @@ -1,11 +1,13 @@ #pragma once #include #include +#include #ifndef BASE_CONVENTION_HPP #define BASE_CONVENTION_HPP 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 }; using parsed_argument = std::pair; @@ -18,6 +20,9 @@ namespace argument_parser::conventions { virtual std::string name() const = 0; virtual std::string short_prec() const = 0; virtual std::string long_prec() const = 0; + virtual std::pair make_help_text(std::string const &short_arg, std::string const &long_arg, + bool requires_value) const = 0; + virtual std::vector get_features() const = 0; protected: base_convention() = default; diff --git a/src/headers/conventions/implementations/gnu_argument_convention.hpp b/src/headers/conventions/implementations/gnu_argument_convention.hpp index 986e6d0..b79c689 100644 --- a/src/headers/conventions/implementations/gnu_argument_convention.hpp +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -14,6 +14,10 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; + std::pair make_help_text(std::string const &short_arg, std::string const &long_arg, + bool requires_value) const override; + std::vector get_features() const override; + static gnu_argument_convention instance; private: @@ -28,6 +32,10 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; + std::pair make_help_text(std::string const &short_arg, std::string const &long_arg, + bool requires_value) const override; + std::vector get_features() const override; + static gnu_equal_argument_convention instance; private: diff --git a/src/headers/conventions/implementations/windows_argument_convention.hpp b/src/headers/conventions/implementations/windows_argument_convention.hpp index 06e7a5d..5a6de0b 100644 --- a/src/headers/conventions/implementations/windows_argument_convention.hpp +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -18,6 +18,10 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; + std::pair make_help_text(std::string const &short_arg, std::string const &long_arg, + bool requires_value) const override; + std::vector get_features() const override; + static windows_argument_convention instance; private: @@ -33,6 +37,10 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; + std::pair make_help_text(std::string const &short_arg, std::string const &long_arg, + bool requires_value) const override; + std::vector get_features() const override; + static windows_kv_argument_convention instance; private: diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp index 799a088..da89814 100644 --- a/src/headers/parser/argument_parser.hpp +++ b/src/headers/parser/argument_parser.hpp @@ -256,7 +256,7 @@ namespace argument_parser { std::optional &found_help); void invoke_arguments(std::unordered_map const &values_for_arguments, - std::vector> const &found_arguments, + std::vector> &found_arguments, std::optional const &found_help); void enforce_creation_thread(); diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 0298805..a67209c 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -1,4 +1,5 @@ #pragma once +#include "traits.hpp" #include #include #include @@ -103,6 +104,30 @@ namespace argument_parser::v2 { } private: + template struct has_format_hint { + private: + typedef char YesType[1]; + typedef char NoType[2]; + + template static YesType &test(decltype(&C::format_hint)); + template static NoType &test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(YesType); + }; + + template struct has_purpose_hint { + private: + typedef char YesType[1]; + typedef char NoType[2]; + + template static YesType &test(decltype(&C::purpose_hint)); + template static NoType &test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(YesType); + }; + template void add_argument_impl(ArgsMap const &argument_pairs) { std::unordered_map found_params{ @@ -120,10 +145,10 @@ namespace argument_parser::v2 { found_params[extended_add_argument_flags::LongArgument] = true; long_arg = get_or_throw(argument_pairs.at(add_argument_flags::LongArgument), "long"); if (short_arg.empty()) - short_arg = long_arg; + short_arg = "-"; } else { if (!short_arg.empty()) - long_arg = short_arg; + long_arg = "-"; } 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()) { help_text = get_or_throw(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() && @@ -150,10 +173,34 @@ namespace argument_parser::v2 { if constexpr (IsTyped) { switch (suggested_add) { case candidate_type::typed_action: + if (help_text.empty()) { + if constexpr (has_format_hint>::value && + has_purpose_hint>::value) { + auto format_hint = parsing_traits::parser_trait::format_hint; + auto purpose_hint = parsing_traits::parser_trait::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(&(*action)), required); break; case candidate_type::store_other: + if (help_text.empty()) { + if constexpr (has_format_hint>::value && + has_purpose_hint>::value) { + auto format_hint = parsing_traits::parser_trait::format_hint; + auto purpose_hint = parsing_traits::parser_trait::purpose_hint; + help_text = + "Accepts " + std::string(purpose_hint) + " in " + std::string(format_hint) + " format."; + } else { + help_text = "Accepts value."; + } + } + base::add_argument(short_arg, long_arg, help_text, required); break; default: @@ -162,10 +209,21 @@ namespace argument_parser::v2 { } else { switch (suggested_add) { 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(&(*action)), required); break; case candidate_type::store_boolean: + if (help_text.empty()) { + auto boolPurpose = parsing_traits::parser_trait::purpose_hint; + auto boolFormat = parsing_traits::parser_trait::format_hint; + help_text = + "Accepts " + std::string(boolPurpose) + " in " + std::string(boolFormat) + " format."; + } + base::add_argument(short_arg, long_arg, help_text, required); break; default: diff --git a/src/headers/parser/parsing_traits/traits.hpp b/src/headers/parser/parsing_traits/traits.hpp index 63189c3..6efd12e 100644 --- a/src/headers/parser/parsing_traits/traits.hpp +++ b/src/headers/parser/parsing_traits/traits.hpp @@ -5,29 +5,43 @@ #include namespace argument_parser::parsing_traits { + using hint_type = const char *; + template struct parser_trait { using type = T_; 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 { 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 { 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 { 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 { 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 { 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 diff --git a/src/main.cpp b/src/main.cpp index 67727e7..5422cd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,10 @@ +#include "headers/parser/parsing_traits/traits.hpp" #include #define ALLOW_DASH_FOR_WINDOWS 0 #include #include #include -#include #include #include #include @@ -23,12 +23,16 @@ template <> struct argument_parser::parsing_traits::parser_trait { int y = std::stoi(input.substr(comma_pos + 1)); 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 { static std::regex parse(const std::string &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> { @@ -41,6 +45,8 @@ template <> struct argument_parser::parsing_traits::parser_trait struct argument_parser::parsing_traits::parser_trait> { @@ -53,13 +59,16 @@ template <> struct argument_parser::parsing_traits::parser_trait conventions = { &argument_parser::conventions::gnu_argument_convention, &argument_parser::conventions::gnu_equal_argument_convention, - &argument_parser::conventions::windows_argument_convention, - &argument_parser::conventions::windows_equal_argument_convention}; + // &argument_parser::conventions::windows_argument_convention, + // &argument_parser::conventions::windows_equal_argument_convention +}; const auto echo = argument_parser::helpers::make_parametered_action( [](std::string const &text) { std::cout << text << std::endl; }); @@ -132,8 +141,7 @@ int v2Examples() { parser.add_argument( {{ShortArgument, "e"}, {LongArgument, "echo"}, {Action, echo}, {HelpText, "echoes given variable"}}); - parser.add_argument( - {{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_point}, {HelpText, "echoes given point"}}); + parser.add_argument({{ShortArgument, "ep"}, {LongArgument, "echo-point"}, {Action, echo_point}}); parser.add_argument({ // stores string for f/file flag @@ -160,6 +168,8 @@ int v2Examples() { {Required, true} // makes this flag required }); + parser.add_argument({{ShortArgument, "v"}, {LongArgument, "verbose"}}); + parser.on_complete(::run_grep); parser.on_complete(::run_store_point); @@ -168,8 +178,9 @@ int v2Examples() { } int main() { + return v2Examples(); + try { - return v2Examples(); } catch (std::exception const &e) { std::cerr << "Error: " << e.what() << std::endl; return -1; diff --git a/src/source/conventions/implementations/gnu_argument_convention.cpp b/src/source/conventions/implementations/gnu_argument_convention.cpp index 85baa88..6bd704d 100644 --- a/src/source/conventions/implementations/gnu_argument_convention.cpp +++ b/src/source/conventions/implementations/gnu_argument_convention.cpp @@ -35,6 +35,32 @@ namespace argument_parser::conventions::implementations { std::string gnu_argument_convention::long_prec() const { return "--"; } + + std::vector gnu_argument_convention::get_features() const { + return {}; // no fallback allowed + } + + std::pair 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 += " "; + } + } + + std::string l_part = ""; + if (long_arg != "-" && long_arg != "") { + l_part += long_prec() + long_arg; + if (requires_value) { + l_part += " "; + } + } + + return {s_part, l_part}; + } } // namespace argument_parser::conventions::implementations namespace argument_parser::conventions::implementations { @@ -72,4 +98,29 @@ namespace argument_parser::conventions::implementations { return "--"; } + std::pair 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 += "="; + } + } + + std::string l_part = ""; + if (long_arg != "-" && long_arg != "") { + l_part += long_prec() + long_arg; + if (requires_value) { + l_part += "="; + } + } + + return {s_part, l_part}; + } + + std::vector gnu_equal_argument_convention::get_features() const { + return {}; // no fallback allowed + } } // namespace argument_parser::conventions::implementations \ No newline at end of file diff --git a/src/source/conventions/implementations/windows_argument_convention.cpp b/src/source/conventions/implementations/windows_argument_convention.cpp index 91c9771..bfd9fd1 100644 --- a/src/source/conventions/implementations/windows_argument_convention.cpp +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -1,4 +1,5 @@ #include "windows_argument_convention.hpp" +#include "base_convention.hpp" #include namespace argument_parser::conventions::implementations { @@ -49,6 +50,31 @@ namespace argument_parser::conventions::implementations { return "/"; } + std::pair 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 += " "; + } + } + + std::string l_part = ""; + if (long_arg != "-" && long_arg != "") { + l_part += long_prec() + long_arg; + if (requires_value) { + l_part += " "; + } + } + + return {s_part, l_part}; + } + + std::vector 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 { @@ -101,4 +127,30 @@ namespace argument_parser::conventions::implementations { std::string windows_kv_argument_convention::long_prec() const { return "/"; } + + std::pair 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 += "=, " + short_prec() + short_arg + ":"; + } + } + + std::string l_part = ""; + if (long_arg != "-" && long_arg != "") { + l_part += long_prec() + long_arg; + if (requires_value) { + l_part += "=, " + long_prec() + long_arg + ":"; + } + } + + return {s_part, l_part}; + } + + std::vector 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 \ No newline at end of file diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index a14ed97..0c42b69 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -1,11 +1,14 @@ #include "argument_parser.hpp" #include +#include #include #include #include +#include #include #include +#include #include class deferred_exec { @@ -84,15 +87,56 @@ namespace argument_parser { std::stringstream ss; 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> convention_parts; + std::string desc; + }; + std::vector help_lines; + for (auto const &[id, arg] : argument_map) { - auto short_arg = reverse_short_arguments.at(id); - auto long_arg = reverse_long_arguments.at(id); - ss << "\t"; + auto short_arg = + reverse_short_arguments.find(id) != reverse_short_arguments.end() ? reverse_short_arguments.at(id) : ""; + auto long_arg = + reverse_long_arguments.find(id) != reverse_long_arguments.end() ? reverse_long_arguments.at(id) : ""; + + std::vector> parts; + std::unordered_set hasOnce; 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(max_short_len)) << parts.first << " " + << std::setw(static_cast(max_long_len)) << parts.second; + } + ss << "\t" << line.desc << "\n"; + } + return ss.str(); } @@ -143,20 +187,19 @@ namespace argument_parser { if (extracted.second == "h" || extracted.second == "help") { found_help = corresponding_argument; - continue; + return true; } found_arguments.emplace_back(extracted.second, corresponding_argument); if (corresponding_argument.expects_parameter()) { 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] = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); } - corresponding_argument.set_invoked(true); return true; } catch (const std::runtime_error &e) { 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 const &values_for_arguments, - std::vector> const &found_arguments, + std::vector> &found_arguments, std::optional const &found_help) { if (found_help) { @@ -192,13 +235,14 @@ namespace argument_parser { } std::stringstream error_stream; - for (auto const &[key, value] : found_arguments) { + for (auto &[key, value] : found_arguments) { try { if (value.expects_parameter()) { value.action->invoke_with_parameter(values_for_arguments.at(key)); } else { value.action->invoke(); } + value.set_invoked(true); } catch (const std::runtime_error &e) { 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, std::string const &long_arg) { argument_map[id] = arg; - short_arguments[short_arg] = id; - reverse_short_arguments[id] = short_arg; - long_arguments[long_arg] = id; - reverse_long_arguments[id] = long_arg; + if (short_arg != "-") { + short_arguments[short_arg] = id; + reverse_short_arguments[id] = short_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( std::initializer_list convention_types) { - std::vector> required_args; + std::vector> required_args; for (auto const &[key, arg] : argument_map) { if (arg.is_required() && !arg.is_invoked()) { - required_args.emplace_back>( - {reverse_short_arguments[key], reverse_long_arguments[key]}); + auto short_arg = reverse_short_arguments.find(key) != reverse_short_arguments.end() + ? 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>( + {short_arg, long_arg, arg.expects_parameter()}); } } if (!required_args.empty()) { - std::cerr << "These arguments were expected but not provided: "; - for (auto const &[s, l] : required_args) { - std::cerr << "[-" << s << ", --" << l << "] "; + std::cerr << "These arguments were expected but not provided: \n"; + for (auto const &[s, l, p] : required_args) { + 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"; display_help(convention_types);