1 Introduction
Command Line Interface (CLI) definition language is a domain-specific language (DSL) for defining command line interfaces of C++ programs. CLI definitions are automatically translated to C++ classes using the CLI compiler. These classes implement parsing of the command line arguments and provide a convenient and type-safe interface for accessing the extracted data.
Beyond this guide, you may also find the following sources of information useful:
- CLI Compiler Command Line Manual
- The INSTALLfile in the CLI distribution provides build instructions for various platforms.
- The examples/directory in the CLI distribution contains a collection of examples and a README file with an overview of each example.
- The cli-users mailing list is the place to ask technical questions about the CLI language and compiler. Furthermore, the cli-users mailing list archives may already have answers to some of your questions.
2 Hello World Example
In this chapter we will examine how to define a very simple command
     line interface in CLI, translate this interface to C++, and use the
     result in an application. The code presented in this chapter is based
     on the hello example which can be found in the
     examples/hello/ directory of the CLI distribution.
2.1 Defining Command Line Interface
Our hello application is going to print a greeting
     line for each name supplied on the command line. It will also
     support two command line options, --greeting
     and --exclamations, that can be used to
     customize the greeting line. The --greeting
     option allows us to specify the greeting phrase instead of the
     default "Hello". The --exclamations
     option is used to specify how many exclamation marks should
     be printed at the end of each greeting. We will also support
     the --help option which triggers printing of the
     usage information.
We can now write a description of the above command line interface
     in the CLI language and save it into hello.cli:
include <string>;
class options
{
  bool --help;
  std::string --greeting = "Hello";
  unsigned int --exclamations = 1;
};
  
  While some details in the above code fragment might not be completely
     clear (the CLI language is covered in greater detail in the next
     chapter), it should be easy to connect declarations in
     hello.cli to the command line interface described in
     the preceding paragraphs. The next step is to translate this
     interface specification to C++.
2.2 Translating CLI Definitions to C++
Now we are ready to translate hello.cli to C++.
     To do this we invoke the CLI compiler from a terminal (UNIX) or
     a command prompt (Windows):
  
$ cli hello.cli
This invocation of the CLI compiler produces three C++ files:
     hello.hxx hello.ixx, and
     hello.cxx. You can change the file name extensions
     for these files with the compiler command line options. See the
     CLI
     Compiler Command Line Manual for more information.
The following code fragment is taken from hello.hxx; it
     should give you an idea about what gets generated:
#include <string>
class options
{
public:
  options (int argc, char** argv);
  options (int argc, char** argv, int& end);
  // Option accessors.
  //
public:
  bool
  help () const;
  const std::string&
  greeting () const;
  unsigned int
  exclamations () const;
private:
  ..
};
  
  The options C++ class corresponds to the options
     CLI class. For each option in this CLI class an accessor function is
     generated inside the C++ class. The options C++ class also
     defines a number of overloaded constructs that we can use to parse the
     argc/argv array. Let's now see how we can use this generated
     class to implement option parsing in our hello application.
2.3 Implementing Application Logic
At this point we have everything we need to implement our application:
#include <iostream>
#include "hello.hxx"
using namespace std;
void
usage ()
{
  cerr << "usage: driver [options] <names>" << endl
       << "options:" << endl;
  options::print_usage (cerr);
}
int
main (int argc, char* argv[])
{
  try
  {
    int end; // End of options.
    options o (argc, argv, end);
    if (o.help ())
    {
      usage ();
      return 0;
    }
    if (end == argc)
    {
      cerr << "no names provided" << endl;
      usage ();
      return 1;
    }
    // Print the greetings.
    //
    for (int i = end; i < argc; i++)
    {
      cout << o.greeting () << ", " << argv[i];
      for (unsigned int j = 0; j < o.exclamations (); j++)
        cout << '!';
      cout << endl;
    }
  }
  catch (const cli::exception& e)
  {
    cerr << e << endl;
    usage ();
    return 1;
  }
}
  At the beginning of our application we create the options
     object which parses the command line. The end variable
     contains the index of the first non-option argument. We then access
     the option values as needed during the application execution. We also
     catch and print cli::exception in case something goes
     wrong, for example, an unknown option is specified or an option value
     is invalid.
  
2.4 Compiling and Running
After saving our application from the previous section in
     driver.cxx, we are ready to build and run our program.
     On UNIX this can be done with the following commands:
