diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..258c14d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,96 @@ +cmake_minimum_required(VERSION 3.12) + +# check if CPP-AP is a top level project +if (NOT DEFINED PROJECT_NAME) + set(CPP_AP_IS_TOP_LEVEL_PROJECT ON) +else() + set(CPP_AP_IS_TOP_LEVEL_PROJECT OFF) +endif() + +project( + cpp-ap + VERSION 1.0 + DESCRIPTION "Command-line argument parser for C++20" + HOMEPAGE_URL "https://github.com/SpectraL519/cpp-ap" + LANGUAGES CXX +) + +# define the library target +add_library(cpp-ap INTERFACE) +target_include_directories(cpp-ap INTERFACE + $ + $ +) + +# installation rules +include(GNUInstallDirs) +install(TARGETS cpp-ap + EXPORT cpp-ap-targets + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# generate and install the CMake package configuration files +install(EXPORT cpp-ap-targets + FILE cpp-ap-targets.cmake + NAMESPACE cpp-ap:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY ExactVersion +) + +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/cpp-ap-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap +) + +if (CPP_AP_IS_TOP_LEVEL_PROJECT) + option(CPP_AP_BUILD_TESTS "Build tests" ON) + option(CPP_AP_BUILD_EXAMPLES "Build examples" OFF) + + if(CPP_AP_BUILD_EXAMPLES) + add_subdirectory(examples) + endif() + + if(CPP_AP_BUILD_TESTS) + enable_testing() + add_subdirectory(test) + endif() + + if(CPP_AP_INSTALL) + install(EXPORT cpp-ap-targets + FILE cpp-ap-config.cmake + NAMESPACE ap:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap + ) + + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/cpp-ap-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpp-ap + ) + endif() + + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PROJECT_DESCRIPTION}") + set(CPACK_PACKAGE_VENDOR "SpectraL519") + set(CPACK_PACKAGE_CONTACT "Jakub Musiał jmusial2003@gmail.com") + set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") + set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") + set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") + set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") + + include(CPack) +endif() diff --git a/README.md b/README.md index 7bf39c7..c354633 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,19 @@ Command-line argument parser for C++20 ## Overview -The `CPP-AP` library has been developed for the *Team Programming* course at the *Wrocław University of Science and Technology*. - -Faculty: *W04N - Faculty of Information and Communication Technology* - -Field of study: *Algorithmic Computer Science* - -
- The goal of the project was to create a light, intuitive and simple to use command-line argument parser library for the `C++20` and newer standards. The `CPP-AP` library does not require installing any additional tools or heavy libraries, like with `boost::program_options`. Much like with the `Doctest` framework - the only thing you need to do is copy the `argument_parser.hpp` file into the include directory of your project and you're set to go. +> [!NOTE] +> [v1.0](https://github.com/SpectraL519/cpp-ap/commit/9a9e5360766b732f322ae2efe3cf5ec5f9268eef) of the library has been developed for the *Team Programming* course at the *Wrocław University of Science and Technology*. +> +> Faculty: *W04N - Faculty of Information and Communication Technology* +> +> Field of study: *Algorithmic Computer Science* +> +> The project has received the 1st place at the 2024 CreatiWITy competition organized by the faculty. The article in Polish can be found on the [faculty website](https://wit.pwr.edu.pl/aktualnosci/oto-laureaci-konkursu-creatiwity-273.html). Please note that this is not a technical article :) +

