August 05, 2014

ae.utils.funopt

ae.utils.funopt is a std.getopt enhancer:

import std.stdio, ae.utils.funopt;

int run(bool verbose, string input, string output)
{
	// ...
	return 0;
}

int main(string[] args)
{
	try
		return funopt!run(args);
	catch (Exception e)
	{
		stderr.writeln("Error: ", e.msg);
		return 1;
	}
}

Running program --verbose input.txt output.txt will cause run to be called with the appropriate arguments.

Additionally, funopt will generate and print a usage text if a mandatory parameter was not specified, or when --help is present:

$ program
Usage: program [--verbose] INPUT OUTPUT

Error: No input specified.

The usage text is generated from the function signature as a string constant during compilation. Optional arguments and arrays are handled appropriately.

If naked types are not sufficiently expressive, ae.utils.funopt provides some wrapper templates which allow specifying additional properties, such as a single-letter shorthand variant, --help descriptions, or non-boolean switches:

int run(
	Switch!("Enable verbose logging", 'v') verbose,
	Option!(int, "Number of tries") tries,
	Option!(int, "Seconds to wait each try", "SECS") timeout,
	string filename,
	string output = "default",
	string[] extraFiles = null,
)

The automatically-generated usage for the above function signature will be:

Usage: program [OPTION]... FILENAME [OUTPUT] [EXTRA-FILES]...

Options:
  -v, --verbose       Enable verbose logging
      --tries=N       Number of tries
      --timeout=SECS  Seconds to wait each try

If your program has multiple sub-commands (as do e.g. git/hg/svn), ae.utils.funopt has a funoptDispatch function which can call an appropriate static method, as well as generate a list of actions for --help text:

import ae.utils.funopt;

struct Actions
{
static:
	@(`Creates a new repository at this location.`)
	void init()	{ /* ... */ }

	@(`Pulls in changes from an existing repository at the given URL.`)
	void pull(bool shallow, bool recursive, string url) { /* ... */ }

	@(`Pushes changes to the repository at specified URL.`)
	void push(bool force, string url) { /* ... */ }
}

void main(string[] args)
{
	return funoptDispatch!Actions(args);
}

Method descriptions are currently specified using a string UDA, however it should be possible to move to documentation comments if/when DMD pull request #3531 is merged.

Example output:

$ scm --help
Usage: scm ACTION [ACTION-ARGUMENTS]...

Actions:
  init  Creates a new repository at this location.
  pull  Pulls in changes from an existing repository at the given URL.
  push  Pushes changes to the repository at specified URL.

$ scm pull --help
Usage: scm pull [--shallow] [--recursive] URL

Code: ae.utils.funopt