$ c++ -o driver driver.cxx hello.cxx $ ./driver world Hello, world! $ ./driver --greeting Hi --exclamations 3 John Jane Hi, John!!! Hi, Jane!!!
We can also test the error handling:
$ ./driver -n 3 Jane unknown option '-n' usage: driver [options] <names> options: --help --greeting <arg> --exclamations <arg> $ ./driver --exclamations abc Jane invalid value 'abc' for option '--exclamations' usage: driver [options] <names> options: --help --greeting <arg> --exclamations <arg>
2.5 Adding Documentation
As we have seen in the previous sections, the options
     C++ class provides the print_usage() function which we
     can use to display the application usage information. Right now this
     information is very basic and does not include any description of
     the purpose of each option:
$ ./driver --help usage: driver [options] <names> options: --help --greeting <arg> --exclamations <arg>
To make the usage information more descriptive we can document each option in the command line interface definition. This information can also be used to automatically generate program documentation in various formats, such as HTML and man page. For example:
include <string>;
class options
{
  bool --help {"Print usage information and exit."};
  std::string --greeting = "Hello"
  {
    "<text>",
    "Use <text> as a greeting phrase instead of the default \"Hello\"."
  };
  unsigned int --exclamations = 1
  {
    "<num>",
    "Print <num> exclamation marks instead of 1 by default."
  };
};
  
  If we now save this updated command line interface to
     hello.cli and recompile our application, the usage
     information printed by the program will look like this:
usage: driver [options] <names>
options:
--help               Print usage information and exit.
--greeting <text>    Use <text> as a greeting phrase instead of the
                     default "Hello".
--exclamations <num> Print <num> exclamation marks instead of 1 by
                     default.
  
  We can also generate the program documentation in the HTML
     (--generate-html CLI option) and man page
     (--generate-man CLI option) formats. For example:
$ cli --generate-html hello.cli
The resulting hello.html file contains the following
     documentation:
This HTML fragment can be combined with custom prologue and epilogue
     to create a complete program documentation
     (--html-prologue/--html-epilogue options for the HTML
     output, --man-prologue/--man-epilogue options for the
     man page output). For an example of such complete documentation see
     the CLI
     Compiler Command Line Manual and the cli(1) man
     page. For more information on the option documentation syntax,
     see Section 3.3, Option Documentation.
3 CLI Language
This chapter describes the CLI language and its mapping to C++. A CLI file consists of zero or more Include Directives followed by one or more Namespace Definitions or Option Class Definitions. C and C++-style comments can be used anywhere in the CLI file except in character and string literals.
3.1 Option Class Definition
The central part of the CLI language is option class. An option class contains one or more option definitions, for example:
class options
{
  bool --help;
  int --compression;
};
  
  If we translate the above CLI fragment to C++, we will get a C++ class with the following interface:
class options
{
public:
  options (int& argc,
           char** argv,
           bool erase = false,
           cli::unknown_mode opt_mode = cli::unknown_mode::fail,
           cli::unknown_mode arg_mode = cli::unknown_mode::stop);
  options (int start,
           int& argc,
           char** argv,
           bool erase = false,
           cli::unknown_mode opt_mode = cli::unknown_mode::fail,
           cli::unknown_mode arg_mode = cli::unknown_mode::stop);
  options (int& argc,
           char** argv,
           int& end,
           bool erase = false,
           cli::unknown_mode opt_mode = cli::unknown_mode::fail,
           cli::unknown_mode arg_mode = cli::unknown_mode::stop);
  options (int start,
           int& argc,
           char** argv,
           int& end,
           bool erase = false,
           cli::unknown_mode opt_mode = cli::unknown_mode::fail,
           cli::unknown_mode arg_mode = cli::unknown_mode::stop);
  options (cli::scanner&,
           cli::unknown_mode opt_mode = cli::unknown_mode::fail,
           cli::unknown_mode arg_mode = cli::unknown_mode::stop);
  options (const options&);
  options&
  operator= (const options&);
public:
  static void
  print_usage (std::ostream&);
public:
  bool
  help () const;
  int
  compression () const;
};
  
  An option class is mapped to a C++ class with the same name. The
     C++ class defines a set of public overloaded constructors, a public
     copy constructor and an assignment operator, as well as a set of public
     accessor functions and, if the --generate-modifier CLI
     compiler option is specified, modifier functions corresponding to option
     definitions. It also defines a public static print_usage()
     function that can be used to print the usage information for the options
     defined by the class.