@@ -47,6 +48,8 @@ The `CPP-AP` library does not require installing any additional tools or heavy l ## Tutorial + + To use the `CPP-AP` library in your project, copy the [argument_parser.hpp](include/ap/argument_parser.hpp) file into your include directory, e.g. `include/ap`. No other setup is necessary - you only need to include this header in your code: ```c++ @@ -74,25 +77,28 @@ parser.program_name("Name of the program") The parser supports both positional and optional arguments. Both argument types are identified by their names represented as strings. Arguments can be defined with only a primary name or with a primary and a secondary (short) name. -**NOTE:** The basic rules of parsing positional and optional arguments are described in the [Parsing arguments](#parsing-arguments) section. +> [!NOTE] +> The basic rules of parsing positional and optional arguments are described in the [Parsing arguments](#parsing-arguments) section. To add an argument to the parameter's configurations use the following syntax: ```c++ -parser.add__argument("argument_name"); +parser.add__argument("argument"); ``` or ```c++ -parser.add__argument("argument_name", "a"); +parser.add__argument("argument", "a"); ``` -**NOTE:** The library supports any argument value types which meet the following requirements: -* The `std::ostream& operator<<` must be overloaded for a value type -* The type must have a copy constructor and an assignment operator +> [!NOTE] +> The library supports any argument value types which meet the following requirements: +* The `std::ostream& operator<<` is overloaded for the value type +* The value type has a copy constructor and an assignment operator -**NOTE:** If the `value_type` is not provided, `std::string` will be used. +> [!IMPORTANT] +> If the `value_type` is not provided, `std::string` will be used. You can also add boolean flags: @@ -135,12 +141,13 @@ Parameters which can be specified for both positional and optional arguments inc * `choices` - a list of valid argument values. The `choices` parameter takes a `const std::vector&` as an argument. - **NOTE:** To use the `choices` the `value_type` must overload the equaility comparison operator: `==`; - ```c++ parser.add_optional_argument("method", "m").choices({'a', 'b', 'c'}); ``` +> [!IMPORTANT] +> To use the `choices` the `value_type` must overload the equaility comparison operator: `==`; + * `action` - a function performed after reading an argument's value. Actions are represented as functions, which take the argument's value as an argument. There are two types of actions: @@ -160,10 +167,10 @@ Parameters which can be specified for both positional and optional arguments inc .action([](const double& value) { return 1. / value; }); ``` - Actions can also be used to perform some value checking logic, e.g. the predefined `check_file_exists_action` which checks if a file with a given name exists: + 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::action::check_file_exists_action); + .action(ap::action::check_file_exists()); ``` **Optional argument specific parameters** @@ -196,7 +203,8 @@ Parameters which can be specified for both positional and optional arguments inc parser.add_optional_argument("input", "i").nargs(ap::nargs::any()); ``` - **NOTE:** The default nargs value is `1`. +> [!NOTE] +> The default nargs value is `1`. * `default_value` - the default value for an argument which will be used if no values for this argument are parsed ```c++ @@ -231,7 +239,7 @@ The supported default arguments are: ```c++ // equivalent to: parser.add_positional_argument("input") - .action(ap::action::check_file_exists_action) + .action(ap::action::check_file_exists()) .help("Input file path"); ``` @@ -247,14 +255,15 @@ The supported default arguments are: parser.add_flag("help", "h").bypass_required().help("Display help message"); ``` - **Note:** As of now the *on flag action* functionality is not implemented in the library - this will be added in a future release. - To properly use the help argument in the current release add the following right beneath the `parser.parse_args(argc, argv)` try-catch block: - ```c++ - if (parser.has_value("help")) { - std::cout << parser << std::endl; - std::exit(EXIT_SUCCESS); - } - ``` +> [!NOTE] +> As of now the *on flag action* functionality is not implemented in the library - this will be added in a future release. +> To properly use the help argument in the current release add the following right beneath the `parser.parse_args(argc, argv)` try-catch block: +> ```c++ +> if (parser.value("help")) { +> std::cout << parser << std::endl; +> std::exit(EXIT_SUCCESS); +> } +> ``` * `optional::input` and `optional::multi_input`: ```c++ @@ -262,14 +271,14 @@ The supported default arguments are: parser.add_optional_argument("input", "i") .required() .nargs(1) - .action(ap::action::check_file_exists_action) + .action(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::action::check_file_exists_action) + .action(ap::action::check_file_exists()) .help("Input files paths"); ``` @@ -288,7 +297,8 @@ The supported default arguments are: .help("Output files paths"); ``` -**NOTE:** The `argument_parser::default__arguments` functions will be modified to use a variadic argument list instead of a `std::vector` in a future release. +> [!NOTE] +> The `argument_parser::default__arguments` functions will be modified to use a variadic argument list instead of a `std::vector` in a future release. ### Parsing arguments @@ -298,14 +308,14 @@ To parse the command-line arguments use the `argument_parser::parse_args` method // power.cpp #include -#include #include +#include int main(int argc, char* argv[]) { // create the parser class instance ap::argument_parser parser; parser.program_name("power calculator") - .program_description("calculates the value of an expression: base & exponent"); + .program_description("calculates the value of an expression: base ^ exponent"); // add arguments parser.add_positional_argument("base").help("the exponentation base value"); @@ -325,7 +335,7 @@ int main(int argc, char* argv[]) { } // check for the help argument presence - if (parser.has_value("help")) { + if (parser.value("help")) { std::cout << parser << std::endl; std::exit(EXIT_SUCCESS); } @@ -361,18 +371,20 @@ int main(int argc, char* argv[]) { # no exponent values given ``` - **NOTE:** For each positional argument there must be **exactly one value**. ```shell - ./test_program + ./power # out: # [ERROR] : No values parsed for a required argument [base] # power calculator - # calculates the value of an expression: base & exponent + # calculates the value of an expression: base ^ exponent # [base] : the exponentation base value # [exponent,e] : the exponent value # [help,h] : Display help message ``` +> [!IMPORTANT] +> For each positional argument there must be **exactly one value**. + * Optional arguments are parsed only with a flag: ```shell ./power 2 --exponent 1 2 3 @@ -400,7 +412,8 @@ int main(int argc, char* argv[]) { # [help,h] : Display help message ``` -**NOTE:** The parser behaviour depends on the argument definitions. The argument parameters are described int the [Argument parameters](#argument-parameters) section. +> [!IMPORTANT] +> The parser behaviour depends on the argument definitions. The argument parameters are described int the [Argument parameters](#argument-parameters) section.

