From 7fb2c88673fd1bd0fae565f5bc53cb8f527a9498 Mon Sep 17 00:00:00 2001 From: Miroslaw Opoka Date: Sun, 5 May 2024 19:16:21 +0200 Subject: [PATCH] Updated README and added simplified example --- .vscode/launch.json | 8 +- .vscode/settings.json | 13 ++- README.md | 110 +++++++++++++++--- src/example/CMakeLists.txt | 7 +- src/example/{example.cpp => example_full.cpp} | 12 +- src/example/example_simple.cpp | 43 +++++++ 6 files changed, 163 insertions(+), 30 deletions(-) rename src/example/{example.cpp => example_full.cpp} (96%) create mode 100644 src/example/example_simple.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 04ae053..d9fc2bf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -29,10 +29,10 @@ ] }, { - "name": "Example call (gdb)", + "name": "Example full call (gdb)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build_example_debug/example", + "program": "${workspaceFolder}/build_example_debug/example_full", "args": ["-c", "some_config", "-v", "-d", "5.12"], "stopAtEntry": false, "cwd": "${fileDirname}", @@ -52,10 +52,10 @@ ] }, { - "name": "Example call (lldb)", + "name": "Example full call (lldb)", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build_example_debug/example", + "program": "${workspaceFolder}/build_example_debug/example_full", "args": ["-c", "some_config", "-v", "-d", "5.12"], "stopAtEntry": false, "cwd": "${fileDirname}", diff --git a/.vscode/settings.json b/.vscode/settings.json index 59cdc59..d4a1e42 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,10 +8,21 @@ "build_*/**": true }, "cSpell.words": [ + "boolalpha", "bugprone", "Constexpr", + "cout", "cppcoreguidelines", + "cstdint", + "endl", + "iostream", "positionals", "Struct" - ] + ], + "files.associations": { + ".clang-tidy": "yaml", + ".clang-format": "yaml", + "*.yml": "yaml", + "ostream": "cpp" + } } diff --git a/README.md b/README.md index 20e4637..4abe5a2 100644 --- a/README.md +++ b/README.md @@ -3,38 +3,114 @@ [![Cpp Standard](https://img.shields.io/badge/C%2B%2B-11-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B11) [![Coverage Status](https://coveralls.io/repos/github/opokatech/options/badge.svg?branch=master)](https://coveralls.io/github/opokatech/options?branch=master) -This is yet another alternative for parsing command line options in C++. +This is yet another alternative for parsing command line arguments in C++. The main design criteria were: -* fast to recompile, -* ease of declaring options and accessing them, -* only C++11 or higher +* Fast to recompile the main. The library has very small footprint as regards to implicitly included headers. The main using this library includes indirectly only `string` and `cstdint`. +* Easily allows validation of passed values. +* Ease of declaring options and accessing them. +* Requires C++11 or higher. -An example with comments can be found in [src/example/example.cpp](src/example/example.cpp). +## How to use it -Some notes: +A [simple example](src/example/example_simple.cpp), without validation looks as follows: -* an option may have an argument or not. An option without an argument is a flag, -* an option is always identified by a long name. A long name is used with 2 dashes in front of it, - like so `--something`. It may have a single character short version which is then used with a - single dash in front of it, like so `-s`. +```cpp +#include -* an option can be mandatory - which means it must be specified, -* it is **not** checked if the defined options are unique, -* it is **not** checked if the default value passes the validation (if used). +#include "options/Parser.hpp" + +int main(int argc, char *argv[]) +{ + Options::Parser args_parser; + + args_parser.add_mandatory("config", 'c', "Configuration file"); // --config, -c + args_parser.add_optional("count", "Number of iterations", "10"); // --count + args_parser.add_optional("threshold", 't', "Some threshold", "3.14"); // --threshold, -t + args_parser.add_flag("help", 'h', "This help is accessible via short and long option"); + + using std::cout; + using std::endl; + + if (!args_parser.parse(argc, argv) || args_parser.as_bool("help")) + { + cout << "Usage: " << argv[0] << " [options] [-- [positional arguments]]" << endl; + cout << args_parser.get_possible_options() << endl; + return -1; + } + + // Print all options + cout << std::boolalpha; + cout << "Options:" << endl; + cout << " config : " << args_parser.as_string("config") << endl; + cout << " count : " << args_parser.as_int("count") << endl; + cout << " threshold : " << args_parser.as_double("threshold") << endl; + + // positional arguments are all the strings after "--" separator, for example: + // ./example_simple -c config_file.txt -- these strings are positional arguments + if (args_parser.positional_count() > 0) + { + cout << "Positional parameters:" << endl; + for (size_t i = 0; i < args_parser.positional_count(); ++i) + cout << " [" << i << "]: " << args_parser.positional(i) << endl; + } + else + cout << "No positional parameters." << endl; + + return 0; +} +``` + +An example "session" on the console may look like this: + +```bash +./build_example_debug/example_simple +Usage: ./build_example_debug/example_simple [options] [-- [positional arguments]] + -c, --config M Configuration file + --count Number of iterations (default: 10) + -t, --threshold Some threshold (default: 3.14) + -h, --help This help is accessible via short and long option -## Usage +./build_example_debug/example_simple -c some_file.cfg -- this is a string +Options: + config : some_file.cfg + count : 10 + threshold : 3.14 +Positional parameters: + [0]: this + [1]: is + [2]: a + [3]: string + + + +``` + +A similar example, but with validation of data can be found [here](src/example/example_full.cpp). + +## How to compile it This library can be used in a few ways: -A simplest way is to copy `src/options` directory to your project and add the files in there to the compilation process either by hand OR by including only this directory via `add_subdirectory`: +A simplest and not recommended way is to copy `src/options` directory to your project and add the files in there to the compilation process either by hand OR by including only this directory via `add_subdirectory`. After all the whole library is just 7 files. Another option is to clone the whole repository and add it via `add_subdirectory`: ```cmake -add_subdirectory(external/options) # location of cloned repository +add_subdirectory(external/options) # location of the cloned repository -add_executable(example example.cpp) +add_executable(example my_main.cpp) target_link_libraries(example PRIVATE options) ``` + +## Some notes: + +* an option may have an argument or not. An option without an argument is a flag, +* an option is always identified by a long name. A long name is used with 2 dashes in front of it, + like so `--something`. It may have a single character short version which is then used with a + single dash in front of it, like so `-s`. + +* an option can be mandatory - which means it must be specified, +* it is **not** checked if the defined options are unique, +* it is **not** checked if the default value passes the validation (if used). diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index c7c4a85..143f0e3 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -1,2 +1,5 @@ -add_executable(example example.cpp) -target_link_libraries(example PRIVATE options options_compile_flags) +add_executable(example_simple example_simple.cpp) +target_link_libraries(example_simple PRIVATE options options_compile_flags) + +add_executable(example_full example_full.cpp) +target_link_libraries(example_full PRIVATE options options_compile_flags) diff --git a/src/example/example.cpp b/src/example/example_full.cpp similarity index 96% rename from src/example/example.cpp rename to src/example/example_full.cpp index 87838b7..cc3e17d 100644 --- a/src/example/example.cpp +++ b/src/example/example_full.cpp @@ -7,13 +7,11 @@ int main(int argc, char *argv[]) { Options::Parser args_parser; - args_parser.add_flag("help", 'h', "This help is accessible via short and long option"); - args_parser.add_flag("verbose", 'v', "Verbose - accessible via -v and --verbose"); + args_parser.add_mandatory("config", 'c', "Configuration file"); args_parser.add_optional( "level", "Debug level, one of none, debug, error - it is checked by the validator", "none", [](const std::string &value) { return (value == "none" || value == "debug" || value == "error"); }); - args_parser.add_mandatory("config", 'c', "Configuration file"); args_parser.add_optional("int", 'i', "Some small integer in range <-10..10> as checked by validator", "4", [](const std::string &value) { int32_t num = Options::as_int(value); @@ -26,6 +24,8 @@ int main(int argc, char *argv[]) args_parser.add_optional("bf", "Boolean value", "false"); args_parser.add_optional("bt", "Boolean value", "true"); + args_parser.add_flag("help", 'h', "This help is accessible via short and long option"); + args_parser.add_flag("verbose", 'v', "Verbose - accessible via -v and --verbose"); using std::cout; using std::endl; @@ -39,16 +39,16 @@ int main(int argc, char *argv[]) cout << std::boolalpha; cout << "Options:" << endl; - cout << " verbose : " << args_parser.as_bool("verbose") << endl; - cout << " level : " << args_parser.as_string("level") << endl; cout << " config : " << args_parser.as_string("config") << endl; + cout << " level : " << args_parser.as_string("level") << endl; cout << " int : " << args_parser.as_int("int") << endl; cout << " double : " << args_parser.as_double("double") << endl; cout << " bf : " << args_parser.as_bool("bf") << endl; cout << " bt : " << args_parser.as_bool("bt") << endl; + cout << " verbose : " << args_parser.as_bool("verbose") << endl; // positional arguments are all the strings after "--" separator, for example: - // ./example -c config_file.txt -v -- these strings are positional arguments + // ./example_full -c config_file.txt -v -- these strings are positional arguments if (args_parser.positional_count() > 0) { cout << "Positional parameters:" << endl; diff --git a/src/example/example_simple.cpp b/src/example/example_simple.cpp new file mode 100644 index 0000000..f1bdb38 --- /dev/null +++ b/src/example/example_simple.cpp @@ -0,0 +1,43 @@ +#include + +#include "options/Parser.hpp" + +int main(int argc, char *argv[]) +{ + Options::Parser args_parser; + + args_parser.add_mandatory("config", 'c', "Configuration file"); // --config, -c + args_parser.add_optional("count", "Number of iterations", "10"); // --count + args_parser.add_optional("threshold", 't', "Some threshold", "3.14"); // --threshold, -t + args_parser.add_flag("help", 'h', "This help is accessible via short and long option"); + + using std::cout; + using std::endl; + + if (!args_parser.parse(argc, argv) || args_parser.as_bool("help")) + { + cout << "Usage: " << argv[0] << " [options] [-- [positional arguments]]" << endl; + cout << args_parser.get_possible_options() << endl; + return -1; + } + + // Print all options + cout << std::boolalpha; + cout << "Options:" << endl; + cout << " config : " << args_parser.as_string("config") << endl; + cout << " count : " << args_parser.as_int("count") << endl; + cout << " threshold : " << args_parser.as_double("threshold") << endl; + + // positional arguments are all the strings after "--" separator, for example: + // ./example_simple -c config_file.txt -- these strings are positional arguments + if (args_parser.positional_count() > 0) + { + cout << "Positional parameters:" << endl; + for (size_t i = 0; i < args_parser.positional_count(); ++i) + cout << " [" << i << "]: " << args_parser.positional(i) << endl; + } + else + cout << "No positional parameters." << endl; + + return 0; +}