Gottlieb t Freitag

Sargparse

A very easy to use argument parser. Sargparse was manly inspired by the parameter handling that comes with tornado which itself was inspired by Google’s gflags. The idea is that parameters should live where they are needed, i.e., in the same compilation unit. In that regard sargparse puts no burden on already existing concepts of modularity within any project.

Under the hood a sargp::Parameter registers itself and its storage at a command during initialization time. When the main() is executed all this is done already and all parameters can be programmatically queried. That way a help text as well as the groff-text that makes up a manpage entry can be generated automatically. Further bash and zsh completion are very easily realized with sargparse.

Sargparse comes with two major concepts: sargp::Command and sargp::Parameter.

Parameters

Parameters are instances of typed containers that can be accessed from the rest of the program as if they are pointers. The value of a parameter can be set by providing the corresponding command line argument.

Parameters can be grouped in sections to keep the parameter names clean. Sections don’t have any practical use apart from adding prefixes to parameter names.

However, every parameter has a type, a default value, a name and a description. You can further provide callbacks for when the parameter is defined as well as a HintFunction to generate completion suggestions.

// this parameter can be set by providing the argument --paramA=123 or --paramA 123
auto paramA = sargp::Parameter<int>(
    1, // default value
    "paramA", // name
    "description of what paramA does" // description
    );
auto section = sargp::Section("my_section");
// this parameter can be set by providing the argument --my_section.paramB=barfoo
auto paramB = section.Parameter<std::string>("foobar", "paramB", "description of what paramB does");

When a parameter was defined then a cast to bool will return true; otherwise false. This is even true when the default value was specified!

Types of parameters

There are three types of Parameters:

Flags and choices can automatically generate suggestions for bash completions.

auto myFlag = mySection.Flag("flag", "a simple flag");
enum class MyEnumType {Foo, Bar};
auto myChoice = sargparse::Choice<MyEnumType>(
        MyEnumType::Foo, // default type
        "my_enum",       // name
		{ {"Foo", MyEnumType::Foo},\{"Bar", MyEnumType::Bar} }, // possible values and their namings
        "a choice demonstration" // description
    );

Commands

A sargp::Command is something that can be called; therefore you need to provide a C-style callback to a command.

void run_me_callback();
auto my_command = sargp::Command(
        "run_me",  // command name
        "run the command run_me", // description
        run_me_callback // callback
    );
void run_me_callback() {
    // do stuff here
}

When your programm is run and the first parameter is “run_me” the run_me_callback will be called.

Further commands act as registry for parameters, i.e., they know all parameters that are specific to them. Parameters which are defined without a corresponding command are registered at the default_command (the command that is run when no command is specified) and are considered global parameters. Here’s code to demonstrate how command specific parameters work:

void run_me_callback();
auto my_command = sargp::Command(
        "run_me",  // command name
        "run the command run_me", // description
        run_me_callback // callback
    );
auto fancyfy = my_command.Flag("fancyfy", "make things fancy")
void run_me_callback() {
    // do stuff here
    if (*fancyfy) {
        // fancy!
    } else {
        // not fancy
    }
}

When the help text is generated the parameter “fancyfy” will be associated to the “run_me” command. Also suggestions for bash completion will only be generated for the “fancyfy” parameter if the “run_me” command would be active.

Parsing and everything

To use sargparse you need to hand the command line parameters to the parser:

#include <sargparse/ArgumentParsing.h>
#include <sargparse/Parameter.h>
#include <iostream>

namespace {
// the type here is an optional to make it possible to _not_ provide a value to help. e.g., myapplication --help --foobar
auto printHelp = sargparse::Parameter<std::optional<std::string>>{ {}, "help", "print this help add a string which will be used in a grep-like search through the parameters"};
}

int main(int argc, char** argv)
{
	// create you own bash completion with this helper
	if (std::string(argv[argc-1]) == "--bash_completion") {
		auto hints = sargparse::getNextArgHint(argc-2, argv+1);
		for (auto const& hint : hints) { std::cout << hint << " "; }
		return 0;
	}

	// parse the arguments (excluding the application name) and fill all parameters/flags/choices with their respective values
	sargparse::parseArguments(argc-1, argv+1);
	if (printHelp) { // print the help
		std::cout << sargparse::generateHelpString(std::regex{".*" + printHelp.get().value_or("") + ".*"});
		return 0;
	}
    // this invokes the commands (if specified)
	sargparse::callCommands();
	return 0;
}

In most of my software where I utilize sargparse the above code is the only content of my main.cpp.

The neat thing about sargparse is that parameters and commands can live in different parts of the application without the need for shared headers (apart for the headers provided by sargparse of course).