From 912eb6c76e712f36f4092612d0e35875a83eebf6 Mon Sep 17 00:00:00 2001 From: Gabriel Galli Date: Sat, 15 Feb 2025 15:26:30 -0300 Subject: [PATCH] feat: add assignments for all arg types --- include/exceptions.hpp | 6 +++ include/experimental/arg.hpp | 9 ++++ include/experimental/parsing.hpp | 87 +++++++++++++++++++++++++------- 3 files changed, 84 insertions(+), 18 deletions(-) diff --git a/include/exceptions.hpp b/include/exceptions.hpp index 2be70c8..0b03f74 100644 --- a/include/exceptions.hpp +++ b/include/exceptions.hpp @@ -78,6 +78,12 @@ class UnknownArgument : public UserError { UnknownArgument(std::string_view name) : UserError(fmt::format("Unknown argument `{}`", name)) {} }; +class WrongType : public UserError { +public: + WrongType(std::string_view name, std::string_view expected_type, std::string_view received_type) + : UserError(fmt::format("Argument `{}` is a known {}, but was provided as {}", name, expected_type, received_type)) {} +}; + } // namespace opzioni #endif // OPZIONI_EXCEPTIONS_H diff --git a/include/experimental/arg.hpp b/include/experimental/arg.hpp index 9214481..0ea29ab 100644 --- a/include/experimental/arg.hpp +++ b/include/experimental/arg.hpp @@ -12,6 +12,15 @@ struct ArgMeta { enum struct ArgType { POS, OPT, FLG }; +std::string_view ToString(ArgType at) noexcept { + switch (at) + { + case ArgType::POS: return "positional"; + case ArgType::OPT: return "option"; + case ArgType::FLG: return "flag"; + } +} + struct Arg { ArgType type = ArgType::POS; std::string_view name{}; diff --git a/include/experimental/parsing.hpp b/include/experimental/parsing.hpp index 50e9f7d..c93386d 100644 --- a/include/experimental/parsing.hpp +++ b/include/experimental/parsing.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "converters.hpp" @@ -102,30 +103,22 @@ struct ArgParser, TypeList> { auto const arg = std::string_view(args[index]); if (is_dash_dash(arg)) { // +1 to ignore the dash-dash - auto const rest = args.subspan(index + 1); - view.positionals.reserve(view.positionals.size() + rest.size()); - for (auto const &str : rest) { - view.positionals.emplace_back(str); + for (std::size_t offset = index + 1; offset < args.size();) { + offset += assign_positional(view, args.subspan(offset), current_positional_idx); + ++current_positional_idx; } - current_positional_idx += rest.size(); index = args.size(); // } else if (auto cmd = is_command(program, arg); cmd != nullptr) { // index += assign_command(map, args.subspan(index), *cmd); } else if (looks_positional(arg)) { - view.positionals.emplace_back(arg); + index += assign_positional(view, args.subspan(index), current_positional_idx); ++current_positional_idx; - ++index; - } else if (auto const option = try_parse_option(arg); option.value.has_value()) { - view.options[option.name] = *option.value; - index += 1; // TODO: tmply only accepting options with their values "glued" together + } else if (auto const option = try_parse_option(arg); !option.name.empty()) { + index += assign_option(view, option, args.subspan(index)); } else if (auto const flag = get_if_long_flag(arg); !flag.empty()) { - view.options[flag] = ""; - index += 1; + index += assign_long_flag(view, flag); } else if (auto const flags = get_if_short_flags(arg); !flags.empty()) { - for (decltype(flags)::size_type i = 0; i < flags.size(); ++i) { - view.options[flags.substr(i, 1)] = ""; - } - index += 1; + index += assign_short_flags(view, flags); } else { throw opzioni::UnknownArgument(arg); } @@ -134,6 +127,65 @@ struct ArgParser, TypeList> { return view; } + std::size_t assign_positional(ArgsView &view, std::span args, std::size_t cur_pos_idx) const { + if (cur_pos_idx + 1 > program.amount_pos) + throw opzioni::UnexpectedPositional(args[0], program.amount_pos); + view.positionals.emplace_back(args[0]); + return 1; + } + + std::size_t assign_option(ArgsView &view, ParsedOption const option, std::span args) const { + if (option.value) { + view.options[option.name] = *option.value; + return 1; + } + + auto const it = std::ranges::find_if(program.args, [&option](auto const &a) { return a.name == option.name; }); + if (it == program.args.end()) { + throw opzioni::UnknownArgument(option.name); + } + if (it->type != ArgType::OPT) { + throw opzioni::WrongType(option.name, ToString(it->type), ToString(ArgType::OPT)); + } + + if (args.size() > 1 && looks_positional(args[1])) { + view.options[option.name] = args[1]; + return 2; + } + + throw opzioni::MissingValue(option.name, 1, 0); + } + + std::size_t assign_long_flag(ArgsView &view, std::string_view const flag) const { + auto const it = std::ranges::find_if(program.args, [&flag](auto const &a) { return a.name == flag; }); + if (it == program.args.end()) { + throw opzioni::UnknownArgument(flag); + } + if (it->type != ArgType::FLG) { + throw opzioni::WrongType(flag, ToString(it->type), ToString(ArgType::FLG)); + } + + view.options[it->name] = ""; + return 1; + } + + std::size_t assign_short_flags(ArgsView &view, std::string_view const flags) const { + for (std::size_t i = 0; i < flags.size(); ++i) { + auto const flag = flags.substr(i, 1); + auto const it = std::ranges::find_if(program.args, [&flag](auto const &a) { return a.abbrev == flag; }); + if (it == program.args.end()) { + throw opzioni::UnknownArgument(flag); + } + if (it->type != ArgType::FLG) { + throw opzioni::WrongType(flag, ToString(it->type), ToString(ArgType::FLG)); + } + + // value lookup is by name, not abbrev + view.options[it->name] = ""; + } + return 1; + } + auto get_args_map(ArgsView const &view) const { auto map = ArgsMap, TypeList>{.exec_path = view.exec_path}; std::size_t idx = sizeof...(ArgTypes) - 1; @@ -267,8 +319,7 @@ constexpr ParsedOption try_parse_option(std::string_view const whole_arg) noexce } // has no value (long options cannot have "glued" values like `-O2`; next CLI argument could be it) - // TODO: return {whole_arg.substr(2), std::nullopt}; - return {"", std::nullopt}; // tmply considered not an option + return {whole_arg.substr(2), std::nullopt}; } // not an option