The argc/argv arguments in the overloaded constructors
     are used to pass the command line arguments array, normally as passed
     to main(). The start argument is used to
     specify the position in the arguments array from which the parsing
     should start. The constructors that don't have this argument, start
     from position 1, skipping the executable name in argv[0].
     The end argument is used to return the position in
     the arguments array where the parsing of options stopped. This is the
     position of the first program argument, if any. If the erase
     argument is true, then the recognized options and their
     values are removed from the argv array and the
     argc count is updated accordingly.
The opt_mode and arg_mode arguments
     specify the parser behavior when it encounters an unknown option
     and argument, respectively. The unknown_mode type
     is part of the generated CLI runtime support code. It has the
     following interface:
namespace cli
{
  class unknown_mode
  {
  public:
    enum value
    {
      skip,
      stop,
      fail
    };
    unknown_mode (value v);
    operator value () const;
  };
}
  
  If the mode is skip, the parser skips an unknown
     option or argument and continue parsing. If the mode is
     stop, the parser stops the parsing process. The
     position of the unknown entity is stored in the end
     argument. If the mode is fail, the parser throws the
     cli::unknown_option or cli::unknown_argument
     exception (described blow) on encountering an unknown option or argument,
     respectively.
Instead of the argc/argv arguments, the last overloaded
     constructor accepts the cli::scanner object. It is part
     of the generated CLI runtime support code and has the following
     abstract interface:
namespace cli
{
  class scanner
  {
  public:
    virtual bool
    more () = 0;
    virtual const char*
    peek () = 0;
    virtual const char*
    next () = 0;
    virtual void
    skip () = 0;
  };
}
  
  The CLI runtime also provides two implementations of this interface:
     cli::argv_scanner and cli::argv_file_scanner.
     The first implementation is a simple scanner for the argv
     array (it is used internally by all the other constructors) and has the
     following interface:
namespace cli
{
  class argv_scanner
  {
  public:
    argv_scanner (int& argc, char** argv, bool erase = false);
    argv_scanner (int start, int& argc, char** argv, bool erase = false);
    int
    end () const;
    ...
  };
}
  
  The cli::argv_file_scanner implementation provides
     support for reading command line arguments from the argv
     array as well as files specified with command line options. It is
     generated only if explicitly requested with the
     --generate-file-scanner CLI compiler option and has
     the following interface:
namespace cli
{
  class argv_file_scanner
  {
  public:
    argv_file_scanner (int& argc,
                       char** argv,
                       const std::string& file_option,
                       bool erase = false);
    argv_file_scanner (int start,
                       int& argc,
                       char** argv,
                       const std::string& file_option,
                       bool erase = false);
    ...
  };
}
  
  The file_option argument is used to pass the option name
     that should be recognized as specifying the file containing additional
     options. Such a file contains a set of options, each appearing on a
     separate line optionally followed by space and an option value. Empty lines
     and lines starting with # are ignored. The semantics
     of providing options in a file is equivalent to providing the same set
     of options in the same order on the command line at the point where the
     options file is specified, except that shell escaping and quoting is not
     required. Multiple files can be specified by including several file
     options on the command line or inside other files.
The parsing constructor (those with the argc/argv or
     cli::scanner arguments) can throw the following exceptions: cli::unknown_option,
     cli::unknown_argument, cli::missing_value, and
     cli::invalid_value. The first two exceptions are thrown
     on encountering unknown options and arguments, respectively, as
     described above. The missing_value exception is thrown when
     an option value is missing. The invalid_value exception is
     thrown when an option value is invalid, for example, a non-integer value
     is specified for an option of type int.
Furthermore, all scanners (and thus the parsing constructors that
     call them) can throw the cli::eos_reached exception
     which indicates that one of the peek(), next(),
     or skip() functions were called while more()
     returns false. Catching this exception normally indicates an
     error in an option parser implementation. The argv_file_scanner
     class can also throw the cli::file_io_failure exception
     which indicates that a file could not be opened or there was a reading
     error.
All CLI exceptions are derived from the common cli::exception
     class which implements the polymorphic std::ostream insertion.
     For example, if you catch the cli::unknown_option
     exception as cli::exception and print it to
     std::cerr, you will get the error message corresponding
     to the unknown_option exception.
