From 4917b8b7638011d70d8e830862d0376c35056bac Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Fri, 6 Sep 2019 14:25:27 -0400 Subject: [PATCH] Moving tutorial docs (#304) --- .travis.yml | 6 ++++++ book/.gitignore | 20 ++++++++++++++++++++ book/README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/SUMMARY.md | 16 ++++++++++++++++ book/book.json | 16 ++++++++++++++++ book/chapters/advanced-topics.md | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/an-advanced-example.md | 33 +++++++++++++++++++++++++++++++++ book/chapters/basics.md | 27 +++++++++++++++++++++++++++ book/chapters/config.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/flags.md | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/formatting.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/installation.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/internals.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ book/chapters/options.md | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/subcommands.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/chapters/toolkits.md | 30 ++++++++++++++++++++++++++++++ book/chapters/validators.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/code/CLI11.hpp | 4625 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ book/code/CMakeLists.txt | 38 ++++++++++++++++++++++++++++++++++++++ book/code/flags.cpp | 36 ++++++++++++++++++++++++++++++++++++ book/code/geet.cpp | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ book/code/intro.cpp | 15 +++++++++++++++ book/code/simplest.cpp | 11 +++++++++++ docs/mainpage.md | 2 +- 24 files changed, 5853 insertions(+), 1 deletion(-) create mode 100644 book/.gitignore create mode 100644 book/README.md create mode 100644 book/SUMMARY.md create mode 100644 book/book.json create mode 100644 book/chapters/advanced-topics.md create mode 100644 book/chapters/an-advanced-example.md create mode 100644 book/chapters/basics.md create mode 100644 book/chapters/config.md create mode 100644 book/chapters/flags.md create mode 100644 book/chapters/formatting.md create mode 100644 book/chapters/installation.md create mode 100644 book/chapters/internals.md create mode 100644 book/chapters/options.md create mode 100644 book/chapters/subcommands.md create mode 100644 book/chapters/toolkits.md create mode 100644 book/chapters/validators.md create mode 100644 book/code/CLI11.hpp create mode 100644 book/code/CMakeLists.txt create mode 100644 book/code/flags.cpp create mode 100644 book/code/geet.cpp create mode 100644 book/code/intro.cpp create mode 100644 book/code/simplest.cpp diff --git a/.travis.yml b/.travis.yml index eb9870b..cf78d66 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,8 @@ matrix: # Docs and clang 3.5 - compiler: clang + language: node_js + node_js: "7.4.0" env: - DEPLOY_MAT=yes addons: @@ -34,6 +36,9 @@ matrix: install: - export CC=clang-3.5 - export CXX=clang++-3.5 + - npm install gitbook-cli -g + - gitbook fetch 3.2.3 + - (cd book && gitbook install) script: - .ci/make_and_test.sh 11 after_success: @@ -45,6 +50,7 @@ matrix: . .ci/build_doxygen.sh .ci/build_docs.sh fi + - (cd book && gitbook build . ../docs/html/book) # GCC 7 and coverage (8 does not support lcov, wait till 9 and new lcov) - compiler: gcc diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 0000000..bb77454 --- /dev/null +++ b/book/.gitignore @@ -0,0 +1,20 @@ +# Node rules: +## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +## Dependency directory +## Commenting this out is preferred by some people, see +## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git +node_modules + +# Book build output +_book + +# eBook build output +*.epub +*.mobi +*.pdf + +a.out + +*build* diff --git a/book/README.md b/book/README.md new file mode 100644 index 0000000..75020fb --- /dev/null +++ b/book/README.md @@ -0,0 +1,71 @@ +# CLI11: An introduction + +This gitbook is designed to provide an introduction to using the CLI11 library to write your own command line programs. The library is designed to be clean, intuitive, but powerful. There are no requirements beyond C++11 support (and even `` support not required). It works on Mac, Linux, and Windows, and has 100% test coverage on all three systems. You can simply drop in a single header file (`CLI11.hpp` available in [releases]) to use CLI11 in your own application. Other ways to integrate it into a build system are listed in the [README]. + +The library was inspired the Python libraries [Plumbum] and [Click], and incorporates many of their user friendly features. The library is extensively documented, with a [friendly introduction][README], this tutorial book, and more technical [API docs]. + +> Feel free to contribute to [this documentation here][CLI11Tutorial] if something can be improved! + +The syntax is simple and scales from a basic application to a massive physics analysis with multiple models and many parameters and switches. For example, this is a simple program that has an optional parameter that defaults to 1: + +```term +gitbook $ ./a.out +Parameter value: 0 + +gitbook $ ./a.out -p 4 +Parameter value: 4 + +gitbook $ ./a.out --help +App description +Usage: ./a.out [OPTIONS] + +Options: + -h,--help Print this help message and exit + -p INT Parameter +``` + +Like any good command line application, help is provided. This program can be implemented in 10 lines: + +[include](code/intro.cpp) + +[Source code](https://gitlab.com/CLIUtils/CLI11Tutorial/blob/master/code/intro.cpp) + +Unlike some other libraries, this is enough to exit correctly and cleanly if help is requested or if incorrect arguments are passed. You can try this example out for yourself. To compile with GCC: + +```term +gitbook:examples $ c++ -std=c++11 intro.cpp +``` + +Much more complicated options are handled elegantly: + +```cpp +std::string req_real_file; +app.add_option("-f,--file", file, "Require an existing file") + ->required() + ->check(CLI::ExistingFile); +``` + +You can use any valid type; the above example could have used a `boost::file_system` file instead of a `std::string`. The value is a real value and does not require any special lookups to access. You do not have to risk typos by repeating the values after parsing like some libraries require. The library also handles positional arguments, flags, fixed or unlimited repeating options, interdependent options, flags, custom validators, help groups, and more. + +You can use subcommands, as well. Subcommands support callback lambda functions when parsed, or they can be checked later. You can infinitely nest subcommands, and each is a full `App` instance, supporting everything listed above. + +Reading/producing `.ini` files for configuration is also supported, as is using environment variables as input. The base `App` can be subclassed and customized for use in a toolkit (like [GooFit]). All the standard shell idioms, like `--`, work as well. + +CLI11 was developed at the [University of Cincinnati] in support of the [GooFit] library under [NSF Award 1414736][NSF 1414736]. It was featured in a [DIANA/HEP] meeting at CERN. Please give it a try! Feedback is always welcome. + +This guide was based on CLI11 1.7. + +[GooFit]: https://github.com/GooFit/GooFit +[DIANA/HEP]: http://diana-hep.org +[CLI11]: https://github.com/CLIUtils/CLI11 +[CLI11Tutorial]: https://gitlab.com/CLIUtils/CLI11Tutorial +[releases]: https://github.com/CLIUtils/CLI11/releases +[API docs]: https://cliutils.github.io/CLI11 +[README]: https://github.com/CLIUtils/CLI11/blob/master/README.md +[NSF 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 +[University of Cincinnati]: http://www.uc.edu +[Plumbum]: http://plumbum.readthedocs.io/en/latest/ +[Click]: http://click.pocoo.org/5/ + + + diff --git a/book/SUMMARY.md b/book/SUMMARY.md new file mode 100644 index 0000000..cdbc049 --- /dev/null +++ b/book/SUMMARY.md @@ -0,0 +1,16 @@ +# Summary + +* [Introduction](/README.md) +* [Installation](/chapters/installation.md) +* [Basics](/chapters/basics.md) +* [Flags](/chapters/flags.md) +* [Options](/chapters/options.md) +* [Validators](/chapters/validators.md) +* [Subcommands and the App](/chapters/subcommands.md) +* [An advanced example](/chapters/an-advanced-example.md) +* [Configuration files](/chapters/config.md) +* [Formatting help output](/chapters/formatting.md) +* [Toolkits](/chapters/toolkits.md) +* [Advanced topics](/chapters/advanced-topics.md) +* [Internals](/chapters/internals.md) + diff --git a/book/book.json b/book/book.json new file mode 100644 index 0000000..f8745d6 --- /dev/null +++ b/book/book.json @@ -0,0 +1,16 @@ +{ +"title": "CLI11 Tutorial", +"description": "A set of examples and detailed information about CLI11", +"author": "Henry Schreiner", +"plugins": [ + "include-codeblock", + "term", + "hints" + ], +"pluginsConfig": { + "include-codeblock": { + "unindent": true, + "fixlang": true + } + } +} diff --git a/book/chapters/advanced-topics.md b/book/chapters/advanced-topics.md new file mode 100644 index 0000000..1a7ef53 --- /dev/null +++ b/book/chapters/advanced-topics.md @@ -0,0 +1,138 @@ +# Advanced topics + + +## Environment variables + +Environment variables can be used to fill in the value of an option: + +```cpp +std::string opt; +app.add_option("--my_option", opt)->envname("MY_OPTION"); +``` +If not given on the command line, the environment variable will be checked and read from if it exists. All the standard tools, like default and required, work as expected. +If passed on the command line, this will ignore the environment variable. + +## Needs/excludes + +You can set a network of requirements. For example, if flag a needs flag b but cannot be given with flag c, that would be: + +```cpp +auto a = app.add_flag("-a"); +auto b = app.add_flag("-b"); +auto c = app.add_flag("-c"); + +a->needs(b); +a->excludes(c); +``` + +CLI11 will make sure your network of requirements makes sense, and will throw an error immediately if it does not. + +## Custom option callbacks + +You can make a completely generic option with a custom callback. For example, if you wanted to add a complex number (already exists, so please don't actually do this): + +```cpp +CLI::Option * +add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { + CLI::callback_t fun = [&variable](CLI::results_t res) { + double x, y; + bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); + if(worked) + variable = cx(x, y); + return worked; + }; + + CLI::Option *opt = app.add_option(name, fun, description, defaulted); + opt->set_custom_option("COMPLEX", 2); + if(defaulted) { + std::stringstream out; + out << variable; + opt->set_default_str(out.str()); + } + return opt; +} +``` + +Then you could use it like this: + +``` +std::complex comp{0, 0}; +add_option(app, "-c,--complex", comp); +``` + +## Custom converters + +You can add your own converters to allow CLI11 to accept more option types in the standard calls. These can only be used for "single" size options (so complex, vector, etc. are a separate topic). If you set up a custom `istringstream& operator <<` overload before include CLI11, you can support different conversions. If you place this in the CLI namespace, you can even keep this from affecting the rest of your code. Here's how you could add `boost::optional` for a compiler that does not have `__has_include`: + +```cpp +// CLI11 already does this if __has_include is defined +#ifndef __has_include + +#include + +// Use CLI namespace to avoid the conversion leaking into your other code +namespace CLI { + +template std::istringstream &operator>>(std::istringstream &in, boost::optional &val) { + T v; + in >> v; + val = v; + return in; +} + +} + +#endif + +#include +``` + +This is an example of how to use the system only; if you are just looking for a way to activate `boost::optional` support on older compilers, you should define `CLI11_BOOST_OPTIONAL` before including a CLI11 file, you'll get the `boost::optional` support. + + +## Custom converters and type names: std::chrono example + +An example of adding a custom converter and typename for `std::chrono` follows: + +```cpp +namespace CLI +{ + template + std::istringstream &operator>>(std::istringstream &in, std::chrono::duration &val) + { + T v; + in >> v; + val = std::chrono::duration(v); + return in; + } + + template + std::stringstream &operator<<(std::stringstream &in, std::chrono::duration &val) + { + in << val.count(); + return in; + } + } + +#include + +namespace CLI +{ + namespace detail + { + template <> + constexpr const char *type_name() + { + return "TIME [H]"; + } + + template <> + constexpr const char *type_name() + { + return "TIME [MIN]"; + } + } +} +``` + +Thanks to Olivier Hartmann for the example. diff --git a/book/chapters/an-advanced-example.md b/book/chapters/an-advanced-example.md new file mode 100644 index 0000000..6a9bd26 --- /dev/null +++ b/book/chapters/an-advanced-example.md @@ -0,0 +1,33 @@ +# Making a git clone + + + +Let's try our hand at a little `git` clone, called `geet`. It will just print it's intent, rather than running actual code, since it's just a demonstration. Let's start by adding an app and requiring 1 subcommand to run: + +[include:"Intro"](../code/geet.cpp) + +Now, let's define the first subcommand, `add`, along with a few options: + +[include:"Add"](../code/geet.cpp) + +Now, let's add `commit`: + +[include:"Commit"](../code/geet.cpp) + +All that's need now is the parse call. We'll print a little message after the code runs, and then return: + +[include:"Parse"](../code/geet.cpp) + +[Source code](https://gitlab.com/CLIUtils/CLI11Tutorial/blob/master/code/flags.cpp) + +If you compile and run: + +```term +gitbook:examples $ c++ -std=c++11 geet.cpp -o geet +``` + +You'll see it behaves pretty much like `git`. + +## Multi-file App parse code + +This example could be made much nicer if it was split into files, one per subcommand. If you simply use shared pointers instead of raw values in the lambda capture, you can tie the lifetime to the lambda function lifetime. CLI11 has a multifile example in its example folder. diff --git a/book/chapters/basics.md b/book/chapters/basics.md new file mode 100644 index 0000000..aa52cff --- /dev/null +++ b/book/chapters/basics.md @@ -0,0 +1,27 @@ +# The Basics + +The simplest CLI11 program looks like this: + +[include](../code/simplest.cpp) + +The first line includes the library; this explicitly uses the single file edition (see [Selecting an edition](/chapters/installation)). + +After entering the main function, you'll see that a `CLI::App` object is created. This is the basis for all interactions with the library. You could optionally provide a description for your app here. + +A normal CLI11 application would define some flags and options next. This is a simplest possible example, so we'll go on. + +The macro `CLI11_PARSE` just runs five simple lines. This internally runs `app.parse(argc, argv)`, which takes the command line info from C++ and parses it. If there is an error, it throws a `ParseError`; if you catch it, you can use `app.exit` with the error as an argument to print a nice message and produce the correct return code for your application. + +If you just use `app.parse` directly, your application will still work, but the stack will not be correctly unwound since you have an uncaught exception, and the command line output will be cluttered, especially for help. + +For this (and most of the examples in this book) we will assume that we have the `CLI11.hpp` file in the current directory and that we are creating an output executable `a.out` on a macOS or Linux system. The commands to compile and test this example would be: + +```term +gitbook:examples $ g++ -std=c++11 simplest.cpp +gitbook:examples $ ./a.out -h +Usage: ./a.out [OPTIONS] + +Options: + -h,--help Print this help message and exit +``` + diff --git a/book/chapters/config.md b/book/chapters/config.md new file mode 100644 index 0000000..d83278a --- /dev/null +++ b/book/chapters/config.md @@ -0,0 +1,53 @@ +# Accepting configure files + +## Reading a configure file + +You can tell your app to allow configure files with `set_config("--config")`. There are arguments: the first is the option name. If empty, it will clear the config flag. The second item is the default file name. If that is specified, the config will try to read that file. The third item is the help string, with a reasonable default, and the final argument is a boolean (default: false) that indicates that the configuration file is required and an error will be thrown if the file +is not found and this is set to true. + +## Configure file format + +Here is an example configuration file, in INI format: + +```ini +; Commments are supported, using a ; +; The default section is [default], case insensitive + +value = 1 +str = "A string" +vector = 1 2 3 + +; Section map to subcommands +[subcommand] +in_subcommand = Wow +sub.subcommand = true +``` + +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`; or `false`, `off`, `0`, `no` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not mean that subcommand was passed, it just sets the "defaults". + +## Writing out a configure file + +To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. + +## Custom formats + +{% hint style='info' %} +New in CLI11 1.6 +{% endhint %} + +You can invent a custom format and set that instead of the default INI formatter. You need to inherit from `CLI::Config` and implement the following two functions: + +```cpp +std::string to_config(const CLI::App *app, bool default_also, bool, std::string) const; +std::vector from_config(std::istream &input) const; +``` + +The `CLI::ConfigItem`s that you return are simple structures with a name, a vector of parents, and a vector of results. A optionally customizable `to_flag` method on the formatter lets you change what happens when a ConfigItem turns into a flag. + +Finally, set your new class as new config formatter: + +```cpp +app.config_formatter(std::make_shared()); +``` + +See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/master/examples/json.cpp) for a complete JSON config example. diff --git a/book/chapters/flags.md b/book/chapters/flags.md new file mode 100644 index 0000000..ef68b86 --- /dev/null +++ b/book/chapters/flags.md @@ -0,0 +1,100 @@ +# Adding Flags + +The most basic addition to a command line program is a flag. This is simply something that does not take any arguments. Adding a flag in CLI11 is done in one of three ways. + +## Boolean flags + +The simplest way to add a flag is probably a boolean flag: + +```cpp +bool my_flag; +app.add_flag("-f", my_flag, "Optional description"); +``` + +This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `my_flag` will be `false` if the flag was not found on the command line, or `true` if it was. By default, it will be allowed any number of times, but if you explicitly[^1] request `->take_last(false)`, it will only be allowed once; passing something like `./my_app -f -f` or `./my_app -ff` will throw a `ParseError` with a nice help description. + + +## Integer flags + +If you want to allow multiple flags, simply use any integer-like instead of a bool: + +```cpp +int my_flag; +app.add_flag("-f", my_flag, "Optional description"); +``` + +After the parsing step, `my_flag` will contain the number of times this flag was found on the command line, including 0 if not found. + +## Pure flags + +Every command that starts with `add_`, such as the flag commands, return a pointer to the internally stored `CLI::Option` that describes your addition. If you prefer, you can capture this pointer and use it, and that allows you to skip adding a variable to bind to entirely: + +```cpp +CLI::Option* my_flag = app.add_flag("-f", "Optional description"); +``` + +After parsing, you can use `my_flag->count()` to count the number of times this was found. You can also directly use the value (`*my_flag`) as a bool. `CLI::Option` will be discussed in more detail later. + +## Callback flags + +If you want to define a callback that runs when you make a flag, you can use `add_flag_function` (C++11 or newer) or `add_flag` (C++14 or newer only) to add a callback function. The function should have the signature `void(size_t)`. This could be useful for a version printout, etc. + +``` +auto callback = [](int count){std::cout << "This was called " << count << " times";}; +app.add_flag_function("-c", callback, "Optional description"); +``` + + +## Aliases + +The name string, the first item of every `add_` method, can contain as many short and long names as you want, separated by commas. For example, `"-a,--alpha,-b,--beta"` would allow any of those to be recognized on the command line. If you use the same name twice, or if you use the same name in multiple flags, CLI11 will immediately throw a `CLI::ConstructionError` describing your problem (it will not wait until the parsing step). + +If you want to make an option case insensitive, you can use the `->ignore_case()` method on the `CLI::Option` to do that. For example, + +```cpp +bool flag; +app.add_flag("--flag", flag) + ->ignore_case(); +``` + +would allow the following to count as passing the flag: + +```term +gitbook $ ./my_app --fLaG +``` + +## Example + +The following program will take several flags: + +[include:"define"](../code/flags.cpp) + +The values would be used like this: + +[include:"usage"](../code/flags.cpp) + +[Source code](https://gitlab.com/CLIUtils/CLI11Tutorial/blob/master/code/flags.cpp) + +If you compile and run: + +```term +gitbook:examples $ g++ -std=c++11 flags.cpp +gitbook:examples $ ./a.out -h +Flag example program +Usage: ./a.out [OPTIONS] + +Options: + -h,--help Print this help message and exit + -b,--bool This is a bool flag + -i,--int This is an int flag + -p,--plain This is a plain flag + +gitbook:examples $ ./a.out -bii --plain -i +The flags program +Bool flag passed +Flag int: 3 +Flag plain: 1 +``` + + +[^1] It will not inherit this from the parent defaults, since this is often useful even if you don't want all options to allow multiple passed options. diff --git a/book/chapters/formatting.md b/book/chapters/formatting.md new file mode 100644 index 0000000..8c854f6 --- /dev/null +++ b/book/chapters/formatting.md @@ -0,0 +1,78 @@ +# Formatting help output + +{% hint style='info' %} +New in CLI11 1.6 +{% endhint %} + +## Customizing an existing formatter + +In CLI11, you can control the output of the help printout in full or in part. The default formatter was written in such a way as to be customizable. You can use `app.get_formatter()` to get the current formatter. The formatter you set will be inherited by subcommands that are created after you set the formatter. + +There are several configuration options that you can set: + + +| Set method | Description | Availability | +|------------|-------------|--------------| +| `column_width(width)` | The width of the columns | Both | +| `label(key, value)` | Set a label to a different value | Both | + +Labels will map the built in names and type names from key to value if present. For example, if you wanted to change the width of the columns to 40 and the `REQUIRED` label from `(REQUIRED)` to `(MUST HAVE)`: + +```cpp +app.get_formatter()->column_width(40); +app.get_formatter()->label("REQUIRED", "(MUST HAVE)"); +``` + +## Subclassing + +You can further configure pieces of the code while still keeping most of the formatting intact by subclassing either formatter and replacing any of the methods with your own. The formatters use virtual functions most places, so you are free to add or change anything about them. For example, if you wanted to remove the info that shows up between the option name and the description: + +```cpp +class MyFormatter : public CLI::Formatter { + public: + std::string make_opts(const CLI::Option *) const override {return "";} +}; +app.formatter(std::make_shared()); +``` + +Look at the class definitions in `FormatterFwd.hpp` or the method definitions in `Formatter.hpp` to see what methods you have access to and how they are put together. + +## Anatomy of a help message + +This is a normal printout, with `<>` indicating the methods used to produce each line. + +``` + + + + + + + + ... + + + + +``` + +`make_usage` calls `make_option_usage(opt)` on all the positionals to build that part of the line. `make_subcommand` passes the subcommand as the app pointer. + +The `make_groups` print the group name then call `make_option(o)` on the options listed in that group. The normal printout for an option looks like this: + +``` + make_option_opts(o) + ┌───┴────┐ + -n,--name (REQUIRED) This is a description +└────┬────┘ └──────────┬──────────┘ +make_option_name(o,p) make_option_desc(o) +``` + +Notes: + +* `*1`: This signature depends on whether the call is from a positional or optional. +* `o` is opt pointer, `p` is true if positional. + + + + diff --git a/book/chapters/installation.md b/book/chapters/installation.md new file mode 100644 index 0000000..4efdfa1 --- /dev/null +++ b/book/chapters/installation.md @@ -0,0 +1,91 @@ +# Installation + +## Single file edition + +```cpp +#include +``` + +This example uses the single file edition of CLI11. You can download `CLI11.hpp` from the latest release and put it into the same folder as your source code, then compile this with C++ enabled. For a larger project, you can just put this in an include folder and you are set. + +## Full edition + +```cpp +#include +``` + +If you want to use CLI11 in its full form, you can also use the original multiple file edition. This has an extra utility (`Timer`), and is does not require that you use a release. The only change to your code would be the include shown above. + +### CMake support for the full edition + +If you use CMake 3.4+ for your project (highly recommended), CLI11 comes with a powerful CMakeLists.txt file that was designed to also be used with `add_subproject`. You can add the repository to your code (preferably as a git submodule), then add the following line to your project (assuming your folder is called CLI11): + +```cmake +add_subdirectory(CLI11) +``` + +Then, you will have a target `CLI11::CLI11` that you can link to with `target_link_libraries`. It will provide the include paths you need for the library. This is the way [GooFit](https://github.com/GooFit/GooFit) uses CLI11, for example. + +You can also configure and optionally install CLI11, and CMake will create the necessary `lib/cmake/CLI11/CLI11Config.cmake` files, so `find_package(CLI11 CONFIG REQUIRED)` also works. + +If you use conan.io, CLI11 supports that too. + +### Running tests on the full edition + +CLI11 has examples and tests that can be accessed using a CMake build on any platform. Simply build and run ctest to run the 200+ tests to ensure CLI11 works on your system. + +As an example of the build system, the following code will download and test CLI11 in a simple Alpine Linux docker container [^1]: + +```term +gitbook:~ $ docker run -it alpine +root:/ # apk add --no-cache g++ cmake make git +fetch ... +root:/ # git clone https://github.com/CLIUtils/CLI11.git +Cloning into 'CLI11' ... +root:/ # cd CLI11 +root:CLI11 # mkdir build +root:CLI11 # cd build +root:build # cmake .. +-- The CXX compiler identification is GNU 6.3.0 ... +root:build # make +Scanning dependencies ... +root:build # make test +[warning]Running tests... +Test project /CLI11/build + Start 1: HelpersTest + 1/10 Test #1: HelpersTest ...................... Passed 0.01 sec + Start 2: IniTest + 2/10 Test #2: IniTest .......................... Passed 0.01 sec + Start 3: SimpleTest + 3/10 Test #3: SimpleTest ....................... Passed 0.01 sec + Start 4: AppTest + 4/10 Test #4: AppTest .......................... Passed 0.02 sec + Start 5: CreationTest + 5/10 Test #5: CreationTest ..................... Passed 0.01 sec + Start 6: SubcommandTest + 6/10 Test #6: SubcommandTest ................... Passed 0.01 sec + Start 7: HelpTest + 7/10 Test #7: HelpTest ......................... Passed 0.01 sec + Start 8: NewParseTest + 8/10 Test #8: NewParseTest ..................... Passed 0.01 sec + Start 9: TimerTest + 9/10 Test #9: TimerTest ........................ Passed 0.24 sec + Start 10: link_test_2 +10/10 Test #10: link_test_2 ...................... Passed 0.00 sec + +100% tests passed, 0 tests failed out of 10 + +Total Test time (real) = 0.34 sec +``` + +For the curious, the CMake options and defaults are listed below. Most options default to off if CLI11 is used as a subdirectory in another project. + +| Option | Description | +|--------|-------------| +| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). | +| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well | +| `CLI11_EXAMPLES=ON` | Build the example programs. | +| `CLI11_TESTING=ON` | Build the tests. | +| `CLANG_TIDY_FIX=OFF` | Run `clang-tidy` on the examples and headers and apply fixes. (Changes source code!) Requires LLVM and CMake 3.6+. | + +[^1]: Docker is being used to create a pristine disposable environment; there is nothing special about this container. Alpine is being used because it is small, modern, and fast. Commands are similar on any other platform. diff --git a/book/chapters/internals.md b/book/chapters/internals.md new file mode 100644 index 0000000..17bc54a --- /dev/null +++ b/book/chapters/internals.md @@ -0,0 +1,45 @@ +# CLI11 Internals + +## Callbacks + +The library was designed to bind to existing variables without requiring typed classes or inheritance. This is accomplished through lambda functions. + +This looks like: + +```cpp +Option* add_option(string name, T item) { + this->function = [&item](string value){ + item = detail::lexical_cast(value); + } +} +``` + +Obviously, you can't access `T` after the `add_` method is over, so it stores the string representation of the default value if it receives the special `true` value as the final argument (not shown above). + +## Parsing + +Parsing follows the following procedure: + +1. `_validate`: Make sure the defined options are self consistent. +2. `_parse`: Main parsing routine. See below. +3. `_run_callback`: Run an App callback if present. + +The parsing phase is the most interesting: + +1. `_parse_single`: Run on each entry on the command line and fill the options/subcommands. +2. `_process`: Run the procedure listed below. +3. `_process_extra`: This throws an error if needed on extra arguments that didn't fit in the parse. + +The `_process` procedure runs the following steps; each step is recursive and completes all subcommands before moving to the next step (new in 1.7). This ensures that interactions between options and subcommand options is consistent. + +1. `_process_ini`: This reads an INI file and fills/changes options as needed. +2. `_process_env`: Look for environment variables. +3. `_process_callbacks`: Run the callback functions - this fills out the variables. +4. `_process_help_flags`: Run help flags if present (and quit). +5. `_process_requirements`: Make sure needs/excludes, required number of options present. + +## Exceptions + +The library immediately returns a C++ exception when it detects a problem, such as an incorrect construction or a malformed command line. + + diff --git a/book/chapters/options.md b/book/chapters/options.md new file mode 100644 index 0000000..5248315 --- /dev/null +++ b/book/chapters/options.md @@ -0,0 +1,183 @@ +# Options + +## Simple options +The most versatile addition to a command line program is a option. This is like a flag, but it takes an argument. CLI11 handles all the details for many types of options for you, based on their type. To add an option: + + +```cpp +int int_option; +app.add_option("-i", int_option, "Optional description"); +``` + +This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. If you do not add this, you do not even need your option value to be printable[^1]. + +```cpp +int int_option = 0; +app.add_option("-i", int_option, "Optional description", true); +``` + +You can use any C++ int-like type, not just `int`. CLI11 understands the following categories of types: + +| Type | CLI11 | +|-------------|-------| +| int-like | Integer conversion up to 64-bit, can be unsigned | +| float-like | Floating point conversions | +| string-like | Anything else that can be shifted into a StringStream | +| vector-like | A vector of the above three types (see below) | +| function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) | + +By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers. + +## Positional options and aliases + +When you give an option on the command line without a name, that is a positional option. Positional options are accepted in the same order they are defined. So, for example: + +```term +gitbook:examples $ ./a.out one --two three four +``` + +The string `one` would have to be the first positional option. If `--two` is a flag, then the remaining two strings are positional. If `--two` is a one-argument option, then `four` is the second positional. If `--two` accepts two or more arguments, then there are no more positionals. + +To make a positional option, you simply give CLI11 one name that does not start with a dash. You can have as many (non-overlapping) names as you want for an option, but only one positional name. So the following name string is valid: + +```cpp +"-a,-b,--alpha,--beta,mypos" +``` + +This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional. + +## Vectors of options + +If you use a vector instead of a plain option, you can accept more than one value on the command line. By default, a vector accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restore with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options:[^2] + +| Separate names | Combined names | +|-------------------|-----------------| +| `--vec 1 --vec 2` | `--vec 1 2` | + +The original version did allow the option system to access information on the grouping of options received, but was removed for simplicity. + +An example of setting up a vector option: + +```cpp +std::vector int_vec; +app.add_option("--vec", int_vec, "My vector option"); +``` + +Vectors will be replaced by the parsed content if the option is given on the command line. + + +## Option modifiers + +When you call `add_option`, you get a pointer to the added option. You can use that to add option modifiers. A full listing of the option modifiers: + +| Modifier | Description | +|----------|-------------| +| `->required()`| The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. | +| `->expected(N)`| Take `N` values instead of as many as possible, only for vector args.| +| `->needs(opt)`| This option requires another option to also be present, opt is an `Option` pointer.| +| `->excludes(opt)`| This option cannot be given with `opt` present, opt is an `Option` pointer.| +| `->envname(name)`| Gets the value from the environment if present and not passed on the command line.| +| `->group(name)`| The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print.| +| `->ignore_case()`| Ignore the case on the command line (also works on subcommands, does not affect arguments).| +| `->ignore_underscore()`| Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7).| +| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy if 1 argument expected but this was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, and `Join` are also available. See the next three lines for shortcuts to set this more easily. | +| `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`.| +| `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | +| `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines to join all arguments into a single string output. | +| `->check(CLI::ExistingFile)`| Requires that the file exists if given.| +| `->check(CLI::ExistingDirectory)`| Requires that the directory exists.| +| `->check(CLI::NonexistentPath)`| Requires that the path does not exist.| +| `->check(CLI::Range(min,max))`| Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0.| +| `->each(void(std::string))` | Run a function on each parsed value, *in order*.| + +The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed. + +## Using the `CLI::Option` pointer + +Each of the option creation mechanisms returns a pointer to the internally stored option. If you save that pointer, you can continue to access the option, and change setting on it later. The Option object can also be converted to a bool to see if it was passed, or `->count()` can be used to see how many times the option was passed. Since flags are also options, the same methods work on them. + +```cpp +CLI::Option* opt = app.add_flag("--opt"); + +CLI11_PARSE(app, argv, argc); + +if(*opt) + std::cout << "Flag recieved " << opt->count() << " times." << std::endl; +``` + +## Inheritance of defaults + +One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited: + +* `group`: The group name starts as "Options" +* `required`: If the option must be given. Defaults to `false`. Is ignored for flags. +* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. +* `ignore_case`: Allow any mixture of cases for the option or flag name + +An example of usage: + +``` +app.option_defauts()->ignore_case()->group("Required"); + +app.add_flag("--CaSeLeSs"); +app.get_group() // is "Required" +``` + +Groups are mostly for visual organisation, but an empty string for a group name will hide the option. + + +## Listing of specialty options: + +Besides `add_option` and `add_flag`, there are several special ways to create options for sets and complex numbers. + +### Sets + +You can add a set with `add_set`, where you give a variable to set and a `std::set` of choices to pick from. There also is a `add_set_ignore_case` version which ignores case when set matching. If you use an existing set instead of an inline one, you can edit the set after adding it and changes will be reflected in the set checking and help message. + +```cpp +int val; +app.add_set("--even", val, {0,2,4,6,8}); +``` + +### Complex numbers + +You can also add a complex number. This type just needs to support a `(T x, T y)` constructor and be printable. You can also pass one extra argument that will set the label of the type; by default it is "COMPLEX". + +```cpp +std::complex val; +app.add_complex("--cplx", val); +``` + +### Optionals (New in CLI11 1.5) + +If you have a compiler with `__has_include`, you can use `std::optional`, `std::experimental::optional`, and `boost::optional` in `add_option`. You can manually enforce support for one of these by defining the corresponding macro before including CLI11 (or in your build system). For example: + +```cpp +#define CLI11_BOOST_OPTIONAL +#include + +... + +boost::optional x; +app.add_option("-x", x); + +CLI11_PARSE(app, argc, argv); + +if(x) + std::cout << *x << std::endl; +``` + +### Windows style options (New in CLI11 1.7) + +You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line: + +* `/a` (flag) +* `/f filename` (option) +* `/long` (long flag) +* `/file filename` (space) +* `/file:filename` (colon) + +Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined. + +[^1]: For example, enums are not printable to `std::cout`. +[^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values. diff --git a/book/chapters/subcommands.md b/book/chapters/subcommands.md new file mode 100644 index 0000000..ea85bed --- /dev/null +++ b/book/chapters/subcommands.md @@ -0,0 +1,114 @@ +# Subcommands and the App + +Subcommands are keyword that invoke a new set of options and features. For example, the `git` +command has a long series of subcommands, like `add` and `commit`. Each can have its own options +and implementations. This chapter will focus on implementations that are contained in the same +C++ application, though the system git uses to extend the main command by calling other commands +in separate executables is supported too; that's called "Prefix commands" and is included at the +end of this chapter. + + +## The parent App + +We'll start by discussing the parent `App`. You've already used it quite a bit, to create +options and set option defaults. There are several other things you can do with an `App`, however. + +You are given a lot of control the help output. You can set a footer with `app.footer("My Footer")`. +You can replace the default help print when a `ParseError` is thrown with `app.set_failure_message(CLI::FailureMessage::help)`. +The default is `CLI:::FailureMessage::simple`, and you can easily define a new one. Just make a (lambda) function that takes an App pointer +and a reference to an error code (even if you don't use them), and returns a string. + + +## Adding a subcommand + +Subcommands can be added just like an option: + +```cpp +CLI::App* sub = app.add_subcommand("sub", "This is a subcommand"); +``` + +The subcommand should have a name as the first argument, and a little description for the +second argument. A pointer to the internally stored subcommand is provided; you usually will +be capturing that pointer and using it later (though you can use callbacks if you prefer). As +always, feel free to use `auto sub = ...` instead of naming the type. + +You can check to see if the subcommand was received on the command line several ways: + +```cpp +if(*sub) ... +if(sub->parsed()) ... +if(app.got_subcommand(sub)) ... +if(app.got_subcommand("sub")) ... +``` + +You can also get a list of subcommands with `get_subcommands()`, and they will be in parsing order. + +There are a lot of options that you can set on a subcommand; in fact, +subcommands have exactly the same options as your main app, since they are actually +the same class of object (as you may have guessed from the type above). This has the +pleasant side affect of making subcommands infinitely nestable. + +## Required subcommands + +Each App has controls to set the number of subcommands you expect. This is controlled by: + +```cpp +app.require_subcommand(/* min */ 0, /* max */ 1); +``` +If you set the max to 0, CLI11 will allow an unlimited number of subcommands. After the (non-unlimited) maximum +is reached, CLI11 will stop trying to match subcommands. So the if you pass "`one two`" to a command, and both `one` +and `two` are subcommands, it will depend on the maximum number as to whether the "`two`" is a subcommand or an argument to the +"`one`" subcommand. + +As a shortcut, you can also call the `require_subcommand` method with one argument; that will be the fixed number of subcommands if positive, it +will be the maximum number if negative. Calling it without an argument will set the required subcommands to 1 or more. + +The maximum number of subcommands is inherited by subcommands. This allows you to set the maximum to 1 once at the beginning on the parent app if you only want single subcommands throughout your app. You should keep this in mind, if you are dealing with lots of nested subcommands. + +## Using callbacks + +You've already seen how to check to see what subcommands were given. It's often much easier, however, to just define the code you want to run when you are making your parser, and not run a bunch of code after `CLI11_PARSE` to analyse the state (Procedural! Yuck!). You can do that with lambda functions. A `std::function` callback `.callback()` is provided, and CLI11 ensures that all options are prepared and usable by reference capture before entering the callback. An +example is shown below in the `geet` program. + +## Inheritance of defaults + +The following values are inherited when you add a new subcommand. This happens at the point the subcommand is created: + +* The name and description for the help flag +* The footer +* The failure message printer function +* Option defaults +* Allow extras +* Prefix command +* Ignore case +* Ignore underscore +* Allow Windows style options +* Fallthrough +* Group name +* Max required subcommands + +## Special modes + +There are several special modes for Apps and Subcommands. + +### Allow extras + +Normally CLI11 throws an error if you don't match all items given on the command line. However, you can enable `allow_extras()` +to instead store the extra values in `.remaining()`. You can get all remaining options including those in contained subcommands recursively in the original order with `.remaining(true)`. +`.remaining_size()` is also provided; this counts the size but ignores the `--` special separator if present. + +### Fallthrough + +Fallthrough allows an option that does not match in a subcommand to "fall through" to the parent command; if that parent +allows that option, it matches there instead. This was added to allow CLI11 to represent models: + +```term +gitbook:code $ ./my_program my_model_1 --model_flag --shared_flag +``` + +Here, `--shared_option` was set on the main app, and on the command line it "falls through" `my_model_1` to match on the main app. + +### Prefix command + +This is a special mode that allows "prefix" commands, where the parsing completely stops when it gets to an unknown option. Further unknown options are ignored, even if they could match. Git is the traditional example for prefix commands; if you run git with an unknown subcommand, like "`git thing`", it then calls another command called "`git-thing`" with the remaining options intact. + diff --git a/book/chapters/toolkits.md b/book/chapters/toolkits.md new file mode 100644 index 0000000..c03de23 --- /dev/null +++ b/book/chapters/toolkits.md @@ -0,0 +1,30 @@ +# Using CLI11 in a Toolkit + +CLI11 was designed to be integrate into a toolkit, providing a native experience for users. This was used in GooFit to provide `GooFit::Application`, an class designed to make ROOT users feel at home. + +# Custom namespace + +If you want to provide CLI11 in a custom namespace, you'll want to at least put `using CLI::App` in your namespace. You can also include Option, some errors, and validators. You can also put `using namespace CLI` inside your namespace to import everything. + +You may also want to make your own copy of the `CLI11_PARSE` macro. Something like: + +```cpp + #define MYPACKAGE_PARSE(app, argv, argc) \ + try { \ + app.parse(argv, argc); \ + } catch(const CLI::ParseError &e) { \ + return app.exit(e); \ + } +``` + + +# Subclassing App + +If you subclass `App`, you'll just need to do a few things. You'll need a constructor; calling the base `App` constructor is a good idea, but not necessary (it just sets a description and adds a help flag. + +You can call anything you would like to configure in the constructor, like `option_defaults()->take_last()` or `fallthrough()`, and it will be set on all user instances. You can add flags and options, as well. + + +# Virtual functions provided + +You are given a few virtual functions that you can change (only on the main App). `pre_callback` runs right before the callbacks run, letting you print out custom messages at the top of your app. diff --git a/book/chapters/validators.md b/book/chapters/validators.md new file mode 100644 index 0000000..4d5ebf0 --- /dev/null +++ b/book/chapters/validators.md @@ -0,0 +1,56 @@ +# Validators + +{% hint style='info' %} +Improved in CLI11 1.6 +{% endhint %} + +There are two forms of validators: + +* `transform` validators: mutating +* `check` validators: non-mutating (recommended unless the parsed string must be mutated) + +A transform validator comes in one form, a function with the signature `std::string(std::string)`. +The function will take a string and return the modified version of the string. If there is an error, +the function should throw a `CLI::ValidationError` with the appropriate reason as a message. + +However, `check` validators come in two forms; either a simple function with the const version of the +above signature, `std::string(const std::string &)`, or a subclass of `struct CLI::Validator`. This +structure has two members that a user should set; one (`func`) is the function to add to the Option +(exactly matching the above function signature, since it will become that function), and the other is +`tname`, and is the type name to set on the Option (unless empty, in which case the typename will be +left unchanged). + +Validators can be combined with `&` and `|`, and they have an `operator()` so that you can call them +as if they were a function. In CLI11, const static versions of the validators are provided so that +the user does not have to call a constructor also. + +An example of a custom validator: + +```cpp +struct LowerCaseValidator : public Validator { + LowerCaseValidator() { + tname = "LOWER"; + func = [](const std::string &str) { + if(CLI::detail::to_lower(str) != str) + return std::string("String is not lower case"); + else + return std::string(); + }; + } +}; +const static LowerCaseValidator Lowercase; +``` + +If you were not interested in the extra features of Validator, you could simply pass the lambda function above to the `->check()` method of `Option`. + +The built-in validators for CLI11 are: + +| Validator | Description | +|---------------------|-------------| +| `ExistingFile` | Check for existing file (returns error message if check fails) | +| `ExistingDirectory` | Check for an existing directory (returns error message if check fails) | +| `ExistingPath` | Check for an existing path | +| `NonexistentPath` | Check for an non-existing path | +| `Range(min=0, max)` | Produce a range (factory). Min and max are inclusive. | + + diff --git a/book/code/CLI11.hpp b/book/code/CLI11.hpp new file mode 100644 index 0000000..21bcb2e --- /dev/null +++ b/book/code/CLI11.hpp @@ -0,0 +1,4625 @@ +#pragma once + +// CLI11: Version 1.7.1 +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: v1.7.1 +// +// From LICENSE: +// +// CLI11 1.7 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Standard combined includes: + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Verbatim copy from CLI/Version.hpp: + +#define CLI11_VERSION_MAJOR 1 +#define CLI11_VERSION_MINOR 7 +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.7.1" + +// Verbatim copy from CLI/Macros.hpp: + +// The following version macro is very similar to the one in PyBind11 +#if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) +#if __cplusplus >= 201402L +#define CLI11_CPP14 +#if __cplusplus >= 201703L +#define CLI11_CPP17 +#if __cplusplus > 201703L +#define CLI11_CPP20 +#endif +#endif +#endif +#elif defined(_MSC_VER) && __cplusplus == 199711L +// MSVC sets _MSVC_LANG rather than __cplusplus (supposedly until the standard is fully implemented) +// Unless you use the /Zc:__cplusplus flag on Visual Studio 2017 15.7 Preview 3 or newer +#if _MSVC_LANG >= 201402L +#define CLI11_CPP14 +#if _MSVC_LANG > 201402L && _MSC_VER >= 1910 +#define CLI11_CPP17 +#if __MSVC_LANG > 201703L && _MSC_VER >= 1910 +#define CLI11_CPP20 +#endif +#endif +#endif +#endif + +#if defined(CLI11_CPP14) +#define CLI11_DEPRECATED(reason) [[deprecated(reason)]] +#elif defined(_MSC_VER) +#define CLI11_DEPRECATED(reason) __declspec(deprecated(reason)) +#else +#define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) +#endif + +// Verbatim copy from CLI/Optional.hpp: + +#ifdef __has_include + +// You can explicitly enable or disable support +// by defining these to 1 or 0. +#if defined(CLI11_CPP17) && __has_include() && \ + !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 1 +#elif !defined(CLI11_STD_OPTIONAL) +#define CLI11_STD_OPTIONAL 0 +#endif + +#if defined(CLI11_CPP14) && __has_include() && \ + !defined(CLI11_EXPERIMENTAL_OPTIONAL) \ + && (!defined(CLI11_STD_OPTIONAL) || CLI11_STD_OPTIONAL == 0) +#define CLI11_EXPERIMENTAL_OPTIONAL 1 +#elif !defined(CLI11_EXPERIMENTAL_OPTIONAL) +#define CLI11_EXPERIMENTAL_OPTIONAL 0 +#endif + +#if __has_include() && !defined(CLI11_BOOST_OPTIONAL) +#include +#if BOOST_VERSION >= 105800 +#define CLI11_BOOST_OPTIONAL 1 +#endif +#elif !defined(CLI11_BOOST_OPTIONAL) +#define CLI11_BOOST_OPTIONAL 0 +#endif + +#endif + +#if CLI11_STD_OPTIONAL +#include +#endif +#if CLI11_EXPERIMENTAL_OPTIONAL +#include +#endif +#if CLI11_BOOST_OPTIONAL +#include +#endif + +// From CLI/Version.hpp: + +// From CLI/Macros.hpp: + +// From CLI/Optional.hpp: + +namespace CLI { + +#if CLI11_STD_OPTIONAL +template std::istream &operator>>(std::istream &in, std::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_EXPERIMENTAL_OPTIONAL +template std::istream &operator>>(std::istream &in, std::experimental::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +#if CLI11_BOOST_OPTIONAL +template std::istream &operator>>(std::istream &in, boost::optional &val) { + T v; + in >> v; + val = v; + return in; +} +#endif + +// Export the best optional to the CLI namespace +#if CLI11_STD_OPTIONAL +using std::optional; +#elif CLI11_EXPERIMENTAL_OPTIONAL +using std::experimental::optional; +#elif CLI11_BOOST_OPTIONAL +using boost::optional; +#endif + +// This is true if any optional is found +#if CLI11_STD_OPTIONAL || CLI11_EXPERIMENTAL_OPTIONAL || CLI11_BOOST_OPTIONAL +#define CLI11_OPTIONAL 1 +#endif + +} // namespace CLI + +// From CLI/StringTools.hpp: + +namespace CLI { +namespace detail { + +// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c +/// Split a string by a delim +inline std::vector split(const std::string &s, char delim) { + std::vector elems; + // Check to see if empty string, give consistent result + if(s.empty()) + elems.emplace_back(""); + else { + std::stringstream ss; + ss.str(s); + std::string item; + while(std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + return elems; +} + +/// Simple function to join a string +template std::string join(const T &v, std::string delim = ",") { + std::ostringstream s; + size_t start = 0; + for(const auto &i : v) { + if(start++ > 0) + s << delim; + s << i; + } + return s.str(); +} + +/// Join a string in reverse order +template std::string rjoin(const T &v, std::string delim = ",") { + std::ostringstream s; + for(size_t start = 0; start < v.size(); start++) { + if(start > 0) + s << delim; + s << v[v.size() - start - 1]; + } + return s.str(); +} + +// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string + +/// Trim whitespace from left of string +inline std::string <rim(std::string &str) { + auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(str.begin(), it); + return str; +} + +/// Trim anything from left of string +inline std::string <rim(std::string &str, const std::string &filter) { + auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(str.begin(), it); + return str; +} + +/// Trim whitespace from right of string +inline std::string &rtrim(std::string &str) { + auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(ch, std::locale()); }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim anything from right of string +inline std::string &rtrim(std::string &str, const std::string &filter) { + auto it = + std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; }); + str.erase(it.base(), str.end()); + return str; +} + +/// Trim whitespace from string +inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); } + +/// Trim anything from string +inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); } + +/// Make a copy of the string and then trim it +inline std::string trim_copy(const std::string &str) { + std::string s = str; + return trim(s); +} + +/// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) +inline std::string trim_copy(const std::string &str, const std::string &filter) { + std::string s = str; + return trim(s, filter); +} +/// Print a two part "help" string +inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, size_t wid) { + name = " " + name; + out << std::setw(static_cast(wid)) << std::left << name; + if(!description.empty()) { + if(name.length() >= wid) + out << "\n" << std::setw(static_cast(wid)) << ""; + out << description; + } + out << "\n"; + return out; +} + +/// Verify the first character of an option +template bool valid_first_char(T c) { return std::isalpha(c, std::locale()) || c == '_'; } + +/// Verify following characters of an option +template bool valid_later_char(T c) { + return std::isalnum(c, std::locale()) || c == '_' || c == '.' || c == '-'; +} + +/// Verify an option name +inline bool valid_name_string(const std::string &str) { + if(str.empty() || !valid_first_char(str[0])) + return false; + for(auto c : str.substr(1)) + if(!valid_later_char(c)) + return false; + return true; +} + +/// Return a lower case version of a string +inline std::string to_lower(std::string str) { + std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) { + return std::tolower(x, std::locale()); + }); + return str; +} + +/// remove underscores from a string +inline std::string remove_underscore(std::string str) { + str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str)); + return str; +} + +/// Find and replace a substring with another substring +inline std::string find_and_replace(std::string str, std::string from, std::string to) { + + size_t start_pos = 0; + + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; +} + +/// Find a trigger string and call a modify callable function that takes the current string and starting position of the +/// trigger and returns the position in the string to search for the next trigger string +template inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) { + size_t start_pos = 0; + while((start_pos = str.find(trigger, start_pos)) != std::string::npos) { + start_pos = modify(str, start_pos); + } + return str; +} + +/// Split a string '"one two" "three"' into 'one two', 'three' +/// Quote characters can be ` ' or " +inline std::vector split_up(std::string str) { + + const std::string delims("\'\"`"); + auto find_ws = [](char ch) { return std::isspace(ch, std::locale()); }; + trim(str); + + std::vector output; + bool embeddedQuote = false; + char keyChar = ' '; + while(!str.empty()) { + if(delims.find_first_of(str[0]) != std::string::npos) { + keyChar = str[0]; + auto end = str.find_first_of(keyChar, 1); + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + end = str.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + output.push_back(str.substr(1, end - 1)); + str = str.substr(end + 1); + } else { + output.push_back(str.substr(1)); + str = ""; + } + } else { + auto it = std::find_if(std::begin(str), std::end(str), find_ws); + if(it != std::end(str)) { + std::string value = std::string(str.begin(), it); + output.push_back(value); + str = std::string(it, str.end()); + } else { + output.push_back(str); + str = ""; + } + } + // transform any embedded quotes into the regular character + if(embeddedQuote) { + output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); + embeddedQuote = false; + } + trim(str); + } + return output; +} + +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(std::string leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + +/// This function detects an equal or colon followed by an escaped quote after an argument +/// then modifies the string to replace the equality with a space. This is needed +/// to allow the split up function to work properly and is intended to be used with the find_and_modify function +/// the return value is the offset+1 which is required by the find_and_modify function. +inline size_t escape_detect(std::string &str, size_t offset) { + auto next = str[offset + 1]; + if((next == '\"') || (next == '\'') || (next == '`')) { + auto astart = str.find_last_of("-/ \"\'`", offset - 1); + if(astart != std::string::npos) { + if(str[astart] == ((str[offset] == '=') ? '-' : '/')) + str[offset] = ' '; // interpret this as a space so the split_up works properly + } + } + return offset + 1; +} + +/// Add quotes if the string contains spaces +inline std::string &add_quotes_if_needed(std::string &str) { + if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { + char quote = str.find('"') < str.find('\'') ? '\'' : '"'; + if(str.find(' ') != std::string::npos) { + str.insert(0, 1, quote); + str.append(1, quote); + } + } + return str; +} + +} // namespace detail +} // namespace CLI + +// From CLI/Error.hpp: + +namespace CLI { + +// Use one of these on all error classes. +// These are temporary and are undef'd at the end of this file. +#define CLI11_ERROR_DEF(parent, name) \ + protected: \ + name(std::string ename, std::string msg, int exit_code) : parent(std::move(ename), std::move(msg), exit_code) {} \ + name(std::string ename, std::string msg, ExitCodes exit_code) \ + : parent(std::move(ename), std::move(msg), exit_code) {} \ + \ + public: \ + name(std::string msg, ExitCodes exit_code) : parent(#name, std::move(msg), exit_code) {} \ + name(std::string msg, int exit_code) : parent(#name, std::move(msg), exit_code) {} + +// This is added after the one above if a class is used directly and builds its own message +#define CLI11_ERROR_SIMPLE(name) \ + explicit name(std::string msg) : name(#name, msg, ExitCodes::name) {} + +/// These codes are part of every error in CLI. They can be obtained from e using e.exit_code or as a quick shortcut, +/// int values from e.get_error_code(). +enum class ExitCodes { + Success = 0, + IncorrectConstruction = 100, + BadNameString, + OptionAlreadyAdded, + FileError, + ConversionError, + ValidationError, + RequiredError, + RequiresError, + ExcludesError, + ExtrasError, + ConfigError, + InvalidError, + HorribleError, + OptionNotFound, + ArgumentMismatch, + BaseClass = 127 +}; + +// Error definitions + +/// @defgroup error_group Errors +/// @brief Errors thrown by CLI11 +/// +/// These are the errors that can be thrown. Some of them, like CLI::Success, are not really errors. +/// @{ + +/// All errors derive from this one +class Error : public std::runtime_error { + int actual_exit_code; + std::string error_name{"Error"}; + + public: + int get_exit_code() const { return actual_exit_code; } + + std::string get_name() const { return error_name; } + + Error(std::string name, std::string msg, int exit_code = static_cast(ExitCodes::BaseClass)) + : runtime_error(msg), actual_exit_code(exit_code), error_name(std::move(name)) {} + + Error(std::string name, std::string msg, ExitCodes exit_code) : Error(name, msg, static_cast(exit_code)) {} +}; + +// Note: Using Error::Error constructors does not work on GCC 4.7 + +/// Construction errors (not in parsing) +class ConstructionError : public Error { + CLI11_ERROR_DEF(Error, ConstructionError) +}; + +/// Thrown when an option is set to conflicting values (non-vector and multi args, for example) +class IncorrectConstruction : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, IncorrectConstruction) + CLI11_ERROR_SIMPLE(IncorrectConstruction) + static IncorrectConstruction PositionalFlag(std::string name) { + return IncorrectConstruction(name + ": Flags cannot be positional"); + } + static IncorrectConstruction Set0Opt(std::string name) { + return IncorrectConstruction(name + ": Cannot set 0 expected, use a flag instead"); + } + static IncorrectConstruction SetFlag(std::string name) { + return IncorrectConstruction(name + ": Cannot set an expected number for flags"); + } + static IncorrectConstruction ChangeNotVector(std::string name) { + return IncorrectConstruction(name + ": You can only change the expected arguments for vectors"); + } + static IncorrectConstruction AfterMultiOpt(std::string name) { + return IncorrectConstruction( + name + ": You can't change expected arguments after you've changed the multi option policy!"); + } + static IncorrectConstruction MissingOption(std::string name) { + return IncorrectConstruction("Option " + name + " is not defined"); + } + static IncorrectConstruction MultiOptionPolicy(std::string name) { + return IncorrectConstruction(name + ": multi_option_policy only works for flags and exact value options"); + } +}; + +/// Thrown on construction of a bad name +class BadNameString : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, BadNameString) + CLI11_ERROR_SIMPLE(BadNameString) + static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString DashesOnly(std::string name) { + return BadNameString("Must have a name, not just dashes: " + name); + } + static BadNameString MultiPositionalNames(std::string name) { + return BadNameString("Only one positional name allowed, remove: " + name); + } +}; + +/// Thrown when an option already exists +class OptionAlreadyAdded : public ConstructionError { + CLI11_ERROR_DEF(ConstructionError, OptionAlreadyAdded) + explicit OptionAlreadyAdded(std::string name) + : OptionAlreadyAdded(name + " is already added", ExitCodes::OptionAlreadyAdded) {} + static OptionAlreadyAdded Requires(std::string name, std::string other) { + return OptionAlreadyAdded(name + " requires " + other, ExitCodes::OptionAlreadyAdded); + } + static OptionAlreadyAdded Excludes(std::string name, std::string other) { + return OptionAlreadyAdded(name + " excludes " + other, ExitCodes::OptionAlreadyAdded); + } +}; + +// Parsing errors + +/// Anything that can error in Parse +class ParseError : public Error { + CLI11_ERROR_DEF(Error, ParseError) +}; + +// Not really "errors" + +/// This is a successful completion on parsing, supposed to exit +class Success : public ParseError { + CLI11_ERROR_DEF(ParseError, Success) + Success() : Success("Successfully completed, should be caught and quit", ExitCodes::Success) {} +}; + +/// -h or --help on command line +class CallForHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForHelp) + CallForHelp() : CallForHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Usually something like --help-all on command line +class CallForAllHelp : public ParseError { + CLI11_ERROR_DEF(ParseError, CallForAllHelp) + CallForAllHelp() + : CallForAllHelp("This should be caught in your main function, see examples", ExitCodes::Success) {} +}; + +/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +class RuntimeError : public ParseError { + CLI11_ERROR_DEF(ParseError, RuntimeError) + explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} +}; + +/// Thrown when parsing an INI file and it is missing +class FileError : public ParseError { + CLI11_ERROR_DEF(ParseError, FileError) + CLI11_ERROR_SIMPLE(FileError) + static FileError Missing(std::string name) { return FileError(name + " was not readable (missing?)"); } +}; + +/// Thrown when conversion call back fails, such as when an int fails to coerce to a string +class ConversionError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConversionError) + CLI11_ERROR_SIMPLE(ConversionError) + ConversionError(std::string member, std::string name) + : ConversionError("The value " + member + " is not an allowed value for " + name) {} + ConversionError(std::string name, std::vector results) + : ConversionError("Could not convert: " + name + " = " + detail::join(results)) {} + static ConversionError TooManyInputsFlag(std::string name) { + return ConversionError(name + ": too many inputs for a flag"); + } + static ConversionError TrueFalse(std::string name) { + return ConversionError(name + ": Should be true/false or a number"); + } +}; + +/// Thrown when validation of results fails +class ValidationError : public ParseError { + CLI11_ERROR_DEF(ParseError, ValidationError) + CLI11_ERROR_SIMPLE(ValidationError) + explicit ValidationError(std::string name, std::string msg) : ValidationError(name + ": " + msg) {} +}; + +/// Thrown when a required option is missing +class RequiredError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiredError) + explicit RequiredError(std::string name) : RequiredError(name + " is required", ExitCodes::RequiredError) {} + static RequiredError Subcommand(size_t min_subcom) { + if(min_subcom == 1) + return RequiredError("A subcommand"); + else + return RequiredError("Requires at least " + std::to_string(min_subcom) + " subcommands", + ExitCodes::RequiredError); + } +}; + +/// Thrown when the wrong number of arguments has been received +class ArgumentMismatch : public ParseError { + CLI11_ERROR_DEF(ParseError, ArgumentMismatch) + CLI11_ERROR_SIMPLE(ArgumentMismatch) + ArgumentMismatch(std::string name, int expected, size_t recieved) + : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + + ", got " + std::to_string(recieved)) + : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + + ", got " + std::to_string(recieved)), + ExitCodes::ArgumentMismatch) {} + + static ArgumentMismatch AtLeast(std::string name, int num) { + return ArgumentMismatch(name + ": At least " + std::to_string(num) + " required"); + } + static ArgumentMismatch TypedAtLeast(std::string name, int num, std::string type) { + return ArgumentMismatch(name + ": " + std::to_string(num) + " required " + type + " missing"); + } +}; + +/// Thrown when a requires option is missing +class RequiresError : public ParseError { + CLI11_ERROR_DEF(ParseError, RequiresError) + RequiresError(std::string curname, std::string subname) + : RequiresError(curname + " requires " + subname, ExitCodes::RequiresError) {} +}; + +/// Thrown when an excludes option is present +class ExcludesError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExcludesError) + ExcludesError(std::string curname, std::string subname) + : ExcludesError(curname + " excludes " + subname, ExitCodes::ExcludesError) {} +}; + +/// Thrown when too many positionals or options are found +class ExtrasError : public ParseError { + CLI11_ERROR_DEF(ParseError, ExtrasError) + explicit ExtrasError(std::vector args) + : ExtrasError((args.size() > 1 ? "The following arguments were not expected: " + : "The following argument was not expected: ") + + detail::rjoin(args, " "), + ExitCodes::ExtrasError) {} +}; + +/// Thrown when extra values are found in an INI file +class ConfigError : public ParseError { + CLI11_ERROR_DEF(ParseError, ConfigError) + CLI11_ERROR_SIMPLE(ConfigError) + static ConfigError Extras(std::string item) { return ConfigError("INI was not able to parse " + item); } + static ConfigError NotConfigurable(std::string item) { + return ConfigError(item + ": This option is not allowed in a configuration file"); + } +}; + +/// Thrown when validation fails before parsing +class InvalidError : public ParseError { + CLI11_ERROR_DEF(ParseError, InvalidError) + explicit InvalidError(std::string name) + : InvalidError(name + ": Too many positional arguments with unlimited expected args", ExitCodes::InvalidError) { + } +}; + +/// This is just a safety check to verify selection and parsing match - you should not ever see it +/// Strings are directly added to this error, but again, it should never be seen. +class HorribleError : public ParseError { + CLI11_ERROR_DEF(ParseError, HorribleError) + CLI11_ERROR_SIMPLE(HorribleError) +}; + +// After parsing + +/// Thrown when counting a non-existent option +class OptionNotFound : public Error { + CLI11_ERROR_DEF(Error, OptionNotFound) + explicit OptionNotFound(std::string name) : OptionNotFound(name + " not found", ExitCodes::OptionNotFound) {} +}; + +#undef CLI11_ERROR_DEF +#undef CLI11_ERROR_SIMPLE + +/// @} + +} // namespace CLI + +// From CLI/TypeTools.hpp: + +namespace CLI { + +// Type tools + +/// A copy of enable_if_t from C++14, compatible with C++11. +/// +/// We could check to see if C++14 is being used, but it does not hurt to redefine this +/// (even Google does this: https://github.com/google/skia/blob/master/include/private/SkTLogic.h) +/// It is not in the std namespace anyway, so no harm done. + +template using enable_if_t = typename std::enable_if::type; + +/// Check to see if something is a vector (fail check by default) +template struct is_vector { static const bool value = false; }; + +/// Check to see if something is a vector (true if actually a vector) +template struct is_vector> { static bool const value = true; }; + +/// Check to see if something is bool (fail check by default) +template struct is_bool { static const bool value = false; }; + +/// Check to see if something is bool (true if actually a bool) +template <> struct is_bool { static bool const value = true; }; + +namespace detail { +// Based generally on https://rmf.io/cxx11/almost-static-if +/// Simple empty scoped class +enum class enabler {}; + +/// An instance to use in EnableIf +constexpr enabler dummy = {}; + +// Type name print + +/// Was going to be based on +/// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template +/// But this is cleaner and works better in this case + +template ::value && std::is_signed::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "INT"; +} + +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "UINT"; +} + +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "FLOAT"; +} + +/// This one should not be used, since vector types print the internal type +template ::value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "VECTOR"; +} + +template ::value && !std::is_integral::value && !is_vector::value, + detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "TEXT"; +} + +// Lexical cast + +/// Signed integers / enums +template ::value && std::is_signed::value), detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + long long output_ll = std::stoll(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Unsigned integers +template ::value && std::is_unsigned::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + if(!input.empty() && input.front() == '-') + return false; // std::stoull happily converts negative values to junk without any errors. + + try { + size_t n = 0; + unsigned long long output_ll = std::stoull(input, &n, 0); + output = static_cast(output_ll); + return n == input.size() && static_cast(output) == output_ll; + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// Floats +template ::value, detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + try { + size_t n = 0; + output = static_cast(std::stold(input, &n)); + return n == input.size(); + } catch(const std::invalid_argument &) { + return false; + } catch(const std::out_of_range &) { + return false; + } +} + +/// String and similar +template ::value && !std::is_integral::value && + std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + output = input; + return true; +} + +/// Non-string parsable +template ::value && !std::is_integral::value && + !std::is_assignable::value, + detail::enabler> = detail::dummy> +bool lexical_cast(std::string input, T &output) { + std::istringstream is; + + is.str(input); + is >> output; + return !is.fail() && !is.rdbuf()->in_avail(); +} + +} // namespace detail +} // namespace CLI + +// From CLI/Split.hpp: + +namespace CLI { +namespace detail { + +// Returns false if not a short option. Otherwise, sets opt name and rest and returns true +inline bool split_short(const std::string ¤t, std::string &name, std::string &rest) { + if(current.size() > 1 && current[0] == '-' && valid_first_char(current[1])) { + name = current.substr(1, 1); + rest = current.substr(2); + return true; + } else + return false; +} + +// Returns false if not a long option. Otherwise, sets opt name and other side of = and returns true +inline bool split_long(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + auto loc = current.find_first_of('='); + if(loc != std::string::npos) { + name = current.substr(2, loc - 2); + value = current.substr(loc + 1); + } else { + name = current.substr(2); + value = ""; + } + return true; + } else + return false; +} + +// Returns false if not a windows style option. Otherwise, sets opt name and value and returns true +inline bool split_windows(const std::string ¤t, std::string &name, std::string &value) { + if(current.size() > 1 && current[0] == '/' && valid_first_char(current[1])) { + auto loc = current.find_first_of(':'); + if(loc != std::string::npos) { + name = current.substr(1, loc - 1); + value = current.substr(loc + 1); + } else { + name = current.substr(1); + value = ""; + } + return true; + } else + return false; +} + +// Splits a string into multiple long and short names +inline std::vector split_names(std::string current) { + std::vector output; + size_t val; + while((val = current.find(",")) != std::string::npos) { + output.push_back(trim_copy(current.substr(0, val))); + current = current.substr(val + 1); + } + output.push_back(trim_copy(current)); + return output; +} + +/// Get a vector of short names, one of long names, and a single name +inline std::tuple, std::vector, std::string> +get_names(const std::vector &input) { + + std::vector short_names; + std::vector long_names; + std::string pos_name; + + for(std::string name : input) { + if(name.length() == 0) + continue; + else if(name.length() > 1 && name[0] == '-' && name[1] != '-') { + if(name.length() == 2 && valid_first_char(name[1])) + short_names.emplace_back(1, name[1]); + else + throw BadNameString::OneCharName(name); + } else if(name.length() > 2 && name.substr(0, 2) == "--") { + name = name.substr(2); + if(valid_name_string(name)) + long_names.push_back(name); + else + throw BadNameString::BadLongName(name); + } else if(name == "-" || name == "--") { + throw BadNameString::DashesOnly(name); + } else { + if(pos_name.length() > 0) + throw BadNameString::MultiPositionalNames(name); + pos_name = name; + } + } + + return std::tuple, std::vector, std::string>( + short_names, long_names, pos_name); +} + +} // namespace detail +} // namespace CLI + +// From CLI/ConfigFwd.hpp: + +namespace CLI { + +class App; + +namespace detail { + +/// Comma separated join, adds quotes if needed +inline std::string ini_join(std::vector args) { + std::ostringstream s; + size_t start = 0; + for(const auto &arg : args) { + if(start++ > 0) + s << " "; + + auto it = std::find_if(arg.begin(), arg.end(), [](char ch) { return std::isspace(ch, std::locale()); }); + if(it == arg.end()) + s << arg; + else if(arg.find(R"(")") == std::string::npos) + s << R"(")" << arg << R"(")"; + else + s << R"(')" << arg << R"(')"; + } + + return s.str(); +} + +} // namespace detail + +/// Holds values to load into Options +struct ConfigItem { + /// This is the list of parents + std::vector parents; + + /// This is the name + std::string name; + + /// Listing of inputs + std::vector inputs; + + /// The list of parents and name joined by "." + std::string fullname() const { + std::vector tmp = parents; + tmp.emplace_back(name); + return detail::join(tmp, "."); + } +}; + +/// This class provides a converter for configuration files. +class Config { + protected: + std::vector items; + + public: + /// Convert an app into a configuration + virtual std::string to_config(const App *, bool, bool, std::string) const = 0; + + /// Convert a configuration into an app + virtual std::vector from_config(std::istream &) const = 0; + + /// Convert a flag to a bool + virtual std::vector to_flag(const ConfigItem &item) const { + if(item.inputs.size() == 1) { + std::string val = item.inputs.at(0); + val = detail::to_lower(val); + + if(val == "true" || val == "on" || val == "yes") { + return std::vector(1); + } else if(val == "false" || val == "off" || val == "no") { + return std::vector(); + } else { + try { + size_t ui = std::stoul(val); + return std::vector(ui); + } catch(const std::invalid_argument &) { + throw ConversionError::TrueFalse(item.fullname()); + } + } + } else { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + } + + /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure + std::vector from_file(const std::string &name) { + std::ifstream input{name}; + if(!input.good()) + throw FileError::Missing(name); + + return from_config(input); + } + + /// virtual destructor + virtual ~Config() = default; +}; + +/// This converter works with INI files +class ConfigINI : public Config { + public: + std::string to_config(const App *, bool default_also, bool write_description, std::string prefix) const override; + + std::vector from_config(std::istream &input) const override { + std::string line; + std::string section = "default"; + + std::vector output; + + while(getline(input, line)) { + std::vector items_buffer; + + detail::trim(line); + size_t len = line.length(); + if(len > 1 && line[0] == '[' && line[len - 1] == ']') { + section = line.substr(1, len - 2); + } else if(len > 0 && line[0] != ';') { + output.emplace_back(); + ConfigItem &out = output.back(); + + // Find = in string, split and recombine + auto pos = line.find('='); + if(pos != std::string::npos) { + out.name = detail::trim_copy(line.substr(0, pos)); + std::string item = detail::trim_copy(line.substr(pos + 1)); + items_buffer = detail::split_up(item); + } else { + out.name = detail::trim_copy(line); + items_buffer = {"ON"}; + } + + if(detail::to_lower(section) != "default") { + out.parents = {section}; + } + + if(out.name.find('.') != std::string::npos) { + std::vector plist = detail::split(out.name, '.'); + out.name = plist.back(); + plist.pop_back(); + out.parents.insert(out.parents.end(), plist.begin(), plist.end()); + } + + out.inputs.insert(std::end(out.inputs), std::begin(items_buffer), std::end(items_buffer)); + } + } + return output; + } +}; + +} // namespace CLI + +// From CLI/Validators.hpp: + +namespace CLI { + +/// @defgroup validator_group Validators + +/// @brief Some validators that are provided +/// +/// These are simple `std::string(const std::string&)` validators that are useful. They return +/// a string if the validation fails. A custom struct is provided, as well, with the same user +/// semantics, but with the ability to provide a new type name. +/// @{ + +/// +struct Validator { + /// This is the type name, if empty the type name will not be changed + std::string tname; + + /// This it the base function that is to be called. + /// Returns a string error message if validation fails. + std::function func; + + /// This is the required operator for a validator - provided to help + /// users (CLI11 uses the member `func` directly) + std::string operator()(const std::string &str) const { return func(str); }; + + /// Combining validators is a new validator + Validator operator&(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(!s1.empty() && !s2.empty()) + return s1 + " & " + s2; + else + return s1 + s2; + }; + return newval; + } + + /// Combining validators is a new validator + Validator operator|(const Validator &other) const { + Validator newval; + newval.tname = (tname == other.tname ? tname : ""); + + // Give references (will make a copy in lambda function) + const std::function &f1 = func; + const std::function &f2 = other.func; + + newval.func = [f1, f2](const std::string &filename) { + std::string s1 = f1(filename); + std::string s2 = f2(filename); + if(s1.empty() || s2.empty()) + return std::string(); + else + return s1 + " & " + s2; + }; + return newval; + } +}; + +// The implementation of the built in validators is using the Validator class; +// the user is only expected to use the const (static) versions (since there's no setup). +// Therefore, this is in detail. +namespace detail { + +/// Check for an existing file (returns error message if check fails) +struct ExistingFileValidator : public Validator { + ExistingFileValidator() { + tname = "FILE"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "File does not exist: " + filename; + } else if(is_dir) { + return "File is actually a directory: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing directory (returns error message if check fails) +struct ExistingDirectoryValidator : public Validator { + ExistingDirectoryValidator() { + tname = "DIR"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + bool is_dir = (buffer.st_mode & S_IFDIR) != 0; + if(!exist) { + return "Directory does not exist: " + filename; + } else if(!is_dir) { + return "Directory is actually a file: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an existing path +struct ExistingPathValidator : public Validator { + ExistingPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool const exist = stat(filename.c_str(), &buffer) == 0; + if(!exist) { + return "Path does not exist: " + filename; + } + return std::string(); + }; + } +}; + +/// Check for an non-existing path +struct NonexistentPathValidator : public Validator { + NonexistentPathValidator() { + tname = "PATH"; + func = [](const std::string &filename) { + struct stat buffer; + bool exist = stat(filename.c_str(), &buffer) == 0; + if(exist) { + return "Path already exists: " + filename; + } + return std::string(); + }; + } +}; +} // namespace detail + +// Static is not needed here, because global const implies static. + +/// Check for existing file (returns error message if check fails) +const detail::ExistingFileValidator ExistingFile; + +/// Check for an existing directory (returns error message if check fails) +const detail::ExistingDirectoryValidator ExistingDirectory; + +/// Check for an existing path +const detail::ExistingPathValidator ExistingPath; + +/// Check for an non-existing path +const detail::NonexistentPathValidator NonexistentPath; + +/// Produce a range (factory). Min and max are inclusive. +struct Range : public Validator { + /// This produces a range with min and max inclusive. + /// + /// Note that the constructor is templated, but the struct is not, so C++17 is not + /// needed to provide nice syntax for Range(a,b). + template Range(T min, T max) { + std::stringstream out; + out << detail::type_name() << " in [" << min << " - " << max << "]"; + + tname = out.str(); + func = [min, max](std::string input) { + T val; + detail::lexical_cast(input, val); + if(val < min || val > max) + return "Value " + input + " not in range " + std::to_string(min) + " to " + std::to_string(max); + + return std::string(); + }; + } + + /// Range of one value is 0 to value + template explicit Range(T max) : Range(static_cast(0), max) {} +}; + +namespace detail { +/// split a string into a program name and command line arguments +/// the string is assumed to contain a file name followed by other arguments +/// the return value contains is a pair with the first argument containing the program name and the second everything +/// else +inline std::pair split_program_name(std::string commandline) { + // try to determine the programName + std::pair vals; + trim(commandline); + auto esp = commandline.find_first_of(' ', 1); + while(!ExistingFile(commandline.substr(0, esp)).empty()) { + esp = commandline.find_first_of(' ', esp + 1); + if(esp == std::string::npos) { + // if we have reached the end and haven't found a valid file just assume the first argument is the + // program name + esp = commandline.find_first_of(' ', 1); + break; + } + } + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + // strip the program name + vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; + ltrim(vals.second); + return vals; +} +} // namespace detail +/// @} + +} // namespace CLI + +// From CLI/FormatterFwd.hpp: + +namespace CLI { + +class Option; +class App; + +/// This enum signifies the type of help requested +/// +/// This is passed in by App; all user classes must accept this as +/// the second argument. + +enum class AppFormatMode { + Normal, //< The normal, detailed help + All, //< A fully expanded help + Sub, //< Used when printed as part of expanded subcommand +}; + +/// This is the minimum requirements to run a formatter. +/// +/// A user can subclass this is if they do not care at all +/// about the structure in CLI::Formatter. +class FormatterBase { + protected: + /// @name Options + ///@{ + + /// The width of the first column + size_t column_width_{30}; + + /// @brief The required help printout labels (user changeable) + /// Values are Needs, Excludes, etc. + std::map labels_; + + ///@} + /// @name Basic + ///@{ + + public: + FormatterBase() = default; + FormatterBase(const FormatterBase &) = default; + FormatterBase(FormatterBase &&) = default; + virtual ~FormatterBase() = default; + + /// This is the key method that puts together help + virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; + + ///@} + /// @name Setters + ///@{ + + /// Set the "REQUIRED" label + void label(std::string key, std::string val) { labels_[key] = val; } + + /// Set the column width + void column_width(size_t val) { column_width_ = val; } + + ///@} + /// @name Getters + ///@{ + + /// Get the current value of a name (REQUIRED, etc.) + std::string get_label(std::string key) const { + if(labels_.find(key) == labels_.end()) + return key; + else + return labels_.at(key); + } + + /// Get the current column width + size_t get_column_width() const { return column_width_; } + + ///@} +}; + +/// This is a specialty override for lambda functions +class FormatterLambda final : public FormatterBase { + using funct_t = std::function; + + /// The lambda to hold and run + funct_t lambda_; + + public: + /// Create a FormatterLambda with a lambda function + explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} + + /// This will simply call the lambda function + std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { + return lambda_(app, name, mode); + } +}; + +/// This is the default Formatter for CLI11. It pretty prints help output, and is broken into quite a few +/// overridable methods, to be highly customizable with minimal effort. +class Formatter : public FormatterBase { + public: + Formatter() = default; + Formatter(const Formatter &) = default; + Formatter(Formatter &&) = default; + + /// @name Overridables + ///@{ + + /// This prints out a group of options with title + /// + virtual std::string make_group(std::string group, bool is_positional, std::vector opts) const; + + /// This prints out just the positionals "group" + virtual std::string make_positionals(const App *app) const; + + /// This prints out all the groups of options + std::string make_groups(const App *app, AppFormatMode mode) const; + + /// This prints out all the subcommands + virtual std::string make_subcommands(const App *app, AppFormatMode mode) const; + + /// This prints out a subcommand + virtual std::string make_subcommand(const App *sub) const; + + /// This prints out a subcommand in help-all + virtual std::string make_expanded(const App *sub) const; + + /// This prints out all the groups of options + virtual std::string make_footer(const App *app) const; + + /// This displays the description line + virtual std::string make_description(const App *app) const; + + /// This displays the usage line + virtual std::string make_usage(const App *app, std::string name) const; + + /// This puts everything together + std::string make_help(const App *, std::string, AppFormatMode) const override; + + ///@} + /// @name Options + ///@{ + + /// This prints out an option help line, either positional or optional form + virtual std::string make_option(const Option *opt, bool is_positional) const { + std::stringstream out; + detail::format_help( + out, make_option_name(opt, is_positional) + make_option_opts(opt), make_option_desc(opt), column_width_); + return out.str(); + } + + /// @brief This is the name part of an option, Default: left column + virtual std::string make_option_name(const Option *, bool) const; + + /// @brief This is the options part of the name, Default: combined into left column + virtual std::string make_option_opts(const Option *) const; + + /// @brief This is the description. Default: Right column, on new line if left column too large + virtual std::string make_option_desc(const Option *) const; + + /// @brief This is used to print the name on the USAGE line + virtual std::string make_option_usage(const Option *opt) const; + + ///@} +}; + +} // namespace CLI + +// From CLI/Option.hpp: + +namespace CLI { + +using results_t = std::vector; +using callback_t = std::function; + +class Option; +class App; + +using Option_p = std::unique_ptr