@@ -416,7 +429,8 @@ cd /example The examples' source files are in the `/example/source` directory. -> **Note:** Each source file is a sepparate example. +> [!NOTE] +> Each source file is a sepparate example. Building the examples: @@ -461,11 +475,13 @@ cmake .. make ``` -> **NOTE:** Building on Windows - use the `-G "Unix Makefiles"` option when running CMake to build a GNU Make project instead of a default Visual Studio project. +> [!TIP] +> Building on Windows - use the `-G "Unix Makefiles"` option when running CMake to build a GNU Make project instead of a default Visual Studio project. Run the tests: -> **NOTE:** The test executable is generated in the `/test/build` directory. +> [!NOTE] +> The test executable is generated in the `/test/build` directory. * All tests: @@ -479,18 +495,21 @@ Run the tests: ./test -ts="" ``` - > **Note**: Test suites in the project have the same name as the files they're in. + > [!NOTE] + > Test suites in the project have the same names as the files they're in.
### Formatting -> **NOTE:** To ensure new line encoding compatibility the project uses unix new line encoding. +> [!IMPORTANT] +> To ensure new line encoding compatibility the project uses unix new line encoding. > > This can be set using the `git config --global core.autocrlf true` command. > More details can be found [here](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings) -> **NOTE:** The project uses `clang-format-17`. +> [!NOTE] +> The project uses `clang-format-17`. > > To install this tool on ubuntu run `sudo bash ./scripts/env/install_clang17_toolchain.sh`. > @@ -534,7 +553,8 @@ The documentation for this project can be generated using Doxygen: As of now the project supports the **GNU G++** and **Clang++** compilers with `C++20` support on Linux and Windows. -> **NOTE:** To build the project using clang you will need to install the `clang-17` toolchain using the script or website mentioned in the [Formatting](#formatting) section. +> [!NOTE] +> To build the project using clang you will need to install the `clang-17` toolchain using the script or website mentioned in the [Formatting](#formatting) section.