The exceptions described above are part of the generated CLI runtime support code and have the following interfaces:
#include <exception>
namespace cli
{
  class exception: public std::exception
  {
  public:
    virtual void
    print (std::ostream&) const = 0;
  };
  inline std::ostream&
  operator<< (std::ostream& os, const exception& e)
  {
    e.print (os);
    return os;
  }
  class unknown_option: public exception
  {
  public:
    unknown_option (const std::string& option);
    const std::string&
    option () const;
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
  class unknown_argument: public exception
  {
  public:
    unknown_argument (const std::string& argument);
    const std::string&
    argument () const;
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
  class missing_value: public exception
  {
  public:
    missing_value (const std::string& option);
    const std::string&
    option () const;
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
  class invalid_value: public exception
  {
  public:
    invalid_value (const std::string& option,
                   const std::string& value);
    const std::string&
    option () const;
    const std::string&
    value () const;
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
  class eos_reached: public exception
  {
  public:
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
  class file_io_failure: public exception
  {
  public:
    file_io_failure (const std::string& file);
    const std::string&
    file () const;
    virtual void
    print (std::ostream&) const;
    virtual const char*
    what () const throw ();
  };
}
  
  3.2 Option Definition
An option definition consists of four components: type,
   name, default value, and documentation.
   An option type can be any C++ type as long as its string representation
   can be parsed using the std::istream interface. If the option
   type is user-defined then you will need to include its declaration using
   the Include Directive.
An option of a type other than bool is expected to
   have a value. An option of type bool is treated as
   a flag and does not have a value. That is, a mere presence of such
   an option on the command line sets this option's value to
   true.
The name component specifies the option name as it will be entered
   in the command line. A name can contain any number of aliases separated
   by |. The C++ accessor and modifier function names are
   derived from the first name by removing any leading special characters,
   such as -, /, etc., and replacing special
   characters in other places with underscores. For example, the following
   option definition:
class options
{
  int --compression-level | --comp | -c;
};
  
  Will result in the following accessor function:
class options
{
  int
  compression_level () const;
};
  
  While any option alias can be used on the command line to specify this option's value.
If the option name conflicts with one of the CLI language keywords, it can be specified as a string literal:
class options
{
  bool "int";
};
  
  The following component of the option definition is the optional default
     value. If the default value is not specified, then the option is
     initialized with the default constructor. In particular, this means
     that a bool option will be initialized to false,
     an int option will be initialized to 0, etc.
Similar to C++ variable initialization, the default option value can be specified using two syntactic forms: an assignment initialization and constructor initialization. The two forms are equivalent except that the constructor initialization can be used with multiple arguments, for example:
include <string>;
class options
{
  int -i1 = 5;
  int -i2 (5);
  std::string -s1 = "John";
  std::string -s2 ("Mr John Doe", 8, 3);
};
  
  The assignment initialization supports character, string, boolean, and simple integer literals (including negative integers) as well as identifiers. For more complex expressions use the constructor initialization or wrap the expressions in parenthesis, for example:
include "constants.hxx"; // Defines default_value.
class options
{
  int -a = default_value;
  int -b (25 * 4);
  int -c = (25 / default_value + 3);
};
  
  By default, when an option is specified two or more times on the command
     line, the last value overrides all the previous ones. However, a number
     of standard C++ containers are handled differently to allow collecting
     multiple option values or building key-value maps. These
     containers are std::vector, std::set, and
     std::map.
When std::vector or std::set is specified
     as an option type, all the values for this option are inserted into the
     container in the order they are encountered. As a result,
     std::vector will contain all the values, including
     duplicates while std::set will contain all the unique
     values. For example:
include <set>;
include <vector>;
class options
{
  std::vector<int> --vector | -v;
  std::set<int> --set | -s;
};
  
  If we have a command line like this:
     -v 1 -v 2 -v 1 -s 1 -s 2 -s 1, then the vector returned
     by the vector() accessor function will contain three
     elements: 1, 2, and 1 while
     the set returned by the set() accessor will contain
     two elements: 1 and 2.
When std::map is specified as an option type, the option
     value is expected to have two parts: the key and the value, separated
     by =. All the option values are then parsed into key/value
     pairs and inserted into the map. For example:
include <map>;
include <string>;
class options
{
  std::map<std::string, std::string> --map | -m;
};
  
