#pragma once #include "argument_parser.hpp" #include #include #include #include #ifndef ARGUMENT_PARSER_PARSER_BUILDER_HPP #define ARGUMENT_PARSER_PARSER_BUILDER_HPP namespace argument_parser::builder { class non_type {}; 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, accumulate, count, action_no_param, action_with_param }; enum class extra_capability : unsigned { Store = static_cast(v2_flag::Reference) + 1, Flag }; constexpr auto bit(v2_flag flag) -> mask_type { return mask_type{1} << static_cast(flag); } constexpr auto bit(extra_capability capability) -> mask_type { return mask_type{1} << static_cast(capability); } constexpr mask_type short_argument = bit(v2_flag::ShortArgument); constexpr mask_type long_argument = bit(v2_flag::LongArgument); constexpr mask_type positional = bit(v2_flag::Positional); constexpr mask_type position = bit(v2_flag::Position); constexpr mask_type help_text = bit(v2_flag::HelpText); 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 count = bit( static_cast(static_cast(v2_flag::Accumulate) + (static_cast(extra_capability::Store) | static_cast(extra_capability::Flag)))); constexpr mask_type store = bit(extra_capability::Store); constexpr mask_type flag = bit(extra_capability::Flag); constexpr mask_type value_mode_group = action | reference | accumulate | count | store | flag; constexpr mask_type initial = short_argument | long_argument | positional | help_text | action | required | reference | accumulate | count | store | flag; constexpr mask_type storable_mode_group = (count | store | flag | accumulate | reference) >> 1; constexpr auto has(mask_type mask, mask_type capability) -> bool { return (mask & capability) == capability; } constexpr auto remove(mask_type mask, mask_type capability) -> mask_type { return mask & ~capability; } constexpr auto replace(mask_type mask, mask_type remove_bits, mask_type add_bits = 0) -> mask_type { return (mask & ~remove_bits) | add_bits; } constexpr auto has_selected_identifier(mask_type mask) -> bool { return !has(mask, short_argument) || !has(mask, long_argument) || !has(mask, positional); } constexpr auto is_buildable(mask_type mask) -> bool { return has_selected_identifier(mask); } constexpr auto is_build_and_gettable(mask_type mask) -> bool { return has_selected_identifier(mask) && has(mask, storable_mode_group); } } // 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; using v2_flag = argument_parser::v2::add_argument_flags; using value_mode = builder_mask::value_mode; static auto start() -> argument { return {}; } template = 0> auto short_argument(std::string short_name) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_short_argument = std::move(short_name); return next; } template = 0> auto long_argument(std::string long_name) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_long_argument = std::move(long_name); return next; } template = 0> auto positional(std::string positional_name) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_positional_name = std::move(positional_name); return next; } template = 0> auto position(int index) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_position = index; return next; } template = 0> auto help_text(std::string help) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_help_text = std::move(help); return next; } template = 0> auto required(bool value = true) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_required = value; return next; } template = 0> auto store() const -> argument { static_assert(!std::is_same_v, "store() is not supported. Use flag() for boolean-style arguments."); using next_argument = argument; next_argument next{*this}; next.m_value_mode = value_mode::store; 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< builder_mask::replace(current_mask, builder_mask::value_mode_group, builder_mask::storable_mode_group), T> { 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 count() const -> argument { using next_argument = argument; next_argument next{*this}; next.m_value_mode = value_mode::count; return next; } template = 0> auto count(int &value) const -> argument { using next_argument = argument; next_argument next{*this}; next.m_reference = std::addressof(value); next.m_value_mode = value_mode::count; return next; } template = 0> auto flag() const -> argument { using next_argument = argument; next_argument next{*this}; next.m_value_mode = value_mode::flag; return next; } template = 0, typename T> auto reference(T &value) const -> argument< builder_mask::replace(current_mask, builder_mask::value_mode_group, builder_mask::storable_mode_group), T> { using next_argument = argument; next_argument next{*this}; next.m_reference = std::addressof(value); next.m_value_mode = value_mode::reference; return next; } template = 0, typename Callable> auto action(Callable &&handler) const -> std::enable_if_t< std::is_invocable_r_v, argument> { using next_argument = argument; next_argument next{*this}; next.m_action = std::make_shared( std::function(std::forward(handler))); next.m_value_mode = value_mode::action_no_param; return next; } template = 0, typename Callable> auto action(Callable &&handler) const -> std::enable_if_t, argument> { static_assert(!std::is_same_v, "action(...) is not supported. Use action([] { ... }) instead."); using next_argument = argument; next_argument next{*this}; next.m_action = std::make_shared>( std::function(std::forward(handler))); next.m_value_mode = value_mode::action_with_param; return next; } template = 0> auto build(argument_parser::v2::base_parser &parser) const -> void { assert_has_identifier(); switch (m_value_mode) { case value_mode::flag: build_flag(parser); return; case value_mode::action_no_param: build_action_with_no_param(parser); return; case value_mode::store: if constexpr (!std::is_same_v) { build_store(parser); return; } break; case value_mode::reference: if constexpr (!std::is_same_v) { build_reference(parser); return; } break; case value_mode::accumulate: case value_mode::count: if constexpr (!std::is_same_v) { build_accumulate(parser); return; } break; case value_mode::action_with_param: if constexpr (!std::is_same_v) { build_action_with_param(parser); return; } break; case value_mode::unresolved: if (is_positional()) { build_default_positional(parser); } else { build_flag(parser); } return; } 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: case value_mode::count: 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; template argument(argument const &other) : m_short_argument(other.m_short_argument), m_long_argument(other.m_long_argument), m_positional_name(other.m_positional_name), m_position(other.m_position), m_help_text(other.m_help_text), m_required(other.m_required), m_action(other.m_action), m_reference(copy_reference(other.m_reference)), m_value_mode(other.m_value_mode) {} template using typed_map = std::unordered_map>; using non_typed_map = std::unordered_map; auto is_positional() const -> bool { return !m_positional_name.empty(); } auto lookup_key() const -> std::string { if (is_positional()) { return m_positional_name; } if (!m_long_argument.empty()) { return m_long_argument; } if (!m_short_argument.empty()) { return m_short_argument; } throw std::logic_error("No argument identifier is available for lookup."); } auto assert_has_identifier() const -> void { if (!is_positional() && m_short_argument.empty() && m_long_argument.empty()) { throw std::logic_error("build() requires short_argument(), long_argument(), or positional()."); } } template auto add_common_pairs(Map &pairs) const -> void { using namespace argument_parser::v2::flags; if (is_positional()) { pairs[Positional] = m_positional_name; if (m_position.has_value()) { pairs[Position] = m_position.value(); } } else { if (!m_short_argument.empty()) { pairs[ShortArgument] = m_short_argument; } if (!m_long_argument.empty()) { pairs[LongArgument] = m_long_argument; } } if (!m_help_text.empty()) { pairs[HelpText] = m_help_text; } if (m_required) { pairs[Required] = true; } } template auto make_typed_pairs() const -> typed_map { typed_map pairs; add_common_pairs(pairs); return pairs; } auto make_non_typed_pairs() const -> non_typed_map { non_typed_map pairs; add_common_pairs(pairs); return pairs; } auto build_flag(argument_parser::v2::base_parser &parser) const -> void { auto pairs = make_non_typed_pairs(); parser.add_argument(pairs); } auto build_default_positional(argument_parser::v2::base_parser &parser) const -> void { auto pairs = make_non_typed_pairs(); parser.add_argument(pairs); } auto build_store(argument_parser::v2::base_parser &parser) const -> void { auto pairs = make_typed_pairs(); parser.template add_argument(pairs); } auto build_reference(argument_parser::v2::base_parser &parser) const -> void { auto pairs = make_typed_pairs(); auto *target = m_reference; if (target == nullptr) { throw std::logic_error("reference() was selected without a target."); } pairs[argument_parser::v2::flags::Reference] = target; 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_action_with_param(argument_parser::v2::base_parser &parser) const -> void { auto const *typed_action = dynamic_cast const *>(m_action.get()); if (typed_action == nullptr) { throw std::logic_error("Stored action is not compatible with the requested parameter type."); } auto pairs = make_typed_pairs(); pairs[argument_parser::v2::flags::Action] = *typed_action; parser.template add_argument(pairs); } auto build_action_with_no_param(argument_parser::v2::base_parser &parser) const -> void { auto const *nonparametered_action = dynamic_cast(m_action.get()); if (nonparametered_action == nullptr) { throw std::logic_error("Stored action is not a non-parametered action."); } if (is_positional()) { auto wrapped_action = argument_parser::helpers::make_action( [action = *nonparametered_action](std::string const &) { action.invoke(); }); auto pairs = make_typed_pairs(); pairs[argument_parser::v2::flags::Action] = wrapped_action; parser.template add_argument(pairs); return; } auto pairs = make_non_typed_pairs(); pairs[argument_parser::v2::flags::Action] = *nonparametered_action; parser.add_argument(pairs); } std::string m_short_argument{}; std::string m_long_argument{}; std::string m_positional_name{}; std::optional m_position{}; std::string m_help_text{}; bool m_required = false; std::shared_ptr m_action{}; store_type *m_reference = nullptr; value_mode m_value_mode = value_mode::unresolved; template static auto copy_reference(other_store_type *reference) -> store_type * { if constexpr (std::is_same_v) { return reference; } else { return nullptr; } } template friend class argument; }; static inline auto new_argument() { return argument<>::start(); } namespace assertions { struct noop_handler { void operator()() const {} }; template struct parameter_sink { void operator()(T const &) const {} }; template struct can_use_help_text : std::false_type {}; template struct can_use_help_text().help_text(std::declval()))>> : std::true_type {}; template struct can_use_position : std::false_type {}; template struct can_use_position().position(0))>> : std::true_type {}; template struct can_use_nonparametered_action : std::false_type {}; template struct can_use_nonparametered_action().action(noop_handler{}))>> : std::true_type {}; template struct can_use_parametered_action : std::false_type {}; template struct can_use_parametered_action< T, U, std::void_t().template action(parameter_sink{}))>> : std::true_type {}; template struct can_use_store : std::false_type {}; template struct can_use_store().template store())>> : std::true_type {}; template struct can_use_flag : std::false_type {}; template struct can_use_flag().flag())>> : std::true_type {}; template struct can_use_reference : std::false_type {}; template struct can_use_reference().reference(std::declval()))>> : std::true_type {}; using after_help_text = decltype(argument<>::start().help_text("help")); static_assert(!can_use_help_text::value, "help_text() should be single-use."); using after_positional = decltype(argument<>::start().positional("path")); static_assert(can_use_position::value, "positional() should unlock position()."); using after_position = decltype(argument<>::start().positional("path").position(0)); static_assert(!can_use_position::value, "position() should be single-use."); using after_positional_mode_selection = decltype(argument<>::start().positional("path").store<>()); static_assert(!can_use_flag::value, "flag() should not be available for positional arguments."); using after_nonparametered_action = decltype(argument<>::start().short_argument("v").help_text("verbose").action(noop_handler{})); static_assert(!can_use_nonparametered_action::value, "action() should not remain callable after selecting a non-parametered action."); static_assert(!can_use_parametered_action::value, "typed action() should also be disabled after selecting a non-parametered action."); static_assert(!can_use_store::value, "store() should be mutually exclusive with action()."); static_assert(!can_use_flag::value, "flag() should be mutually exclusive with action()."); static_assert(!can_use_reference::value, "reference() should be mutually exclusive with action()."); using after_parametered_action = decltype(argument<>::start().positional("count").position(0).action(parameter_sink{})); static_assert(!can_use_nonparametered_action::value, "non-parametered action() should be disabled after selecting a typed action."); static_assert(!can_use_parametered_action::value, "typed action() should be single-use regardless of the parameter type."); } // namespace assertions } // namespace argument_parser::builder #endif