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 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 |
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:
|