  The possible option values for this interface are: -m a=A,
     -m =B (key is an empty string),  -m c= (value
      is an empty string), or -m d (same as -m d=).
The last component in the option definition is optional documentation. It is discussed in the next section.
3.3 Option Documentation
Option documentation mimics C++ string array initialization:
     it is enclosed in {} and consists of one or more
     documentation strings separated by a comma, for example:
class options
{
  int --compression = 5
  {
    "<level>",
    "Set compression to <level> instead of 5 by default.
     With the higher compression levels the program may produce a
     smaller output but may also take longer and use more memory."
  };
};
  
  The option documentation consists of a maximum of three documentation
     strings. The first string is the value documentation string.
     It describes the option value and is only applicable to options
     with types other than bool (options of type
     bool are flags and don't have an explicit value).
     The second string (or the first string for options of type
     bool) is the short documentation string. It
     provides a brief description of the option. The last entry
     in the option documentation is the long documentation string.
     It provides a detailed description of the option. The short
     documentation string is optional. If only two strings are
     present in the option documentation (one string for options
     of type bool), then the second (first) string is
     assumed to be the long documentation string.
Option documentation is used to print the usage information
     as well as to generate program documentation in the HTML and
     man page formats. For usage information the short documentation
     string is used if provided. If only the long string is available,
     then, by default, only the first sentence from the long string
     is used. You can override this behavior and include the complete
     long string in the usage information by specifying the
     --long-usage CLI compiler option. When generating
     the program documentation, the long documentation strings are
     always used.
The value documentation string can contain text enclosed in
     <> which is automatically recognized by the CLI
     compiler and typeset according to the selected output in all three
     documentation strings. For example, in usage the level
     value for the --compression option presented above
     will be displayed as <level> while in the HTML and
     man page output it will be typeset in italic as
     level. Here is another example using the
     std::map type:
include <map>;
include <string>;
class options
{
  std::map<std::string, std::string> --map
  {
    "<key>=<value>",
    "Add the <key>, <value> pair to the map."
  };
};
  
  The resulting HTML output for this option would look like this:
As you might have noticed from the examples presented so far, the
     documentation strings can span multiple lines which is not possible
     in C++. Also, all three documentation strings support the following
     basic formatting mechanisms. The start of a new paragraph is indicated
     by a blank line. A fragment of text can be typeset in monospace font
     (normally used for code fragments) by enclosing it in the
     \c{} block. Similarly, text can be typeset in bold or
     italic fonts using the \b{} and \i{} blocks,
     respectively. You can also combine several font properties in a single
     block, for example, \cb{bold code}. If you need to include
     literal } in a formatting block, you can use the
     \} escape sequence, for example,
     \c{int a[] = {1, 2\}}. The following example shows how we
     can use these mechanisms:
class options
{
  int --compression = 5
  {
    "<level>",
    "Set compression to <level> instead of 5 by default.
     With the higher compression levels the program \i{may}
     produce a smaller output but may also \b{take longer}
     and \b{use more memory}."
  };
};
  
  The resulting HTML output for this option would look like this:
3.4 Include Directive
If you are using user-defined types in your option definitions,
     you will need to include their declarations with the include
     directive. Include directives can use < > or
     " "-enclosed paths. The CLI compiler does not
     actually open or read these files. Instead, the include directives
     are translated to C++ preprocessor #include directives
     in the generated C++ header file. For example, the following CLI
     definition:
include <string>;
include "types.hxx"; // Defines the name_type class.
class options
{
  std::string --string;
  name_type --name;
};
  
  Will result in the following C++ header file:
#include <string>
#include "types.hxx"
class options
{
  ...
  const std::string&
  string () const;
  const name_type&
  name () const;
  ...
};
  
  Without the #include directives the std::string
     and name_type types in the options class would
     be undeclared and result in compilation errors.
3.5 Namespace Definition
Option classes can be placed into namespaces which are translated directly to C++ namespaces. For example:
namespace compiler
{
  namespace lexer
  {
    class options
    {
      int --warning-level = 0;
    };
  }
  namespace parser
  {
    class options
    {
      int --warning-level = 0;
    };
  }
  namespace generator
  {
    class options
    {
      int --target-width = 32;
    };
  }
}
  
  The above CLI namespace structure would result in the equivalent C++ namespaces structure:
namespace compiler
{
  namespace lexer
  {
    class options
    {
      int
      warning_level () const;
    };
  }
  namespace parser
  {
    class options
    {
      int
      warning_level () const;
    };
  }
  namespace generator
  {
    class options
    {
      int
      target_width () const;
    };
  }
}