Product, Use Cases

Using Stout to parse command-line options

May 14, 2015

Marco Massenzio

D2iQ

4 min read

 
Almost every program needs to parse some form of command-line argument. Often, this is a pretty large set of possible options, and the management of the various options is usually pretty tedious while adding virtually no value to the program's functionality.
 
Google's gflags are thus a very welcome contribution in that they remove the tediousness. They let program developers give their users a fairly complex set of options to choose from, without wasting time re-inventing the wheel.
 
However, the use of macros and certain other quirks within gflags led the team developing Apache Mesos to create a more object-oriented approach to the same problem. This new approach yields a more familiar pattern to the programmer (and those familiar with Python's 'argparse' library will see several similarities there too).
 
Stout is a header-only library that can be used independently from Mesos. However, the most up-to-date and recent version should be extracted from the Apache Mesos 3rdparty folder.
 
Beyond Flags, Stout offers a wealth of modern abstractions that make coding in C++ a more pleasant experience, especially for folks used to the facilities offered "natively" by other languages such as Scala or Python: 'Try'/'Option' classes (see also below); facilities to deal with Hashmaps; IP/MAC addresses manipulation; the 'Duration' class for time units (which will look very familiar to users of Joda Time) and the nifty 'Stopwatch' utility.
 
In the following, we show how to use Stout to simplify management of command-line argument flags, but we invite you to explore the library and find out how it can make your coding life easier.
 
Use
 
To use Stout_ CLI arguments ("flags") all we have to do is include the header file and derive our custom flags' class from 'FlagsBase.' In 'FlagsXxxx, add the (appropriately typed) fields that will be, at runtime, populated with the correct values from the command line (or the given default values, if any):
 
#include <stout/flags/flags.hpp>
 
using std::string;
 
// Program flags, allows user to run the tests (--test) or the Scheduler// against a Mesos Master at --master IP:PORT; or the Executor, which will// invoke Mongo using the --config FILE configuration file.//// All the flags are optional, but at least ONE (and at most one) MUST be// present.class MongoFlags: public flags::FlagsBase{public:  MongoFlags();
 
  Option<string> master;  Option<string> config;  string role;  bool test;};
 
In the class's constructor, the actual value of the flag (the '--flag') is defined along with a simple help message and, where necessary, a default value:
 
MongoFlags::MongoFlags(){  add(&MongoFlags::master, "master", "The host address of the Mesos Master.");  add(&MongoFlags::config, "config", "The location of the configuration file,"      " on the Worker node (this file MUST exist).");  add(&MongoFlags::role, "role", "The role for the executor", "*");  add(&MongoFlags::test, "test", "Will only run unit tests and exit.", false);}
 
One convenient feature is that flags gives you a 'usage()' method that generates a nicely-formatted string that is suitable to be emitted to the user (either upon request, or if something goes wrong):
 
void printUsage(const string& prog, const MongoFlags& flags){  cout << "Usage: " << os::basename(prog).get() << " [options]\n\n"      "One (and only one) of the following options MUST be present.\n\n"      "Options:\n" << flags.usage() << endl;}
 
Finally, in your 'main()' you simply call the FlagsBase::load()' method to initialize the class's members, which can then be used as you would normally:
 
int main(int argc, char** argv){  MongoFlags flags;  bool help;
 
  // flags can be also added outside the Flags class:  flags.add(&help, "help", "Prints this help message", false);
 
  Try<Nothing> load = flags.load(None(), argc, argv);
 
  if (load.isError()) {    std::cerr << "Failed to load flags: " << load.error() << std::endl;    return -1;  }
 
  if (!help) {    if (flags.test) {      cout << "Running unit tests for Playground App\n";      return test(argc, argv);    }    if (flags.config.isSome()) {      return run_executor(flags.config.get());    }
 
    if (flags.master.isSome()) {      string uri = os::realpath(argv[0]).get();      auto masterIp = flags.master.get();      cout << "MongoExecutor starting - launching Scheduler rev. "           << MongoScheduler::REV << " starting Executor at: " << uri << '\n';      return run_scheduler(uri, masterIp);    }  }  printUsage(argv[0], flags);}
 
For a full-fledged (and extensive) use of 'stout/flags,' see the 'master.cpp and associated header file in the 'src/master' folder of the Apache Mesos repo.
 
Optional Values
 
Optional arguments can be wrapped in Stout's 'Option type, which is an extremely convenient abstraction of objects that may optionally be unassigned. That means circumventing all the awkwardness of using 'NULL' -- which, in fact, you should avoid at all costs in your code.
 
The customary pattern of usage for an 'Option' object is exemplified in the snippet:
 
void doSomething(const std::string& widget) {    // as far as this method is concerned, strings are all there is    // ...}
 
// in another part of your program
 
Option<std::string> foo;
 
// other code that may (or may not) set foo to some value
 
if (foo.isSome()) {  doSomething(foo.get());}
 
Again, more examples can be found in several places in the source code of Mesos (see, for example, 'main.cpp' in the same source folder as above).

Ready to get started?