The Database Managers, Inc.

Contact The Database Managers, Inc.


Use an RSS enabled news reader to read these articles.Use an RSS enabled news reader to read these articles.

Opt 3.19

review and upgrades
by Curtis Krauskopf

Based on the applications I've seen, command line parsing is one of the most commonly hand-coded sections of an application.

A comparison of styles

Listing A shows a typical program. Here are some areas that you might recognize in your own programs:
  • help message for users
  • home-grown parsing algorithm
  • error messages intermingled with parsing logic
  • multiple exit points when errors are detected
  • parsing actions intermingled with parsing logic

The real meat of the program doesn't start until the two final cout lines that echo the port and network settings.

Listing A
#include <iostream>
#include <stdlib.h>

// This is okay for small programs
using std::cout;
using std::endl;


// Help message for clueless users
void help() {
  cout << "typical: [(-p|--port) ####] ";
          "[(-n|--network network_id) ####]\n";
  cout << "  where:\n";
  cout << "  -p (--port) is the port number\n";
  cout << "     defaults to port 80 if not specified.\n";
  cout << "     Port 0 is an invalid port.\n";
  cout << "  -n (--network) is the network id\n";
  cout << "     defaults to 128.0.0.1 if not specified.\n";
}


int main(int argc, char* argv[]) {
  int  iPort = 80;
  char *network = "128.0.0.1";

  // A home-grown parsing algorithm starts here
  for(int i = 1; i < argc; ++i) {
    if (strcmp(argv[i], "-p") == 0 || 
strcmp(argv[i], "--port") == 0) { if (i+1 == argc) { // error messages intermingled with parsing logic cout << "Invalid " << argv[i]; cout << " parameter: no port number specified\n"; help(); exit(1); // multiple exit points in parsing algorithm } iPort = atoi(argv[++i]); // parsing action goes here } else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--network") == 0) { if (i+1 == argc) { cout << "Invalid " << argv[i]; cout << " parameter: no network ID specified\n"; help(); exit(1); } network = argv[++i]; } else if (strcmp(argv[i], "--version") == 0) { cout << "Version 1.0\n"; exit(0); } } // post-parsing parameter validation if (iPort == 0) { help(); exit(1); } cout << "Port = " << iPort << endl; cout << "Network = " << network << endl; return 0; }
typical.cpp: a typical command-line parser

Listing B is almost the same program except it uses the Opt 3.19 library. Opt 3.19 is a command line options processing library that greatly simplifies the program. Opt supports both short command line options (-p) and long options (- - port). Options can also be placed in a file and in an environment variable.

Listing B
#include <iostream>
#include <stdlib.h>
#include <opt.h>

using std::cout;
using std::endl;

int iPort = 80;
char *network = "128.0.0.1";   // localhost


int opt_typical_main(int, char**) {
  cout << "Port = " << iPort << endl;
  cout << "Network = " << network << endl;
  return OPT_OK;
}


int main(int argc, char** argv)
{
  OptRegister(&iPort,   'p', "port",    "Port number");
  OptRegister(&network, 'n', "network", "Network ID");

  optVersion("1.0");

  optMain(opt_typical_main);
  opt(&argc, &argv);

  return opt_typical_main(argc, argv);
}
opt_typical.cpp: a typical program that uses Opt

Opt is similar to the getopts package available on many Unix-type environments. Previous knowledge of getopts is not needed because of the excellent HTML documentation in Opt 3.19.

Hidden functionality

An Opt-enabled program has the following additional command line options:
  • The "- - help" parameter displays a typical help message showing the available options
  • The "- - menu" parameter invokes an automatically generated user interface
  • The "- - version" parameter shows the program's version number
  • The "- - optVersion" parameter shows the Opt library's version number

Figure A shows the help information that was automatically generated by the program in listing B.

Figure A
The automatically generated help screen for the opt_typical.cpp program in Listing B is available by using the - - help parameter on the command line. The parameters are defined by the OptRegister function calls. An interactive user interface is available with the - - menu option.

The figure shows two of the supported data types: string and int. Almost all of the normal data types are supported and several Boolean variants are also available:

  • OPT_BOOL for setting a value to 1
  • OPT_TOGGLE for toggling between 0 and 1
  • OPT_NEGBOOL for setting a value to 0

The - -menu command line option puts the user into a text-based interactive user interface. Figure B shows a user-interface session for the program in listing B.

Figure B
The Opt 3.19 user interface is initiated by using the - - menu option. Every program that uses the Opt 3.19 library automatically has a user interface. The user interface allows the end-user to query command line parameters, modify their values, and read and write parameters from/to an external file.

Both the "=" and "$" menu commands run the application using the currently set parameters. The difference between them is that the "=" command returns to the user interface menu when the application is finished whereas the "$" command terminates the program when the application is finished.

How does it work?

As shown in listing B, the OptRegister function associates global variables with short and long delimiters. The example uses both short and long delimiters but Opt also supports using one or the other.

The program's functionality is moved from main() to a separate function. The example program uses the name opt_typical_main() but there's nothing magical about the name -- a function named doit() could have been used instead.

The function that will be called is registered with the optMain() function. This is the function that is called by the user interface with the "=" or "$" menu commands.

The opt() function parses the command line options and optionally invokes the user interface. Any unrecognized command line options are left on the argv array.

When the user does not invoke the user interface, execution drops through the opt() function and the

return opt_typical_main(argc, argv);

function call executes the application normally.

Flexibility

Table 1 shows the command line variations that are allowed for the program in listing B.

Positional parameters are specified by using an OPT_POSITIONAL parameter in the OptRegister() function call, like this:

OptRegister(&x, OPT_POSITIONAL, "x-value", "x in feet");
OptRegister(&y, OPT_POSITIONAL, "y-value", "y in feet");
OptRegister(&z, OPT_POSITIONAL, "z-value", "z in feet");
The above definitions would allow a program called position.exe to be called like this:
position 1000 2000 15

Flexible parameters are a combination of delimited parameters and positional parameters. The first undelimited parameter is associated with the first positional or flexible parameter. This happens in an intuitive way. Changing the iPort definition in listing B to:

OptRegister(&iPort, OPT_FLEXIBLE, 'p', "port", "Port number");
allows all of the opt_typical parameter variations in table 1 and the ones in table 2.
Table 1: Command line variations for opt_typical.cpp
opt_typlical
opt_typlical --port 128
opt_typlical --network www.decompile.com
opt_typlical --network www.decompile.com --port 128
opt_typlical --port 128 --network www.decompile.com
All of these command line are valid variations for the opt_typlical.cpp program in Listing B

Table 2: Flexible variations for opt_typical.cpp
opt_typlical 128
opt_typlical --network www.decompile.com 128
opt_typlical 128 --network www.decompile.com

Availability

Opt was created by James Theiler at the Los Alamos National Laboratory.

Opt is licensed under the GNU General Public License and both the source code and compiled binaries can be freely distributed and used in commercial applications.

Opt 3.19 is available from http://public.lanl.gov/jt/Software/opt/opt-3.19.tar.gz. The updates for Opt 3.19 that support the Borland compiler are available at http://www.decompile.com/not_invented_here/opt. Installation instructions for the Opt 3.19 library and the upgrade for the Borland compiler are available at The Database Managers (www.decompile.com).

Installation

The Opt 3.19 distribution files have a Unix-type flavor to them. All of the .c and .cc files in the ~/src folder were placed into a hand-built Borland C++ Builder library (see Creating a Static Library in C++ Builder for step-by-step instructions).

Opt 3.19 deprecated several compile-time symbols (CHAR, DOUBLE, BOOL and others). Because those symbols conflicted with names defined in another library I was using, I chose to comment-out all of the Opt 3.19 deprecated symbols. In ~\src\opt.h, search for the line:

#define CHAR OPT_CHAR
and comment it out. There are several other lines that I also commented out that were scattered throughout opt.h:
#define DOUBLE OPT_DOUBLE 
#define BOOL OPT_BOOL 
// and others.
The only code change I had to make was an incompatibility in the ~\src\opt_reg.c file (line 231):
char str_delim[2] = { delim, '\0' };
The Borland C++ Builder compiler didn't like that declaration, so I coded it the long way:
char str_delim[2];
str_delim[0] = delim;
str_delim[1] = 0; 

Another change I made to the hand-built Borland C++ Builder library was to define a compile-time symbol named VERSION. Opt uses the VERSION preprocessor symbol to report the library's version. I defined VERSION to be "3.19" (including the quotes).

The original distribution file used a C++ file called opt_regcc.cc. Instead of adding Yet Another File Extension to the list of file extensions recognized by Borland C++ Builder, I renamed opt_regcc.cc to opt_regcc.cpp. You should delete opt_regcc.cc from the distribution files to prevent confusion.

Documentation

The documentation is excellent and it has many examples of using Opt. I have never needed to refer to the C and C++ source code for help in understanding a feature.

One section in the documentation provides a cookbook-style recipe for installing Opt into an existing program.

One gripe I had with the documentation is that every hyperlink was a reference to an opt.html document at http://www.lanl.gov that is now a dead link. The Opt 3.19 update available at The Database Managers' Not Invented Here web site provides an HTML document with local references.

The Opt 3.19 documentation is also available online at http://www.decompile.com/not_invented_here/opt/documentation.htm.

Limitations

Although Opt does some pretty amazing things, parts of it are not as polished:
  • All long command line options must use the double-dash (- -) prefix. There is no way to automatically handle "-network" as a command line option.
  • Command line options can have, at most, only one parameter. The following parameter cannot be automatically handled by Opt:
    - - driveRange c: g:

Opt is written in C and C++. This is an advantage for the C++ coder that also codes in C because Opt can be used for both programs. Unfortunately, support for the native bool type does not exist in Opt. Instead, Booleans are handled as ints. The affect on your program is that you need to compare against 0 or 1 instead of checking for false or true.

One limitation that is strictly for the Borland C++ Builder compiler is that a ^c issued while running an application started with the "=" menu option will not return to the menu. Instead, it will terminate the program just like a "$" menu option would have done.

One bug I found during testing is with the OPT_FLEXIBLE (flexible options). I found that flexible options do not appear in the automatically generated menu list. The file written by the Opt library does not include any positional or flexible parameters. So far, this bug hasn't caused me any problems but I can anticipate the day when I will need to fix this problem.

Conclusion

Opt 3.19 is a powerful command line parsing library. It provides an automatically generated user interface, an automatically generated help screen and it can read parameters from files and environment variables and create files using the current set of parameters. Programs that use Opt are simplified because the command line parsing and program execution are separated into two functions.

Contact Curtis at

Curtis Krauskopf is a software engineer and the president of The Database Managers (www.decompile.com). He has been writing code professionally for over 25 years. His prior projects include multiple web e-commerce applications, decompilers for the DataFlex language, aircraft simulators, an automated Y2K conversion program for over 3,000,000 compiled DataFlex programs, and inventory control projects. Curtis has spoken at many domestic and international DataFlex developer conferences and has been published in FlexLines Online, JavaPro Magazine, C/C++ Users Journal and C++ Builder Developer's Journal.


Popular C++ topics at The Database Managers:

Services | Programming | Contact Us | Recent Updates
Send feedback to: