Journal Articles

CVu Journal Vol 28, #1 - March 2016 + Programming Topics
Browse in : All > Journals > CVu > 281 (11)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Using Clara to Parse Command Lines in C++

Author: Martin Moene

Date: 07 March 2016 20:26:35 +00:00 or Mon, 07 March 2016 20:26:35 +00:00

Summary: Malcolm Noyes demonstrates how to get up and running.

Body: 

Recently I needed a command line parser. Previously I have used Boost.ProgramOptions which has some nice features but this wasn’t available in the build of Boost libraries I was using. I needed something simple and preferably header only. Some of you may know that I’m a bit of fan of Phil Nash’s Catch as a C++ unit testing framework; Catch uses Clara internally to do the command line parsing, so I thought I’d give that a try...what follows is a brief description of the basic features, which hopefully will fill a few holes in the documentation!

Help!

Listing 1 shows a simple program that I’ll use to demonstrate some features of Clara.

#define CLARA_CONFIG_MAIN
#include <clara.h>
#include <iostream>

struct ArticleOptions {
  ArticleOptions() : showHelp(false), 
     send(false) {}
  std::string processName;
  bool showHelp;
  bool send;
  std::string reviewer;
};
int main(int argc, char* argv[])
{
  using namespace Clara;
  CommandLine<ArticleOptions> cli;
  cli.bindProcessName
    (&ArticleOptions::processName);
  cli["-?"]["-h"]["--help"]
    .describe("describes how to use the program")
    .bind(&ArticleOptions::showHelp);
  cli["-s"]["--send"]
    .describe("should the article be sent?")
    .bind(&ArticleOptions::send);
  cli["-r"]["--reviewer"]
    .describe("who should review it?")
    .bind(&ArticleOptions::reviewer,
       "who to send to");
  ArticleOptions opt;
  cli.parseInto(argc,
     const_cast<const char**>(argv), opt);
  if (opt.showHelp) {
    cli.usage(std::cout, opt.processName);
  }
  else {
    if (opt.send) {
      std::cout 
        << "Send article for review by..." 
        << opt.reviewer 
        << std::endl;
  }
  }
  return 0;
}
			
Listing 1

This example has three options. The first is to output help text and is introduced with cli["-?"]["-h"]["--help"]. This tells Clara that there are three ways to request help, two short options (-? and -h) and one long one (--help). If we run that we get something like this:

  c:\Projects\ClaraExample\Debug>ClaraExample -h
  usage:
    ClaraExample  [options]
  where options are:
    -?, -h, --help describes how to use the program
    -s, --send     should the article be sent?
    -r, --reviewer <who to send to>  
                   who should review it?

The help text gives us the options defined, along with the description that we supply to the describe clause. For an option with no additional arguments (-s) this is all we get, but for an option that requires input, we get the option together with the ‘placeholder’ text that we specified in the bind clause.

For the option with no arguments, I have ‘bound’ this option to the ArticleOptions bool member variable send. If I set this option, Clara will set the member variable...nice and simple.

The option that requires an argument I have bound to a string. If the argument is set then the member variable will take the value of the input. Let’s see how that works:

  c:\Projects\ClaraExample\Debug>ClaraExample -sr
  "Phil Nash"
  Send article for review by...Phil Nash

There are several ways to specify the input to an option that requires one; all these achieve the same result:

  ClaraExample -s -r="Phil Nash"
  ClaraExample -s -r:"Phil Nash"
  ClaraExample -s --reviewer "Phil Nash"

Did I ask for that?

Often I found that I wanted to know if a particular option was set and take some action based on that. Also, I wanted some default value to be set if the option wasn’t set. It seems the best way to do that is to bind the option to a function (for example, see Listing 2).

struct ArticleOptions {
    ArticleOptions() : showHelp(false)
       , send(false)
       , reviewer("Steve Love")
    {}
    // ... as before ... 
    bool send;
    std::string reviewer;

    void reviewerOptionSet(const std::string& v){
      send = true;
      reviewer = v;
    }
};

int main(int argc, char* argv[])
{
  //... as before ...
  cli["-r"]["--reviewer"]
    .describe("who should review it?")
    .bind(&ArticleOptions::reviewerOptionSet
       , "who to send to");

  //... as before ...
  if (opt.send) {
    std::cout << "Send article for review by..."
    << opt.reviewer << std::endl;
  }
  else {
    std::cout << "Option not set...review by..."
    << opt.reviewer << std::endl;
  }
}
			
Listing 2

This tells Clara to call the function if the option is set; the called function can do what it likes so in this case I can set both the flag and the value. If the flag is not set, then I use the default initialisation of the member variable. So now we have:

  c:\Projects\ClaraExample\Debug>ClaraExample
  Option not set...review by...Steve Love

  c:\Projects\ClaraExample\Debug>ClaraExample 
  --reviewer "Phil Nash"
  Send article for review by...Phil Nash

Positional arguments

If your program requires more than one argument, you can do that too. First, you can bind the option to a specific position, as in Listing 3.

//...
cli[1]
  .describe("other reviewer")
  .bind(&ArticleOptions::other, 
    "other reviewer");
cli[2]
  .describe("another reviewer")
  .bind(&ArticleOptions::another, 
    "another reviewer");

//...
  std::cout << "Option not set...review by..." 
    << opt.reviewer << ", " << opt.other << ", " 
    << opt.another << std::endl;
			
Listing 3
  c:\Projects\ClaraExample\Debug>ClaraExample 
  "Phil Nash"
  Option not set...review by...Steve Love, 
  Phil Nash,

  c:\Projects\ClaraExample\Debug>ClaraExample 
  "Phil Nash" "Roger Orr"
  Option not set...review by...Steve Love, Phil
  Nash, Roger Orr

Note that these are not bound to a specific named option, so we still specify an option if we want to:

  c:\Projects\ClaraExample\Debug>ClaraExample 
  "Phil Nash" -r "You know who" "Roger Orr"
  Send article for review by...You know who, 
  Phil Nash, Roger Orr

On the other hand, if you don’t care where an option should appear, the option can be bound to an ‘unpositional’ variable, as in Listing 4.

struct ArticleOptions {
  //...
  std::string omnificent;
};

  //...
  cli[_]
    .describe("if all else fails")
    .bind(&ArticleOptions::omnificent, 
      "seek help from above");

  // ...
  std::cout << "If that fails, review by..." 
            << opt.omnificent << std::endl;
			
Listing 4
  c:\Projects\ClaraExample\Debug>ClaraExample 
  "Phil Nash" "Roger Orr" -r "You know who" God
  If that fails, review by...God

Bah humbug...but I’m lazy...can’t Clara figure that out?

One thing I would like to see is that Clara could do the test for whether an option was set; after all, Clara ‘knows’ whether the option was set since it either parse it or it didn’t. That would save me having to have a flag for each option and possibly could be tested with something like:

  if(cli['-r'].wasSet()) {
    ...
  }

That would mean I wouldn’t need to bind to a function that sets a flag and a value; instead I could just bind to the member. The lookup for the option would take longer than just testing the flag but I doubt if testing command line options is a time killer for most applications.

The other thing that I’d like is for the ‘placeholder’ text to be the default value, even if the option is not set. Then I wouldn’t need to initialise the member in the struct, I can do it in the bind, which would improve the locality of the information associated with the option, something like Listing 5

struct Opt
{
  std::string value;
};
//...
cli["-v"]
  .bind(&Opt::value, 
    "this is the default value");
//...
if(opt.wasSet("-v")) {
  std::cout << "Value set: " << opt.value 
            << std::endl;
} else {
  std::cout << "Not set: " << opt.value 
            << std::endl;
}
			
Listing 5

which would be used like this:

  c:\Projects\ClaraExample\Debug>ClaraExample 
  -v "Option overide"
  Value set: Option override

  c:\Projects\ClaraExample\Debug>ClaraExample
  Not set: this is the default value

I’ll have to ask Phil if he thinks either of those is a good idea...

Summary

Clara is very simple to get going; just download the header (1). It is header only, so there’s no linking to incompatible libraries. What it does, it does very well and what it does less well can be worked around very easily. In short, it solved my problem, so I’m (mostly) happy!

Notes: 

More fields may be available via dynamicdata ..