It's funny, I was doing this right now. I'll only describe the general idea here. The full code is more than 500 lines currently, and using more utilities for formatting etc.
As an example, I'm making program help
(which is quite dummy). A plain struct contains all options the program needs, along with default values:
struct help_opt
{
string owner;
string path;
bool rec;
int depth;
help_opt() : owner(""), path("/"), rec(false), depth(0) { }
};
Another struct models the command-line arguments:
struct help_args : public help_opt, public arguments <help_args>
{
template <typename S>
void define(S& s)
{
set(s, owner, "owner", 'w', "force <name> as owner", "name");
set(s, path, "output", 'o', "save all output to <path>", "path");
set(s, rec, "recurse", 'r', "recurse into sub-topics");
set(s, depth, "depth", 'd', "set <number> as maximum depth", "number");
}
string title() const { return "my help"; }
string usage() const { return "help [<options>] [<topic>]"; }
string info() const
{
return
"Display help on <topic>, "
"or list available topics if no <topic> is given.";
}
help_args(int argc, char* argv[]) : arguments(argc, argv) { }
};
where class arguments
is all the infrastructure I need. All arguments along with metadata (name, abbreviation etc.) are defined in define()
, each by a call to method set()
.
Argument s
is used to support multiple operations, e.g. collect user input, build the help text, or display the values given by the user. We then pass on the reference to each member, which may be given an appropriate value according to user input, or left with the default value. Then follows the argument name, abbreviation, full help text, and optionally a parameter name. Boolean arguments are treated separately as flags, with no parameter.
Additional methods title()
, info()
etc. specify information messages that are custom to the program.
Actual processing is done by the constructor of arguments
. This calls define()
in help_args
, which in turn calls set()
in arguments
. For each option, the command line input is scanned and the variables are updated as needed. At the same time, metadata are collected and output is generated automatically for options like --help
, --usage
, --version
as in gnu programs.
The fact that help_args
is passed as a template parameter to arguments
is to allow define()
to be called from the base class, even though it is a template hence not virtual. If a single operation is only needed, then we can switch back to plain virtual methods.
I hope this is quite clean. I know there are many tools and libraries available, but I really prefer it this way. I can share the implementation when ready, it's almost done.