From 256d3f93c3a99f3ee4535ca3d27736e0e0b2f2e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Musia=C5=82?=
<111433005+SpectraL519@users.noreply.github.com>
Date: Mon, 22 Apr 2024 13:47:49 +0200
Subject: [PATCH] YT-CPPAP-8: Fix typos and rename some elements
- Fixed some typos and incorrect examples in the readme file
- Added markdown alerts in the readme file
- Renamed and refactored the `ap::action::check_file_exists_action()` function
---
README.md | 104 ++++----
change_log.md | 3 +-
include/ap/argument_parser.hpp | 419 ++++++++++++++++-----------------
3 files changed, 271 insertions(+), 255 deletions(-)
diff --git a/README.md b/README.md
index 7bf39c7..9133cdd 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 :)
+
@@ -74,25 +75,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,7 +139,8 @@ 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: `==`;
+ > [!IMPORTANT]
+ > To use the `choices` the `value_type` must overload the equaility comparison operator: `==`;
```c++
parser.add_optional_argument("method", "m").choices({'a', 'b', 'c'});
@@ -160,10 +165,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 +201,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 +237,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 +253,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.has_value("help")) {
+ > std::cout << parser << std::endl;
+ > std::exit(EXIT_SUCCESS);
+ > }
+ > ```
* `optional::input` and `optional::multi_input`:
```c++
@@ -262,14 +269,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 +295,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 +306,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");
@@ -361,13 +369,14 @@ int main(int argc, char* argv[]) {
# no exponent values given
```
- **NOTE:** For each positional argument there must be **exactly one value**.
+ > [!IMPORTANT]
+ > 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
@@ -400,7 +409,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 +426,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 +472,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 +492,20 @@ 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 +549,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/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;