From 4366b0ac33ad89a75d94d306ef34ab19e63d69ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Musia=C5=82?= <111433005+SpectraL519@users.noreply.github.com> Date: Fri, 9 Feb 2024 15:02:01 +0100 Subject: [PATCH] version 1.0 (#38) * Switched entirely to CMake * Updated README - added usage tutorial, compiler support and licence info, updated dev notes * Small doc comments changes * Added `g++` and `test` workflows --- .github/workflows/gpp.yaml | 29 + .github/workflows/test.yaml | 39 ++ .gitignore | 6 +- Doxyfile | 6 +- README.md | 580 +++++++++++++++--- example/CMakeLists.txt | 49 ++ example/Makefile | 26 - example/exe/.gitkeep | 0 example/make/unix_like.mk | 66 -- example/make/windows.mk | 73 --- example/src/power.cpp | 49 ++ include/ap/argument_parser.hpp | 45 +- test/CMakeLists.txt | 38 +- test/Makefile | 26 - test/cmake/.gitkeep | 0 test/include/argument_parser_test_fixture.hpp | 2 +- test/make/unix_like.mk | 76 --- test/make/windows.mk | 81 --- test/out/.gitkeep | 0 test/src/test_argument_parser_parse_args.cpp | 8 +- 20 files changed, 704 insertions(+), 495 deletions(-) create mode 100644 .github/workflows/gpp.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 example/CMakeLists.txt delete mode 100644 example/Makefile delete mode 100644 example/exe/.gitkeep delete mode 100644 example/make/unix_like.mk delete mode 100644 example/make/windows.mk create mode 100644 example/src/power.cpp delete mode 100644 test/Makefile delete mode 100644 test/cmake/.gitkeep delete mode 100644 test/make/unix_like.mk delete mode 100644 test/make/windows.mk delete mode 100644 test/out/.gitkeep diff --git a/.github/workflows/gpp.yaml b/.github/workflows/gpp.yaml new file mode 100644 index 0000000..8a327c9 --- /dev/null +++ b/.github/workflows/gpp.yaml @@ -0,0 +1,29 @@ +name: g++ +on: + push: + branches: + - '*' + pull_request: + branches: + - master + +jobs: + build: + name: Build examples + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Build + shell: bash + env: + CC: gcc-11 + CXX: g++-11 + run: | + cd example + cmake -B build -DCMAKE_CXX_COMPILER=g++-11 + cd build + make + continue-on-error: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..8532e7a --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,39 @@ +name: test +on: + push: + branches: + - '*' + pull_request: + branches: + - master + +jobs: + build_and_test: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Prepare + run: | + cd test + cmake -B build -DCMAKE_CXX_COMPILER=g++-11 + continue-on-error: false + + - name: Build test executable + shell: bash + env: + CC: gcc-11 + CXX: g++-11 + run: | + cd test/build + make + continue-on-error: false + + - name: Run tests + run: | + cd test/build + ./test + continue-on-error: false diff --git a/.gitignore b/.gitignore index b23989d..a006ac9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,11 +3,9 @@ # build files *.exe -test/out/* test/test -log/* -test/cmake/* -!test/cmake/.gitkeep +test/build/ +example/build/ # documentation files /documentation diff --git a/Doxyfile b/Doxyfile index 55ff6cf..2488367 100644 --- a/Doxyfile +++ b/Doxyfile @@ -42,19 +42,19 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "Argument Parser for C++20" +PROJECT_NAME = "CPP-AP" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = 1.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = +PROJECT_BRIEF = "Command-line argument parser for C++20" # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 diff --git a/README.md b/README.md index 9667f13..418eb3e 100644 --- a/README.md +++ b/README.md @@ -1,130 +1,520 @@ -# cpp-ap -Argument Parser for C++20 +# CPP-AP +Command-line argument parser for C++20 + +[![g++](https://github.com/SpectraL519/cpp-ap/actions/workflows/gpp.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/g++) +[![test](https://github.com/SpectraL519/cpp-ap/actions/workflows/test.yaml/badge.svg)](https://github.com/SpectraL519/cpp-ap/actions/workflows/test) + +
+ +## 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*
-> CPP-AP is a project 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.

-## DEV NOTES +## Table of contents -1. Requirements: - * g++ >= g++-11 - * clang-format-17 ([ubuntu download tutorial](https://ubuntuhandbook.org/index.php/2023/09/how-to-install-clang-17-or-16-in-ubuntu-22-04-20-04/amp/?fbclid=IwAR1ZfJpoiitjwn8aMlKVWpFdkYmUqtaQwraJBju09v1gtc0jQANTgVeCuMY)) +* [Tutorial](#tutorial) + * [The parser class](#the-parser-class) + * [Adding arguments](#adding-arguments) + * [Argument parameters](#argument-parameters) + * [Default arguments](#default-arguments) + * [Parsing arguments](#parsing-arguments) +* [Examples](#examples) +* [Dev notes](#dev-notes) + * [Requirements](#requirements) + * [Building and testing](#building-and-testing) +* [Documentation](#documentation) +* [Compiler support](#compiler-support) +* [Licence](#licence) +

-2. Building and running tests: +## Tutorial - * With GNU Make +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: - ``` - > cd /test - > make all - ``` - ``` - > ./test - ``` - ``` - > ./test -ts="" - ``` - - * With CMake - ``` - > cd /test/cmake - > cmake .. - > make - ``` - ``` - > ./test/out/test - ``` - ``` - > ./test/out/test -ts="" - ``` +```c++ +#include +``` -3. Error fixing: +If you wish to use the library across multiple projects without copying the header into each one, you can copy it into a common directory and add the `-I ` option when compiling your project. - * Makefile error +### The parser class - In case you face an error in Windows saying that: - ``` - ...\profile.ps1 cannot be loaded because running scripts is disable on this system. ... - ``` - Then you should run the following command to enable running scripts by Windows' Makefile: - ``` - > PowerShell -ExecutionPolicy Bypass - ``` - * Wrong CMake generator +To use the argument parser in your code you need to use the `ap::argument_parser` class. - In case you generate VS Studio files by using CMake instead of Makefile then you should change used generator in CMake by using the following command: - ``` - > cmake -G "Unix Makefiles" .. - ``` - Instead of this command: - ``` - > cmake .. - ``` - In case this fix does not work then you will probably have to search for another generator matching your system's requirements. You can do that by listing all available generators in "Generators" section when running the following command: - ``` - > cmake --help - ``` - Just try to find a right generator for your system and run mentioned command by subtituting a chosen generator into this command: - ``` - > cmake -G "" .. - ``` +The parameters you can specify for a parser's instance are: +* Program name +* Program description +* [Arguments](#adding-arguments) -4. Tips and tricks: +```c++ +ap::argument_parser parser; +parser.program_name("Name of the program") + .program_description("Description of the program"); +``` - * CMake compiler swap +### Adding arguments - In case you would like to swap used compiler in CMake to the same compiler with other version or a different compiler then you should run cmake command by using -D flag in the following way: - ``` - > cmake -DCMAKE_CXX_COMPILER= .. - ``` - The default is: - ``` - > cmake -DCMAKE_CXX_COMPILER=g++ .. - ``` - You can also change used flags in current compiler in the following way: - ``` - > cmake -DCMAKE_CXX_FLAGS=" ..." .. - ``` - Or both things at once: - ``` - > cmake -DCMAKE_CXX_COMPILER= -DCMAKE_CXX_FLAGS=" ..." .. +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. + +To add an argument to the parameter's configurations use the following syntax: + +```c++ +parser.add__argument("argument_name"); +``` + +or + +```c++ +parser.add__argument("argument_name", "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:** If the `value_type` is not provided, `std::string` will be used. + +You can also add boolean flags: + +```c++ +parser.add_flag("enable_some_option", "eso").help("enables option: some option"); +/* equivalent to: +parser.add_optional_argument("enable_some_option", "eso") + .default_value(false) + .implicit_value(true) + .nargs(0) + .help("enables option: some option"); +*/ +``` + +Boolean flags store `true` by default but you can specify whether the flag should store `true` or `false` when used: +```c++ +parser.add_flag("disable_another_option", "dao").help("disables option: another option"); +/* equivalent to: +parser.add_optional_argument("disable_another_option", "dao") + .default_value(true) + .implicit_value(false) + .nargs(0) + .help("disables option: another option"); +*/ +``` + +### Argument parameters + +**Common parameters** + +Parameters which can be specified for both positional and optional arguments include: + +* `help` - the argument's description which will be printed when printing the parser class instance. + + ```c++ + parser.add_positional_argument("number", "n") + .help("a positive integer value"); + ``` + +* `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'}); + ``` + +* `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: + * Void actions - `void(value_type&)` + * Valued actions - `value_type(const value_type&)` + + The default action is an empty void function. + + Actions can be used to modify a value parsed from the command-line: + ```c++ + parser.add_optional_argument("denominator", "d") + .action([](double& value) { value = 1. / value; }); + ``` + or en equivalent valued action: + ```c++ + parser.add_optional_argument("denominator", "d") + .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: + ```c++ + parser.add_optional_argument("input", "i") + .action(ap::action::check_file_exists_action); + ``` + +**Optional argument specific parameters** + +* `required` - if this option is set for an argument, failure of parsing it's value will result in an error. + ```c++ + parser.add_optional_argument("output", "o").required(); + ``` + +* `bypass_required` - if this option is set for an argument, parsing it's value will overrite the `required` option for other optional arguments and all positional arguments. + +* `nargs` - sets the allowed number of values to be parsed for an argument. This can be set as a: + * Concrete number: + ```c++ + parser.add_optional_argument("input", "i").nargs(1); + ``` + * Bound range: + ```c++ + parser.add_optional_argument("input", "i").nargs(1, 3); + ``` + * Partially bound range: + ```c++ + parser.add_optional_argument("input", "i").nargs(ap::nargs::at_least(1)); // n >= 1 + parser.add_optional_argument("input", "i").nargs(ap::nargs::more_than(1)); // n > 1 + parser.add_optional_argument("input", "i").nargs(ap::nargs::less_than(5)); // n < 5 + parser.add_optional_argument("input", "i").nargs(ap::nargs::up_to(5)); // n <= 5 + ``` + * Unbound range: + ```c++ + parser.add_optional_argument("input", "i").nargs(ap::nargs::any()); ``` - * GNU Make non-default compiler usage + **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++ + parser.add_opitonal_argument("output", "o").default_value("out.txt"); + ``` + +* `implicit_value` - a value which will be set for an argument if only it's flag is parsed from the command-line but no value follows + ```c++ + // program.cpp + parser.add_optional_argument("save", "s") + .implicit_value("out.txt") + .help("save the program's output to a file"); + ``` + In this example if you run the program with only a `-s` or `--save` flag and no value, the value will be set to `out.txt`. + +### Default arguments + +The `CPP-AP` library has a few default arguments defined. To add a default argument to the parser use the following: + +```c++ +// add positional arguments - pass a std::vector of default positional arguments +parser.default_positional_arguments({...}); + +// add optional arguments - pass a std::vector of default optional arguments +parser.default_positional_arguments({...}); +``` - In case you would like to use non-default compiler in GNU Make which is clang++ then you should run make with following parameters: +The supported default arguments are: +* `positional::input`: + ```c++ + // equivalent to: + parser.add_positional_argument("input") + .action(ap::action::check_file_exists_action) + .help("Input file path"); + ``` + +* `positional::output`: + ```c++ + // equivalent to: + parser.add_positional_argument("output").help("Output file path"); + ``` + +* `optional::help`: + ```c++ + // equivalent to: + 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); + } + ``` + +* `optional::input` and `optional::multi_input`: + ```c++ + // input - equivalent to: + parser.add_optional_argument("input", "i") + .required() + .nargs(1) + .action(ap::action::check_file_exists_action) + .help("Input file path"); + + // multi_input - equivalen to: + parser.add_optional_argument("input", "i") + .required() + .nargs(ap::nargs::at_least(1)) + .action(ap::action::check_file_exists_action) + .help("Input files paths"); + ``` + +* `optional::output` and `optional::multi_output`: + ```c++ + // output - equivalent to: + parser.add_optional_argument("output", "o") + .required() + .nargs(1) + .help("Output file path"); + + // multi_otput - equivalent to: + parser.add_optional_argument("output", "o") + .required() + .nargs(ap::nargs::at_least(1)) + .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. + +### Parsing arguments + +To parse the command-line arguments use the `argument_parser::parse_args` method: + +```c++ +// power.cpp +#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"); + + // add arguments + parser.add_positional_argument("base").help("the exponentation base value"); + parser.add_optional_argument("exponent", "e") + .nargs(ap::nargs::any()) + .help("the exponent value"); + + parser.default_optional_arguments({ap::default_argument::optional::help}); + + // parse command-line arguments + try { + parser.parse_args(argc, argv); + } + catch (const ap::argument_parser_error& err) { + std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; + std::exit(EXIT_FAILURE); + } + + // check for the help argument presence + if (parser.has_value("help")) { + std::cout << parser << std::endl; + std::exit(EXIT_SUCCESS); + } + + // check if any values for the `exponent` argument have been parsed + if (not parser.has_value("exponent")) { + std::cout << "no exponent values given" << std::endl; + std::exit(EXIT_SUCCESS); + } + + const double base = parser.value("base"); + const std::vector exponent_values = parser.values("exponent"); + + for (const int exponent : exponent_values) { + std::cout << base << " ^ " << exponent << " = " << std::pow(base, exponent) << std::endl; + } + + return 0; +} + +// compiled with: +// g++ -o power power.cpp -I +``` + +**Argument parsing rules:** + +* Positional arguments are parsed first, in the order they were defined in and without a flag. + + In the example above the first command-line argument must be the value for `positional_argument`: + ```shell + ./power 2 + # out: + # no exponent values given + ``` + + **NOTE:** For each positional argument there must be **exactly one value**. + ```shell + ./test_program + # out: + # [ERROR] : No values parsed for a required argument [base] + # power calculator + # calculates the value of an expression: base & exponent + # [base] : the exponentation base value + # [exponent,e] : the exponent value + # [help,h] : Display help message + ``` + +* Optional arguments are parsed only with a flag: + ```shell + ./power 2 --exponent 1 2 3 + # equivalent to: ./power 2 -e 1 2 3 + # out: + # 2 ^ 1 = 2 + # 2 ^ 2 = 4 + # 2 ^ 3 = 8 + ``` + + You can use the flag for each command-line value: + ```shell + ./power 2 -e 1 -e 2 -e 3 + ``` + + Not using a flag will result in an error: + ```shell + ./power 2 1 2 3 + # out: + # [ERROR] : Failed to deduce the argument for the given value `1` + # power calculator + # calculates the value of an expression: base & exponent + # [base] : the exponentation base value + # [exponent,e] : the exponent value + # [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. + +
+
+ +## Examples + +If you wish to test the parser functionality with some real examples then follow these steps: + +Open your terminal in the project's example directory: +```shell +cd /example +``` + +The examples' source files are in the `/example/src` directory. + +> **Note:** Each source file is a sepparate example. + +Building the examples: + +```shell +cmake -B build +cd build +make +``` + +or + +```shell +mkdir build && cd build +cmake .. +make +``` + +The compiled binaries will appear in the `/example/build/bin` directory. + +
+
+ +## Dev notes + +#### Requirements: + * Supported compiler (check compiler support [here](#compiler-support)) + * clang-format-17 ([ubuntu download tutorial](https://ubuntuhandbook.org/index.php/2023/09/how-to-install-clang-17-or-16-in-ubuntu-22-04-20-04/amp/?fbclid=IwAR1ZfJpoiitjwn8aMlKVWpFdkYmUqtaQwraJBju09v1gtc0jQANTgVeCuMY)) + +
+ +#### Building and testing: + +1. Build the testing executable: + + ```shell + cd /test/ + cmake -B build + cd build + make + ``` + + or + + ```shell + cd /test/ + mkdir build && cd build + cmake .. + make + ``` + +2. Run tests + + Run all tests: + ```shell + cd /test/build + ./test + ``` + + Run a single test suite: + ```shell + ./test -ts="" + ``` + + > **Note**: Test suites in the project have the same name as the files they're in. + +3. Tips and tricks: + + * Changing the CMake generator: + + If you wish for CMake to generate a different type of project, use the `-G` option, e.g. (building a Make project on Windows instead of a VS project): ``` - > make CXX=clang++ + cmake -G "Unix Makefiles" ``` - Default compiler is g++. Flags for other compilers than g++ and clang++ are not prepared. You will have to change them manually in the code in case of another compiler usage. - - Remember that this project does not work with older standards of c++ than c++-20. +
+
## Documentation -The documentation for this project is generated using Doxygen. Follow the steps below to generate and view the documentation. +The documentation for this project can be generated using Doxygen: + +1. Make sure you have `Doxygen` installed on your machine. If not you can download it from [here](https://www.doxygen.nl/download.html). + +2. Generate the documentation: -### Prerequisites + Open your terminal and use the following instructions: + ```shell + cd + doxygen Doxyfile + ``` -1. **Doxygen Installation**: Make sure you have Doxygen installed on your system. If not, you can download it from [here](https://www.doxygen.nl/download.html). +
+
-### Generating Documentation +## Compiler support -1. Open a terminal in the root directory of the project. +As of now the project supports the **GNU G++** compilers with `C++20` support (g++ >= 11.3.0) on Linux and Windows. + +The project does compile with **clang** compilers, however it is not officialy tested yet. + +
+
-2. Run the following command to generate the documentation: +## Licence - ```bash - doxygen Doxyfile - ``` +The `CPP-AP` project uses the [MIT Licence](https://opensource.org/license/mit/) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..eeb5d80 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,49 @@ +# Minimum CMake version required to build the project +cmake_minimum_required(VERSION 3.12) + +# Project +project(cpp-ap-examples) + +# Structure +set(SOURCE_DIR "src") +set(INCLUDE_DIRS "include" "../include") +set(BINARY_DIR "bin") +set(EXECUTABLE_DIR "bin") + +# Source files +file(GLOB_RECURSE SOURCES "${SOURCE_DIR}/*.cpp") + +# Include dirs +include_directories(${INCLUDE_DIRS}) + +# Default compiler options +set(DEFAULT_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3") + +# Set compiler options +if(NOT DEFINED CMAKE_CXX_FLAGS) + set(CMAKE_CXX_FLAGS ${DEFAULT_CXX_FLAGS} CACHE STRING "Default C++ compile flags" FORCE) +endif() + +# Executables +foreach(source_file ${SOURCES}) + # get file name without extensions + get_filename_component(executable_name ${source_file} NAME_WE) + + add_executable(${executable_name} ${source_file}) + # Target properties + set_target_properties(${executable_name} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) + target_compile_options(${executable_name} PRIVATE ${CMAKE_CXX_FLAGS}) +endforeach() + +# Executable path +set(EXECUTABLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${EXECUTABLE_DIR}") + +# Symbolic links +foreach(config ${CMAKE_CONFIGURATION_TYPES}) + file(CREATE_LINK "${BINARY_DIR}/test" "${EXECUTABLE_PATH}/test_${config}" SYMBOLIC) +endforeach(config) diff --git a/example/Makefile b/example/Makefile deleted file mode 100644 index d01c949..0000000 --- a/example/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -DIR_MAKEFILE := ./make - -ifndef SYSTEM - ifeq ($(OS), Windows_NT) - SYSTEM := windows - else - SYSTEM := $(shell uname -s) - - ifeq ($(filter $(SYSTEM),Linux Darwin),$(SYSTEM)) - SYSTEM := unix_like - else - SYSTEM := unknown - endif - endif -endif - -ifeq ($(SYSTEM), unknown) - $(info This program does not support your operating system.) -else - MAKEFILE := $(DIR_MAKEFILE)/$(SYSTEM).mk - - $(info Including: $(MAKEFILE)) - $(info ) - - include $(MAKEFILE) -endif diff --git a/example/exe/.gitkeep b/example/exe/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/example/make/unix_like.mk b/example/make/unix_like.mk deleted file mode 100644 index bc7f514..0000000 --- a/example/make/unix_like.mk +++ /dev/null @@ -1,66 +0,0 @@ -# Structure -DIR_CURR := . -DIR_PREV := .. -DIR_INC_GLOB := $(DIR_PREV)/include -DIR_SRC := $(DIR_CURR)/src -DIR_EXE := $(DIR_CURR)/exe - -# Shell -RM := rm -rf -FIND := find - -# Compiler & flags -CXX ?= g++ - -ifeq ($(CXX), clang++) -CXX_FLAGS := -std=c++2a -pedantic -Wno-c++98-compat -g -else -CXX_FLAGS := -std=c++2a -Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3 -endif - -CXX_ARGS := -I $(DIR_INC_GLOB) $(CXX_FLAGS) - -# Enumeration -ENUMARATE := false - -ifeq ($(filter all build,$(MAKECMDGOALS)),) -else -ENUMARATE := true -endif - -# Test source & object files -SOURCES := $(wildcard $(DIR_SRC)/*.cpp) -OBJECTS := $(notdir $(SOURCES:.cpp=)) - -COUNT_OBJ := 0 -COUNT_SRC := $(words $(SOURCES)) - -.PHONY: all build clean help - -all: clean build - -build: $(OBJECTS) - @echo - @echo Build successful! - -%: $(DIR_SRC)/%.cpp -ifeq ($(ENUMARATE), true) - $(eval COUNT_OBJ=$(shell echo $$(($(COUNT_OBJ)+1)))) - @echo [$(COUNT_OBJ)/$(COUNT_SRC)] Compiling: $< -else - @echo Compiling: $< -endif - @$(CXX) $< -o $(DIR_EXE)/$@ $(CXX_ARGS) - -clean: - @echo Cleaning all generated files... - @$(FIND) $(DIR_EXE) -type f ! -name .gitkeep -delete - @echo All generated files removed! - @echo - -help: - @echo "Available targets:" - @echo " all - Clean and build the example module" - @echo " build - Build the example module" - @echo " clean - Clean all generated files in example module" - @echo " help - Display this help message" diff --git a/example/make/windows.mk b/example/make/windows.mk deleted file mode 100644 index 85a6d64..0000000 --- a/example/make/windows.mk +++ /dev/null @@ -1,73 +0,0 @@ -# Structure -DIR_CURR := . -DIR_PREV := .. -DIR_INC_GLOB := $(DIR_PREV)/include -DIR_SRC := $(DIR_CURR)/src -DIR_EXE := $(DIR_CURR)/exe - -# Shell -DEL := del /Q - -# Compiler & flags -CXX ?= g++ - -ifeq ($(CXX), clang++) -CXX_FLAGS := -std=c++2a -pedantic -Wno-c++98-compat -g -else -CXX_FLAGS := -std=c++2a -Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3 -endif - -CXX_ARGS := -I $(DIR_INC_GLOB) $(CXX_FLAGS) - -# Enumeration -ENUMARATE := false - -ifeq ($(filter all build,$(MAKECMDGOALS)),) -else -ENUMARATE := true -endif - -# Test source & object files -SOURCES := $(wildcard $(DIR_SRC)/*.cpp) -OBJECTS := $(notdir $(SOURCES:.cpp=)) - -FILE_COUNT := count.tmp -COUNT_OBJ := 0 -COUNT_SRC := $(words $(SOURCES)) - -.PHONY: all build init destroy clean help - -all: clean build - -build: init $(OBJECTS) destroy - @echo. - @echo Build successful! - -%: $(DIR_SRC)/%.cpp -ifeq ($(ENUMARATE), true) - @$(shell powershell -command echo $$(($(shell powershell -command type $(DIR_CURR)/$(FILE_COUNT))+1)) > $(DIR_CURR)/$(FILE_COUNT)) - @echo [$(shell powershell -command type $(DIR_CURR)/$(FILE_COUNT))/$(COUNT_SRC)] Compiling: $< -else - @echo Compiling: $< -endif - @$(CXX) $< -o $(DIR_EXE)/$@ $(CXX_ARGS) - -init: - @$(shell powershell -command echo 0 > $(DIR_CURR)/$(FILE_COUNT)) - -destroy: - @$(DEL) $(DIR_CURR)\$(FILE_COUNT) 2>NUL - -clean: - @echo Cleaning all generated files... - @for %%i in ($(DIR_EXE)\*) do if /I not "%%~nxi" == ".gitkeep" del "%%i" - @$(DEL) $(DIR_CURR)\$(FILE_COUNT) 2>NUL - @echo All generated files removed! - @echo. - -help: - @echo Available targets: - @echo all - Clean and build the example module - @echo build - Build the example module - @echo clean - Clean all generated files in example module - @echo help - Display this help message diff --git a/example/src/power.cpp b/example/src/power.cpp new file mode 100644 index 0000000..2d9c4f6 --- /dev/null +++ b/example/src/power.cpp @@ -0,0 +1,49 @@ +#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"); + + // add arguments + parser.add_positional_argument("base").help("the exponentation base value"); + parser.add_optional_argument("exponent", "e") + .nargs(ap::nargs::any()) + .help("the exponent value"); + + parser.default_optional_arguments({ap::default_argument::optional::help}); + + // parse command-line arguments + try { + parser.parse_args(argc, argv); + } + catch (const ap::argument_parser_error& err) { + std::cerr << "[ERROR] : " << err.what() << std::endl << parser << std::endl; + std::exit(EXIT_FAILURE); + } + + // check for the help argument presence + if (parser.has_value("help")) { + std::cout << parser << std::endl; + std::exit(EXIT_SUCCESS); + } + + // check if any values for the `exponent` argument have been parsed + if (not parser.has_value("exponent")) { + std::cout << "no exponent values given" << std::endl; + std::exit(EXIT_SUCCESS); + } + + const double base = parser.value("base"); + const std::vector exponent_values = parser.values("exponent"); + + for (const int exponent : exponent_values) { + std::cout << base << " ^ " << exponent << " = " << std::pow(base, exponent) << std::endl; + } + + return 0; +} diff --git a/include/ap/argument_parser.hpp b/include/ap/argument_parser.hpp index d7b00a1..37492bf 100644 --- a/include/ap/argument_parser.hpp +++ b/include/ap/argument_parser.hpp @@ -27,7 +27,11 @@ SOFTWARE. /*! * @file argument_parser.hpp - * @brief Header file for the C++20 argument parser library. + * @brief CPP-AP library header file. + * + * This header file contians the entire CPP-AP library implementation. + * + * @version 1.0 */ #pragma once @@ -135,7 +139,7 @@ class range { * @brief Assignment operator. * @return Reference to the initialized range instance. */ - range& operator= (const range&) = default; + range& operator=(const range&) = default; /// @brief Class destructor. ~range() = default; @@ -294,8 +298,7 @@ template template detail::callable_type default_action{ [](T&) {} }; -/// @brief Predefined action for file name handling arguments. \ - Checks whether a file with the given name exists. +/// @brief Predefined action for file name handling arguments. Checks whether a file with the given name exists. inline detail::callable_type check_file_exists_action{ [](std::string& file_path) { if (not std::filesystem::exists(file_path)) { @@ -305,6 +308,8 @@ inline detail::callable_type check_file_exists_act } }; +// TODO: on_flag_action + } // namespace action @@ -317,7 +322,7 @@ struct argument_name { argument_name() = delete; /// @brief Assignment operator for argument_name (deleted). - argument_name& operator= (const argument_name&) = delete; + argument_name& operator=(const argument_name&) = delete; /// @brief Copy constructor argument_name(const argument_name&) = default; @@ -347,7 +352,7 @@ struct argument_name { * @param other The argument_name instance to compare with. * @return Equality of argument names. */ - inline bool operator== (const argument_name& other) const { + inline bool operator==(const argument_name& other) const { return this->name == other.name; } @@ -356,7 +361,7 @@ struct argument_name { * @param name The string view to compare with. * @return Equality of names comparison (either full or short name). */ - inline bool operator== (std::string_view name) const { + inline bool operator==(std::string_view name) const { return name == this->name or (this->short_name and name == this->short_name.value()); } @@ -373,7 +378,7 @@ struct argument_name { * @param arg_name The argument name to be inserted into the stream. * @return The modified output stream. */ - friend std::ostream& operator<< (std::ostream& os, const argument_name& arg_name) { + friend std::ostream& operator<<(std::ostream& os, const argument_name& arg_name) { os << arg_name.str(); return os; } @@ -407,7 +412,7 @@ class argument_interface { * @param argument The argument_interface to output. * @return The output stream. */ - friend std::ostream& operator<< (std::ostream& os, const argument_interface& argument) { + friend std::ostream& operator<<(std::ostream& os, const argument_interface& argument) { os << argument.name() << " : "; const auto& argument_help_msg = argument.help(); os << (argument_help_msg ? argument_help_msg.value() : "[ostream(argument)] TODO: msg"); @@ -661,7 +666,7 @@ class positional_argument : public detail::argument_interface { * @param other Another positional_argument for comparison. * @return Result of equality */ - inline bool operator== (const positional_argument& other) const { + inline bool operator==(const positional_argument& other) const { return this->_name == other._name; } @@ -876,7 +881,7 @@ class optional_argument : public detail::argument_interface { * @param other The optional_argument to compare with. * @return Equality of comparison. */ - inline bool operator== (const optional_argument& other) const { + inline bool operator==(const optional_argument& other) const { return this->_name == other._name; } @@ -1180,7 +1185,7 @@ class argument_parser { argument_parser(argument_parser&&) = delete; /// @brief Deleted copy assignment operator. - argument_parser& operator= (const argument_parser&) = delete; + argument_parser& operator=(const argument_parser&) = delete; /// @brief Destructor for the argument parser. ~argument_parser() = default; @@ -1345,7 +1350,7 @@ class argument_parser { * @param argv Array of command-line argument strings. */ void parse_args(int argc, char* argv[]) { - this->_parse_args_impl(this->_process_input(argc, argv)); + this->_parse_args_impl(this->_preprocess_input(argc, argv)); if (this->_bypass_required_args()) return; @@ -1435,7 +1440,7 @@ class argument_parser { * @param parser The argument parser to print. * @return The modified output stream. */ - friend std::ostream& operator<< (std::ostream& os, const argument_parser& parser) { + friend std::ostream& operator<<(std::ostream& os, const argument_parser& parser) { if (parser._program_name) os << parser._program_name.value() << std::endl; @@ -1508,14 +1513,14 @@ class argument_parser { .required() .nargs(ap::nargs::at_least(1)) .action(ap::action::check_file_exists_action) - .help("Input file path"); + .help("Input files paths"); break; case default_argument::optional::multi_output: this->add_optional_argument("output", "o") .required() .nargs(ap::nargs::at_least(1)) - .help("Input file path"); + .help("Output files paths"); break; } } @@ -1527,7 +1532,7 @@ class argument_parser { cmd_argument() = default; cmd_argument(const cmd_argument&) = default; cmd_argument(cmd_argument&&) = default; - cmd_argument& operator= (const cmd_argument&) = default; + cmd_argument& operator=(const cmd_argument&) = default; /** * @brief Constructor of a command-line argument. @@ -1545,7 +1550,7 @@ class argument_parser { * @param other Another cmd_argument to compare with. * @return Boolean statement of equality comparison. */ - inline bool operator== (const cmd_argument& other) const { + inline bool operator==(const cmd_argument& other) const { return this->discriminator == other.discriminator and this->value == other.value; } @@ -1633,9 +1638,9 @@ class argument_parser { * @brief Process command-line input arguments. * @param argc Number of command-line arguments. * @param argv Array of command-line argument strings. - * @return List of processed command-line arguments. + * @return List of preprocessed command-line arguments. */ - [[nodiscard]] cmd_argument_list _process_input(int argc, char* argv[]) const { + [[nodiscard]] cmd_argument_list _preprocess_input(int argc, char* argv[]) const { if (argc < 2) return cmd_argument_list{}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a102513..db4e883 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,14 +1,14 @@ # Minimum CMake version required to build the project cmake_minimum_required(VERSION 3.12) -# Project name -project(cpp-ap) +# Project +project(cpp-ap-test) -# Setting paths to directories +# Structure set(SOURCE_DIRS "src" "app") set(INCLUDE_DIRS "include" "../include") -set(BINARY_DIR "..") -set(EXECUTABLE_DIR "cmake/exe") +set(BINARY_DIR ".") +set(EXECUTABLE_DIR ".") # Source files file(GLOB_RECURSE SOURCES "") @@ -17,35 +17,33 @@ foreach(SOURCE_DIR ${SOURCE_DIRS}) list(APPEND SOURCES ${CURRENT_SOURCES}) endforeach() -# Directories with header files +# Include dirs include_directories(${INCLUDE_DIRS}) -# Creating an executable target +# Default compile options +set(DEFAULT_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3") + +# Set compile options +if(NOT DEFINED CMAKE_CXX_FLAGS) + set(CMAKE_CXX_FLAGS ${DEFAULT_CXX_FLAGS} CACHE STRING "Default C++ compile flags" FORCE) +endif() + +# Executable add_executable(test ${SOURCES}) -# Setting the output directory path +# Target properties set_target_properties(test PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${BINARY_DIR}" CXX_STANDARD 20 CXX_STANDARD_REQUIRED YES CXX_EXTENSIONS NO ) - -# Default compile options -set(DEFAULT_CXX_FLAGS "-Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3") - -# Setting the compile options with default or command line values -if(NOT DEFINED CMAKE_CXX_FLAGS) - set(CMAKE_CXX_FLAGS ${DEFAULT_CXX_FLAGS} CACHE STRING "Default C++ compile flags" FORCE) -endif() - -# Adding compile options target_compile_options(test PRIVATE ${CMAKE_CXX_FLAGS}) -# Setting the path to the directory where executables will be linked +# Executable path set(EXECUTABLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${EXECUTABLE_DIR}") -# Creating symbolic links for executable files +# Symbolic links foreach(config ${CMAKE_CONFIGURATION_TYPES}) file(CREATE_LINK "${BINARY_DIR}/test" "${EXECUTABLE_PATH}/test_${config}" SYMBOLIC) endforeach(config) diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index d01c949..0000000 --- a/test/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -DIR_MAKEFILE := ./make - -ifndef SYSTEM - ifeq ($(OS), Windows_NT) - SYSTEM := windows - else - SYSTEM := $(shell uname -s) - - ifeq ($(filter $(SYSTEM),Linux Darwin),$(SYSTEM)) - SYSTEM := unix_like - else - SYSTEM := unknown - endif - endif -endif - -ifeq ($(SYSTEM), unknown) - $(info This program does not support your operating system.) -else - MAKEFILE := $(DIR_MAKEFILE)/$(SYSTEM).mk - - $(info Including: $(MAKEFILE)) - $(info ) - - include $(MAKEFILE) -endif diff --git a/test/cmake/.gitkeep b/test/cmake/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/include/argument_parser_test_fixture.hpp b/test/include/argument_parser_test_fixture.hpp index 951d643..0c24899 100644 --- a/test/include/argument_parser_test_fixture.hpp +++ b/test/include/argument_parser_test_fixture.hpp @@ -130,7 +130,7 @@ struct argument_parser_test_fixture { } [[nodiscard]] cmd_argument_list sut_process_input(int argc, char* argv[]) const { - return sut._process_input(argc, argv); + return sut._preprocess_input(argc, argv); } void sut_parse_args_impl(const cmd_argument_list& cmd_args) { diff --git a/test/make/unix_like.mk b/test/make/unix_like.mk deleted file mode 100644 index 3f20257..0000000 --- a/test/make/unix_like.mk +++ /dev/null @@ -1,76 +0,0 @@ -# Structure -DIR_CURR := . -DIR_PREV := .. -DIR_INC := $(DIR_CURR)/include -DIR_INC_GLOB := $(DIR_PREV)/include -DIR_SRC := $(DIR_CURR)/src -DIR_OUT := $(DIR_CURR)/out -DIR_APP := $(DIR_CURR)/app - -APP_TARGET := test - -# Shell -RM := rm -rf -FIND := find - -# Compiler & flags -CXX ?= g++ - -ifeq ($(CXX), clang++) -CXX_FLAGS := -std=c++2a -pedantic -Wno-c++98-compat -g -else -CXX_FLAGS := -std=c++2a -Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3 -endif - -CXX_ARGS := -I $(DIR_INC_GLOB) -I $(DIR_INC) $(CXX_FLAGS) - -# Test source & object files -APP_SRC := $(wildcard $(DIR_APP)/*.cpp) - -ifeq ($(words $(APP_SRC)),1) -APP_SRC += $(wildcard $(DIR_SRC)/*.cpp) -else ifneq (,$(filter all build,$(MAKECMDGOALS))) -$(error More than one .cpp file found in $(DIR_APP)) -endif - -APP_OBJ := $(foreach file, $(APP_SRC), $(DIR_OUT)/$(notdir $(file:.cpp=.o))) - -COUNT_OBJ := 0 -COUNT_SRC := $(words $(APP_SRC)) - -.PHONY: all build clean help - -all: clean build - -build: $(APP_TARGET) - -$(DIR_OUT)/main.o: $(DIR_APP)/main.cpp - $(eval COUNT_OBJ=$(shell echo $$(($(COUNT_OBJ)+1)))) - @echo [$(COUNT_OBJ)/$(COUNT_SRC)] Compiling: $< - @$(CXX) -c $< -o $@ $(CXX_ARGS) - -$(DIR_OUT)/%.o: $(DIR_SRC)/%.cpp - $(eval COUNT_OBJ=$(shell echo $$(($(COUNT_OBJ)+1)))) - @echo [$(COUNT_OBJ)/$(COUNT_SRC)] Compiling: $< - @$(CXX) -c $< -o $@ $(CXX_ARGS) - -$(APP_TARGET): $(APP_OBJ) - @echo - @echo Linking: $@ - @$(CXX) $< -o $(DIR_APP)/$@ $(CXX_ARGS) - @echo - @echo Build successful! - -clean: - @echo Cleaning all generated files... - @$(RM) $(DIR_OUT)/*.o - @$(FIND) . -type f -executable -not -iname "*.*" -not -iname "Makefile" -delete - @echo All generated files removed! - @echo - -help: - @echo "Available targets:" - @echo " all - Clean and build the test module" - @echo " build - Build the test module" - @echo " clean - Clean all generated files in test module" - @echo " help - Display this help message" \ No newline at end of file diff --git a/test/make/windows.mk b/test/make/windows.mk deleted file mode 100644 index 5580431..0000000 --- a/test/make/windows.mk +++ /dev/null @@ -1,81 +0,0 @@ -# Structure -DIR_CURR := . -DIR_PREV := .. -DIR_INC := $(DIR_CURR)/include -DIR_INC_GLOB := $(DIR_PREV)/include -DIR_SRC := $(DIR_CURR)/src -DIR_OUT := $(DIR_CURR)/out -DIR_APP := $(DIR_CURR)/app - -APP_TARGET := test.exe - -# Shell -DEL := del /Q - -# Compiler & flags -CXX ?= g++ - -ifeq ($(CXX), clang++) -CXX_FLAGS := -std=c++2a -pedantic -Wno-c++98-compat -g -else -CXX_FLAGS := -std=c++2a -Wall -Wextra -Wcast-align -Wconversion -Wunreachable-code -Wuninitialized -pedantic -g -O3 -endif - -CXX_ARGS := -I $(DIR_INC_GLOB) -I $(DIR_INC) $(CXX_FLAGS) - -# Test source & object files -APP_SRC := $(wildcard $(DIR_APP)/*.cpp) - -ifeq ($(words $(APP_SRC)),1) -APP_SRC += $(wildcard $(DIR_SRC)/*.cpp) -else ifneq (,$(filter all build,$(MAKECMDGOALS))) -$(error More than one .cpp file found in $(DIR_APP)) -endif - -APP_OBJ := $(foreach file, $(APP_SRC), $(DIR_OUT)/$(notdir $(file:.cpp=.o))) - -FILE_COUNT := count.tmp -COUNT_OBJ := $(shell powershell -command type $(DIR_OUT)/$(FILE_COUNT)) -COUNT_SRC := $(words $(APP_SRC)) - -.PHONY: all build init destroy clean help - -all: clean build - -build: init $(APP_TARGET) destroy - -$(DIR_OUT)/main.o: $(DIR_APP)/main.cpp - @$(shell powershell -command echo $$(($(shell powershell -command type $(DIR_OUT)/$(FILE_COUNT))+1)) > $(DIR_OUT)/$(FILE_COUNT)) - @echo [$(shell powershell -command type $(DIR_OUT)/$(FILE_COUNT))/$(COUNT_SRC)] Compiling: $< - @$(CXX) -c $< -o $@ $(CXX_ARGS) - -$(DIR_OUT)/%.o: $(DIR_SRC)/%.cpp - @$(shell powershell -command echo $$(($(shell powershell -command type $(DIR_OUT)/$(FILE_COUNT))+1)) > $(DIR_OUT)/$(FILE_COUNT)) - @echo [$(shell powershell -command type $(DIR_OUT)/$(FILE_COUNT))/$(COUNT_SRC)] Compiling: $< - @$(CXX) -c $< -o $@ $(CXX_ARGS) - -$(APP_TARGET): $(APP_OBJ) - @echo. - @echo Linking: $@ - @$(CXX) $< -o $(DIR_APP)/$@ $(CXX_ARGS) - @echo. - @echo Build successful! - -init: - @$(shell powershell -command echo 0 > $(DIR_OUT)/$(FILE_COUNT)) - -destroy: - @$(DEL) $(DIR_CURR)\out\*.tmp 2>NUL - -clean: - @echo Cleaning all generated files... - @$(DEL) $(DIR_CURR)\out\*.o $(DIR_CURR)\out\*.tmp $(DIR_CURR)\*.exe 2>NUL - @echo All generated files removed! - @echo. - -help: - @echo Available targets: - @echo all - Clean and build the test module - @echo build - Build the test module - @echo clean - Clean all generated files in the test module - @echo help - Display this help message diff --git a/test/out/.gitkeep b/test/out/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/test/src/test_argument_parser_parse_args.cpp b/test/src/test_argument_parser_parse_args.cpp index d6c3d13..fa898b4 100644 --- a/test/src/test_argument_parser_parse_args.cpp +++ b/test/src/test_argument_parser_parse_args.cpp @@ -32,11 +32,11 @@ namespace ap_testing { TEST_SUITE_BEGIN("test_argument_parser_parse_args"); -TEST_SUITE_BEGIN("test_argument_parser_parse_args::_process_input"); +TEST_SUITE_BEGIN("test_argument_parser_parse_args::_preprocess_input"); TEST_CASE_FIXTURE( argument_parser_test_fixture, - "_process_input should return an empty vector for no command-line arguments" + "_preprocess_input should return an empty vector for no command-line arguments" ) { const auto argc = get_argc(default_num_args, default_num_args); auto argv = prepare_argv(default_num_args, default_num_args); @@ -50,7 +50,7 @@ TEST_CASE_FIXTURE( TEST_CASE_FIXTURE( argument_parser_test_fixture, - "_process_input should return a vector of correct arguments" + "_preprocess_input should return a vector of correct arguments" ) { add_arguments(sut, non_default_num_args, non_default_args_split); @@ -84,7 +84,7 @@ TEST_CASE_FIXTURE( free_argv(argc, argv); } -TEST_SUITE_END(); // test_argument_parser_parse_args::_process_input +TEST_SUITE_END(); // test_argument_parser_parse_args::_preprocess_input TEST_SUITE_BEGIN("test_argument_parser_parse_args::_parse_args_impl");