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;
     }