diff --git a/change_log.md b/change_log.md index 74727e9..c56f156 100644 --- a/change_log.md +++ b/change_log.md @@ -30,9 +30,10 @@ * Aligned the `.clang-format` configuration file * Added the `install_clang17_toolchain.sh` env script * Added the `format` workflow -* Switched from the `` to the `` library for all current container operations +* Switched to the `std::ranges` and `std::views` algorithms for all current container operations * Modified the `argument_name` structure - renamed members: `name` to `primary`, `short_name` to `secondary` * Added `argument_name::match(string_view)` and `argument_name::match(argument_name)` functions * Added aliases for default argument enum classes: * `ap::default_argument::positional` = `ap::default_posarg` * `ap::default_argument::optional` = `ap::default_optarg` +* Renamed the predefined: `ap::action::check_file_exists_action` -> `ap::action::check_file_exists` diff --git a/cmake/cpp-ap-config.cmake.in b/cmake/cpp-ap-config.cmake.in new file mode 100644 index 0000000..6b4bbff --- /dev/null +++ b/cmake/cpp-ap-config.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +# avoid repeatedly including the targets +if(NOT TARGET cpp-ap::cpp-ap) + # Provide path for scripts + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + + include(${CMAKE_CURRENT_LIST_DIR}/cpp-ap-targets.cmake) +endif() diff --git a/include/ap/argument_parser.hpp b/include/ap/argument_parser.hpp index 2ed25a3..e82cbbe 100644 --- a/include/ap/argument_parser.hpp +++ b/include/ap/argument_parser.hpp @@ -106,213 +106,6 @@ inline constexpr bool is_valid_type_v = std::disjunction_v_default; - } - - /** - * @brief Checks if a given value count is within the range. - * @param n The value count to check. - * @return Ordering relationship between the count and the range. - */ - [[nodiscard]] std::weak_ordering contains(const range::count_type n) const noexcept { - if (not (this->_nlow.has_value() or this->_nhigh.has_value())) - return std::weak_ordering::equivalent; - - if (this->_nlow.has_value() and this->_nhigh.has_value()) { - if (n < this->_nlow.value()) - return std::weak_ordering::less; - - if (n > this->_nhigh.value()) - return std::weak_ordering::greater; - - return std::weak_ordering::equivalent; - } - - if (this->_nlow.has_value()) - return (n < this->_nlow.value()) ? std::weak_ordering::less : std::weak_ordering::equivalent; - - return (n > this->_nhigh.value()) ? std::weak_ordering::greater : std::weak_ordering::equivalent; - } - - friend range at_least(const count_type) noexcept; - friend range more_than(const count_type) noexcept; - friend range less_than(const count_type) noexcept; - friend range up_to(const count_type) noexcept; - friend range any() noexcept; - -private: - /** - * @brief Private constructor: creates a possibly unbound range - * @param nlow The optional lower bound of the range. - * @param nhigh The optional upper bound of the range. - */ - range(const std::optional nlow, const std::optional nhigh) - : _nlow(nlow), _nhigh(nhigh) {} - - std::optional _nlow; - std::optional _nhigh; - bool _default = true; - - static constexpr count_type _ndefault = 1; -}; - -/** - * @brief `range` class builder function. Creates a range [n, inf]. - * @param n The lower bound. - * @return Built `range` class instance. - */ -[[nodiscard]] inline range at_least(const range::count_type n) noexcept { - return range(n, std::nullopt); -} - -/** - * @brief `range` class builder function. Creates a range [n + 1, inf]. - * @param n The lower bound. - * @return Built `range` class instance. - */ -[[nodiscard]] inline range more_than(const range::count_type n) noexcept { - return range(n + 1, std::nullopt); -} - -/** - * @brief `range` class builder function. Creates a range [0, n - 1]. - * @param n The upper bound - * @return Built `range` class instance. - */ -[[nodiscard]] inline range less_than(const range::count_type n) noexcept { - return range(std::nullopt, n - 1); -} - -/** - * @brief `range` class builder function. Creates a range [0, n]. - * @param n The upper bound - * @return Built `range` class instance. - */ -[[nodiscard]] inline range up_to(const range::count_type n) noexcept { - return range(std::nullopt, n); -} - -/** - * @brief `range` class builder function. Creates a range [0, inf]. - * @return Built `range` class instance. - */ -[[nodiscard]] inline range any() noexcept { - return range(std::nullopt, std::nullopt); -} - -} // namespace nargs - -/// @brief Defines valued argument action traits. -struct valued_action { - template - using type = std::function; -}; - -/// @brief Defines void argument action traits. -struct void_action { - template - using type = std::function; -}; - -// TODO: on_read_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 -concept valid_action_specifier = ap::utility::is_valid_type_v; - -/// @brief Template argument action callable type alias. -template -using callable_type = typename AS::template type; - -/// @brief Template argument action callabla variant type alias. -template -using action_variant_type = - std::variant, callable_type>; - -/** - * @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 -[[nodiscard]] inline bool is_void_action(const action_variant_type& action) noexcept { - return std::holds_alternative>(action); -} - -} // namespace detail - -/// @brief Returns a default argument action. -template -detail::callable_type 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 check_file_exists_action() noexcept { - return [](std::string& file_path) { - if (not std::filesystem::exists(file_path)) { - std::cerr << "[ERROR] : File " + file_path + " does not exists!"; - std::exit(EXIT_FAILURE); - } - }; -} - -// TODO: on_flag_action - -} // namespace action - /// @brief Internal argument handling utility. namespace argument::detail { @@ -623,6 +416,212 @@ class invalid_nvalues_error : public argument_parser_error { } // namespace error +/// @brief Argument's number of values management utility. +namespace nargs { + +/// @brief Argument's number of values managing class. +class range { +public: + using count_type = std::size_t; + + /// @brief Default constructor: creates range [1, 1]. + range() : _nlow(_ndefault), _nhigh(_ndefault) {} + + /** + * @brief Exact count constructor: creates range [n, n]. + * @param n Expected value count. + */ + explicit range(const count_type n) : _nlow(n), _nhigh(n), _default(n == _ndefault) {} + + /** + * @brief Concrete range constructor: creates range [nlow, nhigh]. + * @param nlow The lower bound. + * @param nhigh The upper bound. + */ + range(const count_type nlow, const count_type nhigh) + : _nlow(nlow), _nhigh(nhigh), _default(nlow == _ndefault and nhigh == _ndefault) {} + + /// @brief Copy constructor + range(const range&) = default; + + /// @brief Move constructor + range(range&&) = default; + + /// @brief Copy assignmner constructor + range& operator=(const range&) = default; + + /// @brief Move assignmner constructor + range& operator=(range&&) = default; + + /// @brief Class destructor. + ~range() = default; + + /// @return True if the range is [1, 1]. + [[nodiscard]] bool is_default() const noexcept { + return this->_default; + } + + /** + * @brief Checks if a given value count is within the range. + * @param n The value count to check. + * @return Ordering relationship between the count and the range. + */ + [[nodiscard]] std::weak_ordering contains(const range::count_type n) const noexcept { + if (not (this->_nlow.has_value() or this->_nhigh.has_value())) + return std::weak_ordering::equivalent; + + if (this->_nlow.has_value() and this->_nhigh.has_value()) { + if (n < this->_nlow.value()) + return std::weak_ordering::less; + + if (n > this->_nhigh.value()) + return std::weak_ordering::greater; + + return std::weak_ordering::equivalent; + } + + if (this->_nlow.has_value()) + return (n < this->_nlow.value()) ? std::weak_ordering::less : std::weak_ordering::equivalent; + + return (n > this->_nhigh.value()) ? std::weak_ordering::greater : std::weak_ordering::equivalent; + } + + friend range at_least(const count_type) noexcept; + friend range more_than(const count_type) noexcept; + friend range less_than(const count_type) noexcept; + friend range up_to(const count_type) noexcept; + friend range any() noexcept; + +private: + /** + * @brief Private constructor: creates a possibly unbound range + * @param nlow The optional lower bound of the range. + * @param nhigh The optional upper bound of the range. + */ + range(const std::optional nlow, const std::optional nhigh) + : _nlow(nlow), _nhigh(nhigh) {} + + std::optional _nlow; + std::optional _nhigh; + bool _default = true; + + static constexpr count_type _ndefault = 1; +}; + +/** + * @brief `range` class builder function. Creates a range [n, inf]. + * @param n The lower bound. + * @return Built `range` class instance. + */ +[[nodiscard]] inline range at_least(const range::count_type n) noexcept { + return range(n, std::nullopt); +} + +/** + * @brief `range` class builder function. Creates a range [n + 1, inf]. + * @param n The lower bound. + * @return Built `range` class instance. + */ +[[nodiscard]] inline range more_than(const range::count_type n) noexcept { + return range(n + 1, std::nullopt); +} + +/** + * @brief `range` class builder function. Creates a range [0, n - 1]. + * @param n The upper bound + * @return Built `range` class instance. + */ +[[nodiscard]] inline range less_than(const range::count_type n) noexcept { + return range(std::nullopt, n - 1); +} + +/** + * @brief `range` class builder function. Creates a range [0, n]. + * @param n The upper bound + * @return Built `range` class instance. + */ +[[nodiscard]] inline range up_to(const range::count_type n) noexcept { + return range(std::nullopt, n); +} + +/** + * @brief `range` class builder function. Creates a range [0, inf]. + * @return Built `range` class instance. + */ +[[nodiscard]] inline range any() noexcept { + return range(std::nullopt, std::nullopt); +} + +} // namespace nargs + +/// @brief Defines valued argument action traits. +struct valued_action { + template + using type = std::function; +}; + +/// @brief Defines void argument action traits. +struct void_action { + template + using type = std::function; +}; + +// 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 +concept valid_action_specifier = ap::utility::is_valid_type_v; + +/// @brief Template argument action callable type alias. +template +using callable_type = typename AS::template type; + +/// @brief Template argument action callabla variant type alias. +template +using action_variant_type = + std::variant, callable_type>; + +/** + * @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 +[[nodiscard]] inline bool is_void_action(const action_variant_type& action) noexcept { + return std::holds_alternative>(action); +} + +} // namespace detail + +/// @brief Returns a default argument action. +template +detail::callable_type 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 check_file_exists() noexcept { + return [](std::string& file_path) { + if (not std::filesystem::exists(file_path)) + throw argument_parser_error("[ERROR] : File " + file_path + " does not exists!"); + }; +} + +} // namespace action + + /// @brief Namespace containing classes and utilities for handling command-line arguments. namespace argument { @@ -1431,7 +1430,7 @@ class argument_parser { switch (arg_discriminator) { case default_posarg::input: this->add_positional_argument("input") - .action(ap::action::check_file_exists_action()) + .action(ap::action::check_file_exists()) .help("Input file path"); break; @@ -1455,7 +1454,7 @@ class argument_parser { this->add_optional_argument("input", "i") .required() .nargs(1) - .action(ap::action::check_file_exists_action()) + .action(ap::action::check_file_exists()) .help("Input file path"); break; @@ -1467,7 +1466,7 @@ class argument_parser { this->add_optional_argument("input", "i") .required() .nargs(ap::nargs::at_least(1)) - .action(ap::action::check_file_exists_action()) + .action(ap::action::check_file_exists()) .help("Input files paths"); break;