diff --git a/example/brunel/brunel.cpp b/example/brunel/brunel.cpp index 8084baaa70d393150daa12d204250806168bfabc..354db0b2e0a17a66e426b96365a394e24cd6a413 100644 --- a/example/brunel/brunel.cpp +++ b/example/brunel/brunel.cpp @@ -8,7 +8,7 @@ #include <set> #include <vector> -#include <tinyopt/smolopt.h> +#include <tinyopt/tinyopt.h> #include <arbor/context.hpp> #include <arbor/common_types.hpp> diff --git a/example/probe-demo/probe-demo.cpp b/example/probe-demo/probe-demo.cpp index d2391af455031a6da10419df929ccc06c8da9b7f..d0ced90109efb40788038d834949c3ef547b751e 100644 --- a/example/probe-demo/probe-demo.cpp +++ b/example/probe-demo/probe-demo.cpp @@ -16,7 +16,7 @@ #include <arbor/sampling.hpp> #include <arbor/util/any_cast.hpp> #include <arbor/util/any_ptr.hpp> -#include <tinyopt/smolopt.h> +#include <tinyopt/tinyopt.h> // Simulate a cell modelled as a simple cable with HH dynamics, // emitting the results of a user specified probe over time. @@ -82,7 +82,6 @@ struct options { std::string value_name; }; -const char* argv0 = ""; bool parse_options(options&, int& argc, char** argv); void vector_sampler(arb::probe_metadata, std::size_t, const arb::sample_record*); void scalar_sampler(arb::probe_metadata, std::size_t, const arb::sample_record*); @@ -129,7 +128,6 @@ struct cable_recipe: public arb::recipe { }; int main(int argc, char** argv) { - argv0 = argv[0]; try { options opt; if (!parse_options(opt, argc, argv)) { @@ -152,7 +150,7 @@ int main(int argc, char** argv) { sim.run(opt.sim_end, opt.sim_dt); } catch (to::option_error& e) { - to::usage(argv0, "[OPTIONS]... PROBE\nTry '--help' for more information.", e.what()); + to::usage_error(argv[0], "[OPTIONS]... PROBE\nTry '--help' for more information.", e.what()); return 1; } catch (std::exception& e) { @@ -199,7 +197,7 @@ bool parse_options(options& opt, int& argc, char** argv) { using std::get; using namespace to; - auto do_help = [&]() { usage(argv0, help_msg); }; + auto do_help = [&]() { usage(argv[0], help_msg); }; using L = arb::mlocation; diff --git a/example/single/single.cpp b/example/single/single.cpp index bbe7fcbe43e1e52ee6315ddba76c38e1fb6b068a..db95d817c9a71895ad73043b4bc081fd613ca819 100644 --- a/example/single/single.cpp +++ b/example/single/single.cpp @@ -119,19 +119,19 @@ options parse_options(int argc, char** argv) { char** arg = argv+1; while (*arg) { - if (auto dt = parse<double>(arg, 'd', "dt")) { + if (auto dt = parse<double>(arg, "-d", "--dt")) { opt.dt = dt.value(); } - else if (auto t_end = parse<double>(arg, 't', "t-end")) { + else if (auto t_end = parse<double>(arg, "-t", "--t-end")) { opt.t_end = t_end.value(); } - else if (auto weight = parse<float>(arg, 'w', "weight")) { + else if (auto weight = parse<float>(arg, "-w", "--weight")) { opt.syn_weight = weight.value(); } - else if (auto swc = parse<std::string>(arg, 'm', "morphology")) { + else if (auto swc = parse<std::string>(arg, "-m", "--morphology")) { opt.swc_file = swc.value(); } - else if (auto nseg = parse<unsigned>(arg, 'n', "cv-per-branch")) { + else if (auto nseg = parse<unsigned>(arg, "-n", "--cv-per-branch")) { opt.policy = arb::cv_policy_fixed_per_branch(nseg.value()); } else { diff --git a/ext/tinyopt/README.md b/ext/tinyopt/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cee0531a985aeb1070ff9258b9a396f876588777 --- /dev/null +++ b/ext/tinyopt/README.md @@ -0,0 +1,576 @@ +Smaller, better? option parsing. + +## Goals + +This project constitutes yet another header-only option parsing library for C++. + +Design goals: + +* Header only. +* C++14 and C++17 compatible. +* Fairly short and easy to customize. + +Features: + +* Support 'short' and 'long' style options: `-v 3` and `--value=3`. +* Support 'compact' bunching of options: `-abc 3` vs `-a -b -c 3`. +* Save and restore options and arguments in a shell-compatible format, + allowing e.g. `` program `cat previous-options` --foo=bar ``. +* Options can be interpreted modally. + +Non-features: + +* _Does not support multibyte encodings with shift sequences or wide character streams._ + This is due to laziness. But it does try to at least not break UTF-8. +* _Does not automatically generate help/usage text._ + What constitutes good help output is too specific to any given program. +* _Does not support optional or multiple arguments to an option._ + This is mainly due to problems of ambiguous parsing, though in a pinch this can + be set up through the use of modal option parsing (see _Filters and Modals_ below). + +The library actually provides two interfaces: +1. One can iterate through the command line arguments explicitly, testing them + with `to::parse`. This precludes the use of compact-style options or modal parsing, + but gives more control to the user code. +2. Or one can make a table of `to::option` specifications, and pass them to + `to::run`, which will handle all the parsing itself. + +## Simple examples + +More examples are found in the `ex/` subdirectory. + +Simple interface (`to::parse`) code for parsing options three options, one numeric, +one a keyword from a table, and one just a flag. +``` +#include <string> +#include <utility> +#include <tinyopt/tinyopt.h> + +const char* usage_str = + "[OPTION]...\n" + "\n" + " -n, --number=N Specify N\n" + " -f, --function=FUNC Perform FUNC, which is one of: one, two\n" + " -h, --help Display usage information and exit\n"; + +int main(int argc, char** argv) { + try { + int n = 1, fn = 0; + bool help = false; + + std::pair<const char*, int> functions[] = { + { "one", 1 }, { "two", 2 } + }; + + for (auto arg = argv+1; *arg; ) { + bool ok = + help << to::parse(arg, 'h', "help") || + n << to::parse<int>(arg, 'n', "number") || + fn << to::parse<int>(arg, to::keywords(functions), 'f', "function"); + + if (!ok) throw to::option_error("unrecognized argument", *arg); + } + + if (help) { + to::usage(argv[0], usage_str); + return 0; + } + + if (n<1) throw to::option_error("N must be at least 1"); + if (fn<1) throw to::option_error("Require FUNC"); + + // Do things with arguments: + + for (int i = 0; i<n; ++i) { + std::cout << "Performing function #" << fn << "\n"; + } + } + catch (to::option_error& e) { + to::usage_error(argv[0], usage_str, e.what()); + return 1; + } +} +``` + +Equivalent `to::run` version. +``` +#include <string> +#include <utility> +#include <tinyopt/tinyopt.h> + +const char* usage_str = + "[OPTION]...\n" + "\n" + " -n, --number=N Specify N\n" + " -f, --function=FUNC Perform FUNC, which is one of: one, two\n" + " -h, --help Display usage information and exit\n"; + +int main(int argc, char** argv) { + try { + int n = 1, fn = 0; + + std::pair<const char*, int> functions[] = { + { "one", 1 }, { "two", 2 } + }; + + auto help = [argv0 = argv[0]] { to::usage(argv0, usage_str); }; + + to::option opts[] = { + { n, "-n", "--number" }, + { {fn, to::keywords(functions)}, "-f", "--function", to::mandatory }, + { to::action(help), to::flag, to::exit, "-h", "--help" } + }; + + if (!to::run(opts, argc, argv+1)) return 0; + + if (argv[1]) throw to::option_error("unrecogonized argument", argv[1]); + if (n<1) throw to::option_error("N must be at least 1"); + + // Do things with arguments: + + for (int i = 0; i<n; ++i) { + std::cout << "Performing function #" << fn << "\n"; + } + } + catch (to::option_error& e) { + to::usage_error(argv[0], usage_str, e.what()); + return 1; + } +} +``` + + +## Building + +Tinyopt is a header-only library, but the supplied Makefile will build the unit +tests and examples. + +The Makefile is designed to support out-of-tree building, and the recommended +approach is to create a build directory, symbolicly link the project Makefile +into the directory, and build from there. For example, to check out, build the +tests and examples, and then run the unit tests: +``` + % git clone git@github.com:halfflat/tinyopt + % cd tinyopt + % mkdir build + % cd build + % ln -s ../Makefile . + % make + % ./unit +``` + + +## Documentation + +All tinyopt code lives in the namespace `to`. This namespace is omitted in the +descriptions below. + +### Common classes and helpers + +#### `template <typename V> struct maybe` + +`maybe<V>` is a simple work-alike for (some of) the C++17 `std::optional<V>` class. +A default constructed `maybe<V>` has no value, and evaluates to false in a `bool` +context; it will otherwise evaluate to true. + +If `m` is an object of type `maybe<V>`, then `*m` evaluates to the contained value +if defined. `m.value()` does the same, but will throw an exception of type +`std::invalid_argument` if `m` does not contain a value. + +The special value `nothing` is implicitly convertible to an empty `maybe<V>` for any `V`. +The expression `just(v)` function returns a `maybe<V>` holding the value `v`. + +As a special case, `maybe<void>` simply maintains a has-value state; it will return +true in a `bool` context if has been initialized or assigned with any `maybe<V>` +that contains a value, or by any other value that is not `nothing`. `something` +is a pre-defined non-empty value of type `maybe<void>`. + +`maybe<V>` values support basic monadic-like functionality via `operator<<`. +* If `x` is an lvalue and `m` is of type `maybe<U>`, then + `x << m` has type `maybe<V>` (`V` is the type of `x=*m`) and assigns `m.value()` to `x` + if `m` has a value. In the case that `U` is `void`, then the value of `m` is taken + to be `true`. +* If `f` is a function or function object with signature `V f(U)`, and `m` is of type `maybe<U>`, then + `f << m` has type `maybe<V>` and contains `f(*m)` if `m` has a value. +* if `f` has signature `V f()`, and `m` is of type `maybe<U>` or `maybe<void>`, then + `f << m` has type `maybe<V>` and contains `f()` if `m` has a value. + +#### `option_error` + +An exception class derived from `std::runtime_error`. It has two constructors: +* `option_error(const std::string&)` simply sets the what string to the argument. +* `option_error(const std::string& message, const std::string& arg)` sets the what string to + the value of `arg+": "+mesage`. + +The option parsers can throw exceptions derived from `option_error`, namely: +`option_parse_error`, `missing_mandatory_option`, and `missing_argument`. + +#### `usage(const char *argv0, const std::string& usagemsg, const std::string& prefix = "Usage: ")` + +Extract a program name from `argv0` (everything after the last '/' if present) and +print a message to standard out in the form "Usage: <program-name> <usagemsg>\n". +An alternative prefix to "Usage: " can be supplied optionally. + +#### `usage_error(const char *argv0, const std::string& usagemsg, const std::string& error, const std::string& prefix = "Usage: ")` + +Extract a program name from `argv0` (everything after the last '/' if present) and +print a message to standard error in the form +`<program-name>: <error>\nUsage: <program-name> <usagemsg>\n`. +An alternative prefix to "Usage: " can be supplied optionally. + +### Parsers + +A parser is a function or functional object with signature `maybe<X> (const char*)` +for some type `X`. They are used to try to convert a C-string argument into a value. + +If no explicit parser is given to a the`parse` function or to an `option` specification, +the default parser `default_parser` is used, which will use `std::istream::operator>>` +to read the supplied argument. + +Tinyopt supplies additional parsers: + +* `keyword_parser<V>` + + Constructed from a table of key-value pairs, the `keyword_parser<V>` parser + will return the first value found in the table with matching key, or `nothing` + if there is no match. + + The `keywords(pairs)` function constructs a `keyword_parser<V>` object from the + collection of keyword pairs `pairs`, where each element in the collection is + a `std::pair`. The first component of each pair is used to construct the `std::string` + key in the keyword table, and the second the value. The value type `V` is deduced + from this second component. + +* `delimited_parser<P>` + + The delimited parser uses another parser of type `P` to parse individual + elements in a delimited sequence, and returns a `std::vector` of the + corresponding values. + + The convenience constructor `delimited<V>(char delim = ',')` will make + a `delimited_parser` using the default parser for `V` and delimiter + `delim` (by default, a comma). + + `delimited(char delim, P&& parser)` is a convenience wrapper for + `delimited_parser<P>::delimited_parser(delim, parser)`. + +### Keys + +Keys are how options are specified on the command line. They consist of +a string label and a style, which is one of `key::shortfmt`, +`key::longfmt`, or `key::compact`. + +All options that take an argument will take that argument from the +next item in the argument list, and only options with a 'compact' +key can be combined together in a single argument. + +An option with a 'long' key can additionally take its argument by +following the key with an equals sign and then the argument value. + +As an example, let "-s" be a short option key, "--long" a long option key, +"-a" a compact option key for a flag, and "-b" a compact option key for +an option that takes a value. Then the follwing are equivalent ways +for specifying these options on a command line: + +``` +-s 1 --long 2 -a -b 3 +``` + +``` +-s 1 --long=2 -a -b3 +``` + +``` +-s 1 --long=2 -ab3 +``` + +Keys can be constructed explicitly, implicitly from labels, or +from the use of string literal functions: + +* `key(std::string label, enum key::style style)`, `key(const char* label, enum key::style style)` + + Make a key with given label and style. + +* `key(std::string label)`, `key(const char* label)` + + Make a key with the given label. The style will be `key::shortfmt`, unless + the label starts with a double dash "--". This constructor is implicit. + +* `operator""_short`, `operator""_long`, `operator""_compact`. + + Make a key in the corresonding style from a string literal. + +The string literal operators are included in an inline namespace `literals` +that can be included in user code via `using namespace to::literals`. + +### Using `to::parse` + +The Tinyopt `to::parse` functions compare a single command line argument +against one or more short- or long-style options, parsing any corresponding +option argument with the default or explicitly provided parser. + +* `maybe<void> parse(char**& argp, key k, ...)` + + Attempt to parse an option with no argument (i.e. a flag) at `argp`, given + by the option key `k` or subsequent. Returns an empty `maybe<void>` value + if it fails to match any of the keys. + + If the match is successful, increment `argp` to point to the next argument. + +* `maybe<V> parse(char**& argp, const P& parser, key k, ...)`<br/> + `maybe<V> parse(char**& argp, key k, ...)` + + Attempt to parse an option with an argument to be interpreted as type `V`, + matching the option against the key `k` or subsequent. If no `parser` is + supplied, use the default parser for the type `V` to convert the option + argument. + + If the match and value pase is successful, increment `argp` once or twice + as required to advance to the next option. + +The `parse` functions will throw `missing_argument` if no argument is found +for a non-flag option, and `option_parse_error` if the parser for an argument +returns `nothing`. + +The monadic `maybe` return values allow straightforward chaining of multiple +`to::parse` calls in a single expression; see `ex/ex1-parse.cc` for an +example. + +### Using `to::run` + +The `to::run` function hands more control to the library for option parsing. +The basic workflow is: + +1. The user code sets up a collection of `option` objects, each of which describe + a command line option or flag, and how to handle the result of parsing it. +2. This collection is passed to the `run` function, along with `argc` and `argv`. + `argv` is modified in place to remove matched options; anything remaining + can be handled by the user code. +3. The `run` function returns a saved set of matched options that can, for example, + be saved in program output for tracking processing steps, or in some file + to allow for re-execution of the code with the same arguments. + +An `option` describes one command line flag or option with an argument. It has +five components: a `sink`, that describes what to do with a successfully parsed +option; a set of `key`s, which are how the option is presented on the command line; +a sequence of `filter`s, which can limit the scope in which the option is valid; +a sequence of `modal`s, which change the modal state of the parser when the option +is matched; and finally a set of option flags that modify behaviour. + +#### Sinks + +A sink wraps a function that takes a `const char*` value, representing the +argument to an option, and returns a `bool`. A return value of `true` indicates +a successful parsing of the argument; `false` represents a parse error. + +A sink has three constructors: +* `sink(sink::action_t, Action a)` + + `action_t` is a tag type, indicating that the second argument is a functional + to be used directly as the wrapped function. `sink::action` is a value with + this type for use in this constructor. + + This constructor is used by the `action` wrapper function described below. + +* `sink(V& var, P parser)` + + Make a sink that uses the parser `P` (see above for a description of what + constitutes a tinyopt parser) to get a value of type `V`, and write that + value to `var`. + +* `sink(V& var)` + + Equivalent to `sink(var, default_parser<V>{})` + +If an option doesn't have any associated argument, i.e. it is a flag, +the `sink` object is passed `nullptr`. + +The `action` wrapper function takes a nullary or unary function or functional +object, and optionally a parser for the function's argument. It returns a +`sink` object that applies the default or supplied parser object +and if successful, calls the function with the parsed value. + +A special action wrapper is `error(const std::string& message)`, which will +throw a `user_option_error` with the given message. + +#### Sink adaptors + +The library supplies some convenience adaptors for making `sink`s for common +situations: + +* `push_back(Container& c, P parser = P{})` + + Append parsed values to the container `c` using `Container::push_back`. + The default parser is `default_parser<Container::value_type>`. + +* `set(V& v, X value)` + + Set `v` to `value`, ignoring any argument. + +* `set(V& v)` + + Set `v` to `true`, ignoring any argument. + +* `increment(V& v, X delta)` + + Perform `v += delta`, ignoring any argument. + +* `increment(V& v)` + + Perform `++v`, ignoring any argument. + +#### Filters and modals + +Options are by default always available for consideration. The `single` +flag described below provides one simple constraint on option matching; the +modal interfaces provide a more elaborate system should it be required. + +Each option maintains a sequence of _filters_ and a sequence of _modals_. + +A _filter_ is a `filter` object (an alias for `std::function<bool (int)>`) +that takes the current mode (an integer, by default zero) and returns `false` +if the option should not be considered for matching. Options will be ignored if +any of its filters return false. + +A _modal_ is a `modal` object (an alias for `std::functional<int (int)>`) +that is passed the current mode value and returns the new mode value when the +option is successfully matched and parsed. + +Filters can be made with the `when` adaptor. Given a functional object, +it will wrap it in a `filter`. Given an integer value, it will make +a `filter` that returns true only if the mode matches that value. +If `when` is given multiple arguments, it constructs a filter that is +the disjunction of filters constructed from each argument. + +`then(f)` constructs a `modal` object wrapping the functional object `f`; +`then(int v)` constructs a `modal` that just returns the value `m`. + +#### Flags + +Option behaviour can be modified by supplying `enum option_flag` values: + +* `flag` — Option takes no argument, i.e. it is a flag. +* `ephemeral` — Do not record this option in the saved data returned by `run()`. +* `single` — This option will be checked at most once, and then ignored for the remainder of option processing. +* `mandatory` — Throw an exception if this option does not appear in the command line arguments. +* `exit` — On successful parsing of this option, stop any further option processing and return `nothing` from `run()`. +* `stop` — On successful parsing of this option, stop any further option processing but return saved options as normal from `run()`. + +These enum values are all powers of two and can be combined via bitwise or `|`. + +When to use `exit`, and when to use `stop`? `exit` is intended to describe +the situation where the program should not proceed further with normal +processing; a standard use case for `exit` is for `--help` options, which +should cause the program to exit after printing help text. `stop`, on the +other hand, is used for options that indicate that no further special +argument processing should be performed; this corresponds to the common +convention of `--` on the command line indicating that all remaining +arguments should be interpreted as command line options. + +#### Specifying an option + +The `option` constructor takes a `sink` as the first argument, followed by +any number of keys, filters, modals, and flags in any order. + +An `option` may have no keys at all — these will always match an item in the +command line argument list, and that item will be passed directly to the +option's sink. + +Some example specifications: +``` + // Saves integer argument to variable 'n': + int n = 0; + to::option opt_n = { n, "-n" }; + + // Flag '-v' or '--verbose' that increases verbosity level, but is not + // kept in the returned list of saved options. + int verbosity = 0; + to::option opt_v = { to::increment(verbosity), "-v", "--verbose", to::flag, to::ephemeral }; + + // Save vector of values from one argument of comma separated values, e.g. + // -x 1,2,3,4,5: + std::vector<int> xs; + to::option opt_x = { {xs, to::delimited<int>()}, "-x" }; + + // Save vector of values one by one, e.g., + // -k 1 -k 2 -k 3 -k 4 -k 5 + to::option opt_k = { to::push_back(xs), "-k" }; + + // A 'help' flag that calls a help() function and stops further option processing. + to::option opt_h = { to::action(help), to::flag, to::exit, "-h", "--help" }; + + // A modal flag that sets the mode to 2, and a filtered option that only + // applies when the mode is 2. + enum mode { none = 0, list = 1, install = 2} prog_mode = none; + to::option opt_m = { to::set(prog_mode, install), "install", to::then(install) }; + to::option opt_h = { to::action(install_help), to::flag, to::exit, "-h", "--help", to::when(install) }; + + // Compact option keys using to::literals: + using namespace to::literals; + bool a = false, b = false, c = false; + to::option flags[] = { + { to::set(a), "-a"_compact, to::flag }, + { to::set(b), "-b"_compact, to::flag }, + { to::set(c), "-c"_compact, to::flag } + }; +``` + +#### Saved options + +The `run()` function (see below) returns a value of type +`maybe<saved_options>`. The `saved_options` object holds a record of the +successfully parsed options. It wraps a `std::vector<std::string>` that has one +element per option key and argument, in the order they were matched (excluding +options with the `ephemeral` flag). + +While the contents can be inspected via methods `begin()`, `end()`, `empty()` +and `size()`, it is primarily intended to support serialization. Overloads of +`operator<<` and `operator>>` will write and read a `saved_options` object to a +`std::ostream` or `std::istream` object respectively. The serialized format +uses POSIX shell-compatible escaping with backslashes and single quotes so that +they be incorporated directly on the command line in later program invocations. + +#### Running a set of options + +A command line argument list or `saved_options` object is run against a +collection of `option` specifications with `run()`. There are five overloads, +each of which returns a `saved_options` value in normal execution or `nothing` +if an option with the `exit` flag is matched. + +In the following `Options` is any iterable collection of `option` values. + +* `maybe<saved_options> run(const Options& options, int& argc, char** argv)` + + Parse the items in `argv` against the options provided in the first argument. + Starting at the beginning of the `argv` list, options with keys are checked first, + in the order they appear in `options`, followed by options without keys. + + Successfully parsed options are removed from the `argv` list in-place, and + `argc` is adjusted accordingly. + +* `maybe<saved_options> run(const Options& options, int& argc, char** argv, const saved_options& restore)` + + As for `run(options, argc, argv)`, but first run the options against the saved command line + arguments in `restore`, and then again against `argv`. + + A mandatory option can be satisfied by the restore set or by the argv set. + +* `maybe<saved_options> run(const Options& options, const saved_options& restore)` + + As for `run(options, argc, argv, restore)`, but with an empty argc/argv list. + +* `maybe<saved_options> run(const Options& options, char** argv)` + + As for `run(options, argc, argv)`, but ignoring argc. + +* `maybe<saved_options> run(const Options& options, char** argv, const saved_options& restore)` + + As for `run(options, argc, argv, restore)`, but ignoring argc. + +Like the `to::parse` functions, the `run()` function can throw `missing_argument` or +`option_parse_error`. In addition, it will throw `missing_mandatory_option` if an option +marked with `mandatory` is not found during command line argument parsing. + +Note that the arguments in `argv` are checked from the beginning; when calling `run` from within, +e.g the main function `int main(int argc, char** argv)`, one should pass `argv+1` to `run` +so as to avoid including the program name in `argv[0]`. diff --git a/ext/tinyopt/include/tinyopt/common.h b/ext/tinyopt/include/tinyopt/common.h deleted file mode 100644 index fb4a654b3a05ef0eeaab2a625265cb9f9b117fe6..0000000000000000000000000000000000000000 --- a/ext/tinyopt/include/tinyopt/common.h +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once - -#include <algorithm> -#include <cstddef> -#include <cstring> -#include <iomanip> -#include <iostream> -#include <iterator> -#include <sstream> -#include <string> -#include <utility> -#include <vector> - -#include <tinyopt/maybe.h> - -namespace to { - -// `option_error` is the base class for exceptions thrown -// by the option handling functions. - -struct option_error: public std::runtime_error { - option_error(const std::string& message): std::runtime_error(message) {} - option_error(const std::string& message, std::string arg): - std::runtime_error(message+": "+arg), arg(std::move(arg)) {} - - std::string arg; -}; - -struct option_parse_error: option_error { - option_parse_error(const std::string &arg): - option_error("option parse error", arg) {} -}; - -struct missing_mandatory_option: option_error { - missing_mandatory_option(const std::string &arg): - option_error("missing mandatory option", arg) {} -}; - -struct missing_argument: option_error { - missing_argument(const std::string &arg): - option_error("option misssing argument", arg) {} -}; - -struct user_option_error: option_error { - user_option_error(const std::string &arg): - option_error(arg) {} -}; - -// `usage` prints usage information to stdout (no error message) -// or to stderr (with error message). It extracts the program basename -// from the provided argv[0] string. - -inline void usage(const char* argv0, const std::string& usage_str) { - const char* basename = std::strrchr(argv0, '/'); - basename = basename? basename+1: argv0; - - std::cout << "Usage: " << basename << " " << usage_str << "\n"; -} - -inline void usage(const char* argv0, const std::string& usage_str, const std::string& parse_err) { - const char* basename = std::strrchr(argv0, '/'); - basename = basename? basename+1: argv0; - - std::cerr << basename << ": " << parse_err << "\n"; - std::cerr << "Usage: " << basename << " " << usage_str << "\n"; -} - -// Parser objects act as functionals, taking -// a const char* argument and returning maybe<T> -// for some T. - -template <typename V> -struct default_parser { - maybe<V> operator()(const char* text) const { - if (!text) return nothing; - V v; - std::istringstream stream(text); - stream >> v >> std::ws; - return stream && stream.get()==EOF? maybe<V>(v): nothing; - } -}; - -template <> -struct default_parser<const char*> { - maybe<const char*> operator()(const char* text) const { - return just(text); - } -}; - -template <> -struct default_parser<std::string> { - maybe<std::string> operator()(const char* text) const { - return just(std::string(text)); - } -}; - -template <> -struct default_parser<void> { - maybe<void> operator()(const char* text) const { - return something; - } -}; - -template <typename V> -class keyword_parser { - std::vector<std::pair<std::string, V>> map_; - -public: - template <typename KeywordPairs> - keyword_parser(const KeywordPairs& pairs) { - using std::begin; - using std::end; - map_.assign(begin(pairs), end(pairs)); - } - - maybe<V> operator()(const char* text) const { - if (!text) return nothing; - for (const auto& p: map_) { - if (text==p.first) return p.second; - } - return nothing; - } -}; - -// Returns a parser that matches a set of keywords, -// returning the corresponding values in the supplied -// pairs. - -template <typename KeywordPairs> -auto keywords(const KeywordPairs& pairs) { - using std::begin; - using value_type = std::decay_t<decltype(std::get<1>(*begin(pairs)))>; - return keyword_parser<value_type>(pairs); -} - - -// A parser for delimited sequences of values; returns -// a vector of the values obtained from the supplied -// per-item parser. - -template <typename P> -class delimited_parser { - char delim_; - P parse_; - using inner_value_type = std::decay_t<decltype(*std::declval<P>()(""))>; - -public: - template <typename Q> - delimited_parser(char delim, Q&& parse): delim_(delim), parse_(std::forward<Q>(parse)) {} - - maybe<std::vector<inner_value_type>> operator()(const char* text) const { - if (!text) return nothing; - - std::vector<inner_value_type> values; - if (!*text) return values; - - std::size_t n = std::strlen(text); - std::vector<char> input(1+n); - std::copy(text, text+n, input.data()); - - char* p = input.data(); - char* end = input.data()+1+n; - do { - char* q = p; - while (*q && *q!=delim_) ++q; - *q++ = 0; - - if (auto mv = parse_(p)) values.push_back(*mv); - else return nothing; - - p = q; - } while (p<end); - - return values; - } -}; - -// Convenience constructors for delimited parser. - -template <typename Q> -auto delimited(char delim, Q&& parse) { - using P = std::decay_t<Q>; - return delimited_parser<P>(delim, std::forward<Q>(parse)); -} - -template <typename V> -auto delimited(char delim = ',') { - return delimited(delim, default_parser<V>{}); -} - - -} // namespace to diff --git a/ext/tinyopt/include/tinyopt/maybe.h b/ext/tinyopt/include/tinyopt/maybe.h deleted file mode 100644 index a8ab24ccb67ff2033320a36d251d3c6f0ea89781..0000000000000000000000000000000000000000 --- a/ext/tinyopt/include/tinyopt/maybe.h +++ /dev/null @@ -1,170 +0,0 @@ -#pragma once - -#include <stdexcept> -#include <utility> -#include <type_traits> - -#include <iostream> - -namespace to { - -// nothing is a special value that converts -// to an empty maybe<T> for any T. - -constexpr struct nothing_t {} nothing; - -// maybe<T> represents an optional value of type T, -// with an interface similar to C++17 std::optional. - -template <typename T> -struct maybe { - bool ok = false; - alignas(T) char data[sizeof(T)]; - - maybe() noexcept: ok(false) {} - maybe(nothing_t) noexcept: ok(false) {} - maybe(const T& v): ok(true) { construct(v); } - maybe(T&& v): ok(true) { construct(std::move(v)); } - maybe(const maybe& m): ok(m.ok) { if (ok) construct(*m); } - maybe(maybe&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } - - ~maybe() { destroy(); } - - template <typename U> - maybe(const maybe<U>& m): ok(m.ok) { if (ok) construct(*m); } - - template <typename U> - maybe(maybe<U>&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } - - maybe& operator=(nothing_t) { return destroy(), *this; } - maybe& operator=(const T& v) { return assign(v), *this; } - maybe& operator=(T&& v) { return assign(std::move(v)), *this; } - maybe& operator=(const maybe& m) { return m.ok? assign(*m): destroy(), *this; } - maybe& operator=(maybe&& m) { return m.ok? assign(std::move(*m)): destroy(), *this; } - - const T& value() const & { return assert_ok(), *vptr(); } - T&& value() && { return assert_ok(), std::move(*vptr()); } - - const T& operator*() const & noexcept { return *vptr(); } - const T* operator->() const & noexcept { return vptr(); } - T&& operator*() && { return std::move(*vptr()); } - - bool has_value() const noexcept { return ok; } - explicit operator bool() const noexcept { return ok; } - -private: - T* vptr() noexcept { return reinterpret_cast<T*>(data); } - const T* vptr() const noexcept { return reinterpret_cast<const T*>(data); } - - void construct(const T& v) { new (data) T(v); ok = true; } - void construct(T&& v) { new (data) T(std::move(v)); ok = true; } - - void assign(const T& v) { if (ok) *vptr()=v; else construct(v); } - void assign(T&& v) { if (ok) *vptr()=std::move(v); else construct(std::move(v)); } - - void destroy() { if (ok) (**this).~T(); ok = false; } - void assert_ok() const { if (!ok) throw std::invalid_argument("is nothing"); } -}; - -namespace impl { - template <typename T> - struct is_maybe_: std::false_type {}; - - template <typename T> - struct is_maybe_<maybe<T>>: std::true_type {}; -} - -template <typename T> -using is_maybe = impl::is_maybe_<std::remove_cv_t<std::remove_reference_t<T>>>; - -// maybe<void> acts as a maybe<T> with an empty or inaccessible wrapped value. - -template <> -struct maybe<void> { - bool ok = false; - - constexpr maybe() noexcept: ok(false) {} - constexpr maybe(nothing_t&) noexcept: ok(false) {} - constexpr maybe(const nothing_t&) noexcept: ok(false) {} - constexpr maybe(nothing_t&&) noexcept: ok(false) {} - - template <typename X, typename = std::enable_if_t<!is_maybe<X>::value>> - constexpr maybe(X&&) noexcept: ok(true) {} - - template <typename U> - constexpr maybe(const maybe<U>& m) noexcept: ok(m.ok) {} - - maybe& operator=(nothing_t) noexcept { return ok = false, *this; } - maybe& operator=(const maybe& m) noexcept { return ok = m.ok, *this; } - template <typename U> - maybe& operator=(U&& v) noexcept { return ok = true, *this; } - - bool has_value() const noexcept { return ok; } - constexpr explicit operator bool() const noexcept { return ok; } -}; - -// something is a non-empty maybe<void> value. - -constexpr maybe<void> something(true); - -// just<X> converts a value of type X to a maybe<X> containing the value. - -template <typename X> -auto just(X&& x) { return maybe<std::decay_t<X>>(std::forward<X>(x)); } - -// operator<< offers monadic-style chaining of maybe<X> values: -// (f << m) evaluates to an empty maybe if m is empty, or else to -// a maybe<V> value wrapping the result of applying f to the value -// in m. - -template < - typename F, - typename T, - typename R = std::decay_t<decltype(std::declval<F>()(std::declval<const T&>()))>, - typename = std::enable_if_t<std::is_same<R, void>::value> -> -maybe<void> operator<<(F&& f, const maybe<T>& m) { - if (m) return f(*m), something; else return nothing; -} - -template < - typename F, - typename T, - typename R = std::decay_t<decltype(std::declval<F>()(std::declval<const T&>()))>, - typename = std::enable_if_t<!std::is_same<R, void>::value> -> -maybe<R> operator<<(F&& f, const maybe<T>& m) { - if (m) return f(*m); else return nothing; -} - -template < - typename F, - typename R = std::decay_t<decltype(std::declval<F>()())>, - typename = std::enable_if_t<std::is_same<R, void>::value> -> -maybe<void> operator<<(F&& f, const maybe<void>& m) { - return m? (f(), something): nothing; -} - -template < - typename F, - typename R = std::decay_t<decltype(std::declval<F>()())>, - typename = std::enable_if_t<!std::is_same<R, void>::value> -> -maybe<R> operator<<(F&& f, const maybe<void>& m) { - return m? just(f()): nothing; -} - -// If the lhs is not functional, return a maybe value with the result -// of assigning the value in the rhs, or nothing if the rhs is nothing. - -template <typename T, typename U> -auto operator<<(T& x, const maybe<U>& m) -> maybe<std::decay_t<decltype(x=*m)>> { - if (m) return x=*m; else return nothing; -} - -template <typename T> -auto operator<<(T& x, const maybe<void>& m) -> maybe<std::decay_t<decltype(x=true)>> { - if (m) return x=true; else return nothing; -} -} // namespace to diff --git a/ext/tinyopt/include/tinyopt/smolopt.h b/ext/tinyopt/include/tinyopt/smolopt.h deleted file mode 100644 index b9dde78ccd9013fe6b50fd0aef305ea559a58043..0000000000000000000000000000000000000000 --- a/ext/tinyopt/include/tinyopt/smolopt.h +++ /dev/null @@ -1,706 +0,0 @@ -#pragma once - -// Small option parsing/handling, but no so teeny as tinyopt. - -#include <cstddef> -#include <cstring> -#include <functional> -#include <type_traits> -#include <utility> - -#include <tinyopt/maybe.h> -#include <tinyopt/common.h> - -namespace to { - -// Option keys -// ----------- -// -// A key is how the option is specified in an argument list, and is typically -// represented as a 'short' (e.g. '-a') option or a 'long' option (e.g. -// '--apple'). -// -// The value for an option can always be taken from the next argument in the -// list, but in addition can be specified together with the key itself, -// depending on the properties of the option key: -// -// --key=value 'Long' style argument for key "--key" -// -kvalue 'Compact' style argument for key "-k" -// -// Compact option keys can be combined in the one item in the argument list, if -// the options do not take any values (that is, they are flags). For example, -// if -a, -b are flags and -c takes an integer argument, with all three keys -// marked as compact, then an item '-abc3' in the argument list will be parsed -// in the same way as the sequence of items '-a', '-b', '-c', '3'. -// -// An option without a key will match any item in the argument list; options -// with keys are always checked first. - -struct key { - std::string label; - enum style { shortfmt, longfmt, compact } style = shortfmt; - - key(std::string l): label(std::move(l)) { - if (label[0]=='-' && label[1]=='-') style = longfmt; - } - - key(const char* label): key(std::string(label)) {} - - key(std::string label, enum style style): - label(std::move(label)), style(style) {} -}; - -inline namespace literals { - -inline key operator""_short(const char* label, std::size_t) { - return key(label, key::shortfmt); -} - -inline key operator""_long(const char* label, std::size_t) { - return key(label, key::longfmt); -} - -inline key operator""_compact(const char* label, std::size_t) { - return key(label, key::compact); -} - -} // namespace literals - -// Argument state -// -------------- -// -// to::state represents the collection of command line arguments. Mutating -// operations (shift(), successful option matching, etc.) will modify the -// underlying set of arguments used to construct the state object. - -struct state { - int& argc; - char** argv; - unsigned optoff = 0; - - state(int& argc, char** argv): argc(argc), argv(argv) {} - - // False => no more arguments. - explicit operator bool() const { return *argv; } - - // Shift arguments left in-place. - void shift(unsigned n = 1) { - char** skip = argv; - while (*skip && n) ++skip, --n; - - argc -= (skip-argv); - auto p = argv; - do { *p++ = *skip; } while (*skip++); - - optoff = 0; - } - - // Skip current argument without modifying list. - void skip() { - if (*argv) ++argv; - } - - // Match an option given by the key which takes an argument. - // If successful, consume option and argument and return pointer to - // argument string, else return nothing. - maybe<const char*> match_option(const key& k) { - const char* p = nullptr; - - if (k.style==key::compact) { - if ((p = match_compact_key(k.label.c_str()))) { - if (!*p) { - p = argv[1]; - shift(2); - } - else shift(); - return p; - } - } - else if (!optoff && k.label==*argv) { - p = argv[1]; - shift(2); - return p; - } - else if (!optoff && k.style==key::longfmt) { - auto keylen = k.label.length(); - if (!std::strncmp(*argv, k.label.c_str(), keylen) && (*argv)[keylen]=='=') { - p = &(*argv)[keylen+1]; - shift(); - return p; - } - } - - return nothing; - } - - // Match a flag given by the key. - // If successful, consume flag and return true, else return false. - bool match_flag(const key& k) { - if (k.style==key::compact) { - if (auto p = match_compact_key(k.label.c_str())) { - if (!*p) shift(); - return true; - } - } - else if (!optoff && k.label==*argv) { - shift(); - return true; - } - - return false; - } - - // Compact-style keys can be combined in one argument; combined keys - // with a common prefix only need to supply the prefix once at the - // beginning of the argument. - const char* match_compact_key(const char* k) { - unsigned keylen = std::strlen(k); - - unsigned prefix_max = std::min(keylen-1, optoff); - for (std::size_t l = 0; l<=prefix_max; ++l) { - if (l && strncmp(*argv, k, l)) break; - if (strncmp(*argv+optoff, k+l, keylen-l)) continue; - optoff += keylen-l; - return *argv+optoff; - } - - return nullptr; - } -}; - -// Sinks and actions -// ----------------- -// -// Sinks wrap a function that takes a pointer to an option parameter and stores -// or acts upon the parsed result. -// -// They can be constructed from an lvalue reference or a functional object (via -// the `action` function) with or without an explicit parser function. If no -// parser is given, a default one is used if the correct value type can be -// determined. - -namespace impl { - template <typename T> struct fn_arg_type { using type = void; }; - template <typename R, typename X> struct fn_arg_type<R (X)> { using type = X; }; - template <typename R, typename X> struct fn_arg_type<R (*)(X)> { using type = X; }; - template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X)> { using type = X; }; - template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) const> { using type = X; }; - template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) volatile> { using type = X; }; - template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) const volatile> { using type = X; }; - - template <typename...> struct void_type { using type = void; }; -} - -template <typename T, typename = void> -struct unary_argument_type { using type = typename impl::fn_arg_type<T>::type; }; - -template <typename T> -struct unary_argument_type<T, typename impl::void_type<decltype(&T::operator())>::type> { - using type = typename impl::fn_arg_type<decltype(&T::operator())>::type; -}; - -template <typename T> -using unary_argument_type_t = typename unary_argument_type<T>::type; - -struct sink { - // Tag class for constructor. - static struct action_t {} action; - - sink(): - sink(action, [](const char* param) { return true; }) - {} - - template <typename V> - sink(V& var): sink(var, default_parser<V>{}) {} - - template <typename V, typename P> - sink(V& var, P parser): - sink(action, [ref=std::ref(var), parser](const char* param) { - if (auto p = parser(param)) return ref.get() = std::move(*p), true; - else return false; - }) - {} - - template <typename Action> - sink(action_t, Action a): op(std::move(a)) {} - - bool operator()(const char* param) const { return op(param); } - std::function<bool (const char*)> op; - -}; - -// Convenience functions for construction of sink actions -// with explicit or implicit parser. - -template <typename F, typename A = std::decay_t<unary_argument_type_t<F>>> -sink action(F f) { - return sink(sink::action, - [f = std::move(f)](const char* arg) -> bool { - return static_cast<bool>(f << default_parser<A>{}(arg)); - }); -} - -template <typename F, typename P> -sink action(F f, P parser) { - return sink(sink::action, - [f = std::move(f), parser = std::move(parser)](const char* arg) -> bool { - return static_cast<bool>(f << parser(arg)); - }); -} - -// Special actions: -// -// error(message) Throw a user_option_error with the supplied message. - -inline sink error(std::string message) { - return sink(sink::action, - [m = std::move(message)](const char*) -> bool { - throw user_option_error(m); - }); -} - -// Sink adaptors: -// -// These adaptors constitute short cuts for making actions that count the -// occurance of a flag, set a fixed value when a flag is provided, or for -// appending an option parameter onto a vector of values. - -// Push parsed option parameter on to container. -template <typename Container, typename P = default_parser<typename Container::value_type>> -sink push_back(Container& c, P parser = P{}) { - return action( - [ref = std::ref(c)](typename Container::value_type v) { ref.get().push_back(std::move(v)); }, - std::move(parser)); -} - -// Set v to value when option parsed; ignore any option parameter. -template <typename V, typename X> -sink set(V& v, X value) { - return action([ref = std::ref(v), value = std::move(value)] { ref.get() = value; }); -} - -// Set v to true when option parsed; ignore any option parameter. -template <typename V> -sink set(V& v) { - return set(v, true); -} - -// Incrememnt v when option parsed; ignore any option parameter. -template <typename V> -sink increment(V& v) { - return action([ref = std::ref(v)] { ++ref.get(); }); -} - -template <typename V, typename X> -sink increment(V& v, X delta) { - return action([ref = std::ref(v), delta = std::move(delta)] { ref.get() += delta; }); -} - -// Modal configuration -// ------------------- -// -// Options can be filtered by some predicate, and can trigger a state -// change when successfully processed. -// -// Filter construction: -// to::when(Fn f) f is a functional with signature bool (int) -// to::when(int s) equivalent to to::when([](int k) { return k==s; }) -// to::when(a, b, ...) filter than is satisfied by to::when(a) or -// to::when(b) or ... -// -// The argument to the filter predicate is the 'mode', a mutable state maintained -// during a single run of to::run(). -// -// Mode changes: -// to::then(Fn f) f is a functional with signature int (int) -// to::then(int s) equivalent to to::then([](int) { return s; }) -// -// The argument to the functional is the current mode; the return value -// sets the new value of mode. -// -// Filters are called before keys are matched; modal changes are called -// after an option is processed. All - -using filter = std::function<bool (int)>; -using modal = std::function<int (int)>; - -template <typename F, typename = decltype(std::declval<F>()(0))> -filter when(F f) { - return [f = std::move(f)](int mode) { return static_cast<bool>(f(mode)); }; -} - -inline filter when(int m) { - return [m](int mode) { return m==mode; }; -} - -template <typename A, typename B, typename... Rest> -filter when(A a, B&& b, Rest&&... rest) { - return - [fhead = when(std::forward<A>(a)), - ftail = when(std::forward<B>(b), std::forward<Rest>(rest)...)](int m) { - return fhead(m) || ftail(m); - }; -} - -template <typename F, typename = decltype(std::declval<F>()(0))> -modal then(F f) { - return [f = std::move(f)](int mode) { return static_cast<int>(f(mode)); }; -} - -inline modal then(int m) { - return [m](int) { return m; }; -} - - -// Option specification -// -------------------- -// -// An option specification comprises zero or more keys (e.g. "-a", "--foo"), -// a sink (where the parsed argument will be sent), a parser - which may be -// the default parser - and zero or more flags that modify its behaviour. -// -// Option flags: - -enum option_flag { - flag = 1, // Option takes no parameter. - ephemeral = 2, // Option is not saved in returned results. - single = 4, // Option is parsed at most once. - mandatory = 8, // Option must be present in argument list. - exit = 16, // Option stops further argument processing. -}; - -struct option { - sink s; - std::vector<key> keys; - std::vector<filter> filters; - std::vector<modal> modals; - std::string prefkey; - - bool is_flag = false; - bool is_ephemeral = false; - bool is_single = false; - bool is_mandatory = false; - bool is_exit = false; - - template <typename... Rest> - option(sink s, Rest&&... rest): s(std::move(s)) { - init_(std::forward<Rest>(rest)...); - } - - void init_() {} - - template <typename... Rest> - void init_(enum option_flag f, Rest&&... rest) { - is_flag |= f & flag; - is_ephemeral |= f & ephemeral; - is_single |= f & single; - is_mandatory |= f & mandatory; - is_exit |= f & exit; - init_(std::forward<Rest>(rest)...); - } - - template <typename... Rest> - void init_(filter f, Rest&&... rest) { - filters.push_back(std::move(f)); - init_(std::forward<Rest>(rest)...); - } - - template <typename... Rest> - void init_(modal f, Rest&&... rest) { - modals.push_back(std::move(f)); - init_(std::forward<Rest>(rest)...); - } - - template <typename... Rest> - void init_(key k, Rest&&... rest) { - if (k.label.length()>prefkey.length()) prefkey = k.label; - keys.push_back(std::move(k)); - init_(std::forward<Rest>(rest)...); - } - - bool has_key(const std::string& arg) const { - if (keys.empty() && arg.empty()) return true; - for (const auto& k: keys) { - if (arg==k.label) return true; - } - return false; - } - - bool check_mode(int mode) const { - for (auto& f: filters) { - if (!f(mode)) return false; - } - return true; - } - - void set_mode(int& mode) const { - for (auto& f: modals) mode = f(mode); - } - - void run(const std::string& label, const char* arg) const { - if (!is_flag && !arg) throw missing_argument(label); - if (!s(arg)) throw option_parse_error(label); - } -}; - -// Saved options -// ------------- -// -// Successfully matched options, excluding those with the to::ephemeral flag -// set, are collated in a saved_options structure for potential documentation -// or replay. - -struct saved_options: private std::vector<std::string> { - using std::vector<std::string>::begin; - using std::vector<std::string>::end; - using std::vector<std::string>::size; - using std::vector<std::string>::empty; - - void add(std::string s) { push_back(std::move(s)); } - - saved_options& operator+=(const saved_options& so) { - insert(end(), so.begin(), so.end()); - return *this; - } - - struct arglist { - int argc; - char** argv; - std::vector<char*> arg_data; - }; - - // Construct argv representing argument list. - arglist as_arglist() const { - arglist A; - - for (auto& a: *this) A.arg_data.push_back(const_cast<char*>(a.c_str())); - A.arg_data.push_back(nullptr); - A.argv = A.arg_data.data(); - A.argc = A.arg_data.size()-1; - return A; - } - - // Serialized representation: - // - // Saved arguments are separated by white space. If an argument - // contains whitespace or a special character, it is escaped with - // single quotes in a POSIX shell compatible fashion, so that - // the representation can be used directly on a shell command line. - - friend std::ostream& operator<<(std::ostream& out, const saved_options& s) { - auto escape = [](const std::string& v) { - if (!v.empty() && v.find_first_of("\\*?[#~=%|^;<>()$'`\" \t\n")==std::string::npos) return v; - - // Wrap string in single quotes, replacing any internal single quote - // character with: '\'' - - std::string q ="'"; - for (auto c: v) { - c=='\''? q += "'\\''": q += c; - } - return q += '\''; - }; - - bool first = true; - for (auto& p: s) { - if (first) first = false; else out << ' '; - out << escape(p); - } - return out; - } - - friend std::istream& operator>>(std::istream& in, saved_options& s) { - std::string w; - bool have_word = false; - bool quote = false; // true => within single quotes. - bool escape = false; // true => previous character was backslash. - while (in) { - char c = in.get(); - if (c==EOF) break; - - if (quote) { - if (c!='\'') w += c; - else quote = false; - } - else { - if (escape) { - w += c; - escape = false; - } - else if (c=='\\') { - escape = true; - have_word = true; - } - else if (c=='\'') { - quote = true; - have_word = true; - } - else if (c!=' ' && c!='\t' && c!='\n') { - w += c; - have_word = true; - } - else { - if (have_word) s.add(w); - have_word = false; - w = ""; - } - } - } - - if (have_word) s.add(w); - return in; - } -}; - -// Option with mutable state (for checking single and mandatory flags), -// used by to::run(). - -namespace impl { - struct counted_option: option { - int count = 0; - - counted_option(const option& o): option(o) {} - - // On successful match, return pointers to matched key and value. - // For flags, use nullptr for value; for empty key sets, use - // nullptr for key. - maybe<std::pair<const char*, const char*>> match(state& st) { - if (is_flag) { - for (auto& k: keys) { - if (st.match_flag(k)) return set(k.label, nullptr); - } - return nothing; - } - else if (!keys.empty()) { - for (auto& k: keys) { - if (auto param = st.match_option(k)) return set(k.label, *param); - } - return nothing; - } - else { - const char* param = *st.argv; - st.shift(); - return set("", param); - } - } - - std::pair<const char*, const char*> set(const char* arg) { - run("", arg); - ++count; - return {nullptr, arg}; - } - - std::pair<const char*, const char*> set(const std::string& label, const char* arg) { - run(label, arg); - ++count; - return {label.c_str(), arg}; - } - }; -} // namespace impl - -// Running a set of options -// ------------------------ -// -// to::run() can be used to parse options from the command-line and/or from -// saved_options data. -// -// The first argument is a collection or sequence of option specifications, -// followed optionally by command line argc and argv or just argv. A -// saved_options object can be optionally passed as the last parameter. -// -// If an option with the to::exit flag is matched, option parsing will -// immediately stop and an empty value will be returned. Otherwise to::run() -// will return a saved_options structure recording the successfully parsed -// options. - -namespace impl { - inline maybe<saved_options> run(std::vector<impl::counted_option>& opts, int& argc, char** argv) { - saved_options collate; - bool exit = false; - state st{argc, argv}; - int mode = 0; - while (st && !exit) { - // Try options with a key first. - for (auto& o: opts) { - if (o.keys.empty()) continue; - if (o.is_single && o.count) continue; - if (!o.check_mode(mode)) continue; - - if (auto ma = o.match(st)) { - if (!o.is_ephemeral) { - if (ma->first) collate.add(ma->first); - if (ma->second) collate.add(ma->second); - } - o.set_mode(mode); - exit = o.is_exit; - goto next; - } - } - - // Literal "--" terminates option parsing. - if (!std::strcmp(*argv, "--")) { - st.shift(); - return collate; - } - - // Try free options. - for (auto& o: opts) { - if (!o.keys.empty()) continue; - if (o.is_single && o.count) continue; - if (!o.check_mode(mode)) continue; - - if (auto ma = o.match(st)) { - if (!o.is_ephemeral) collate.add(ma->second); - o.set_mode(mode); - exit = o.is_exit; - goto next; - } - } - - // Nothing matched, so increment argv. - st.skip(); - next: ; - } - - return exit? nothing: just(collate); - } -} // namespace impl - - -template <typename Options> -maybe<saved_options> run(const Options& options, int& argc, char** argv, const saved_options& restore = saved_options{}) { - using std::begin; - using std::end; - std::vector<impl::counted_option> opts(begin(options), end(options)); - auto r_args = restore.as_arglist(); - - saved_options coll1, coll2; - if (coll1 << impl::run(opts, r_args.argc, r_args.argv) && coll2 << impl::run(opts, argc, argv)) { - for (auto& o: opts) { - if (o.is_mandatory && !o.count) throw missing_mandatory_option(o.prefkey); - } - return coll1 += coll2; - } - - return nothing; -} - -template <typename Options> -maybe<saved_options> run(const Options& options, const saved_options& restore) { - int ignore_argc = 0; - char* end_of_args = nullptr; - return run(options, ignore_argc, &end_of_args, restore); -} - -template <typename Options> -maybe<saved_options> run(const Options& options, char** argv) { - int ignore_argc = 0; - return run(options, ignore_argc, argv); -} - -template <typename Options> -maybe<saved_options> run(const Options& options, char** argv, const saved_options& restore) { - int ignore_argc = 0; - return run(options, ignore_argc, argv, restore); -} -} // namespace to diff --git a/ext/tinyopt/include/tinyopt/tinyopt.h b/ext/tinyopt/include/tinyopt/tinyopt.h index 73d64bce1530ed16b0dbc01daf66a630b3378b45..8845e51c3def4a86d10fe93f931098ec762944e5 100644 --- a/ext/tinyopt/include/tinyopt/tinyopt.h +++ b/ext/tinyopt/include/tinyopt/tinyopt.h @@ -1,6 +1,8 @@ #pragma once +#include <cstddef> #include <cstring> +#include <functional> #include <iostream> #include <iterator> #include <sstream> @@ -10,68 +12,1143 @@ #include <type_traits> #include <vector> -#include <tinyopt/common.h> -#include <tinyopt/maybe.h> +#define TINYOPT_VERSION "1.0" +#define TINYOPT_VERSION_MAJOR 1 +#define TINYOPT_VERSION_MINOR 0 +#define TINYOPT_VERSION_PATCH 0 +#define TINYOPT_VERSION_PRERELEASE "" namespace to { -template <typename V = std::string, typename P = default_parser<V>, typename = std::enable_if_t<!std::is_same<V, void>::value>> -maybe<V> parse(char**& argp, char shortopt, const char* longopt = nullptr, const P& parser = P{}) { - const char* arg = argp[0]; +// maybe<T> represents an optional value of type T, +// with an interface similar to C++17 std::optional. +// +// Other than C++14 compatibility, the main deviations/extensions +// from std::optional are: +// +// 1. maybe<void> represents an optional value of an 'empty' type. +// This is used primarily for consistency in generic uses of +// maybe in contexts where functions may not return or consume +// a value. +// +// 2. Monadic-like overloads of operator<< that lift a function +// or a functional object on the left hand side when the +// right hand side is maybe<T> (see below). With an lvalue +// on the left hand side, operator<< acts as a conditional +// assignment. +// +// nothing is a special value that converts to an empty maybe<T> for any T. + +constexpr struct nothing_t {} nothing; + +template <typename T> +struct maybe { + maybe() noexcept: ok(false) {} + maybe(nothing_t) noexcept: ok(false) {} + maybe(const T& v): ok(true) { construct(v); } + maybe(T&& v): ok(true) { construct(std::move(v)); } + maybe(const maybe& m): ok(m.ok) { if (ok) construct(*m); } + maybe(maybe&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } + + ~maybe() { destroy(); } + + template <typename U> + maybe(const maybe<U>& m): ok(m.ok) { if (ok) construct(*m); } + + template <typename U> + maybe(maybe<U>&& m): ok(m.ok) { if (ok) construct(std::move(*m)); } + + maybe& operator=(nothing_t) { return destroy(), *this; } + maybe& operator=(const T& v) { return assign(v), *this; } + maybe& operator=(T&& v) { return assign(std::move(v)), *this; } + maybe& operator=(const maybe& m) { return m.ok? assign(*m): destroy(), *this; } + maybe& operator=(maybe&& m) { return m.ok? assign(std::move(*m)): destroy(), *this; } + + const T& value() const & { return assert_ok(), *vptr(); } + T&& value() && { return assert_ok(), std::move(*vptr()); } + + const T& operator*() const & noexcept { return *vptr(); } + const T* operator->() const & noexcept { return vptr(); } + T&& operator*() && { return std::move(*vptr()); } + + bool has_value() const noexcept { return ok; } + explicit operator bool() const noexcept { return ok; } + + template <typename> friend struct maybe; + +private: + bool ok = false; + alignas(T) char data[sizeof(T)]; + + T* vptr() noexcept { return reinterpret_cast<T*>(data); } + const T* vptr() const noexcept { return reinterpret_cast<const T*>(data); } + + void construct(const T& v) { new (data) T(v); ok = true; } + void construct(T&& v) { new (data) T(std::move(v)); ok = true; } + + void assign(const T& v) { if (ok) *vptr()=v; else construct(v); } + void assign(T&& v) { if (ok) *vptr()=std::move(v); else construct(std::move(v)); } + + void destroy() { if (ok) (**this).~T(); ok = false; } + void assert_ok() const { if (!ok) throw std::invalid_argument("is nothing"); } +}; + +namespace impl { + template <typename T> + struct is_maybe_: std::false_type {}; + + template <typename T> + struct is_maybe_<maybe<T>>: std::true_type {}; +} + +template <typename T> +using is_maybe = impl::is_maybe_<std::remove_cv_t<std::remove_reference_t<T>>>; + +// maybe<void> acts as a maybe<T> with an empty or inaccessible wrapped value. + +template <> +struct maybe<void> { + bool ok = false; + + constexpr maybe() noexcept: ok(false) {} + constexpr maybe(nothing_t&) noexcept: ok(false) {} + constexpr maybe(const nothing_t&) noexcept: ok(false) {} + constexpr maybe(nothing_t&&) noexcept: ok(false) {} + constexpr maybe(const maybe<void>& m) noexcept: ok(m.ok) {} + + template <typename X, typename = std::enable_if_t<!is_maybe<X>::value>> + constexpr maybe(X&&) noexcept: ok(true) {} + + template <typename U> + constexpr maybe(const maybe<U>& m) noexcept: ok(m.ok) {} + + maybe& operator=(nothing_t) noexcept { return ok = false, *this; } + maybe& operator=(const maybe& m) noexcept { return ok = m.ok, *this; } + template <typename U> + maybe& operator=(U&&) noexcept { return ok = true, *this; } + + bool has_value() const noexcept { return ok; } + constexpr explicit operator bool() const noexcept { return ok; } +}; + +// something is a non-empty maybe<void> value. + +constexpr maybe<void> something(true); + +// just<X> converts a value of type X to a maybe<X> containing the value. + +template <typename X> +auto just(X&& x) { return maybe<std::decay_t<X>>(std::forward<X>(x)); } + +// operator<< offers monadic-style chaining of maybe<X> values: +// (f << m) evaluates to an empty maybe if m is empty, or else to +// a maybe<V> value wrapping the result of applying f to the value +// in m. + +template < + typename F, + typename T, + typename R = std::decay_t<decltype(std::declval<F>()(std::declval<const T&>()))>, + typename = std::enable_if_t<std::is_same<R, void>::value> +> +maybe<void> operator<<(F&& f, const maybe<T>& m) { + if (m) return f(*m), something; else return nothing; +} + +template < + typename F, + typename T, + typename R = std::decay_t<decltype(std::declval<F>()(std::declval<const T&>()))>, + typename = std::enable_if_t<!std::is_same<R, void>::value> +> +maybe<R> operator<<(F&& f, const maybe<T>& m) { + if (m) return f(*m); else return nothing; +} + +template < + typename F, + typename R = std::decay_t<decltype(std::declval<F>()())>, + typename = std::enable_if_t<std::is_same<R, void>::value> +> +maybe<void> operator<<(F&& f, const maybe<void>& m) { + return m? (f(), something): nothing; +} + +template < + typename F, + typename R = std::decay_t<decltype(std::declval<F>()())>, + typename = std::enable_if_t<!std::is_same<R, void>::value> +> +maybe<R> operator<<(F&& f, const maybe<void>& m) { + return m? just(f()): nothing; +} + +// If the lhs is not functional, return a maybe value with the result +// of assigning the value in the rhs, or nothing if the rhs is nothing. + +template <typename T, typename U> +auto operator<<(T& x, const maybe<U>& m) -> maybe<std::decay_t<decltype(x=*m)>> { + if (m) return x=*m; else return nothing; +} + +template <typename T> +auto operator<<(T& x, const maybe<void>& m) -> maybe<std::decay_t<decltype(x=true)>> { + if (m) return x=true; else return nothing; +} + +// Tinyopt exceptions, usage, error reporting functions: + +// `option_error` is the base class for exceptions thrown +// by the option handling functions. + +struct option_error: public std::runtime_error { + option_error(const std::string& message): std::runtime_error(message) {} + option_error(const std::string& message, std::string arg): + std::runtime_error(message+": "+arg), arg(std::move(arg)) {} + + std::string arg; +}; + +struct option_parse_error: option_error { + option_parse_error(const std::string &arg): + option_error("option parse error", arg) {} +}; + +struct missing_mandatory_option: option_error { + missing_mandatory_option(const std::string &arg): + option_error("missing mandatory option", arg) {} +}; + +struct missing_argument: option_error { + missing_argument(const std::string &arg): + option_error("option misssing argument", arg) {} +}; + +struct user_option_error: option_error { + user_option_error(const std::string &arg): + option_error(arg) {} +}; + +// `usage` prints usage information to stdout (no error message) +// or to stderr (with error message). It extracts the program basename +// from the provided argv[0] string. + +inline void usage(const char* argv0, const std::string& usage_str, const std::string& prefix = "Usage: ") { + const char* basename = std::strrchr(argv0, '/'); + basename = basename? basename+1: argv0; + + std::cout << prefix << basename << " " << usage_str << "\n"; +} + +inline void usage_error(const char* argv0, const std::string& usage_str, const std::string& parse_err, const std::string& prefix = "Usage: ") { + const char* basename = std::strrchr(argv0, '/'); + basename = basename? basename+1: argv0; + + std::cerr << basename << ": " << parse_err << "\n"; + std::cerr << prefix << basename << " " << usage_str << "\n"; +} + +// Parser objects act as functionals, taking +// a const char* argument and returning maybe<T> +// for some T. + +template <typename V> +struct default_parser { + maybe<V> operator()(const char* text) const { + if (!text) return nothing; + V v; + std::istringstream stream(text); + if (!(stream >> v)) return nothing; + if (!stream.eof()) stream >> std::ws; + return stream.eof()? maybe<V>(v): nothing; + } +}; + +template <> +struct default_parser<const char*> { + maybe<const char*> operator()(const char* text) const { + return just(text); + } +}; + +template <> +struct default_parser<std::string> { + maybe<std::string> operator()(const char* text) const { + return just(std::string(text)); + } +}; + +template <> +struct default_parser<void> { + maybe<void> operator()(const char*) const { + return something; + } +}; - if (!arg || arg[0]!='-') { +template <typename V> +class keyword_parser { + std::vector<std::pair<std::string, V>> map_; + +public: + template <typename KeywordPairs> + keyword_parser(const KeywordPairs& pairs) { + using std::begin; + using std::end; + map_.assign(begin(pairs), end(pairs)); + } + + maybe<V> operator()(const char* text) const { + if (!text) return nothing; + for (const auto& p: map_) { + if (text==p.first) return p.second; + } return nothing; } +}; - const char* text; +// Returns a parser that matches a set of keywords, +// returning the corresponding values in the supplied +// pairs. - if (arg[1]=='-' && longopt) { - const char* rest = arg+2; - const char* eq = std::strchr(rest, '='); +template <typename KeywordPairs> +auto keywords(const KeywordPairs& pairs) { + using std::begin; + using value_type = std::decay_t<decltype(std::get<1>(*begin(pairs)))>; + return keyword_parser<value_type>(pairs); +} - if (!std::strcmp(rest, longopt)) { - if (!argp[1]) throw missing_argument(arg); - text = argp[1]; - argp += 2; + +// A parser for delimited sequences of values; returns +// a vector of the values obtained from the supplied +// per-item parser. + +template <typename P> +class delimited_parser { + char delim_; + P parse_; + using inner_value_type = std::decay_t<decltype(*std::declval<P>()(""))>; + +public: + template <typename Q> + delimited_parser(char delim, Q&& parse): delim_(delim), parse_(std::forward<Q>(parse)) {} + + maybe<std::vector<inner_value_type>> operator()(const char* text) const { + if (!text) return nothing; + + std::vector<inner_value_type> values; + if (!*text) return values; + + std::size_t n = std::strlen(text); + std::vector<char> input(1+n); + std::copy(text, text+n, input.data()); + + char* p = input.data(); + char* end = input.data()+1+n; + do { + char* q = p; + while (*q && *q!=delim_) ++q; + *q++ = 0; + + if (auto mv = parse_(p)) values.push_back(*mv); + else return nothing; + + p = q; + } while (p<end); + + return values; + } +}; + +// Convenience constructors for delimited parser. + +template <typename Q> +auto delimited(char delim, Q&& parse) { + using P = std::decay_t<Q>; + return delimited_parser<P>(delim, std::forward<Q>(parse)); +} + +template <typename V> +auto delimited(char delim = ',') { + return delimited(delim, default_parser<V>{}); +} + +// Option keys +// ----------- +// +// A key is how the option is specified in an argument list, and is typically +// represented as a 'short' (e.g. '-a') option or a 'long' option (e.g. +// '--apple'). +// +// The value for an option can always be taken from the next argument in the +// list, but in addition can be specified together with the key itself, +// depending on the properties of the option key: +// +// --key=value 'Long' style argument for key "--key" +// -kvalue 'Compact' style argument for key "-k" +// +// Compact option keys can be combined in the one item in the argument list, if +// the options do not take any values (that is, they are flags). For example, +// if -a, -b are flags and -c takes an integer argument, with all three keys +// marked as compact, then an item '-abc3' in the argument list will be parsed +// in the same way as the sequence of items '-a', '-b', '-c', '3'. +// +// An option without a key will match any item in the argument list; options +// with keys are always checked first. +// +// Only short and long kets can be used with to::parse. + +struct key { + std::string label; + enum style { shortfmt, longfmt, compact } style = shortfmt; + + key(std::string l): label(std::move(l)) { + if (label[0]=='-' && label[1]=='-') style = longfmt; + } + + key(const char* label): key(std::string(label)) {} + + key(std::string label, enum style style): + label(std::move(label)), style(style) {} +}; + +inline namespace literals { + +inline key operator""_short(const char* label, std::size_t) { + return key(label, key::shortfmt); +} + +inline key operator""_long(const char* label, std::size_t) { + return key(label, key::longfmt); +} + +inline key operator""_compact(const char* label, std::size_t) { + return key(label, key::compact); +} + +} // namespace literals + +// Argument state +// -------------- +// +// to::state represents the collection of command line arguments. Mutating +// operations (shift(), successful option matching, etc.) will modify the +// underlying set of arguments used to construct the state object. +// +// This is used only internally — it is not part of the public API. +// Members are left public for the purpose of unit testing. + +struct state { + int& argc; + char** argv; + unsigned optoff = 0; + + state(int& argc, char** argv): argc(argc), argv(argv) {} + + // False => no more arguments. + explicit operator bool() const { return *argv; } + + // Shift arguments left in-place. + void shift(unsigned n = 1) { + char** skip = argv; + while (*skip && n) ++skip, --n; + + argc -= (skip-argv); + auto p = argv; + do { *p++ = *skip; } while (*skip++); + + optoff = 0; + } + + // Skip current argument without modifying list. + void skip() { + if (*argv) ++argv; + } + + // Match an option given by the key which takes an argument. + // If successful, consume option and argument and return pointer to + // argument string, else return nothing. + maybe<const char*> match_option(const key& k) { + const char* p = nullptr; + + if (k.style==key::compact) { + if ((p = match_compact_key(k.label.c_str()))) { + if (!*p) { + p = argv[1]; + shift(2); + } + else shift(); + return p; + } } - else if (eq && !std::strncmp(rest, longopt, eq-rest)) { - text = eq+1; - argp += 1; + else if (!optoff && k.label==*argv) { + p = argv[1]; + shift(2); + return p; } - else { - return nothing; + else if (!optoff && k.style==key::longfmt) { + auto keylen = k.label.length(); + if (!std::strncmp(*argv, k.label.c_str(), keylen) && (*argv)[keylen]=='=') { + p = &(*argv)[keylen+1]; + shift(); + return p; + } } + + return nothing; } - else if (shortopt && arg[1]==shortopt && arg[2]==0) { - if (!argp[1]) throw missing_argument(arg); - text = argp[1]; - argp += 2; + + // Match a flag given by the key. + // If successful, consume flag and return true, else return false. + bool match_flag(const key& k) { + if (k.style==key::compact) { + if (auto p = match_compact_key(k.label.c_str())) { + if (!*p) shift(); + return true; + } + } + else if (!optoff && k.label==*argv) { + shift(); + return true; + } + + return false; } - else { - return nothing; + + // Compact-style keys can be combined in one argument; combined keys + // with a common prefix only need to supply the prefix once at the + // beginning of the argument. + const char* match_compact_key(const char* k) { + unsigned keylen = std::strlen(k); + + unsigned prefix_max = std::min(keylen-1, optoff); + for (std::size_t l = 0; l<=prefix_max; ++l) { + if (l && strncmp(*argv, k, l)) break; + if (strncmp(*argv+optoff, k+l, keylen-l)) continue; + optoff += keylen-l; + return *argv+optoff; + } + + return nullptr; } +}; + +// Sinks and actions +// ----------------- +// +// Sinks wrap a function that takes a pointer to an option parameter and stores +// or acts upon the parsed result. +// +// They can be constructed from an lvalue reference or a functional object (via +// the `action` function) with or without an explicit parser function. If no +// parser is given, a default one is used if the correct value type can be +// determined. - auto v = parser(text); +namespace impl { + template <typename T> struct fn_arg_type { using type = void; }; + template <typename R, typename X> struct fn_arg_type<R (X)> { using type = X; }; + template <typename R, typename X> struct fn_arg_type<R (*)(X)> { using type = X; }; + template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X)> { using type = X; }; + template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) const> { using type = X; }; + template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) volatile> { using type = X; }; + template <typename R, typename C, typename X> struct fn_arg_type<R (C::*)(X) const volatile> { using type = X; }; - if (!v) throw option_parse_error(arg); - return v; + template <typename...> struct void_type { using type = void; }; } -inline maybe<void> parse(char**& argp, char shortopt, const char* longopt = nullptr) { - if (!*argp || *argp[0]!='-') { - return nothing; +template <typename T, typename = void> +struct unary_argument_type { using type = typename impl::fn_arg_type<T>::type; }; + +template <typename T> +struct unary_argument_type<T, typename impl::void_type<decltype(&T::operator())>::type> { + using type = typename impl::fn_arg_type<decltype(&T::operator())>::type; +}; + +template <typename T> +using unary_argument_type_t = typename unary_argument_type<T>::type; + +struct sink { + // Tag class for constructor. + static struct action_t {} action; + + sink(): + sink(action, [](const char*) { return true; }) + {} + + template <typename V> + sink(V& var): sink(var, default_parser<V>{}) {} + + template <typename V, typename P> + sink(V& var, P parser): + sink(action, [ref=std::ref(var), parser](const char* param) { + if (auto p = parser(param)) return ref.get() = std::move(*p), true; + else return false; + }) + {} + + template <typename Action> + sink(action_t, Action a): op(std::move(a)) {} + + bool operator()(const char* param) const { return op(param); } + std::function<bool (const char*)> op; + +}; + +// Convenience functions for construction of sink actions +// with explicit or implicit parser. + +template <typename F, typename A = std::decay_t<unary_argument_type_t<F>>> +sink action(F f) { + return sink(sink::action, + [f = std::move(f)](const char* arg) -> bool { + return static_cast<bool>(f << default_parser<A>{}(arg)); + }); +} + +template <typename F, typename P> +sink action(F f, P parser) { + return sink(sink::action, + [f = std::move(f), parser = std::move(parser)](const char* arg) -> bool { + return static_cast<bool>(f << parser(arg)); + }); +} + +// Special actions: +// +// error(message) Throw a user_option_error with the supplied message. + +inline sink error(std::string message) { + return sink(sink::action, + [m = std::move(message)](const char*) -> bool { + throw user_option_error(m); + }); +} + +// Sink adaptors: +// +// These adaptors constitute short cuts for making actions that count the +// occurance of a flag, set a fixed value when a flag is provided, or for +// appending an option parameter onto a vector of values. + +// Push parsed option parameter on to container. +template <typename Container, typename P = default_parser<typename Container::value_type>> +sink push_back(Container& c, P parser = P{}) { + return action( + [ref = std::ref(c)](typename Container::value_type v) { ref.get().push_back(std::move(v)); }, + std::move(parser)); +} + +// Set v to value when option parsed; ignore any option parameter. +template <typename V, typename X> +sink set(V& v, X value) { + return action([ref = std::ref(v), value = std::move(value)] { ref.get() = value; }); +} + +// Set v to true when option parsed; ignore any option parameter. +template <typename V> +sink set(V& v) { + return set(v, true); +} + +// Incrememnt v when option parsed; ignore any option parameter. +template <typename V> +sink increment(V& v) { + return action([ref = std::ref(v)] { ++ref.get(); }); +} + +template <typename V, typename X> +sink increment(V& v, X delta) { + return action([ref = std::ref(v), delta = std::move(delta)] { ref.get() += delta; }); +} + +// Modal configuration +// ------------------- +// +// Options can be filtered by some predicate, and can trigger a state +// change when successfully processed. +// +// Filter construction: +// to::when(Fn f) f is a functional with signature bool (int) +// to::when(int s) equivalent to to::when([](int k) { return k==s; }) +// to::when(a, b, ...) filter than is satisfied by to::when(a) or +// to::when(b) or ... +// +// The argument to the filter predicate is the 'mode', a mutable state maintained +// during a single run of to::run(). +// +// Mode changes: +// to::then(Fn f) f is a functional with signature int (int) +// to::then(int s) equivalent to to::then([](int) { return s; }) +// +// The argument to the functional is the current mode; the return value +// sets the new value of mode. +// +// Filters are called before keys are matched; modal changes are called +// after an option is processed. All + +using filter = std::function<bool (int)>; +using modal = std::function<int (int)>; + +template <typename F, typename = decltype(std::declval<F>()(0))> +filter when(F f) { + return [f = std::move(f)](int mode) { return static_cast<bool>(f(mode)); }; +} + +inline filter when(int m) { + return [m](int mode) { return m==mode; }; +} + +template <typename A, typename B, typename... Rest> +filter when(A a, B&& b, Rest&&... rest) { + return + [fhead = when(std::forward<A>(a)), + ftail = when(std::forward<B>(b), std::forward<Rest>(rest)...)](int m) { + return fhead(m) || ftail(m); + }; +} + +template <typename F, typename = decltype(std::declval<F>()(0))> +modal then(F f) { + return [f = std::move(f)](int mode) { return static_cast<int>(f(mode)); }; +} + +inline modal then(int m) { + return [m](int) { return m; }; +} + + +// Option specification +// -------------------- +// +// An option specification comprises zero or more keys (e.g. "-a", "--foo"), +// a sink (where the parsed argument will be sent), a parser - which may be +// the default parser - and zero or more flags that modify its behaviour. +// +// Option flags: + +enum option_flag { + flag = 1, // Option takes no parameter. + ephemeral = 2, // Option is not saved in returned results. + single = 4, // Option is parsed at most once. + mandatory = 8, // Option must be present in argument list. + exit = 16, // Option stops further argument processing, return `nothing` from run(). + stop = 32, // Option stops further argument processing, return saved options. +}; + +struct option { + sink s; + std::vector<key> keys; + std::vector<filter> filters; + std::vector<modal> modals; + + bool is_flag = false; + bool is_ephemeral = false; + bool is_single = false; + bool is_mandatory = false; + bool is_exit = false; + bool is_stop = false; + + template <typename... Rest> + option(sink s, Rest&&... rest): s(std::move(s)) { + init_(std::forward<Rest>(rest)...); } - else if (argp[0][1]=='-' && longopt && !std::strcmp(argp[0]+2, longopt)) { - ++argp; - return true; + + bool has_key(const std::string& arg) const { + if (keys.empty() && arg.empty()) return true; + for (const auto& k: keys) { + if (arg==k.label) return true; + } + return false; } - else if (shortopt && argp[0][1]==shortopt && argp[0][2]==0) { - ++argp; + + bool check_mode(int mode) const { + for (auto& f: filters) { + if (!f(mode)) return false; + } return true; } - else { - return nothing; + + void set_mode(int& mode) const { + for (auto& f: modals) mode = f(mode); + } + + void run(const std::string& label, const char* arg) const { + if (!is_flag && !arg) throw missing_argument(label); + if (!s(arg)) throw option_parse_error(label); + } + + std::string longest_label() const { + const std::string* p = 0; + for (auto& k: keys) { + if (!p || k.label.size()>p->size()) p = &k.label; + } + return p? *p: std::string{}; + } + +private: + void init_() {} + + template <typename... Rest> + void init_(enum option_flag f, Rest&&... rest) { + is_flag |= f & flag; + is_ephemeral |= f & ephemeral; + is_single |= f & single; + is_mandatory |= f & mandatory; + is_exit |= f & exit; + is_stop |= f & stop; + init_(std::forward<Rest>(rest)...); + } + + template <typename... Rest> + void init_(filter f, Rest&&... rest) { + filters.push_back(std::move(f)); + init_(std::forward<Rest>(rest)...); + } + + template <typename... Rest> + void init_(modal f, Rest&&... rest) { + modals.push_back(std::move(f)); + init_(std::forward<Rest>(rest)...); + } + + template <typename... Rest> + void init_(key k, Rest&&... rest) { + keys.push_back(std::move(k)); + init_(std::forward<Rest>(rest)...); + } + +}; + +// Saved options +// ------------- +// +// Successfully matched options, excluding those with the to::ephemeral flag +// set, are collated in a saved_options structure for potential documentation +// or replay. + +struct saved_options: private std::vector<std::string> { + using std::vector<std::string>::begin; + using std::vector<std::string>::end; + using std::vector<std::string>::size; + using std::vector<std::string>::empty; + + void add(std::string s) { push_back(std::move(s)); } + + saved_options& operator+=(const saved_options& so) { + insert(end(), so.begin(), so.end()); + return *this; + } + + struct arglist { + int argc; + char** argv; + std::vector<char*> arg_data; + }; + + // Construct argv representing argument list. + arglist as_arglist() const { + arglist A; + + for (auto& a: *this) A.arg_data.push_back(const_cast<char*>(a.c_str())); + A.arg_data.push_back(nullptr); + A.argv = A.arg_data.data(); + A.argc = A.arg_data.size()-1; + return A; + } + + // Serialized representation: + // + // Saved arguments are separated by white space. If an argument + // contains whitespace or a special character, it is escaped with + // single quotes in a POSIX shell compatible fashion, so that + // the representation can be used directly on a shell command line. + + friend std::ostream& operator<<(std::ostream& out, const saved_options& s) { + auto escape = [](const std::string& v) { + if (!v.empty() && v.find_first_of("\\*?[#~=%|^;<>()$'`\" \t\n")==std::string::npos) return v; + + // Wrap string in single quotes, replacing any internal single quote + // character with: '\'' + + std::string q ="'"; + for (auto c: v) { + c=='\''? q += "'\\''": q += c; + } + return q += '\''; + }; + + bool first = true; + for (auto& p: s) { + if (first) first = false; else out << ' '; + out << escape(p); + } + return out; + } + + friend std::istream& operator>>(std::istream& in, saved_options& s) { + std::string w; + bool have_word = false; + bool quote = false; // true => within single quotes. + bool escape = false; // true => previous character was backslash. + while (in) { + char c = in.get(); + if (c==EOF) break; + + if (quote) { + if (c!='\'') w += c; + else quote = false; + } + else { + if (escape) { + w += c; + escape = false; + } + else if (c=='\\') { + escape = true; + have_word = true; + } + else if (c=='\'') { + quote = true; + have_word = true; + } + else if (c!=' ' && c!='\t' && c!='\n') { + w += c; + have_word = true; + } + else { + if (have_word) s.add(w); + have_word = false; + w = ""; + } + } + } + + if (have_word) s.add(w); + return in; + } +}; + +// Option with mutable state (for checking single and mandatory flags), +// used by to::run(). + +namespace impl { + struct counted_option: option { + int count = 0; + + counted_option(const option& o): option(o) {} + + // On successful match, return pointers to matched key and value. + // For flags, use nullptr for value; for empty key sets, use + // nullptr for key. + maybe<std::pair<const char*, const char*>> match(state& st) { + if (is_flag) { + for (auto& k: keys) { + if (st.match_flag(k)) return set(k.label, nullptr); + } + return nothing; + } + else if (!keys.empty()) { + for (auto& k: keys) { + if (auto param = st.match_option(k)) return set(k.label, *param); + } + return nothing; + } + else { + const char* param = *st.argv; + st.shift(); + return set("", param); + } + } + + std::pair<const char*, const char*> set(const char* arg) { + run("", arg); + ++count; + return {nullptr, arg}; + } + + std::pair<const char*, const char*> set(const std::string& label, const char* arg) { + run(label, arg); + ++count; + return {label.c_str(), arg}; + } + }; +} // namespace impl + +// Running a set of options +// ------------------------ +// +// to::run() can be used to parse options from the command-line and/or from +// saved_options data. +// +// The first argument is a collection or sequence of option specifications, +// followed optionally by command line argc and argv or just argv. A +// saved_options object can be optionally passed as the last parameter. +// +// If an option with the to::exit flag is matched, option parsing will +// immediately stop and an empty value will be returned. Otherwise to::run() +// will return a saved_options structure recording the successfully parsed +// options. + +namespace impl { + inline maybe<saved_options> run(std::vector<impl::counted_option>& opts, int& argc, char** argv) { + saved_options collate; + bool exit = false; + bool stop = false; + state st{argc, argv}; + int mode = 0; + while (st && !exit && !stop) { + // Try options with a key first. + for (auto& o: opts) { + if (o.keys.empty()) continue; + if (o.is_single && o.count) continue; + if (!o.check_mode(mode)) continue; + + if (auto ma = o.match(st)) { + if (!o.is_ephemeral) { + if (ma->first) collate.add(ma->first); + if (ma->second) collate.add(ma->second); + } + o.set_mode(mode); + exit = o.is_exit; + stop = o.is_stop; + goto next; + } + } + + // Literal "--" terminates option parsing. + if (!std::strcmp(*argv, "--")) { + st.shift(); + return collate; + } + + // Try free options. + for (auto& o: opts) { + if (!o.keys.empty()) continue; + if (o.is_single && o.count) continue; + if (!o.check_mode(mode)) continue; + + if (auto ma = o.match(st)) { + if (!o.is_ephemeral) collate.add(ma->second); + o.set_mode(mode); + exit = o.is_exit; + goto next; + } + } + + // Nothing matched, so increment argv. + st.skip(); + next: ; + } + + return exit? nothing: just(collate); + } +} // namespace impl + + +template <typename Options> +maybe<saved_options> run(const Options& options, int& argc, char** argv, const saved_options& restore = saved_options{}) { + using std::begin; + using std::end; + std::vector<impl::counted_option> opts(begin(options), end(options)); + auto r_args = restore.as_arglist(); + + saved_options coll1, coll2; + if (coll1 << impl::run(opts, r_args.argc, r_args.argv) && coll2 << impl::run(opts, argc, argv)) { + for (auto& o: opts) { + if (o.is_mandatory && !o.count) throw missing_mandatory_option(o.longest_label()); + } + return coll1 += coll2; + } + + return nothing; +} + +template <typename Options> +maybe<saved_options> run(const Options& options, const saved_options& restore) { + int ignore_argc = 0; + char* end_of_args = nullptr; + return run(options, ignore_argc, &end_of_args, restore); +} + +template <typename Options> +maybe<saved_options> run(const Options& options, char** argv) { + int ignore_argc = 0; + return run(options, ignore_argc, argv); +} + +template <typename Options> +maybe<saved_options> run(const Options& options, char** argv, const saved_options& restore) { + int ignore_argc = 0; + return run(options, ignore_argc, argv, restore); +} + +// Running through command line arguments explicitly. +// -------------------------------------------------- +// +// `to::parse<V>` checks the given argument against the provided keys, and on a match will try to +// parse and consume an argument of type V. A custom parser can be supplied. +// +// `to::parse<void>` will do the same, for flag options that take no argument. + +template < + typename V, + typename P, + typename = std::enable_if_t<!std::is_same<V, void>::value>, + typename = std::enable_if_t<!std::is_convertible<const P&, key>::value>, + typename... Tail +> +maybe<V> parse(char**& argp, const P& parser, key k0, Tail... krest) { + key keys[] = { std::move(k0), std::move(krest)... }; + + const char* arg = argp[0]; + if (!arg) return nothing; + + const char* text = nullptr; + for (const key& k: keys) { + if (k.label==arg) { + if (!argp[1]) throw missing_argument(arg); + text = argp[1]; + argp += 2; + goto match; + } + else if (k.style==key::longfmt) { + const char* eq = std::strrchr(arg, '='); + if (eq && !std::strncmp(arg, k.label.c_str(), eq-arg)) { + text = eq+1; + argp += 1; + goto match; + } + } + } + return nothing; + +match: + if (auto v = parser(text)) return v; + else throw option_parse_error(arg); +} + +template < + typename V, + typename = std::enable_if_t<!std::is_same<V, void>::value>, + typename... Tail +> +maybe<V> parse(char**& argp, key k0, Tail... krest) { + return parse<V>(argp, default_parser<V>{}, std::move(k0), std::move(krest)...); +} + +template <typename... Tail> +maybe<void> parse(char**& argp, key k0, Tail... krest) { + key keys[] = { std::move(k0), std::move(krest)... }; + + const char* arg = argp[0]; + if (!arg) return nothing; + + for (const key& k: keys) { + if (k.label==arg) { + ++argp; + return true; + } } + return nothing; } } // namespace to diff --git a/modcc/modcc.cpp b/modcc/modcc.cpp index 40c522a73279e7b1c3ff6a8ca8834d6597289330..a9bceb1c95fd2158c312baa2ca8d436ce54cb8bf 100644 --- a/modcc/modcc.cpp +++ b/modcc/modcc.cpp @@ -4,7 +4,7 @@ #include <unordered_map> #include <unordered_set> -#include <tinyopt/smolopt.h> +#include <tinyopt/tinyopt.h> #include "printer/cprinter.hpp" #include "printer/gpuprinter.hpp" @@ -184,7 +184,7 @@ int main(int argc, char **argv) { if (!to::run(options, argc, argv+1)) return 0; } catch (to::option_error& e) { - to::usage(argv[0], usage_str, e.what()); + to::usage_error(argv[0], usage_str, e.what()); return 1; } diff --git a/test/unit-distributed/test.cpp b/test/unit-distributed/test.cpp index cf2abddd76b56f95ef40ef7b5d634c2c9a5537c6..3f7cccbd8f1cc6b6f2f82794b7178ac55fe69197 100644 --- a/test/unit-distributed/test.cpp +++ b/test/unit-distributed/test.cpp @@ -57,9 +57,9 @@ int main(int argc, char **argv) { try { auto arg = argv+1; while (*arg) { - if (auto comm_size = to::parse<unsigned>(arg, 'd', "dryrun")) { + if (auto comm_size = to::parse<unsigned>(arg, "-d", "--dryrun")) { if (*comm_size==0) { - throw to::option_error("must be positive integer", *arg); + throw to::user_option_error("number of dry run ranks must be positive"); } // Note that this must be set again for each test that uses a different // number of cells per domain, e.g. @@ -67,7 +67,7 @@ int main(int argc, char **argv) { // TODO: fix when dry run mode reimplemented //policy::set_sizes(*comm_size, 0); } - else if (auto o = to::parse(arg, 'h', "help")) { + else if (to::parse(arg, "-h", "--help")) { to::usage(argv[0], usage_str); return 0; } @@ -82,11 +82,10 @@ int main(int argc, char **argv) { return_value = RUN_ALL_TESTS(); } catch (to::option_error& e) { - to::usage(argv[0], usage_str, e.what()); + to::usage_error(argv[0], usage_str, e.what()); return_value = 1; } catch (std::exception& e) { - //std::cerr << "caught exception: " << e.what() << "\n"; std::cout << "caught exception: " << e.what() << std::endl; return_value = 1; }