/* opt.c - command-line option parsing
*
****************************************************************
* Copyright (C) 1998, 2000 Thomas Lord
*
* See the file "COPYING" for further information about
* the copyright and warranty status of this work.
*/
#include "hackerlab/os/alloca.h"
#include "hackerlab/os/stddef.h"
#include "hackerlab/os/limits.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/char/str.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/cmd/opt.h"
/************************************************************************
*(h0 "Parsing Command-line Options"
* :include ("hackerlab/cmd/opt.h"))
*
* The functions and macros in this chapter provide a convenient and
* standard way to parse command line arguments and provide on-line
* help for command-line invocation. An example at the end of the
* chapter illustrates their use.
*
* It is important to understand that these functions are designed
* to support *consistent* command line interfaces, rather than
* *arbitrary* command line interfaces. Consequently, these functions
* aren't especially useful for implementing some of the standard Posix
* command line syntaxes.
*
*/
/*(menu)
*/
/*(include-documentation "opt.h")
*/
/************************************************************************
*(h1 "Parsing Options")
*
*/
/*(c opt_low)
* int opt_low (t_uchar * opt_string,
* t_uchar * opt_arg,
* struct opt_desc ** desc,
* struct opt_desc * opts,
* int * argcp,
* char ** argv,
* t_uchar * opt_string_space);
*
* Return the next command line option and modify argc/argv to
* remove that option.
*
* `opts' should point to an array of `struct opt_desc' whose final
* element contains a value less than 0 in the field `opt_value'.
*
* Ordinarily `opt' returns the `opt_value' field from the element of
* `opts' that matches the next argument. If the field `requires_arg'
* is not 0, then the option argument is also returned. The option
* and its argument are removed from argc/argv. `opt_string' returns
* a string naming the option ("-x", "--long-opt" or
* "--long-opt=value"). The string returned in `opt_string' may be
* overwritten by later calls to `opt'. `opt_arg' returns
* the argument to the option, if the argument requires an argument
* and one was provided. A string returned in `opt_arg' points to
* a string or substring from `argv'. `desc' returns the option
* description for the option returned.
*
* Any of `opt_string', `opt_arg', and `desc' may be 0.
*
* `opt_string_space' should point to an array of three characters.
* If the next option is a short option, the value stored in `*opt_string'
* will point to `opt_string_space'. `opt_string_space' may be 0
* if `opt_string' is 0.
*
* If the option requires an argument but none is provided, `*opt_arg'
* is set to 0, and the `opt_value' field of the option is returned;
* argc/argv is not modified. To see that an option with required
* argument has been correctly provided, it is necessary to check both
* the return value of `opt', and the value of `*opt_arg'. This
* behavior is to facilitate providing a specific error message to the
* user -- to quote POSIX: "option arguments may not be optional".
*
* If the next argument is not an ordinary option, a value less than 0
* is returned. These special values are given symbolic names in
* `opt.h' (`enum opt_return_values'):
*
* opt_dash -- the next argument is simply "-".
* opt_double_dash -- the next argument is simply "--".
*
* In either case, the argument ("-" or "--") is removed from
* argc/argv.
*
* opt_unknown -- the next argument does not match
* any known option.
*
* opt_bogus_argument -- a recognized long option was
* provided with an option argument,
* but the option does not accept
* an argument.
*
* opt_none -- the next argument does not begin
* with "-".
*
* In any of those cases, argc/argv is not modified.
*/
int
opt_low (t_uchar ** opt_string,
t_uchar ** opt_arg,
struct opt_desc ** desc,
struct opt_desc * opts,
int * argcp,
char ** argv,
t_uchar * opt_string_space)
{
int argc;
int choice;
t_uchar ** argvx;
argc = *argcp;
argvx = (t_uchar **)argv;
if (opt_string)
*opt_string = 0;
if (opt_arg)
*opt_arg = 0;
if (desc)
*desc = 0;
if (argc == 0)
return opt_none;
if (!argvx[1])
return opt_none;
if (argvx[1][0] != '-')
return opt_none;
if (argvx[1][1] != '-')
{
/* short option */
if (opt_string)
{
opt_string_space[0] = '-';
opt_string_space[1] = argvx[1][1];
opt_string_space[2] = 0;
*opt_string = opt_string_space;
}
if (!argvx[1][1])
{
opt_shift (argcp, argv);
return opt_dash;
}
for (choice = 0; opts[choice].opt_value >= 0; ++choice)
{
if (opts[choice].char_name)
{
if (opts[choice].char_name[0] == argvx[1][1])
{
if (desc)
*desc = &opts[choice];
if (!opts[choice].requires_arg)
{
/* character option, no option argument. */
opt_shift_char_option (argcp, argv);
return opts[choice].opt_value;
}
if (argvx[1][2])
{
/* character option, with argument, "-xargument". */
if (opt_arg)
*opt_arg = argvx[1] + 2;
opt_shift (argcp, argv);
return opts[choice].opt_value;
}
if (argc > 2)
{
/* character option, with argument, "-x argument". */
if (opt_arg)
*opt_arg = argvx[2];
opt_shift (argcp, argv); /* shift away "-x" */
opt_shift (argcp, argv); /* shift away "argument" */
return opts[choice].opt_value;
}
{
/* character option, missing argument. */
if (opt_arg)
*opt_arg = 0;
return opts[choice].opt_value;
}
}
}
}
/* unrecognized character option */
return opt_unknown;
}
else
{
/* long option */
t_uchar * opt_name;
t_uchar * opt_end;
int opt_length;
t_uchar * eq_arg;
if (!str_cmp (argvx[1], "--"))
{
if (opt_string)
*opt_string = "--";
opt_shift (argcp, argv);
return opt_double_dash;
}
if (opt_string)
*opt_string = argvx[1];
opt_name = argvx[1] + 2;
eq_arg = str_chr_index (opt_name, '=');
if (eq_arg)
opt_end = eq_arg;
else
opt_end = opt_name + str_length (opt_name);
opt_length = opt_end - opt_name;
for (choice = 0; opts[choice].opt_value >= 0; ++choice)
{
t_uchar * spec_end;
int spec_length;
if (!opts[choice].long_name)
continue;
spec_end = str_chr_index (opts[choice].long_name, '=');
if (!spec_end)
spec_end = str_chr_index (opts[choice].long_name, ' ');
if (spec_end)
spec_length = spec_end - opts[choice].long_name;
else
spec_length = str_length (opts[choice].long_name);
if ( (opt_length <= spec_length)
&& !str_cmp_n (opt_name, opt_length, opts[choice].long_name, spec_length)
&& ( (opt_length == spec_length)
|| char_is_space(opts[choice].long_name[opt_length])
|| ('=' == opts[choice].long_name[opt_length])))
{
if (desc)
*desc = &opts[choice];
if (!opts[choice].requires_arg)
{
/* long option, no argument expected */
if (eq_arg)
{
/* command line contained an argument */
return opt_bogus_argument;
}
opt_shift (argcp, argv);
return opts[choice].opt_value;
}
/* long option, argument expected */
if (eq_arg)
{
/* --foo=bar */
if (opt_arg)
*opt_arg = eq_arg + 1;
opt_shift (argcp, argv);
return opts[choice].opt_value;
}
if (argc > 2)
{
/* --foo bar */
if (opt_arg)
*opt_arg = argvx[2];
opt_shift (argcp, argv);
opt_shift (argcp, argv);
return opts[choice].opt_value;
}
{
/* --foo, missing argument. */
if (opt_arg)
*opt_arg = 0;
return opts[choice].opt_value;
}
}
}
/* unrecognized long option */
return opt_unknown;
}
}
int
opt (alloc_limits limits,
struct opt_parsed ** parsed_p,
struct opt_desc * opts,
int * argcp,
char ** argv)
{
t_uchar * opt_string;
t_uchar * arg_string;
t_uchar arg_string_space[MB_LEN_MAX * 3];
struct opt_desc * desc;
int o;
struct opt_parsed * parsed;
o = opt_low (&opt_string, &arg_string, &desc, opts, argcp, argv, arg_string_space);
parsed = (struct opt_parsed *)lim_malloc_contiguous (limits,
sizeof (struct opt_parsed),
offsetof (struct opt_parsed, opt_string),
str_length (opt_string) + 1,
offsetof (struct opt_parsed, arg_string),
str_length (arg_string) + 1,
LIM_END);
if (!parsed)
return opt_allocation_failure;
parsed->opt_value = o;
if (!opt_string)
parsed->opt_string = 0;
else
str_cpy (parsed->opt_string, opt_string);
if (!arg_string)
parsed->arg_string = 0;
else
str_cpy (parsed->arg_string, arg_string);
parsed->desc = desc;
if (*parsed_p)
lim_free (limits, (void *)*parsed_p);
*parsed_p = parsed;
return o;
}
/*(c opt_standard)
* int opt_standard (alloc_limits limits,
* struct opt_parsed ** parsed_p,
* struct opt_desc * opts,
* int * argcp,
* char * argvp[],
* t_uchar * program_name,
* t_uchar * usage,
* t_uchar * version_string,
* t_uchar * long_help,
* int help_option,
* int long_help_option,
* int version_option);
*
* This function calls `opt', but handles some commonly
* implemented options internally, in a standard way.
*
* The parameters `limits', `parsed_p', `opts', `argcp',
* and `argvp' are as to `opt_low'.
*
* The parameters `program_name', `usage' and `version_string' are
* used in error and informational messages printed by this function.
*
* The parameters `help_option', `long_help_option' and `version_option'
* are the `name' value for some standard options, which are
* usually given the command line names:
*
* -h --help help_option
* -H long_help_option
* -V --version version_option
*
* If this function detects the `help_option', it prints a
* multi-line message summarizing the available options
* to standard output and exits the process with a 0 status.
*
* If this function detects the `long_help_option', it prints a
* multi-line message summarizing the available options
* to standard output, adds text from `long_help' and exits the
* process with a 0 status. The first line of `long_help' should
* summarize the command (conventionally uncapitalized and without
* punctuation). The rest of `long_help' should expand upon the
* explanation. Long help messages are formatted as:
*
* first line of long_help
* usage: program_name ....
* option list
*
* rest of long_help
*
* If this function detects the `version_option', it prints a
* single-line message containing `version_string'
* to standard output and exits the process with a 0 status.
*
* If this function detects an unrecognized option, a missing option
* argument, or an argument provided for an option that doesn't accept
* arguments, it prints a usage message to standard error and exits
* the process with a non-0 status.
*/
int
opt_standard (alloc_limits limits,
struct opt_parsed ** parsed_p,
struct opt_desc * opts,
int * argc,
char * argv[],
t_uchar * program_name,
t_uchar * usage,
t_uchar * version_string,
t_uchar * long_help,
int help_option,
int long_help_option,
int version_option)
{
int o;
o = opt (limits, parsed_p, opts, argc, argv);
if (o == opt_none)
return o;
if ((*parsed_p)->desc && (*parsed_p)->desc->requires_arg && !(*parsed_p)->arg_string)
{
safe_printfmt (2, "missing argument for `%s'\n", (*parsed_p)->opt_string);
opt_usage (2, argv[0], program_name, usage, 1);
panic_exit ();
}
if (o == help_option)
{
if (long_help)
{
t_uchar * nl;
nl = str_chr_index (long_help, '\n');
if (nl)
{
safe_printfmt (1, "%.*s\n", (int)(nl - long_help), long_help);
}
}
opt_usage (1, argv[0], program_name, usage, 0);
safe_printfmt (1, "\n");
opt_help (1, opts);
safe_printfmt (1, "\n");
exit (0);
}
if (o == long_help_option)
{
t_uchar * nl;
nl = str_chr_index (long_help, '\n');
if (nl)
safe_printfmt (1, "%.*s\n", (int)(nl - long_help), long_help);
opt_usage (1, argv[0], program_name, usage, 0);
safe_printfmt (1, "\n");
opt_help (1, opts);
safe_printfmt (1, "\n");
if (nl)
safe_printfmt (1, "%s", nl + 1);
else
safe_printfmt (1, "%s", long_help);
safe_printfmt (1, "\n");
exit (0);
}
if (o == version_option)
{
safe_printfmt (1, "%s %s\n", program_name, version_string);
exit (0);
}
if (o == opt_allocation_failure)
{
panic ("allocation failure while parsing arguments");
}
switch (o)
{
case opt_dash:
return o;
case opt_double_dash:
return o;
case opt_unknown:
safe_printfmt (2, "unhandled option `%s'\n", argv[1]);
opt_usage (2, argv[0], program_name, usage, 1);
panic_exit ();
case opt_bogus_argument:
safe_printfmt (2, "option `%s' does not accept an argument\n", argv[1]);
opt_usage (2, argv[0], program_name, usage, 1);
panic_exit ();
default:
if (o < 0)
{
safe_printfmt (2, "illegal option code %d for `%s'\n", o, argv[1]);
panic ("internal error processing options");
}
return o;
}
}
/************************************************************************
*(h1 "Printing Help Messages")
*
*/
/*(c opt_usage)
* void opt_usage (int fd,
* char * argv0,
* t_uchar * program_name,
* t_uchar * usage,
* int suggest_help);
*
* Print a usage message on `fd' using the format:
*
* "usage: %s %s\n", program_name, usage
*
* If `suggest_help' is not 0, also print:
*
* "try %s --help\n", argv0
*
* By convention, `argv0' should be the first command line string
* passed to the program (the name by which the program was invoked).
*
* `program_name' should be the default name for the program (not
* necessarily the name by which it was invoked).
*/
void
opt_usage (int fd,
char * argv0,
t_uchar * program_name,
t_uchar * usage,
int suggest_help)
{
safe_printfmt (fd, "usage: %s %s\n", program_name, usage);
if (suggest_help)
safe_printfmt (fd, "try %s --help\n", argv0);
}
/*(c opt_help)
* void opt_help (int fd, struct opt_desc * opts);
*
* Print a help message based on `opts'.
*
* A long option name in `opts' can have the form "--foo=BAR".
* The text "=BAR" is included in the help message.
*/
void
opt_help (int fd, struct opt_desc * opts)
{
int x;
int max_char_opt_len;
int max_long_opt_len;
int max_desc_len;
int line_count;
int desc_column;
t_uchar * buf;
t_uchar * occ_buf;
t_uchar * occ_this;
t_uchar * occ_next;
max_long_opt_len = 0;
max_char_opt_len = 0;
max_desc_len = 0;
line_count = 0;
for (x = 0; opts[x].opt_value >= 0; ++x)
{
int q;
if (opts[x].char_name)
{
q = 1 + str_length (opts[x].char_name);
if (q > max_char_opt_len)
max_char_opt_len = q;
}
if (opts[x].long_name)
{
q = 2 + str_length (opts[x].long_name);
if (q > max_long_opt_len)
max_long_opt_len = q;
}
if (opts[x].desc)
{
occ_buf = occ_this = occ_next = str_save(0, opts[x].desc);
while ((occ_this = str_separate(&occ_next, "\n")) != NULL)
{
q = str_length (occ_this);
if (q > max_desc_len)
max_desc_len = q;
line_count++;
}
lim_free(0, occ_buf);
}
}
desc_column = 2 + max_char_opt_len + 2 + max_long_opt_len + 2;
buf = (t_uchar *)alloca ((desc_column + max_desc_len) * line_count + 1);
for (x = 0; opts[x].opt_value >= 0; ++x)
{
int column;
int amt;
buf[0] = 0;
str_cat (buf, " ");
if (opts[x].char_name)
{
str_cat (buf, "-");
str_cat (buf, opts[x].char_name);
if (opts[x].long_name)
str_cat (buf, ", ");
}
column = str_length (buf);
if (opts[x].long_name)
{
str_cat (buf, "--");
str_cat (buf, opts[x].long_name);
column = str_length (buf);
}
while (column < desc_column)
buf[column++] = ' ';
buf[column] = 0;
if (opts[x].desc)
{
occ_buf = occ_this = occ_next = str_save(0, opts[x].desc);
while ((occ_this = str_separate(&occ_next, "\n")) != NULL)
{
str_cat (buf, occ_this);
if (occ_next != NULL)
{
size_t l;
str_cat (buf, "\n");
l = str_length (buf);
mem_set (buf + l, ' ', desc_column);
buf[l + desc_column] = 0;
}
}
lim_free(0, occ_buf);
}
str_cat (buf, "\n");
amt = str_length (buf);
safe_write_retry (fd, buf, amt);
}
}
/************************************************************************
*(h1 "Shifting Arguments")
*
*/
/*(c opt_shift_char_option)
* void opt_shift_char_option (int * argcp, char ** argv);
*
* Remove a single character argument from argc/argv. For example,
*
* Before:
*
* *argcp = 3
* argv = {"prog", "-abc", "-def", 0}
*
* After:
*
* *argcp = 3
* argv = {"prog", "-bc", "-def", 0}
*
* Before:
*
* *argcp = 3
* argv = {"prog", "-a", "-def", 0}
*
* After:
*
* *argcp = 2
* argv = {"prog", "-def", 0}
*
*/
void
opt_shift_char_option (int * argcp, char ** argv)
{
t_uchar ** argvx;
argvx = (t_uchar **)argv;
if (!argvx[1][2])
opt_shift (argcp, argv);
else
mem_move (argvx[1] + 1, argvx[1] + 2, 1 + str_length (argvx[1] + 2));
}
/*(c opt_shift)
* void opt_shift (int * argcp, char ** argv);
*
* Remove a single argument from argc/argv. For example,
*
* Before:
*
* *argcp = 3
* argv = {"prog", "--abc", "-def", 0}
*
* After:
*
* *argcp = 2
* argv = {"prog", "-def", 0}
*
*/
void
opt_shift (int * argcp, char ** argv)
{
int argc;
int x;
t_uchar ** argvx;
argc = *argcp;
argvx = (t_uchar **)argv;
--argc;
for (x = 1; x < argc; ++x)
argvx[x] = argvx[x + 1];
argvx[x] = 0;
*argcp = argc;
}
#if 0
/************************************************************************
*(h1 "An Example of Option Parsing ")
*
* The following program illustrates option parsing with `opt'.
* These examples illustrates its usage:
*
* % test --version
* test 1.0.0
*
* % test --help
* usage: ./,test [options] [input-file]
*
* -h, --help Display a help message and exit.
* -V, --version Display a release identifier string
* and exit.
* -o file, --output-file=file Write output to FILE.
*
* % ./,test -o one --output-file two -othree --output-file=four
* output file argument: `one'
* output file argument: `two'
* output file argument: `three'
* output file argument: `four'
*
* % ./,test -xyzzy
* unhandled option `-xyzzy'
* usage: ./,test [options] [input-file]
* try ./,test --help
*
*
* Here's the code:
*
insert*/
#include "hackerlab/cmd/main.h"
static t_uchar * program_name = "test";
static t_uchar * usage = "[options]";
static t_uchar * version_string = "1.0";
#define OPTS(OP, OP2) \
OP (opt_help_msg, "h", "help", 0, \
"Display a help message and exit.") \
OP (opt_version, "V", "version", 0, \
"Display a release identifier string") \
OP2 (opt_version, 0, 0, 0, "and exit.") \
OP (opt_output_file, "o file", "output-file=file", 1, \
"Write output to FILE.")
enum options
{
OPTS (OPT_ENUM, OPT_IGN)
};
struct opt_desc opts[] =
{
OPTS (OPT_DESC, OPT_DESC)
{-1, 0, 0, 0, 0}
};
int
main (int argc, char * argv[])
{
int errn;
int o;
struct opt_parsed * option;
option = 0;
while (1)
{
o = opt_standard (lim_use_must_malloc,
&option,
opts,
&argc, argv,
program_name,
usage,
version_string,
opt_help_msg,
opt_version);
if (o == opt_none)
break;
switch (o)
{
default:
safe_printfmt (2, "unhandled option `%s'\n",
option->opt_string);
panic ("internal error parsing arguments");
usage_error:
opt_usage (2, argv[0], program_name, usage, 1);
panic_exit ();
bogus_arg:
safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n",
option->opt_string,
option->arg_string);
goto usage_error;
case opt_output_file:
if (!opt_arg)
{
printfmt (&errn,
2,
"missing argument for `%s'\n",
opt_string);
goto usage_error;
}
printfmt (&errn,
2,
"output file argument: `%s'\n",
opt_arg);
break;
}
}
return 0;
}
/*end-insert
*/
#endif
syntax highlighted by Code2HTML, v. 0.9.1