diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8e63f7f2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +## Version 0.2 + +* Moved to simpler syntax, where `Option` pointers are returned and operated on +* Removed `make_` style options +* Simplified Validators, now only requires `->check(function)` +* Removed Combiners +* Fixed pointers to Options, stored in `unique_ptr` now +* Added `Option_p` and `App_p`, mostly for internal use +* Startup sequence, including help flag, can be modified by subclasses + +## Version 0.1 + +Initial version + + diff --git a/CMakeLists.txt b/CMakeLists.txt index ef806a89..5a38ba4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ endif() # Be moderately paranoid with flags # But only globally, don't inherit -add_compile_options(-pedantic -Wall -Wextra) +add_compile_options(-pedantic -Wall -Wextra -O0) add_library(CLI INTERFACE) target_include_directories(CLI INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/README.md b/README.md index 10c977ab..01ba655d 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The following attributes are what I believe are important in a CLI parser librar * Standard idioms supported naturally, like grouping flags, the positional seperator, etc. * Easy to execute, with help, parse errors, etc. providing correct exit and details. * Easy to extend as part of a framework that provides "applications". -* Simple support for subcommands. +* Human readable support for subcommands. The major CLI parsers out there include: @@ -30,7 +30,7 @@ So, this library was designed to provide a great syntax, good compiler compatibi To use, there are two methods. 1. Copy `CLI11.hpp` from the [most recent release](https://github.com/henryiii/CLI11/releases) into your include directory, and you are set. This is combined from the source files for every release. -2. To be added. +2. Checkout the repository and add as a subdirectory for CMake. You can use the CLI interface target. (CMake 3.4+ recommended) To build the tests, get the entire directory and use CMake: @@ -54,7 +54,7 @@ app.add_option("-f,--file", file, "A help string"); try { app.run(argc, argv); -} catch (const CLI::Error &e) { +} catch (const CLI::ParseError &e) { return app.exit(e); } ``` @@ -66,33 +66,44 @@ The supported values are: ``` app.add_options(option_name, variable_to_bind_to, // int, float, vector, or string-like - help_string, - flags, ...) // Listed below + help_string="", + default=false) app.add_flag(option_name, - optional_intlike_to_bind_to, - help_string) + int_or_bool = nothing, + help_string="") app.add_set(option_name, variable_to_bind_to, set_of_possible_options, - flags, ...) + help_string="", + default=false) App* subcom = app.add_subcommand(name, discription); ``` +An option name must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. -There are several flags: +> ### Example +> +> * `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--option` +> * `"this"` Can only be passed positionally +> * `"-a,-b,-c"` No limit to the number of non-positional option names -* `CLI::Default`: Print the default value in help -* `CLI::Required`: The program will quit if this option is not present -* `CLI::Opts(N)`: Take `N` values instead of as many as possible, only for vector args -* `CLI::ExistingFile`: Requires that the file exists if given -* `CLI::ExistingDirectory`: Requires that the directory exists -* `CLI::NonexistentPath`: Requires that the path does not exist -Options can be given as: +The add commands return a pointer to an internally stored `Option`. If you set the final argument to true, the default value is captured and printed on the command line with the help flag. This option can be used direcly to check for the count (`->count()`) after parsing to avoid a string based lookup. Before parsing, you can set the following options: + +* `->required()`: The program will quit if this option is not present +* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args +* `->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 + +These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. Check takes any function that has the signature `bool(std::string)`. + + +On the command line, options can be given as: * `-a` (flag) * `-abc` (flags can be combined) @@ -103,8 +114,6 @@ Options can be given as: * `--file filename` (space) * `--file=filename` (equals) -An option must start with a alphabetic character or underscore. For long options, anything but an equals sign or a comma is valid after that. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on help line for its positional form. - Extra positional arguments will cause the program to exit, so at least one positional option with a vector is recommended if you want to allow extraneous arguments If `--` is present in the command line, everything after that is positional only. @@ -121,40 +130,14 @@ even exit the program through the callback. The main `App` has a callback slot, > ## Subclassing > -> The App class was designed allow toolkits to subclass it, to provide default options and setup/teardown code. Subcommands remain `App`'s, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help`. +> The App class was designed allow toolkits to subclass it, to provide default options and setup/teardown code. Subcommands remain `App`'s, since those are not expected to need setup and teardown. The default `App` only adds a help flag, `-h,--help` through the `virtual void setup()` method. If you only want to change this flag, override this method. Since subcommands are always the built in `App` object, they must have a normal help flag. > -> Also, in a related note, the `App`s you get a pointer to are stored in the parent `App` and cannot be deleted. +> Also, in a related note, the `App`s you get a pointer to are stored in the parent `App` in `unique_ptr`s (like `Option`s) and are deleted when the main `App` goes out of scope. -## Make syntax - -A second, provisional syntax looks like this: - -```cpp -CLI::App app{"App description"}; - -auto filename = app.add_option("-f,--file", "default", "A help string"); -auto int_value = app.add_option("-i,--int", "An int with no default"); - -try { - app.run(argc, argv); -} catch (const CLI::Error &e) { - return app.exit(e); -} - -std::cout << "The file was: " << *filename << std::endl; -std::cout << "This will throw an error if int not passed: " << *int_value << std::endl; -``` - - -Internally, it uses the same mechanism to work, it just provides a single line definition, but requires a template argument for non-strings, and creates an object that must be dereferenced to be used. This object (`CLI::Value`) supports conversion to bool, allowing you to easily check if an option was passed without resorting to count. Dereferencing will also throw an error if no value was passed and no default was given. - -The same functions as the first syntax are supported, only with `make` instead of `add`, and with the variable to bind to replaced by the default value (optional). If you want to use something other than a string option and do not want to give a default, you need to give a template parameter with the type. - -`Value` wraps a `shared_ptr` to a `unique_ptr` to a value, so it lasts even if the `App` object is destructed. ## How it works -Every `make_` or `add_` option you've seen depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of vector of strings, so it knows how many times an option was passed, and how many arguments each passing received (flags add empty strings to keep the counts correct). The lambda returns true if it could validate the option strings, and +Every `add_` option you've seen depends on one method that takes a lambda function. Each of these methods is just making a different lambda function with capture to populate the option. The function has full access to the vector of vector of strings, so it knows how many times an option was passed, and how many arguments each passing received (flags add empty strings to keep the counts correct). The lambda returns true if it could validate the option strings, and false if it failed. diff --git a/examples/try.cpp b/examples/try.cpp index 7c932de6..e298b0fd 100644 --- a/examples/try.cpp +++ b/examples/try.cpp @@ -6,13 +6,13 @@ int main (int argc, char** argv) { CLI::App app("K3Pi goofit fitter"); std::string file; - app.add_option("-f,--file,file", file, "File name"); + CLI::Option* opt = app.add_option("-f,--file,file", file, "File name"); int count; - app.add_flag("-c,--count", count, "Counter"); + CLI::Option* copt = app.add_flag("-c,--count", count, "Counter"); - double value = 3.14; - app.add_option("-d,--double", value, "Some Value", false); + double value;// = 3.14; + app.add_option("-d,--double", value, "Some Value"); try { app.run(argc, argv); @@ -20,8 +20,14 @@ int main (int argc, char** argv) { return app.exit(e); } - std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") << std::endl; - std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") << std::endl; + std::cout << "Working on file: " << file + << ", direct count: " << app.count("--file") + << ", opt count: " << opt->count() + << std::endl; + std::cout << "Working on count: " << count + << ", direct count: " << app.count("--count") + << ", opt count: " << copt->count() + << std::endl; std::cout << "Some value: " << value << std::endl; return 0; diff --git a/examples/try1.cpp b/examples/try1.cpp index c4e5a1b5..e42679df 100644 --- a/examples/try1.cpp +++ b/examples/try1.cpp @@ -11,8 +11,7 @@ int main (int argc, char** argv) { std::string file; start->add_option("-f,--file", file, "File name"); - int count; - stop->add_flag("-c,--count", count, "Counter"); + CLI::Option* s = stop->add_flag("-c,--count", "Counter"); try { app.run(argc, argv); @@ -21,7 +20,7 @@ int main (int argc, char** argv) { } std::cout << "Working on file: " << file << ", direct count: " << start->count("--file") << std::endl; - std::cout << "Working on count: " << count << ", direct count: " << stop->count("--count") << std::endl; + std::cout << "Working on count: " << s->count() << ", direct count: " << stop->count("--count") << std::endl; if(app.get_subcommand() != nullptr) std::cout << "Subcommand:" << app.get_subcommand()->get_name() << std::endl; diff --git a/include/CLI/App.hpp b/include/CLI/App.hpp index 58d224ce..16e68220 100644 --- a/include/CLI/App.hpp +++ b/include/CLI/App.hpp @@ -26,6 +26,11 @@ namespace CLI { enum class Classifer {NONE, POSITIONAL_MARK, SHORT, LONG, SUBCOMMAND}; +class App; + +typedef std::unique_ptr