Skip to content

Commit

Permalink
extracted action
Browse files Browse the repository at this point in the history
  • Loading branch information
SpectraL519 committed Jan 30, 2025
1 parent d0b43f2 commit 16e2e94
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 91 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,21 +210,21 @@ Parameters which can be specified for both positional and optional arguments inc
```c++
parser.add_optional_argument<double>("denominator", "d")
.action<ap::void_action>([](double& value) { value = 1. / value; });
.action<ap::action_type::modify>([](double& value) { value = 1. / value; });
```
or en equivalent valued action:
```c++
parser.add_optional_argument<double>("denominator", "d")
.action<ap::valued_action>([](const double& value) { return 1. / value; });
.action<ap::action_type::transform>([](const double& value) { return 1. / value; });
```
Actions can also be used to perform some value checking logic, e.g. the predefined `check_file_exists` which checks if a file with a given name exists:
```c++
parser.add_optional_argument("input", "i")
.action<ap::void_action>(ap::action::check_file_exists());
.action<ap::action_type::modify>(ap::action::check_file_exists());
```
#### Optional argument specific parameters
Expand Down Expand Up @@ -307,7 +307,7 @@ The supported default arguments are:
```c++
// equivalent to:
parser.add_positional_argument<std::string>("input")
.action<ap::void_action>(ap::action::check_file_exists())
.action<ap::action_type::modify>(ap::action::check_file_exists())
.help("Input file path");
```
Expand Down Expand Up @@ -343,14 +343,14 @@ The supported default arguments are:
parser.add_optional_argument("input", "i")
.required()
.nargs(1)
.action<ap::void_action>(ap::action::check_file_exists())
.action<ap::action_type::modify>(ap::action::check_file_exists())
.help("Input file path");
// multi_input - equivalent to:
parser.add_optional_argument("input", "i")
.required()
.nargs(ap::nargs::at_least(1))
.action<ap::void_action>(ap::action::check_file_exists())
.action<ap::action_type::modify>(ap::action::check_file_exists())
.help("Input files paths");
```
Expand Down
34 changes: 34 additions & 0 deletions include/ap/action/detail/utility.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#pragma once

#include "ap/action/specifiers.hpp"

namespace ap::action::detail {

/**
* @brief The concept is satisfied when `AS` is either a valued or void argument action
* @tparam AS The action specifier type.
*/
template <typename AS>
concept c_action_specifier = ap::detail::c_one_of<AS, action_type::transform, action_type::modify>;

/// @brief Template argument action callable type alias.
template <c_action_specifier AS, ap::detail::c_argument_value_type T>
using callable_type = typename AS::template type<T>;

/// @brief Template argument action callabla variant type alias.
template <ap::detail::c_argument_value_type T>
using action_variant_type =
std::variant<callable_type<action_type::transform, T>, callable_type<action_type::modify, T>>;

/**
* @brief Checks if an argument action variant holds a void action.
* @tparam T The argument value type.
* @param action The action variant.
* @return True if the held action is a void action.
*/
template <ap::detail::c_argument_value_type T>
[[nodiscard]] constexpr inline bool is_void_action(const action_variant_type<T>& action) noexcept {
return std::holds_alternative<callable_type<action_type::modify, T>>(action);
}

} // namespace ap::action::detail
24 changes: 24 additions & 0 deletions include/ap/action/predefined_actions.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "ap/error/exceptions.hpp"
#include "detail/utility.hpp"

#include <filesystem>

namespace ap::action {

/// @brief Returns a default argument action.
template <ap::detail::c_argument_value_type T>
detail::callable_type<ap::action_type::modify, T> none() noexcept {
return [](T&) {};
}

/// @brief Returns a predefined action for file name handling arguments. Checks whether a file with the given name exists.
inline detail::callable_type<ap::action_type::modify, std::string> check_file_exists() noexcept {
return [](std::string& file_path) {
if (not std::filesystem::exists(file_path))
throw argument_parser_exception(std::format("File `{}` does not exists!", file_path));
};
}

} // namespace ap::action
39 changes: 39 additions & 0 deletions include/ap/action/specifiers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#pragma once

#include "ap/detail/concepts.hpp"

#include <functional>

namespace ap::action_type {

// TODO:
// * on_read_action
// * on_flag_action

/*
* @brief Represents a transformating action.
*
* Represents an argument action which transforms the parsed value and
* returns a new value with which the argument will be initialized.
*/
struct transform {
template <ap::detail::c_argument_value_type T>
using type = std::function<T(const T&)>;
};

/*
* @brief Represents a modifying action.
*
* Represents an argument action which modifies the value of an
* already initialized argument.
*
* NOTE: The modify action doesn't have to actually modify the
* underlying value - it can simply perform some action on it.
* Example: `ap::action::check_file_exists`
*/
struct modify {
template <ap::detail::c_argument_value_type T>
using type = std::function<void(T&)>;
};

} // namespace ap::action_type
101 changes: 20 additions & 81 deletions include/ap/argument_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ SOFTWARE.

#pragma once

#include "action/predefined_actions.hpp"
#include "action/specifiers.hpp"
#include "detail/argument_interface.hpp"
#include "detail/argument_name.hpp"
#include "detail/concepts.hpp"
Expand Down Expand Up @@ -73,73 +75,6 @@ struct argument_parser_test_fixture;

namespace ap {

/// @brief Defines valued argument action traits.
struct valued_action {
template <ap::detail::c_argument_value_type T>
using type = std::function<T(const T&)>;
};

/// @brief Defines void argument action traits.
struct void_action {
template <ap::detail::c_argument_value_type T>
using type = std::function<void(T&)>;
};

// TODO:
// * on_read_action
// * on_flag_action

/// @brief Argument action handling utility.
namespace action {

/// @brief Internal argument action handling utility
namespace detail {

/**
* @brief The concept is satisfied when `AS` is either a valued or void argument action
* @tparam AS The action specifier type.
*/
template <typename AS>
concept c_action_specifier = ap::detail::c_one_of<AS, ap::valued_action, ap::void_action>;

/// @brief Template argument action callable type alias.
template <c_action_specifier AS, ap::detail::c_argument_value_type T>
using callable_type = typename AS::template type<T>;

/// @brief Template argument action callabla variant type alias.
template <ap::detail::c_argument_value_type T>
using action_variant_type =
std::variant<callable_type<ap::valued_action, T>, callable_type<ap::void_action, T>>;

/**
* @brief Checks if an argument action variant holds a void action.
* @tparam T The argument value type.
* @param action The action variant.
* @return True if the held action is a void action.
*/
template <ap::detail::c_argument_value_type T>
[[nodiscard]] constexpr inline bool is_void_action(const action_variant_type<T>& action) noexcept {
return std::holds_alternative<callable_type<ap::void_action, T>>(action);
}

} // namespace detail

/// @brief Returns a default argument action.
template <ap::detail::c_argument_value_type T>
detail::callable_type<ap::void_action, T> default_action() noexcept {
return [](T&) {};
}

/// @brief Returns a predefined action for file name handling arguments. Checks whether a file with the given name exists.
inline detail::callable_type<ap::void_action, std::string> check_file_exists() noexcept {
return [](std::string& file_path) {
if (not std::filesystem::exists(file_path))
throw argument_parser_exception(std::format("File `{}` does not exists!", file_path));
};
}

} // namespace action

/// @brief Namespace containing classes and utilities for handling command-line arguments.
namespace argument {

Expand Down Expand Up @@ -334,11 +269,13 @@ class positional_argument : public ap::detail::argument_interface {
void _apply_action(value_type& value) const noexcept {
namespace action = ap::action::detail;
if (action::is_void_action(this->_action))
std::get<action::callable_type<ap::void_action, value_type>>(this->_action)(value);
std::get<action::callable_type<ap::action_type::modify, value_type>>(this->_action)(
value
);
else
value =
std::get<action::callable_type<ap::valued_action, value_type>>(this->_action)(value
);
value = std::get<
action::callable_type<ap::action_type::transform, value_type>>(this->_action)(value
);
}

using action_type = ap::action::detail::action_variant_type<T>;
Expand All @@ -351,8 +288,8 @@ class positional_argument : public ap::detail::argument_interface {
static constexpr bool _bypass_required =
false; ///< Bypassing required status is defaultly not allowed for positional arguments.
std::vector<value_type> _choices; ///< Vector of valid choices for the positional argument.
action_type _action = ap::action::default_action<value_type>(
); ///< Action associated with the positional argument.
action_type _action =
ap::action::none<value_type>(); ///< Action associated with the positional argument.

std::any _value; ///< Stored value of the positional argument.

Expand Down Expand Up @@ -643,11 +580,13 @@ class optional_argument : public ap::detail::argument_interface {
void _apply_action(value_type& value) const noexcept {
namespace action = ap::action::detail;
if (action::is_void_action(this->_action))
std::get<action::callable_type<ap::void_action, value_type>>(this->_action)(value);
std::get<action::callable_type<ap::action_type::modify, value_type>>(this->_action)(
value
);
else
value =
std::get<action::callable_type<ap::valued_action, value_type>>(this->_action)(value
);
value = std::get<
action::callable_type<ap::action_type::transform, value_type>>(this->_action)(value
);
}

using action_type = ap::action::detail::action_variant_type<T>;
Expand All @@ -660,7 +599,7 @@ class optional_argument : public ap::detail::argument_interface {
bool _bypass_required = false;
std::optional<ap::nargs::range> _nargs_range;
action_type _action =
ap::action::default_action<value_type>(); ///< Action associated with the opitonal argument.
ap::action::none<value_type>(); ///< Action associated with the opitonal argument.
std::vector<value_type> _choices; ///< Vector of valid choices for the optional argument.
std::any _default_value;
std::any _implicit_value;
Expand Down Expand Up @@ -985,7 +924,7 @@ class argument_parser {
switch (arg_discriminator) {
case default_posarg::input:
this->add_positional_argument("input")
.action<ap::void_action>(ap::action::check_file_exists())
.action<action_type::modify>(action::check_file_exists())
.help("Input file path");
break;

Expand All @@ -1009,7 +948,7 @@ class argument_parser {
this->add_optional_argument("input", "i")
.required()
.nargs(1)
.action<ap::void_action>(ap::action::check_file_exists())
.action<action_type::modify>(action::check_file_exists())
.help("Input file path");
break;

Expand All @@ -1021,7 +960,7 @@ class argument_parser {
this->add_optional_argument("input", "i")
.required()
.nargs(ap::nargs::at_least(1))
.action<ap::void_action>(ap::action::check_file_exists())
.action<action_type::modify>(action::check_file_exists())
.help("Input files paths");
break;

Expand Down
4 changes: 2 additions & 2 deletions tests/source/test_optional_argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ TEST_CASE_FIXTURE(

SUBCASE("valued action") {
const auto double_valued_action = [](const test_value_type& value) { return 2 * value; };
sut.action<ap::valued_action>(double_valued_action);
sut.action<ap::action_type::transform>(double_valued_action);

sut_set_value(sut, std::to_string(value_1));

Expand All @@ -367,7 +367,7 @@ TEST_CASE_FIXTURE(

SUBCASE("void action") {
const auto double_void_action = [](test_value_type& value) { value *= 2; };
sut.action<ap::void_action>(double_void_action);
sut.action<ap::action_type::modify>(double_void_action);

auto test_value = value_1;

Expand Down
4 changes: 2 additions & 2 deletions tests/source/test_positional_argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ TEST_CASE_FIXTURE(

SUBCASE("valued action") {
const auto double_valued_action = [](const test_value_type& value) { return 2 * value; };
sut.action<ap::valued_action>(double_valued_action);
sut.action<ap::action_type::transform>(double_valued_action);

sut_set_value(sut, std::to_string(value_1));

Expand All @@ -231,7 +231,7 @@ TEST_CASE_FIXTURE(

SUBCASE("void action") {
const auto double_void_action = [](test_value_type& value) { value *= 2; };
sut.action<ap::void_action>(double_void_action);
sut.action<ap::action_type::modify>(double_void_action);

auto test_value = value_1;

Expand Down

0 comments on commit 16e2e94

Please sign in to comment.