Logo Search packages:      
Sourcecode: bazaar version File versions

opt.c

/* 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

Generated by  Doxygen 1.6.0   Back to index