diff --git a/src/headers/conventions/base_convention.hpp b/src/headers/conventions/base_convention.hpp index b5cae2c..3c9463c 100644 --- a/src/headers/conventions/base_convention.hpp +++ b/src/headers/conventions/base_convention.hpp @@ -20,7 +20,7 @@ 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::string make_help_text(std::string const &short_arg, std::string const &long_arg, + 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; diff --git a/src/headers/conventions/implementations/gnu_argument_convention.hpp b/src/headers/conventions/implementations/gnu_argument_convention.hpp index 8867eb4..b79c689 100644 --- a/src/headers/conventions/implementations/gnu_argument_convention.hpp +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -14,7 +14,7 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; - std::string make_help_text(std::string const &short_arg, std::string const &long_arg, + 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; @@ -32,7 +32,7 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; - std::string make_help_text(std::string const &short_arg, std::string const &long_arg, + 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; diff --git a/src/headers/conventions/implementations/windows_argument_convention.hpp b/src/headers/conventions/implementations/windows_argument_convention.hpp index 4cfbdd3..5a6de0b 100644 --- a/src/headers/conventions/implementations/windows_argument_convention.hpp +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -18,7 +18,7 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; - std::string make_help_text(std::string const &short_arg, std::string const &long_arg, + 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; @@ -37,7 +37,7 @@ namespace argument_parser::conventions::implementations { std::string name() const override; std::string short_prec() const override; std::string long_prec() const override; - std::string make_help_text(std::string const &short_arg, std::string const &long_arg, + 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; diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 971f7c0..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{ @@ -132,19 +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 = ""; - if (short_arg != "-") { - help_text += short_arg; - } - - if (long_arg != "-") { - if (!help_text.empty()) { - help_text += ", "; - } - - help_text += long_arg; - } } if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && @@ -161,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: @@ -173,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 b798957..5422cd2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,4 @@ +#include "headers/parser/parsing_traits/traits.hpp" #include #define ALLOW_DASH_FOR_WINDOWS 0 @@ -22,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> { @@ -40,6 +45,8 @@ template <> struct argument_parser::parsing_traits::parser_trait struct argument_parser::parsing_traits::parser_trait> { @@ -52,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; }); @@ -131,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 @@ -159,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); diff --git a/src/source/conventions/implementations/gnu_argument_convention.cpp b/src/source/conventions/implementations/gnu_argument_convention.cpp index a0b0e93..6bd704d 100644 --- a/src/source/conventions/implementations/gnu_argument_convention.cpp +++ b/src/source/conventions/implementations/gnu_argument_convention.cpp @@ -40,25 +40,26 @@ namespace argument_parser::conventions::implementations { return {}; // no fallback allowed } - std::string gnu_argument_convention::make_help_text(std::string const &short_arg, std::string const &long_arg, - bool requires_value) const { - std::string res = ""; + 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 != "") { - res += short_prec() + short_arg; + s_part += short_prec() + short_arg; if (requires_value) { - res += " "; + s_part += " "; } } + + std::string l_part = ""; if (long_arg != "-" && long_arg != "") { - if (!res.empty()) { - res += ", "; - } - res += long_prec() + long_arg; + l_part += long_prec() + long_arg; if (requires_value) { - res += " "; + l_part += " "; } } - return res; + + return {s_part, l_part}; } } // namespace argument_parser::conventions::implementations @@ -97,26 +98,26 @@ namespace argument_parser::conventions::implementations { return "--"; } - 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 res = ""; + 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 != "") { - res += short_prec() + short_arg; + s_part += short_prec() + short_arg; if (requires_value) { - res += "="; - } - } - if (long_arg != "-" && long_arg != "") { - if (!res.empty()) { - res += ", "; - } - res += long_prec() + long_arg; - if (requires_value) { - res += "="; + s_part += "="; } } - return res; + 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 { diff --git a/src/source/conventions/implementations/windows_argument_convention.cpp b/src/source/conventions/implementations/windows_argument_convention.cpp index 59a799b..bfd9fd1 100644 --- a/src/source/conventions/implementations/windows_argument_convention.cpp +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -50,27 +50,25 @@ namespace argument_parser::conventions::implementations { return "/"; } - std::string windows_argument_convention::make_help_text(std::string const &short_arg, std::string const &long_arg, + std::pair windows_argument_convention::make_help_text(std::string const &short_arg, std::string const &long_arg, bool requires_value) const { - std::string res = ""; + std::string s_part = ""; if (short_arg != "-" && short_arg != "") { - res += short_prec() + short_arg; + s_part += short_prec() + short_arg; if (requires_value) { - res += " "; + s_part += " "; } } + std::string l_part = ""; if (long_arg != "-" && long_arg != "") { - if (!res.empty()) { - res += ", "; - } - res += long_prec() + long_arg; + l_part += long_prec() + long_arg; if (requires_value) { - res += " "; + l_part += " "; } } - return res; + return {s_part, l_part}; } std::vector windows_argument_convention::get_features() const { @@ -130,30 +128,25 @@ namespace argument_parser::conventions::implementations { return "/"; } - std::string windows_kv_argument_convention::make_help_text(std::string const &short_arg, + 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 res = ""; + std::string s_part = ""; if (short_arg != "-" && short_arg != "") { - res += short_prec() + short_arg; + s_part += short_prec() + short_arg; if (requires_value) { - res += "="; - res += ", " + short_prec() + short_arg; - res += ":"; + s_part += "=, " + short_prec() + short_arg + ":"; } } + std::string l_part = ""; if (long_arg != "-" && long_arg != "") { - if (!res.empty()) { - res += ", "; - } - res += long_prec() + long_arg; + l_part += long_prec() + long_arg; if (requires_value) { - res += "=" - ", " + - long_prec() + long_arg + ":"; + l_part += "=, " + long_prec() + long_arg + ":"; } } - return res; + + return {s_part, l_part}; } std::vector windows_kv_argument_convention::get_features() const { diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index fee3e31..0c42b69 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -1,6 +1,7 @@ #include "argument_parser.hpp" #include +#include #include #include #include @@ -86,23 +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.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) : ""; - ss << "\t"; + std::vector> parts; std::unordered_set hasOnce; for (auto const &convention : convention_types) { - auto generatedHelpText = convention->make_help_text(short_arg, long_arg, arg.expects_parameter()); - if (hasOnce.find(generatedHelpText) == hasOnce.end()) { - ss << generatedHelpText << "\t"; - hasOnce.insert(generatedHelpText); + 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(); } @@ -313,7 +347,18 @@ namespace argument_parser { 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) { - std::cerr << (*it)->make_help_text(s, l, p); + 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 << ", "; }