diff --git a/examples/test/main.cpp b/examples/test/main.cpp index 9d5feef..3b980a3 100644 --- a/examples/test/main.cpp +++ b/examples/test/main.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include using argument = argument_parser::builder::argument<>; @@ -18,9 +20,7 @@ auto echo(std::string const &s) -> void { using namespace argument_parser::parsing_traits; constexpr hint_type vector_purpose_hint = "vector of "; - -template class parser_trait> { -public: +template struct parser_trait> { static std::vector parse(std::string const &s) { std::vector result; std::stringstream ss(s); @@ -96,9 +96,61 @@ auto main() -> int { }) .build(parser); - parser.handle_arguments({&argument_parser::conventions::gnu_argument_convention}); + auto accumulate_vec = + argument::start().long_argument("vecstr1").short_argument("vs1").accumulate().build_and_get(parser); - std::cout << "captured value: " << captured_value << '\n'; + parser.add_argument>({ + {LongArgument, "accumulate"}, + {HelpText, "accumulates given ints into the vector (flag ver)"}, + {Accumulate, true}, + }); + + std::vector captured_vec; + parser.add_argument>({ + {LongArgument, "accumulate2"}, + {HelpText, "accumulates given ints into the vector (ref ver)"}, + {Accumulate, &captured_vec}, + }); + + std::vector captured_vec2; + parser.add_argument>({ + {LongArgument, "accumulate3"}, + {HelpText, "accumulates given ints into the vector (ref ver)"}, + {Accumulate, true}, + {Reference, &captured_vec2}, + }); + + parser.on_complete([](argument_parser::base_parser const &p) { + if (const auto value = p.get_optional>("accumulate"); value.has_value()) { + std::cout << "accumulate: "; + for (auto const &str : *value) { + std::cout << str << '\n'; + } + } + }); + + parser.handle_arguments({&argument_parser::conventions::gnu_argument_convention, + &argument_parser::conventions::windows_argument_convention}); + if (!captured_vec.empty()) { + std::cout << "accumulate2: "; + for (auto const &str : captured_vec) { + std::cout << str << '\n'; + } + } + + if (!captured_vec2.empty()) { + std::cout << "accumulate3: "; + for (auto const &str : captured_vec2) { + std::cout << str << '\n'; + } + } + + if (accumulate_vec) { + std::cout << "accumulate_vec: "; + for (auto const &str : *accumulate_vec) { + std::cout << str << '\n'; + } + } return 0; } diff --git a/src/headers/conventions/base_convention.hpp b/src/headers/conventions/base_convention.hpp index 784a15f..6bd1ff1 100644 --- a/src/headers/conventions/base_convention.hpp +++ b/src/headers/conventions/base_convention.hpp @@ -14,15 +14,15 @@ namespace argument_parser::conventions { class base_convention { public: - virtual std::string extract_value(std::string const &) const = 0; - virtual parsed_argument get_argument(std::string const &) const = 0; - virtual bool requires_next_token() const = 0; - virtual std::string name() const = 0; - virtual std::string short_prec() const = 0; - virtual std::string long_prec() const = 0; - virtual std::pair + [[nodiscard]] virtual std::string extract_value(std::string const &) const = 0; + [[nodiscard]] virtual parsed_argument get_argument(std::string const &) const = 0; + [[nodiscard]] virtual bool requires_next_token() const = 0; + [[nodiscard]] virtual std::string name() const = 0; + [[nodiscard]] virtual std::string short_prec() const = 0; + [[nodiscard]] virtual std::string long_prec() const = 0; + [[nodiscard]] 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; + [[nodiscard]] 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 c2082ef..9244431 100644 --- a/src/headers/conventions/implementations/gnu_argument_convention.hpp +++ b/src/headers/conventions/implementations/gnu_argument_convention.hpp @@ -8,15 +8,16 @@ namespace argument_parser::conventions::implementations { class gnu_argument_convention : public base_convention { public: - parsed_argument get_argument(std::string const &raw) const override; - std::string extract_value(std::string const & /*raw*/) const override; - bool requires_next_token() const override; - 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, + virtual ~gnu_argument_convention() = default; + [[nodiscard]] parsed_argument get_argument(std::string const &raw) const override; + [[nodiscard]] std::string extract_value(std::string const & /*raw*/) const override; + [[nodiscard]] bool requires_next_token() const override; + [[nodiscard]] std::string name() const override; + [[nodiscard]] std::string short_prec() const override; + [[nodiscard]] std::string long_prec() const override; + [[nodiscard]] 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; + [[nodiscard]] std::vector get_features() const override; static gnu_argument_convention instance; @@ -26,15 +27,16 @@ namespace argument_parser::conventions::implementations { class gnu_equal_argument_convention : public base_convention { public: - parsed_argument get_argument(std::string const &raw) const override; - std::string extract_value(std::string const &raw) const override; - bool requires_next_token() const override; - 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, + virtual ~gnu_equal_argument_convention() = default; + [[nodiscard]] parsed_argument get_argument(std::string const &raw) const override; + [[nodiscard]] std::string extract_value(std::string const &raw) const override; + [[nodiscard]] bool requires_next_token() const override; + [[nodiscard]] std::string name() const override; + [[nodiscard]] std::string short_prec() const override; + [[nodiscard]] std::string long_prec() const override; + [[nodiscard]] 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; + [[nodiscard]] std::vector get_features() const override; static gnu_equal_argument_convention instance; diff --git a/src/headers/conventions/implementations/windows_argument_convention.hpp b/src/headers/conventions/implementations/windows_argument_convention.hpp index 80f54fa..8e47fd5 100644 --- a/src/headers/conventions/implementations/windows_argument_convention.hpp +++ b/src/headers/conventions/implementations/windows_argument_convention.hpp @@ -5,22 +5,23 @@ #define WINDOWS_ARGUMENT_CONVENTION_HPP #ifndef ALLOW_DASH_FOR_WINDOWS -#define ALLOW_DASH_FOR_WINDOWS 1 +#define ALLOW_DASH_FOR_WINDOWS true #endif namespace argument_parser::conventions::implementations { class windows_argument_convention : public base_convention { public: + virtual ~windows_argument_convention() = default; explicit windows_argument_convention(bool accept_dash = true); - parsed_argument get_argument(std::string const &raw) const override; - std::string extract_value(std::string const & /*raw*/) const override; - bool requires_next_token() const override; - 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, + [[nodiscard]] parsed_argument get_argument(std::string const &raw) const override; + [[nodiscard]] std::string extract_value(std::string const & /*raw*/) const override; + [[nodiscard]] bool requires_next_token() const override; + [[nodiscard]] std::string name() const override; + [[nodiscard]] std::string short_prec() const override; + [[nodiscard]] std::string long_prec() const override; + [[nodiscard]] 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; + [[nodiscard]] std::vector get_features() const override; static windows_argument_convention instance; @@ -30,16 +31,17 @@ namespace argument_parser::conventions::implementations { class windows_kv_argument_convention : public base_convention { public: + virtual ~windows_kv_argument_convention() = default; explicit windows_kv_argument_convention(bool accept_dash = true); - parsed_argument get_argument(std::string const &raw) const override; - std::string extract_value(std::string const &raw) const override; - bool requires_next_token() const override; - 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, + [[nodiscard]] parsed_argument get_argument(std::string const &raw) const override; + [[nodiscard]] std::string extract_value(std::string const &raw) const override; + [[nodiscard]] bool requires_next_token() const override; + [[nodiscard]] std::string name() const override; + [[nodiscard]] std::string short_prec() const override; + [[nodiscard]] std::string long_prec() const override; + [[nodiscard]] 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; + [[nodiscard]] std::vector get_features() const override; static windows_kv_argument_convention instance; @@ -48,9 +50,9 @@ namespace argument_parser::conventions::implementations { }; inline windows_argument_convention windows_argument_convention::instance = - windows_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); + windows_argument_convention(ALLOW_DASH_FOR_WINDOWS); inline windows_kv_argument_convention windows_kv_argument_convention::instance = - windows_kv_argument_convention(bool(ALLOW_DASH_FOR_WINDOWS)); + windows_kv_argument_convention(ALLOW_DASH_FOR_WINDOWS); } // namespace argument_parser::conventions::implementations namespace argument_parser::conventions { diff --git a/src/headers/parser/argument_builder.hpp b/src/headers/parser/argument_builder.hpp index d372f03..80fb193 100644 --- a/src/headers/parser/argument_builder.hpp +++ b/src/headers/parser/argument_builder.hpp @@ -2,6 +2,7 @@ #include "argument_parser.hpp" #include +#include #include #include @@ -14,7 +15,15 @@ namespace argument_parser::builder { 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 value_mode { + unresolved, + store, + flag, + reference, + accumulate, + nonparametered_action, + parametered_action + }; enum class extra_capability : unsigned { Store = static_cast(v2_flag::Reference) + 1, Flag }; @@ -34,12 +43,13 @@ namespace argument_parser::builder { 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 accumulate = bit(v2_flag::Accumulate); 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 mask_type value_mode_group = action | reference | accumulate | store | flag; + constexpr mask_type initial = short_argument | long_argument | positional | help_text | action | required | + reference | accumulate | store | flag; constexpr auto has(mask_type mask, mask_type capability) -> bool { return (mask & capability) == capability; @@ -62,6 +72,39 @@ namespace argument_parser::builder { } } // namespace builder_mask + template class container { + public: + store_type get() const { + return m_container.get(); + } + + store_type &operator*() { + return m_container.operator*(); + } + + store_type *operator->() { + return m_container.operator->(); + } + + operator bool() { + return m_container.operator bool(); + } + + private: + container() = default; + explicit container(store_type *ptr) { + m_container = std::shared_ptr(ptr, [](store_type *) { + /* noop. we don't own this reference, so it is not ours to free, but still use std::shared_ptr to manage + * it. */ + }); + } + void set_container(store_type const &container) { + m_container = std::make_shared(container); + } + std::shared_ptr m_container; + template friend class argument; + }; + template class argument { public: using mask_type = builder_mask::mask_type; @@ -169,6 +212,34 @@ namespace argument_parser::builder { return next; } + template = 0> + auto accumulate() const + -> argument> { + using vector_type = std::vector; + using next_argument = + argument; + + next_argument next{*this}; + next.m_value_mode = value_mode::accumulate; + return next; + } + + template = 0, typename T> + auto accumulate(T &value) const + -> argument { + static_assert(argument_parser::v2::deducers::is_vector_v, + "accumulate(target) requires a std::vector target."); + + using next_argument = argument; + + next_argument next{*this}; + next.m_reference = std::addressof(value); + next.m_value_mode = value_mode::accumulate; + return next; + } + template = 0> auto flag() const -> argument { @@ -246,6 +317,12 @@ namespace argument_parser::builder { return; } break; + case value_mode::accumulate: + if constexpr (!std::is_same_v) { + build_accumulate(parser); + return; + } + break; case value_mode::parametered_action: if constexpr (!std::is_same_v) { build_parametered_action(parser); @@ -264,6 +341,35 @@ namespace argument_parser::builder { throw std::logic_error("The builder reached build() without a supported terminal value mode."); } + template = 0> + auto build_and_get(argument_parser::v2::base_parser &parser) const -> container { + assert_has_identifier(); + switch (m_value_mode) { + case value_mode::store: + case value_mode::flag: + case value_mode::unresolved: + case value_mode::accumulate: + build(parser); + break; + default: + throw std::logic_error("The builder reached build() without a supported terminal value mode."); + } + + if (m_value_mode == value_mode::accumulate && m_reference != nullptr) { + return container(m_reference); + } + + std::string lk = lookup_key(); + container container; + parser.on_complete([lk, &container](base_parser const &p) { + auto value = p.get_optional(lk); + if (value.has_value()) { + container.set_container(*value); + } + }); + return container; + } + private: argument() = default; @@ -276,9 +382,9 @@ namespace argument_parser::builder { template using typed_map = - std::unordered_map>; + std::unordered_map>; - using non_typed_map = std::unordered_map; + using non_typed_map = std::unordered_map; auto is_positional() const -> bool { return !m_positional_name.empty(); @@ -359,7 +465,6 @@ namespace argument_parser::builder { auto build_reference(argument_parser::v2::base_parser &parser) const -> void { auto pairs = make_typed_pairs(); auto *target = m_reference; - auto key = lookup_key(); if (target == nullptr) { throw std::logic_error("reference() was selected without a target."); @@ -369,6 +474,16 @@ namespace argument_parser::builder { parser.template add_argument(pairs); } + auto build_accumulate(argument_parser::v2::base_parser &parser) const -> void { + auto pairs = make_typed_pairs(); + if (m_reference != nullptr) { + pairs[argument_parser::v2::flags::Accumulate] = m_reference; + } else { + pairs[argument_parser::v2::flags::Accumulate] = true; + } + parser.template add_argument(pairs); + } + auto build_parametered_action(argument_parser::v2::base_parser &parser) const -> void { auto const *typed_action = dynamic_cast const *>(m_action.get()); diff --git a/src/headers/parser/argument_parser.hpp b/src/headers/parser/argument_parser.hpp index 53a1d68..f218e27 100644 --- a/src/headers/parser/argument_parser.hpp +++ b/src/headers/parser/argument_parser.hpp @@ -1,3 +1,5 @@ +// ReSharper disable CppFunctionIsNotImplemented +// ReSharper disable All #pragma once #ifndef ARGUMENT_PARSER_HPP #define ARGUMENT_PARSER_HPP @@ -64,9 +66,9 @@ namespace argument_parser { copyable_atomic &operator=(copyable_atomic &&other) noexcept = default; ~copyable_atomic() = default; - T operator=(T desired) noexcept { + copyable_atomic& operator=(T desired) noexcept { store(desired); - return desired; + return *this; } operator T() const noexcept { @@ -79,7 +81,7 @@ namespace argument_parser { } } - T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { + [[nodiscard]] T load(std::memory_order order = std::memory_order_seq_cst) const noexcept { return value ? value->load(order) : T{}; } @@ -124,7 +126,7 @@ namespace argument_parser { T parsed_value = parsing_traits::parser_trait::parse(param); parse_success = true; invoke(parsed_value); - } catch (const std::runtime_error &e) { + } catch (const std::runtime_error &_) { if (!parse_success) { auto [format_hint, purpose_hint] = get_trait_hints(); if (purpose_hint.empty()) @@ -203,6 +205,7 @@ namespace argument_parser { [[nodiscard]] bool expects_parameter() const; [[nodiscard]] std::string get_help_text() const; [[nodiscard]] bool is_positional() const; + [[nodiscard]] bool is_positional_accumulator() const; [[nodiscard]] std::optional get_position_index() const; private: @@ -210,6 +213,7 @@ namespace argument_parser { void set_invoked(bool val); void set_help_text(std::string const &text); void set_positional(bool val); + void set_positional_accumulator(bool val); void set_position_index(std::optional idx); friend class base_parser; @@ -221,6 +225,7 @@ namespace argument_parser { bool invoked; std::string help_text; bool positional = false; + bool positional_accumulator = false; std::optional position_index = std::nullopt; }; @@ -279,6 +284,13 @@ namespace argument_parser { base_add_positional_argument(name, help_text, required, position); } + template + void add_positional_accumulator(std::string const &name, std::string const &help_text, + parametered_action const &action, bool required, + std::optional position = std::nullopt) { + base_add_positional_argument(name, help_text, action, required, position, true); + } + void on_complete(std::function const &action); template std::optional get_optional(std::string const &arg) const { @@ -317,28 +329,39 @@ namespace argument_parser { return _current_conventions; } + std::unordered_map &ref_stored_arguments() { + return stored_arguments; + } + + void on_complete(std::function const &handler, bool to_start); + private: + struct found_argument { + std::string key; + argument arg; + std::optional value = std::nullopt; + }; + bool test_conventions(std::initializer_list convention_types, - std::unordered_map &values_for_arguments, - std::vector> &found_arguments, + std::vector &found_arguments, std::optional &found_help, std::vector::iterator &it, std::stringstream &error_stream); void extract_arguments(std::initializer_list convention_types, - std::unordered_map &values_for_arguments, - std::vector> &found_arguments, + std::vector &found_arguments, std::optional &found_help); - void invoke_arguments(std::unordered_map const &values_for_arguments, - std::vector> &found_arguments, + void invoke_arguments(std::vector &found_arguments, std::optional const &found_help); - void enforce_creation_thread(); + void enforce_creation_thread() const; void assert_argument_not_exist(std::string const &short_arg, std::string const &long_arg) const; void assert_positional_not_exist(std::string const &name) const; + void assert_can_place_positional(int id, std::optional position, bool accumulator) const; static void set_argument_status(bool is_required, std::string const &help_text, argument &arg); void place_argument(int id, argument const &arg, std::string const &short_arg, std::string const &long_arg); void place_positional_argument(int id, argument const &arg, std::string const &name, - std::optional position); + std::optional position, bool accumulator = false); + [[nodiscard]] std::optional next_positional_slot(size_t start) const; template void base_add_argument(std::string const &short_arg, std::string const &long_arg, std::string const &help_text, @@ -373,14 +396,15 @@ namespace argument_parser { template void base_add_positional_argument(std::string const &name, std::string const &help_text, ActionType const &action, bool required, - std::optional position = std::nullopt) { + std::optional position = std::nullopt, bool accumulator = false) { assert_positional_not_exist(name); int id = id_counter.fetch_add(1); argument arg(id, name, action); set_argument_status(required, help_text, arg); arg.set_positional(true); + arg.set_positional_accumulator(accumulator); arg.set_position_index(position); - place_positional_argument(id, arg, name, position); + place_positional_argument(id, arg, name, position, accumulator); } template diff --git a/src/headers/parser/fake_parser.hpp b/src/headers/parser/fake_parser.hpp index 47ac13b..75650b6 100644 --- a/src/headers/parser/fake_parser.hpp +++ b/src/headers/parser/fake_parser.hpp @@ -25,8 +25,8 @@ namespace argument_parser { public: fake_parser() = default; fake_parser(std::string program_name, std::vector const &arguments); - fake_parser(std::string const &program_name, std::vector &&arguments); - fake_parser(std::string const &program_name, std::initializer_list const &arguments); + fake_parser(std::string program_name, std::vector &&arguments); + fake_parser(std::string program_name, std::initializer_list const &arguments); void set_program_name(std::string const &program_name); void set_parsed_arguments(std::vector const &parsed_arguments); diff --git a/src/headers/parser/parser_v2.hpp b/src/headers/parser/parser_v2.hpp index 7e8b8e4..24a10fe 100644 --- a/src/headers/parser/parser_v2.hpp +++ b/src/headers/parser/parser_v2.hpp @@ -2,7 +2,6 @@ #include "traits.hpp" #include #include -#include #include #include #include @@ -23,7 +22,8 @@ namespace argument_parser::v2 { HelpText, Action, Required, - Reference + Reference, + Accumulate }; namespace flags { @@ -35,9 +35,27 @@ namespace argument_parser::v2 { 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 Reference = add_argument_flags::Reference; + constexpr static inline add_argument_flags Accumulate = add_argument_flags::Accumulate; } // namespace flags - class base_parser : private argument_parser::base_parser { + namespace deducers { + template struct has_value_type : std::false_type {}; + template struct has_value_type> : std::true_type {}; + + template struct is_vector { + static constexpr bool test() { + if constexpr (has_value_type::value) { + return std::is_same_v>; + } else { + return false; + } + } + }; + + template constexpr bool is_vector_v = is_vector::test(); + } // namespace deducers + + class base_parser : argument_parser::base_parser { public: template using typed_flag_value = std::variant, bool, int, T *>; using non_typed_flag_value = std::variant; @@ -104,7 +122,7 @@ namespace argument_parser::v2 { void prepare_help_flag(bool should_exit = true) { add_argument({{flags::ShortArgument, "h"}, {flags::LongArgument, "help"}, - {flags::Action, helpers::make_non_parametered_action([this, should_exit]() { + {flags::Action, helpers::make_non_parametered_action([this, should_exit] { this->display_help(this->current_conventions()); if (should_exit) { std::exit(0); @@ -127,12 +145,13 @@ namespace argument_parser::v2 { std::string short_arg, long_arg, help_text; std::unique_ptr action; bool required = false; + bool accumulates = false; - if (argument_pairs.find(add_argument_flags::ShortArgument) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::ShortArgument)) { found_params[extended_add_argument_flags::ShortArgument] = true; short_arg = get_or_throw(argument_pairs.at(add_argument_flags::ShortArgument), "short"); } - if (argument_pairs.find(add_argument_flags::LongArgument) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::LongArgument)) { 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()) @@ -142,20 +161,17 @@ namespace argument_parser::v2 { long_arg = "-"; } - if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::Action)) { found_params[extended_add_argument_flags::Action] = true; action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); } - if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { - help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); - } + help_text = read_help_text(argument_pairs); + required = read_required(argument_pairs); - if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && - get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { - required = true; - } + bool ref_mode = false; - if (argument_pairs.find(add_argument_flags::Reference) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::Reference)) { + ref_mode = true; if (!IsTyped) { throw std::logic_error("Reference argument must be typed"); } @@ -165,14 +181,52 @@ namespace argument_parser::v2 { auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); if (action) { throw std::logic_error("Cannot use both action and reference for the same argument"); - } else { - action = helpers::make_parametered_action([ref](T const &t) { *ref = t; }).clone(); } + action = make_reference_action(ref); } else { throw std::logic_error("Reference argument must not be void"); } } + if (has_flag(argument_pairs, add_argument_flags::Accumulate)) { + if (!IsTyped) + throw std::logic_error("Accumulate argument must be typed"); + + found_params[extended_add_argument_flags::Action] = true; + accumulates = true; + if constexpr (!std::is_same_v) { + if constexpr (!deducers::is_vector_v) { + throw std::logic_error("Expected vector (type does not have value_type member)"); + } else { + if (action && !ref_mode) { + throw std::logic_error("Cannot use both action and accumulate for the same argument"); + } + + action = make_accumulate_action(argument_pairs, ref_mode, short_arg, long_arg); + } + } else { + throw std::logic_error("Accumulate argument must not be void"); + } + } + + if (accumulates) { + if constexpr (!std::is_same_v && deducers::is_vector_v) { + if (suggest_candidate(found_params) == candidate_type::unknown) { + throw std::runtime_error( + "Could not match any add argument overload to given parameters. Are you " + "missing some required parameter?"); + } + if (help_text.empty()) { + help_text = "Accepts repeated values."; + } + + base::add_argument( + short_arg, long_arg, help_text, + *static_cast *>(&(*action)), required); + return; + } + } + auto suggested_add = suggest_candidate(found_params); if (suggested_add == candidate_type::unknown) { throw std::runtime_error("Could not match any add argument overload to given parameters. Are you " @@ -238,52 +292,67 @@ namespace argument_parser::v2 { default: throw std::runtime_error( "Could not match the arguments against any overload. The suggested candidate was: " + - std::to_string((int(suggested_add)))); + std::to_string(static_cast(suggested_add))); } } } template void add_positional_argument_impl(ArgsMap const &argument_pairs) { - std::string positional_name = + auto positional_name = get_or_throw(argument_pairs.at(add_argument_flags::Positional), "positional"); - std::string help_text; std::unique_ptr action; bool required = false; - std::optional position = std::nullopt; + bool ref_mode = false; + bool accumulates = false; - if (argument_pairs.find(add_argument_flags::Action) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::Action)) { action = get_or_throw(argument_pairs.at(add_argument_flags::Action), "action").clone(); } - if (argument_pairs.find(add_argument_flags::HelpText) != argument_pairs.end()) { - help_text = get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); - } - if (argument_pairs.find(add_argument_flags::Required) != argument_pairs.end() && - get_or_throw(argument_pairs.at(add_argument_flags::Required), "required")) { - required = true; - } - if (argument_pairs.find(add_argument_flags::Position) != argument_pairs.end()) { - position = get_or_throw(argument_pairs.at(add_argument_flags::Position), "position"); - } + std::string help_text = read_help_text(argument_pairs); + required = read_required(argument_pairs); + std::optional position = read_position(argument_pairs); - if (argument_pairs.find(add_argument_flags::Reference) != argument_pairs.end()) { + if (has_flag(argument_pairs, add_argument_flags::Reference)) { + ref_mode = true; if (!IsTyped) { throw std::logic_error("Reference argument must be typed"); } if constexpr (!std::is_same_v) { - auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); - if (action) { - throw std::logic_error("Cannot use both action and reference for the same argument"); - } else { - action = helpers::make_parametered_action([ref](T const &t) { *ref = t; }).clone(); + if (!has_flag(argument_pairs, add_argument_flags::Accumulate)) { + auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); + if (action) { + throw std::logic_error("Cannot use both action and reference for the same argument"); + } + + action = make_reference_action(ref); } } else { throw std::logic_error("Reference argument must not be void"); } } + if (has_flag(argument_pairs, add_argument_flags::Accumulate)) { + if (!IsTyped) + throw std::logic_error("Accumulate positional argument must be typed"); + + accumulates = true; + if constexpr (!std::is_same_v) { + if constexpr (!deducers::is_vector_v) { + throw std::logic_error("Expected vector (type does not have value_type member)"); + } else { + if (action && !ref_mode) { + throw std::logic_error("Cannot use both action and accumulate for the same argument"); + } + action = make_accumulate_action(argument_pairs, ref_mode, positional_name); + } + } else { + throw std::logic_error("Accumulate argument must not be void"); + } + } + if (help_text.empty()) { if constexpr (IsTyped) { if constexpr (internal::sfinae::has_format_hint>::value && @@ -300,15 +369,24 @@ namespace argument_parser::v2 { } } + if (accumulates) { + if constexpr (!std::is_same_v && deducers::is_vector_v) { + base::add_positional_accumulator( + positional_name, help_text, + *static_cast *>(&(*action)), required, position); + return; + } + } + if constexpr (IsTyped) { if (action) { base::add_positional_argument(positional_name, help_text, *static_cast(&(*action)), required, position); } else { - base::template add_positional_argument(positional_name, help_text, required, position); + base::add_positional_argument(positional_name, help_text, required, position); } } else { - base::template add_positional_argument(positional_name, help_text, required, position); + base::add_positional_argument(positional_name, help_text, required, position); } } @@ -319,11 +397,7 @@ namespace argument_parser::v2 { template bool satisfies_at_least_one(std::array const &arr, std::unordered_map const &map) { - for (const auto &req : arr) { - if (map.find(req) != map.end()) - return true; - } - return false; + return std::any_of(arr.begin(), arr.end(), [&map](T const &entry) { return map.find(entry) != map.end(); }); } candidate_type suggest_candidate(std::unordered_map const &available_vars) { @@ -335,8 +409,7 @@ namespace argument_parser::v2 { if (available_vars.find(extended_add_argument_flags::Action) != available_vars.end()) { if (available_vars.at(extended_add_argument_flags::IsTyped)) return candidate_type::typed_action; - else - return candidate_type::non_typed_action; + return candidate_type::non_typed_action; } if (available_vars.at(extended_add_argument_flags::IsTyped)) @@ -344,6 +417,143 @@ namespace argument_parser::v2 { return candidate_type::store_boolean; } + template static bool has_flag(ArgsMap const &argument_pairs, add_argument_flags flag) { + return argument_pairs.find(flag) != argument_pairs.end(); + } + + template std::string read_help_text(ArgsMap const &argument_pairs) { + if (has_flag(argument_pairs, add_argument_flags::HelpText)) { + return get_or_throw(argument_pairs.at(add_argument_flags::HelpText), "help"); + } + return ""; + } + + template bool read_required(ArgsMap const &argument_pairs) { + return has_flag(argument_pairs, add_argument_flags::Required) && + get_or_throw(argument_pairs.at(add_argument_flags::Required), "required"); + } + + template std::optional read_position(ArgsMap const &argument_pairs) { + if (has_flag(argument_pairs, add_argument_flags::Position)) { + return get_or_throw(argument_pairs.at(add_argument_flags::Position), "position"); + } + return std::nullopt; + } + + template + std::variant get_either_or_throw(typed_flag_value const &v, std::string_view key) { + if (auto p = std::get_if(&v)) + return *p; + if (auto p = std::get_if(&v)) + return *p; + throw std::invalid_argument(std::string("variant type mismatch for key: ") + std::string(key)); + } + + template std::unique_ptr make_reference_action(T *target) { + return helpers::make_parametered_action([target](T const &value) { *target = value; }).clone(); + } + + template std::unique_ptr make_accumulate_ref_action(Vector *target) { + using Value = typename Vector::value_type; + return helpers::make_parametered_action( + [target](Value const &value) { target->emplace_back(value); }) + .clone(); + } + + template + void store_accumulated_on_complete(std::string short_arg, std::string long_arg, + std::shared_ptr accumulation_target) { + on_complete( + [this, short_arg = std::move(short_arg), long_arg = std::move(long_arg), + accumulation_target](auto const &) { + if (accumulation_target->empty()) { + return; + } + + const auto sid = this->find_argument_id(short_arg); + const auto lid = this->find_argument_id(long_arg); + + if (const auto id = sid ? *sid : (lid ? *lid : -1); id != -1) { + this->ref_stored_arguments()[id] = *accumulation_target; + } + }, + true); + } + + template + void store_accumulated_on_complete(std::string positional_name, std::shared_ptr accumulation_target) { + on_complete( + [this, positional_name = std::move(positional_name), accumulation_target](auto const &) { + if (accumulation_target->empty()) { + return; + } + + auto id = this->find_argument_id(positional_name); + if (id.has_value()) { + this->ref_stored_arguments()[*id] = *accumulation_target; + } + }, + true); + } + + template + std::unique_ptr make_accumulate_action(ArgsMap const &argument_pairs, bool ref_mode, + std::string const &short_arg, std::string const &long_arg) { + if (ref_mode) { + auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); + return make_accumulate_ref_action(ref); + } + + auto accumulate = + get_either_or_throw(argument_pairs.at(add_argument_flags::Accumulate), "accumulate"); + + return std::visit( + [this, short_arg, long_arg](auto &&acc) -> std::unique_ptr { + using V = std::decay_t; + if constexpr (std::is_same_v) { + if (!acc) { + throw std::logic_error("Accumulate flag must be true when used as a bool"); + } + + auto accumulation_target = std::make_shared(); + store_accumulated_on_complete(short_arg, long_arg, accumulation_target); + return make_accumulate_ref_action(accumulation_target.get()); + } else { + return make_accumulate_ref_action(acc); + } + }, + accumulate); + } + + template + std::unique_ptr make_accumulate_action(ArgsMap const &argument_pairs, bool ref_mode, + std::string const &positional_name) { + if (ref_mode) { + auto ref = get_or_throw(argument_pairs.at(add_argument_flags::Reference), "reference"); + return make_accumulate_ref_action(ref); + } + + auto accumulate = + get_either_or_throw(argument_pairs.at(add_argument_flags::Accumulate), "accumulate"); + + return std::visit( + [this, positional_name](auto &&acc) -> std::unique_ptr { + using V = std::decay_t; + if constexpr (std::is_same_v) { + if (!acc) { + throw std::logic_error("Accumulate flag must be true when used as a bool"); + } + + auto accumulation_target = std::make_shared(); + store_accumulated_on_complete(positional_name, accumulation_target); + return make_accumulate_ref_action(accumulation_target.get()); + } else { + return make_accumulate_ref_action(acc); + } + }, + accumulate); + } + template T get_or_throw(typed_flag_value const &v, std::string_view key) { if (auto p = std::get_if(&v)) return *p; diff --git a/src/headers/parser/parsing_traits/traits.hpp b/src/headers/parser/parsing_traits/traits.hpp index 47ac636..5fa7ced 100644 --- a/src/headers/parser/parsing_traits/traits.hpp +++ b/src/headers/parser/parsing_traits/traits.hpp @@ -1,3 +1,4 @@ +// ReSharper disable CppFunctionIsNotImplemented #pragma once #ifndef PARSING_TRAITS_HPP #define PARSING_TRAITS_HPP @@ -61,10 +62,10 @@ namespace argument_parser::parsing_traits { constexpr size_t total_len = (std::string_view{Providers::value}.length() + ... + 0); std::array arr{}; + // ReSharper disable once CppDFAUnreadVariable size_t offset = 0; - - auto append = [&](hint_type s) { - std::string_view sv{s}; + auto append = [&](const hint_type s) { + const std::string_view sv{s}; for (char c : sv) arr[offset++] = c; return 0; diff --git a/src/headers/parser/platform_headers/macos_parser.hpp b/src/headers/parser/platform_headers/macos_parser.hpp index 2ff1e51..d9396b2 100644 --- a/src/headers/parser/platform_headers/macos_parser.hpp +++ b/src/headers/parser/platform_headers/macos_parser.hpp @@ -15,7 +15,7 @@ namespace argument_parser { namespace v2 { class macos_parser : public v2::base_parser { public: - macos_parser(bool should_exit = true); + explicit macos_parser(bool should_exit = true); using base_parser::display_help; }; } // namespace v2 diff --git a/src/source/conventions/base_convention.cpp b/src/source/conventions/base_convention.cpp index 2214825..0ed2d6d 100644 --- a/src/source/conventions/base_convention.cpp +++ b/src/source/conventions/base_convention.cpp @@ -3,12 +3,12 @@ namespace argument_parser::conventions::helpers { std::string to_lower(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); }); + std::transform(s.begin(), s.end(), s.begin(), [](const unsigned char c) { return std::tolower(c); }); return s; } std::string to_upper(std::string s) { - std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::toupper(c); }); + std::transform(s.begin(), s.end(), s.begin(), [](const unsigned char c) { return std::toupper(c); }); return s; } } // namespace argument_parser::conventions::helpers diff --git a/src/source/conventions/implementations/gnu_argument_convention.cpp b/src/source/conventions/implementations/gnu_argument_convention.cpp index 692d90c..b8af8f4 100644 --- a/src/source/conventions/implementations/gnu_argument_convention.cpp +++ b/src/source/conventions/implementations/gnu_argument_convention.cpp @@ -10,10 +10,9 @@ namespace argument_parser::conventions::implementations { parsed_argument gnu_argument_convention::get_argument(std::string const &raw) const { if (starts_with(raw, long_prec())) return {argument_type::LONG, raw.substr(2)}; - else if (starts_with(raw, short_prec())) + if (starts_with(raw, short_prec())) return {argument_type::SHORT, raw.substr(1)}; - else - return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; + return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; } std::string gnu_argument_convention::extract_value(std::string const & /*raw*/) const { @@ -42,17 +41,17 @@ namespace argument_parser::conventions::implementations { 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 != "") { + bool const requires_value) const { + std::string s_part; + if (short_arg != "-" && !short_arg.empty()) { s_part += short_prec() + short_arg; if (requires_value) { s_part += " "; } } - std::string l_part = ""; - if (long_arg != "-" && long_arg != "") { + std::string l_part; + if (long_arg != "-" && !long_arg.empty()) { l_part += long_prec() + long_arg; if (requires_value) { l_part += " "; @@ -65,18 +64,17 @@ namespace argument_parser::conventions::implementations { namespace argument_parser::conventions::implementations { parsed_argument gnu_equal_argument_convention::get_argument(std::string const &raw) const { - auto pos = raw.find('='); - auto arg = pos != std::string::npos ? raw.substr(0, pos) : raw; + const auto pos = raw.find('='); + const auto arg = pos != std::string::npos ? raw.substr(0, pos) : raw; if (starts_with(arg, long_prec())) return {argument_type::LONG, arg.substr(2)}; - else if (starts_with(arg, short_prec())) + if (starts_with(arg, short_prec())) return {argument_type::SHORT, arg.substr(1)}; - else - return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; + return {argument_type::ERROR, "GNU standard convention does not allow arguments without a preceding dash."}; } std::string gnu_equal_argument_convention::extract_value(std::string const &raw) const { - auto pos = raw.find('='); + const auto pos = raw.find('='); if (pos == std::string::npos || pos + 1 >= raw.size()) throw std::runtime_error("Expected value after '='."); return raw.substr(pos + 1); @@ -100,17 +98,17 @@ namespace argument_parser::conventions::implementations { 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 != "") { + bool const requires_value) const { + std::string s_part; + if (short_arg != "-" && !short_arg.empty()) { s_part += short_prec() + short_arg; if (requires_value) { s_part += "="; } } - std::string l_part = ""; - if (long_arg != "-" && long_arg != "") { + std::string l_part; + if (long_arg != "-" && !long_arg.empty()) { l_part += long_prec() + long_arg; if (requires_value) { l_part += "="; diff --git a/src/source/conventions/implementations/windows_argument_convention.cpp b/src/source/conventions/implementations/windows_argument_convention.cpp index ab82c79..4618498 100644 --- a/src/source/conventions/implementations/windows_argument_convention.cpp +++ b/src/source/conventions/implementations/windows_argument_convention.cpp @@ -3,15 +3,14 @@ #include namespace argument_parser::conventions::implementations { - windows_argument_convention::windows_argument_convention(bool accept_dash) : accept_dash_(accept_dash) {} + windows_argument_convention::windows_argument_convention(bool const accept_dash) : accept_dash_(accept_dash) {} parsed_argument windows_argument_convention::get_argument(std::string const &raw) const { if (raw.empty()) { return {argument_type::ERROR, "Empty argument token."}; } const char c0 = raw[0]; - const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); - if (!ok_prefix) { + if (const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); !ok_prefix) { return {argument_type::ERROR, accept_dash_ ? "Windows-style expects options to start with '/' (or '-' in compat mode)." : "Windows-style expects options to start with '/'."}; @@ -52,17 +51,17 @@ namespace argument_parser::conventions::implementations { 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 != "") { + bool const requires_value) const { + std::string s_part; + if (short_arg != "-" && !short_arg.empty()) { s_part += short_prec() + short_arg; if (requires_value) { s_part += " "; } } - std::string l_part = ""; - if (long_arg != "-" && long_arg != "") { + std::string l_part; + if (long_arg != "-" && !long_arg.empty()) { l_part += long_prec() + long_arg; if (requires_value) { l_part += " "; @@ -79,15 +78,14 @@ namespace argument_parser::conventions::implementations { } // namespace argument_parser::conventions::implementations namespace argument_parser::conventions::implementations { - windows_kv_argument_convention::windows_kv_argument_convention(bool accept_dash) : accept_dash_(accept_dash) {} + windows_kv_argument_convention::windows_kv_argument_convention(bool const accept_dash) : accept_dash_(accept_dash) {} parsed_argument windows_kv_argument_convention::get_argument(std::string const &raw) const { if (raw.empty()) { return {argument_type::ERROR, "Empty argument token."}; } const char c0 = raw[0]; - const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); - if (!ok_prefix) { + if (const bool ok_prefix = (c0 == '/') || (accept_dash_ && c0 == '-'); !ok_prefix) { return {argument_type::ERROR, accept_dash_ ? "Windows-style expects options to start with '/' (or '-' in compat mode)." : "Windows-style expects options to start with '/'."}; @@ -131,17 +129,17 @@ namespace argument_parser::conventions::implementations { 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 != "") { + bool const requires_value) const { + std::string s_part; + if (short_arg != "-" && !short_arg.empty()) { s_part += short_prec() + short_arg; if (requires_value) { s_part += "=, " + short_prec() + short_arg + ":"; } } - std::string l_part = ""; - if (long_arg != "-" && long_arg != "") { + std::string l_part; + if (long_arg != "-" && !long_arg.empty()) { l_part += long_prec() + long_arg; if (requires_value) { l_part += "=, " + long_prec() + long_arg + ":"; diff --git a/src/source/parser/argument_parser.cpp b/src/source/parser/argument_parser.cpp index 509a289..10a274a 100644 --- a/src/source/parser/argument_parser.cpp +++ b/src/source/parser/argument_parser.cpp @@ -1,5 +1,6 @@ #include "argument_parser.hpp" +#include #include #include #include @@ -13,7 +14,7 @@ class deferred_exec { public: - deferred_exec(std::function const &func) : func(func) {} + explicit deferred_exec(std::function const &func) : func(func) {} ~deferred_exec() { func(); } @@ -28,12 +29,12 @@ bool contains(std::unordered_map const &map, std::string const namespace argument_parser { argument::argument() - : id(0), name(), action(std::make_unique([]() {})), required(false), invoked(false) {} + : id(0), action(std::make_unique([] {})), required(false), invoked(false) {} argument::argument(const argument &other) : id(other.id), name(other.name), action(other.action->clone()), required(other.required), invoked(other.invoked), help_text(other.help_text), positional(other.positional), - position_index(other.position_index) {} + positional_accumulator(other.positional_accumulator), position_index(other.position_index) {} argument &argument::operator=(const argument &other) { if (this != &other) { @@ -44,6 +45,7 @@ namespace argument_parser { invoked = other.invoked; help_text = other.help_text; positional = other.positional; + positional_accumulator = other.positional_accumulator; position_index = other.position_index; } return *this; @@ -69,11 +71,11 @@ namespace argument_parser { return help_text; } - void argument::set_required(bool val) { + void argument::set_required(const bool val) { required = val; } - void argument::set_invoked(bool val) { + void argument::set_invoked(const bool val) { invoked = val; } @@ -85,20 +87,36 @@ namespace argument_parser { return positional; } + bool argument::is_positional_accumulator() const { + return positional_accumulator; + } + std::optional argument::get_position_index() const { return position_index; } - void argument::set_positional(bool val) { + void argument::set_positional(const bool val) { positional = val; } - void argument::set_position_index(std::optional idx) { + void argument::set_positional_accumulator(const bool val) { + positional_accumulator = val; + } + + void argument::set_position_index(const std::optional idx) { position_index = idx; } - void base_parser::on_complete(std::function const &handler) { - on_complete_events.emplace_back(handler); + void base_parser::on_complete(std::function const &action) { + on_complete_events.emplace_back(action); + } + + void base_parser::on_complete(std::function const &handler, const bool to_start) { + if (to_start) { + on_complete_events.emplace_front(handler); + } else { + on_complete_events.emplace_back(handler); + } } std::string @@ -112,8 +130,7 @@ namespace argument_parser { auto name_it = reverse_positional_names.find(pos_id); if (name_it == reverse_positional_names.end()) continue; - auto const &arg = argument_map.at(pos_id); - if (arg.is_required()) { + if (auto const &arg = argument_map.at(pos_id); arg.is_required()) { ss << " <" << name_it->second << ">"; } else { ss << " [" << name_it->second << "]"; @@ -143,8 +160,8 @@ namespace argument_parser { std::unordered_set hasOnce; for (auto const &convention : convention_types) { 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()) { + if (std::string combined = generatedParts.first + "|" + generatedParts.second; + hasOnce.find(combined) == hasOnce.end()) { parts.push_back(generatedParts); hasOnce.insert(combined); @@ -155,24 +172,24 @@ namespace argument_parser { max_long_len = generatedParts.second.length(); } } else { - parts.push_back({"", ""}); // trigger empty space in the help text + parts.emplace_back("", ""); // trigger empty space in the help text } } help_lines.push_back({parts, arg.help_text}); } if (!help_lines.empty()) { - for (auto const &line : help_lines) { + for (const auto &[convention_parts, desc] : help_lines) { ss << "\t"; - for (size_t i = 0; i < line.convention_parts.size(); ++i) { - auto const &parts = line.convention_parts[i]; + for (size_t i = 0; i < convention_parts.size(); ++i) { + const auto &[fst, snd] = 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 << std::left << std::setw(static_cast(max_short_len)) << fst << " " + << std::setw(static_cast(max_long_len)) << snd; } - ss << "\t" << line.desc << "\n"; + ss << "\t" << desc << "\n"; } } @@ -182,10 +199,8 @@ namespace argument_parser { for (auto const &pos_id : positional_arguments) { if (pos_id == -1) continue; - auto name_it = reverse_positional_names.find(pos_id); - if (name_it != reverse_positional_names.end()) { - size_t display_len = name_it->second.length() + 2; // for < > - if (display_len > max_pos_name_len) + if (auto name_it = reverse_positional_names.find(pos_id); name_it != reverse_positional_names.end()) { + if (size_t display_len = name_it->second.length() + 2; display_len > max_pos_name_len) max_pos_name_len = display_len; } } @@ -208,37 +223,32 @@ namespace argument_parser { argument &base_parser::get_argument(conventions::parsed_argument const &arg) { if (arg.first == conventions::argument_type::LONG) { - auto long_pos = long_arguments.find(arg.second); - if (long_pos != long_arguments.end()) + if (const auto long_pos = long_arguments.find(arg.second); long_pos != long_arguments.end()) return argument_map.at(long_pos->second); } else if (arg.first == conventions::argument_type::SHORT) { - auto short_pos = short_arguments.find(arg.second); - if (short_pos != short_arguments.end()) + if (const auto short_pos = short_arguments.find(arg.second); short_pos != short_arguments.end()) return argument_map.at(short_pos->second); } else if (arg.first == conventions::argument_type::INTERCHANGABLE) { - auto long_pos = long_arguments.find(arg.second); - if (long_pos != long_arguments.end()) + if (const auto long_pos = long_arguments.find(arg.second); long_pos != long_arguments.end()) return argument_map.at(long_pos->second); - auto short_pos = short_arguments.find(arg.second); - if (short_pos != short_arguments.end()) + if (const auto short_pos = short_arguments.find(arg.second); short_pos != short_arguments.end()) return argument_map.at(short_pos->second); } throw std::runtime_error("Unknown argument: " + arg.second); } - void base_parser::enforce_creation_thread() { + void base_parser::enforce_creation_thread() const { if (std::this_thread::get_id() != this->creation_thread_id.load()) { throw std::runtime_error("handle_arguments must be called from the main thread"); } } - bool base_parser::test_conventions(std::initializer_list convention_types, - std::unordered_map &values_for_arguments, - std::vector> &found_arguments, - std::optional &found_help, std::vector::iterator &it, - std::stringstream &error_stream) { + bool + base_parser::test_conventions(const std::initializer_list convention_types, + std::vector &found_arguments, std::optional &found_help, + std::vector::iterator &it, std::stringstream &error_stream) { - std::string current_argument = *it; + const std::string current_argument = *it; for (auto const &convention_type : convention_types) { auto extracted = convention_type->get_argument(current_argument); @@ -256,16 +266,17 @@ namespace argument_parser { return true; } - found_arguments.emplace_back(extracted.second, corresponding_argument); + found_argument found{extracted.second, corresponding_argument}; 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); } - values_for_arguments[extracted.second] = + found.value = convention_type->requires_next_token() ? *(++it) : convention_type->extract_value(*it); } + found_arguments.emplace_back(std::move(found)); return true; } catch (const std::runtime_error &e) { error_stream << "Convention \"" << convention_type->name() << "\" failed with: " << e.what() << "\n"; @@ -275,10 +286,9 @@ namespace argument_parser { return false; } - void base_parser::extract_arguments(std::initializer_list convention_types, - std::unordered_map &values_for_arguments, - std::vector> &found_arguments, - std::optional &found_help) { + void + base_parser::extract_arguments(const std::initializer_list convention_types, + std::vector &found_arguments, std::optional &found_help) { size_t next_positional_index = 0; bool force_positional = false; @@ -290,29 +300,30 @@ namespace argument_parser { } if (force_positional) { - if (next_positional_index >= positional_arguments.size()) { + auto slot = next_positional_slot(next_positional_index); + if (!slot.has_value()) { throw std::runtime_error("Unexpected positional argument: \"" + *it + "\""); } - int arg_id = positional_arguments[next_positional_index]; + int arg_id = positional_arguments[*slot]; argument &pos_arg = argument_map.at(arg_id); std::string const &pos_name = reverse_positional_names.at(arg_id); - found_arguments.emplace_back(pos_name, pos_arg); - values_for_arguments[pos_name] = *it; - next_positional_index++; + found_arguments.push_back({pos_name, pos_arg, *it}); + if (!pos_arg.is_positional_accumulator()) { + next_positional_index = *slot + 1; + } continue; } - std::stringstream error_stream; - - if (!test_conventions(convention_types, values_for_arguments, found_arguments, found_help, it, - error_stream)) { - if (next_positional_index < positional_arguments.size()) { - int arg_id = positional_arguments[next_positional_index]; + if (std::stringstream error_stream; + !test_conventions(convention_types, found_arguments, found_help, it, error_stream)) { + if (auto slot = next_positional_slot(next_positional_index); slot.has_value()) { + int arg_id = positional_arguments[*slot]; argument &pos_arg = argument_map.at(arg_id); std::string const &pos_name = reverse_positional_names.at(arg_id); - found_arguments.emplace_back(pos_name, pos_arg); - values_for_arguments[pos_name] = *it; - next_positional_index++; + found_arguments.push_back({pos_name, pos_arg, *it}); + if (!pos_arg.is_positional_accumulator()) { + next_positional_index = *slot + 1; + } } else { throw std::runtime_error("All trials for argument: \n\t\"" + *it + "\"\n failed with: \n" + error_stream.str()); @@ -322,7 +333,7 @@ namespace argument_parser { } std::string replace_var(std::string text, const std::string &var_name, const std::string &value) { - std::string placeholder = "${" + var_name + "}"; + const std::string placeholder = "${" + var_name + "}"; size_t pos = text.find(placeholder); while (pos != std::string::npos) { @@ -332,8 +343,7 @@ namespace argument_parser { return text; } - void base_parser::invoke_arguments(std::unordered_map const &values_for_arguments, - std::vector> &found_arguments, + void base_parser::invoke_arguments(std::vector &found_arguments, std::optional const &found_help) { if (found_help) { @@ -342,15 +352,15 @@ namespace argument_parser { } std::stringstream error_stream; - for (auto &[key, value] : found_arguments) { + for (auto &[key, arg, value] : found_arguments) { try { - if (value.expects_parameter()) { - value.action->invoke_with_parameter(values_for_arguments.at(key)); + if (arg.expects_parameter()) { + arg.action->invoke_with_parameter(value.value()); } else { - value.action->invoke(); + arg.action->invoke(); } - value.set_invoked(true); - argument_map.at(value.id).set_invoked(true); + arg.set_invoked(true); + argument_map.at(arg.id).set_invoked(true); } catch (const std::runtime_error &e) { std::string err{e.what()}; err = replace_var(err, "KEY", "for " + key); @@ -358,43 +368,42 @@ namespace argument_parser { } } - std::string error_message = error_stream.str(); - if (!error_message.empty()) { + if (const std::string error_message = error_stream.str(); !error_message.empty()) { throw std::runtime_error(error_message); } } - void base_parser::handle_arguments(std::initializer_list convention_types) { + void + base_parser::handle_arguments(const std::initializer_list convention_types) { enforce_creation_thread(); - deferred_exec reset_current_conventions([this]() { this->reset_current_conventions(); }); + deferred_exec reset_current_conventions([this] { this->reset_current_conventions(); }); this->current_conventions(convention_types); - std::unordered_map values_for_arguments; - std::vector> found_arguments; + std::vector found_arguments; std::optional found_help = std::nullopt; - extract_arguments(convention_types, values_for_arguments, found_arguments, found_help); - invoke_arguments(values_for_arguments, found_arguments, found_help); + extract_arguments(convention_types, found_arguments, found_help); + invoke_arguments(found_arguments, found_help); check_for_required_arguments(convention_types); fire_on_complete_events(); } - void base_parser::display_help(std::initializer_list convention_types) const { + void base_parser::display_help( + const std::initializer_list convention_types) const { std::cout << build_help_text(convention_types); } std::optional base_parser::find_argument_id(std::string const &arg) const { - auto long_pos = long_arguments.find(arg); - auto short_post = short_arguments.find(arg); + const auto long_pos = long_arguments.find(arg); + const auto short_post = short_arguments.find(arg); if (long_pos != long_arguments.end()) return long_pos->second; if (short_post != short_arguments.end()) return short_post->second; - auto pos_it = positional_name_map.find(arg); - if (pos_it != positional_name_map.end()) + if (const auto pos_it = positional_name_map.find(arg); pos_it != positional_name_map.end()) return pos_it->second; return std::nullopt; @@ -406,12 +415,12 @@ namespace argument_parser { } } - void base_parser::set_argument_status(bool is_required, std::string const &help_text, argument &arg) { + void base_parser::set_argument_status(const bool is_required, std::string const &help_text, argument &arg) { arg.set_required(is_required); arg.set_help_text(help_text); } - void base_parser::place_argument(int id, argument const &arg, std::string const &short_arg, + void base_parser::place_argument(int const id, argument const &arg, std::string const &short_arg, std::string const &long_arg) { argument_map[id] = arg; if (short_arg != "-") { @@ -430,19 +439,57 @@ namespace argument_parser { } } - void base_parser::place_positional_argument(int id, argument const &arg, std::string const &name, - std::optional position) { + void base_parser::assert_can_place_positional(const int id, const std::optional position, + const bool accumulator) const { + const auto existing_accumulator = + std::find_if(positional_arguments.begin(), positional_arguments.end(), [this](int const arg_id) { + if (arg_id == -1) { + return false; + } + return argument_map.at(arg_id).is_positional_accumulator(); + }); + + if (accumulator && existing_accumulator != positional_arguments.end()) { + throw std::runtime_error("Only one positional accumulator is allowed."); + } + + if (!accumulator && existing_accumulator != positional_arguments.end()) { + const auto accumulator_slot = + static_cast(std::distance(positional_arguments.begin(), existing_accumulator)); + if (!position.has_value() || static_cast(position.value()) >= accumulator_slot) { + throw std::runtime_error("Positional accumulator must be the last positional argument."); + } + } + + if (accumulator && position.has_value()) { + const auto idx = static_cast(position.value()); + for (size_t i = idx + 1; i < positional_arguments.size(); ++i) { + if (positional_arguments[i] != -1 && positional_arguments[i] != id) { + throw std::runtime_error("Positional accumulator must be the last positional argument."); + } + } + } + } + + void base_parser::place_positional_argument(int const id, argument const &arg, std::string const &name, + const std::optional position, const bool accumulator) { + if (position.has_value() && position.value() < 0) { + throw std::runtime_error("Positional argument position cannot be negative."); + } + assert_can_place_positional(id, position, accumulator); + argument_map[id] = arg; positional_name_map[name] = id; reverse_positional_names[id] = name; if (position.has_value()) { - auto idx = static_cast(position.value()); + const auto idx = static_cast(position.value()); if (idx > positional_arguments.size()) { positional_arguments.resize(idx + 1, -1); } if (idx < positional_arguments.size() && positional_arguments[idx] != -1) { - throw std::runtime_error("Position " + std::to_string(idx) + " is already occupied!"); + positional_arguments.insert(positional_arguments.begin() + static_cast(idx), id); + return; } if (idx == positional_arguments.size()) { positional_arguments.push_back(id); @@ -450,10 +497,24 @@ namespace argument_parser { positional_arguments[idx] = id; } } else { - positional_arguments.push_back(id); + if (const auto empty_slot = std::find(positional_arguments.begin(), positional_arguments.end(), -1); + empty_slot != positional_arguments.end()) { + *empty_slot = id; + } else { + positional_arguments.push_back(id); + } } } + std::optional base_parser::next_positional_slot(size_t const start) const { + for (size_t i = start; i < positional_arguments.size(); ++i) { + if (positional_arguments[i] != -1) { + return i; + } + } + return std::nullopt; + } + std::string get_one_name(std::string const &short_name, std::string const &long_name) { std::string res{}; if (short_name != "-") { @@ -500,15 +561,15 @@ namespace argument_parser { } else { 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()) { + auto [short_part, long_part] = (*it)->make_help_text(s, l, p); + std::string help_str = short_part; + if (!short_part.empty() && !long_part.empty()) { help_str += " "; } - help_str += generatedParts.second; + help_str += long_part; - size_t last_not_space = help_str.find_last_not_of(" \t"); - if (last_not_space != std::string::npos) { + if (size_t last_not_space = help_str.find_last_not_of(" \t"); + last_not_space != std::string::npos) { help_str.erase(last_not_space + 1); } std::cerr << help_str; diff --git a/src/source/parser/fake_parser.cpp b/src/source/parser/fake_parser.cpp index c743a8c..7419709 100644 --- a/src/source/parser/fake_parser.cpp +++ b/src/source/parser/fake_parser.cpp @@ -12,7 +12,7 @@ namespace argument_parser { } fake_parser::fake_parser(std::string const &program_name, std::initializer_list const &arguments) - : fake_parser(program_name, std::vector(arguments)) {} + : fake_parser(program_name, std::vector(arguments)) {} void fake_parser::set_program_name(std::string const &program_name) { this->program_name = program_name; @@ -24,22 +24,22 @@ namespace argument_parser { namespace v2 { fake_parser::fake_parser(std::string program_name, std::vector const &arguments) { - set_program_name(program_name); + base_parser::set_program_name(std::move(program_name)); ref_parsed_args() = arguments; prepare_help_flag(false); } - fake_parser::fake_parser(std::string const &program_name, std::vector &&arguments) { - set_program_name(program_name); + fake_parser::fake_parser(std::string program_name, std::vector &&arguments) { + base_parser::set_program_name(std::move(program_name)); ref_parsed_args() = std::move(arguments); prepare_help_flag(false); } - fake_parser::fake_parser(std::string const &program_name, std::initializer_list const &arguments) - : fake_parser(program_name, std::vector(arguments)) {} + fake_parser::fake_parser(std::string program_name, std::initializer_list const &arguments) + : fake_parser(std::move(program_name), std::vector(arguments)) {} void fake_parser::set_program_name(std::string const &program_name) { - set_program_name(program_name); + base_parser::set_program_name(program_name); } void fake_parser::set_parsed_arguments(std::vector const &parsed_arguments) { diff --git a/src/source/parser/platform_parsers/macos_parser.cpp b/src/source/parser/platform_parsers/macos_parser.cpp index a7c48d4..7a219c9 100644 --- a/src/source/parser/platform_parsers/macos_parser.cpp +++ b/src/source/parser/platform_parsers/macos_parser.cpp @@ -6,13 +6,13 @@ #define MACOS_GETARGS_LOOP(argc_name, argv_name, before_for, for_body) \ do { \ - const int argc_name = *_NSGetArgc(); \ - if (char **argv_name = *_NSGetArgv(); argc_name > 0 && argv_name != nullptr && argv_name[0] != nullptr) { \ + const int (argc_name) = *_NSGetArgc(); \ + if (char **(argv_name) = *_NSGetArgv(); (argc_name) > 0 && (argv_name) != nullptr && (argv_name)[0] != nullptr) { \ do { \ - before_for; \ + (before_for); \ } while (false); \ - for (int i = 1; i < argc_name; ++i) { \ - for_body \ + for (int i = 1; i < (argc_name); ++i) { \ + {for_body} \ } \ } \ } while (false) @@ -26,7 +26,7 @@ namespace argument_parser { } namespace v2 { - macos_parser::macos_parser(bool should_exit) { + macos_parser::macos_parser(const bool should_exit) { MACOS_GETARGS_LOOP(argc, argv, set_program_name(argv[0]), { if (argv[i] != nullptr) ref_parsed_args().emplace_back(argv[i]);