Re: glib parameter handling



Hi,

Appended is my current draft on this problem, which I like parts of
and really hate other parts of. It doesn't actually work, there are
little unimplemented bits. ;-)

I don't think we can get this feature in 2.0, so, lots of time to
think about alternatives.

One big-picture choice is between a huge try-to-do-everything approach
like the one I have drafted here, and a small getopt-type thing like
yours.

Havoc

#include <glib.h>

/* See notes at http://mail.gnome.org/pipermail/gtk-devel-list/2000-May/003357.html */

/* Note that the translation domain mechanism requires the use of gettext.
 * which sort of means we need gettext to work on Windows and other
 * glib targets.
 */

/* Note: handle "--" for end of options */


/* GOptionParser should really be a GObject, but we have a dependency
 * problem with that. (Why is GObject in a separate lib anyway?)
 *
 * Object data could in particular be used to destroy "static"
 * options, when the parser goes away.
 *
 * Object data is probably more useful than callback data, also,
 * since callback data is a pain to stick in static structs.
 * 
 */
typedef struct _GOptionParser GOptionParser;

#define G_OPTION_ERROR g_option_error_quark ()

GQuark g_option_error_quark (void);

typedef enum
{
  /* Missing arg on an option that requires an arg */
  G_OPTION_ERROR_MISSING_ARG,
  /* Couldn't parse an arg for an option with an arg */
  G_OPTION_ERROR_BAD_ARG,
  /* Don't know about this option */
  G_OPTION_ERROR_UNKNOWN_OPTION,
  /* Non-option args aren't allowed in argv,
   * and there were some.
   */
  G_OPTION_ERROR_EXTRA_ARGS,
  /* Option that can only appear once appeared twice */
  G_OPTION_ERROR_DUPLICATE
} GOptionError;

typedef enum
{
  G_OPTION_SINGLE_DASH    = 1 << 0,  /* allow single-dash long arg */
  G_OPTION_NO_DOUBLE_DASH = 1 << 1,  /* don't allow double-dash long arg,
                                      * only useful for legacy code
                                      */
  G_OPTION_UNDOCUMENTED   = 1 << 2,  /* don't include the option in
                                      * help output
                                      */
  G_OPTION_ARG_OPTIONAL   = 1 << 3,  /* OK if the int/string/etc. arg
                                      * is not present. default to
                                      * 0/0.0/NULL
                                      */
  G_OPTION_ARG_REQUIRED   = 1 << 4,  /* Force arg to be present;
                                      * mutually exclusive with ARG_OPTIONAL
                                      */
  G_OPTION_ONLY_DOCUMENT  = 1 << 5,  /* Don't parse this option,
                                      * but do put it in the help output.
                                      * Useful if you intend to parse the
                                      * option yourself.
                                      */
  G_OPTION_NO_DUPLICATES  = 1 << 6   /* Set error if this option appears
                                      * more than once
                                      */
} GOptionFlags;

typedef struct _GOptionName GOptionName;

struct _GOptionName
{
  const gchar *long_name;
  gchar short_name;
};

typedef enum
{
  /* Force POSIX (no options after args). POSIX is normally used
   * anyway if POSIXLY_CORRECT or POSIX_ME_HARDER env
   * variables are set.
   */
  G_OPTION_POSIX = 1 << 0,

  /* Print errors to stderr */
  G_OPTION_PRINT_ERRORS = 1 << 1,
  
  /* Exit with a message on error */
  G_OPTION_FATAL_ERRORS = 1 << 2,

  /* Don't allow non-option arguments in argv (set error if found) */
  G_OPTION_OPTIONS_ONLY = 1 << 3,

  /* Allow unknown options without setting error */
  G_OPTION_ALLOW_UNKNOWN_OPTIONS = 1 << 4,

  /* Strip out options that we understood (not unknown options) */
  G_OPTION_STRIP = 1 << 5
  
} GOptionParseFlags;

typedef void (* GOptionPrePostCallback) (GOptionParser *parser,
                                         gpointer       user_data);

typedef void (* GOptionCallback) (GOptionParser    *parser,
                                  const gchar      *long_name,
                                  gchar             short_name,
                                  const gchar      *option_arg,
                                  gpointer          user_data,
                                  GError          **error);

typedef void (* GOptionArgCallback) (GOptionParser *parser,
                                     const gchar      *option_arg,
                                     gpointer          user_data,
                                     GError          **error);

GOptionParser* g_option_parser_new   (void);
void           g_option_parser_ref   (GOptionParser *parser);
void           g_option_parser_unref (GOptionParser *parser);

void g_option_parser_set_arg_callback (GOptionParser   *parser,
                                       GOptionArgCallback  callback,
                                       gpointer         user_data);

void g_option_parser_push_table_name (GOptionParser *parser,
                                      const gchar   *table_name);
void g_option_parser_pop_table_name  (GOptionParser *parser);

void g_option_parser_push_translation_domain (GOptionParser *parser,
                                              const gchar   *domain);
void g_option_parser_pop_translation_domain  (GOptionParser *parser);

void g_option_parser_add_conflict_set (GOptionParser *parser,
                                       const GOptionName *options_that_conflict,
                                       gboolean is_static);

/* Of course we need to use GClosure, but it's in libgobject */

void g_option_parser_add_alias       (GOptionParser   *parser,

                                      /* FIXME */

                                      /* Instead of using the
                                       * long/short pair to refer to
                                       * an option, we could use a
                                       * single string "--foo", "-f",
                                       * etc. Could even do this
                                       * in GOptionTemplate, and allow
                                       * as many names as you want in an
                                       * array of names there.
                                       */
                                      
                                      const gchar     *alias_name,
                                      gchar            alias_short_name,
                                      GOptionFlags     flags,
                                      const gchar     *target_long_name,
                                      gchar            target_short_name);
void g_option_parser_add_option      (GOptionParser   *parser,
                                      const gchar     *long_name,
                                      gchar            short_name,
                                      GOptionFlags     flags,
                                      GOptionCallback  callback,
                                      gpointer         user_data,
                                      const gchar     *description,
                                      const gchar     *arg_description,
                                      gboolean         is_static);

void g_option_parser_add_pre_callback  (GOptionParser          *parser,
                                        GOptionPrePostCallback  callback,
                                        gpointer                user_data);
void g_option_parser_add_post_callback (GOptionParser          *parser,
                                        GOptionPrePostCallback  callback,
                                        gpointer                user_data);


/* If argc is NULL, assume argv is NULL-terminated.
 * 
 * argv gets shrunk if G_OPTION_STRIP is specified and options are
 * seen
 * (If you dynamically allocated argv, you probably need to save
 * a non-shrunk copy of it to free...)
 * 
 * If args is non-NULL, return non-option args, if flags
 * allow those
 *
 * If unknown_options is non-NULL, return unknown option args,
 * if flags allow those
 *
 * If G_OPTION_STRIP is given, then unknown options and args
 * are left in argc/argv, instead of separated and put in
 * args and unknown_args. So args/unknown_options should always
 * be NULL if you specify G_OPTION_STRIP.
 */
gboolean g_option_parser_parse     (GOptionParser    *parser,
                                    GOptionParseFlags flags,
                                    gint             *argc,
                                    gchar          ***argv,
                                    gchar          ***args,
                                    gchar          ***unknown_options,
                                    GError          **err);


/* long help text */
gchar*   g_option_parser_get_help  (GOptionParser *parser);

/* short usage string */
gchar*   g_option_parser_get_usage (GOptionParser *parser);

/* Add default help options table (-?, --help, --usage) */
void g_option_parser_add_help (GOptionParser *parser);




/* Convenience template stuff */   


typedef enum
{
  G_OPTION_TERMINATOR,         /* terminate the option table */
  
  G_OPTION_CALLBACK,           /* type_data is a GOptionCallback to
                                * use for this GOption table.
                                * user_data is callback user data.
                                */

  G_OPTION_PRE_CALLBACK,       /* type_data is a GOptionPrePostCallback to
                                * invoke before parsing options.
                                * user_data is callback user data.
                                */

  G_OPTION_POST_CALLBACK,      /* type_data is a GOptionPrePostCallback to
                                * invoke after parsing options.
                                * user_data is callback user data.
                                */
  
  G_OPTION_TRANSLATION_DOMAIN, /* type_data is a const char *
                                * translation domain
                                */

  G_OPTION_ALIAS,              /* type_data is a string name of an option
                                * this option is an alias for. All other
                                * elements of this GOption are ignored,
                                * it's as if the other option had been
                                * seen. If the string contains a single
                                * char, it's assumed to be a short name,
                                * otherwise a long name.
                                */
  
  /* The rest of these fill *type_data with some value.
   * If type_data is NULL, then they don't. A standard on/off switch
   * option is G_OPTION_BOOL.
   */

  G_OPTION_DATA,      /* type_data is a gpointer * to fill with user_data */
  
  G_OPTION_BOOLEAN,  /* type_data is a gboolean * to fill with TRUE if
                      * the option is seen
                      */
  G_OPTION_STRING,   /* type_data is a gchar ** to fill with string
                      * value of the option
                      */
  G_OPTION_INT,      /* type_data is an int * to fill with int value
                      * of the option
                      */
  G_OPTION_LONG,     /* type_data is a glong * to fill with long value
                      * of the option
                      */
  G_OPTION_DOUBLE    /* type_data is a gdouble * to fill with float value
                      * of the option
                      */

} GOptionType;


typedef struct _GOptionTemplate GOptionTemplate;

struct _GOptionTemplate
{
  GOptionType type;
  const gchar *long_name;
  gchar short_name;
  gpointer type_data; /* depends on GOptionType */
  gpointer user_data;
  const gchar *description;
  const gchar *argument_description;
  GOptionFlags flags;
};

void g_option_parser_add_auto_option (GOptionParser   *parser,
                                      const gchar     *long_name,
                                      gchar            short_name,
                                      GOptionType      type,
                                      GOptionFlags     flags,
                                      gpointer         address_of_value,
                                      const gchar     *description,
                                      const gchar     *arg_description,
                                      gboolean         is_static);

void g_option_parser_add_table       (GOptionParser   *parser,
                                      const GOptionTemplate   *option_table,
                                      const gchar     *table_name,
                                      /* Don't copy any of the fields */
                                      gboolean         is_static);


/* If you just have a simple program with a single table, you can use
 * this. If you need flexible, do more typing. ;-) Automatically does
 * g_option_parser_add_help()
 */
void g_option_parse_simple (const gchar           *table_name,
                            const GOptionTemplate *table,
                            GOptionParseFlags      flags,
                            gint                  *argc,
                            gchar               ***argv,
                            gchar               ***args,
                            GError               **err);



#include "gargparser.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define _(x) x

typedef struct _GOptionEntry GOptionEntry;

struct _GOptionParser
{
  guint ref_count;

  GSList *pre_callbacks;
  GSList *post_callbacks;
  
  GSList *options;

  /* We optimize speed/mem for building the parser, and let actually
   * parsing options be slow, since there will only be a few options
   * on the average command line. That's why there's no hash table
   * here as you might expect.
   */
   
  GArray *entries;
  
  GOptionArgCallback arg_callback;
  gpointer arg_callback_data;
};


GOptionParser*
g_option_parser_new (void)
{
  GOptionParser *parser;

  parser = g_new (GOptionParser, 1);

  parser->ref_count = 1;

  parser->options = NULL;
  
  return parser;
}

void
g_option_parser_ref (GOptionParser *parser)
{
  g_return_if_fail (parser->ref_count > 0);

  parser->ref_count += 1;
}

void
g_option_parser_unref (GOptionParser *parser)
{
  g_return_if_fail (parser->ref_count > 0);

  parser->ref_count -= 1;

  if (parser->ref_count == 0)
    {
      /* FIXME */
      
    }
}

struct _GOptionEntry
{
  GOptionEntry *alias_target;
  
  gchar *long_name;

  GOptionCallback callback;

  gpointer type_data;
  
  gpointer user_data;

  GOptionFlags flags;
  
  gchar *description;
  gchar *arg_description;
  
  gchar short_name;

  guint is_static : 1;

  guint already_seen : 1;
};

void
g_option_parser_add_option (GOptionParser   *parser,
                            const gchar     *long_name,
                            gchar            short_name,
                            GOptionFlags     flags,
                            GOptionCallback  callback,
                            gpointer         user_data,
                            const gchar     *description,
                            const gchar     *arg_description,
                            gboolean         is_static)
{
  GOptionEntry entry = {
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    0,
    NULL,
    NULL,
    '\0',
    FALSE,
    FALSE
  };
  
  g_return_if_fail (parser != NULL);

  if (parser->entries == NULL)
    parser->entries = g_array_new (FALSE, TRUE, sizeof (GOptionEntry));

  entry.flags = flags;
  entry.long_name = is_static ? long_name : g_strdup (long_name);
  entry.short_name = short_name;
  entry.callback = callback;
  entry.user_data = user_data;
  entry.description = is_static ? description : g_strdup (description);
  entry.arg_description = is_static ? arg_description : g_strdup (arg_description);
  entry.is_static = is_static;
  
  g_array_append_val (parser->entries, entry);  
}

/* in lookups, return NULL if the entry is G_OPTION_ONLY_DOCUMENT */

static GOptionEntry*
resolve_alias (GOptionParser *parser,
               GOptionEntry *entry)
{
  if (entry->alias_target)
    return resolve_alias (parser, entry->alias_target);
  else if ((entry->flags & G_OPTION_ONLY_DOCUMENT) != 0)
    return NULL;
  else
    return entry;
}

static GOptionEntry*
g_option_parser_lookup_single (GOptionParser *parser,
                               const gchar   *arg)
{
  gint i;

  if (parser->entries == NULL)
    return NULL;

  i = 0;
  while (i < parser->entries->len)
    {
      GOptionEntry *entry = & g_array_index (parser->entries,
                                             GOptionEntry,
                                             i);

      if ((entry->flags & G_OPTION_SINGLE_DASH) != 0 &&
          entry->long_name && strcmp (entry->long_name, arg))
        return resolve_alias (parser, entry);
      
      ++i;
    }

  return NULL;
}

static GOptionEntry*
g_option_parser_lookup_double (GOptionParser *parser,
                               const gchar   *arg)
{
  gint i;

  if (parser->entries == NULL)
    return NULL;

  i = 0;
  while (i < parser->entries->len)
    {
      GOptionEntry *entry = & g_array_index (parser->entries,
                                             GOptionEntry,
                                             i);

      if ((entry->flags & G_OPTION_NO_DOUBLE_DASH) == 0 &&
          entry->long_name && strcmp (entry->long_name, arg))
        return resolve_alias (parser, entry);
      
      ++i;
    }

  return NULL;
}

static GOptionEntry*
g_option_parser_lookup_short (GOptionParser *parser,
                              gchar          arg)
{
  gint i;

  if (parser->entries == NULL)
    return NULL;

  i = 0;
  while (i < parser->entries->len)
    {
      GOptionEntry *entry = & g_array_index (parser->entries,
                                             GOptionEntry,
                                             i);

      if (entry->short_name && entry->short_name == arg)
        return resolve_alias (parser, entry);
      
      ++i;
    }

  return NULL;
}

typedef enum
{
  /* next arg is a required value for this option */
  OPTION_ARG_NEXT_REQUIRED,
  /* next arg is a value for this option, if it exists */
  OPTION_ARG_NEXT_OPTIONAL,
  /* we already have an arg */
  OPTION_ARG_PROVIDED,
  /* no arg */
  OPTION_ARG_NONE
} OptionArgType;

static GSList*
find_options (GOptionParser *parser,
              const gchar   *arg,
              GOptionFlags   flags,
              /* Option text we didn't understand */
              gchar        **unhandled,
              OptionArgType *arg_type,
              gchar        **option_arg,
              GError       **err)
{
  GOptionEntry *entry;
  const gchar *arg_delim;
  gchar *lookup;
  GSList *entries = NULL;
  
  g_assert (arg[0] == '-');
  g_assert (arg[1] != '\0'); /* should not have treated '-' as an option */

  *arg_type = OPTION_ARG_NONE;
  
  arg_delim = strchr (arg, '=');
  if (arg_delim == NULL)
    arg_delim = strchr (arg, ' '); /* not likely from the shell */
  
  if (arg_delim)
    lookup = g_strndup (arg, arg_delim - arg);
  else
    lookup = g_strdup (arg);

  if (unhandled)
    *unhandled = NULL;
  
  if (arg[1] == '-')
    {
      g_assert(arg[2] != '\0'); /* we already filtered "--" by itself */
      
      /* Check for long arg */
      entry = g_option_parser_lookup_double (parser, lookup);

      if (entry == NULL)
        {
          if (unhandled)
            *unhandled = g_strdup (arg);
          
          goto out;
        }
      else
        {              
          entries = g_slist_prepend (entries, entry);

          if (arg_delim == NULL)
            {
              if ((entry->flags & G_OPTION_ARG_OPTIONAL) != 0)
                *arg_type = OPTION_ARG_NEXT_OPTIONAL;
              else if ((entry->flags & G_OPTION_ARG_REQUIRED) != 0)
                *arg_type = OPTION_ARG_NEXT_REQUIRED;
            }
          
          goto out;
        }
    }
  else
    {
      /* First try it as a single-dash long option, then as a collection
       * of single char options
       */
      gint i;
      gint n_leftover = 0;
      gchar arg_option = '\0';
      
      entry = g_option_parser_lookup_single (parser, &arg[1]);

      if (entry)
        {          
          entries = g_slist_prepend (entries, entry);

          if (arg_delim == NULL)
            {
              if ((entry->flags & G_OPTION_ARG_OPTIONAL) != 0)
                *arg_type = OPTION_ARG_NEXT_OPTIONAL;
              else if ((entry->flags & G_OPTION_ARG_REQUIRED) != 0)
                *arg_type = OPTION_ARG_NEXT_REQUIRED;
            }
          
          goto out;
        }
      
      i = 1;
      while (arg[i] && arg != arg_delim)
        {
          entry = g_option_parser_lookup_short (parser, arg[i]);

          if (entry)
            {
              entries = g_slist_prepend (entries, entry);

              if ((entry->flags & (G_OPTION_ARG_OPTIONAL | G_OPTION_ARG_REQUIRED)) != 0)
                arg_option = arg[i];
              
              if (arg_delim == NULL)
                {
                  if ((entry->flags & G_OPTION_ARG_OPTIONAL) != 0)
                    *arg_type = OPTION_ARG_NEXT_OPTIONAL;
                  else if ((entry->flags & G_OPTION_ARG_REQUIRED) != 0)
                    *arg_type = OPTION_ARG_NEXT_REQUIRED;
                }
            }
          else
            {
              if (unhandled)
                {
                  ++n_leftover;
                  /* - plus options plus nul */
                  *unhandled = g_realloc (*unhandled, n_leftover + 2);
                  (*unhandled)[0] = '-';
                  (*unhandled)[n_leftover + 1] = '\0';
                  (*unhandled)[n_leftover] = arg[i];
                }

              /* This has the somewhat odd effect that we can strip
               * "-cgfqa" to "-cq" or something, and then someone
               * after us could parse "-cq" as a long option called "cq",
               * but since that case involves multiple levels of crack
               * smoking we aren't going to try to work around it
               */
            }
          
          ++i;
        }

      /* For short options, we don't allow arguments if multiple
       * short options are merged in a block
       */
      if (arg_option != '\0' && g_slist_length (entries) > 1)
        {
          if (unhandled)
            {
              g_free (*unhandled);
              *unhandled = g_strdup (arg);
            }

          g_slist_free (entries);
          entries = NULL;
          g_set_error (err,
                       G_OPTION_ERROR,
                       G_OPTION_ERROR_BAD_ARG,
                       _("Option '%c' can have an argument, which means it can't be mixed in a block of options such as '%s' - specify it by itself, i.e. '-%c'"),
                       arg_option, lookup, arg_option);
        }
      
      goto out;
    }

 out:
  if (entries && arg_delim)
    {
      *option_arg = g_strdup (arg_delim + 1);
      *arg_type = OPTION_ARG_PROVIDED;
    }
  
  g_free (lookup);

  return entries;
}

static gboolean
process_option (GOptionParser *parser,
                GOptionEntry  *entry,
                const gchar   *option_arg,
                GError       **error)
{
  if (entry->already_seen &&
      (entry->flags & G_OPTION_NO_DUPLICATES) != 0)
    {
      g_set_error (error,
                   G_OPTION_ERROR,
                   G_OPTION_ERROR_DUPLICATE,
                   _("Option 'FIXME' appears twice"));
      return FALSE;
    }
  
  if (entry->callback)
    {
      (* entry->callback) (parser,
                           entry->long_name,
                           entry->short_name,
                           option_arg,
                           entry->user_data,
                           error);
      if (error && *error)
        return FALSE;
    }
  
  return TRUE;
}

static void
strip_null_args (gint    *argc,
                 gchar ***argv)
{
  gint i, j, k;

  for (i = 1; i < *argc; i++)
    {
      for (k = i; k < *argc; k++)
        if ((*argv)[k] != NULL)
          break;
	  
      if (k > i)
        {
          k -= i;
          for (j = i + k; j < *argc; j++)
            (*argv)[j-k] = (*argv)[j];
          *argc -= k;
        }
    }
}

gboolean
g_option_parser_parse (GOptionParser    *parser,
                       GOptionParseFlags flags,
                       gint             *argc,
                       gchar          ***argv,
                       gchar          ***args,
                       gchar          ***unknown_options,
                       GError          **err)
{
  GSList *arg_list = NULL;
  GSList *unknowns = NULL;    
  gint i;
  gboolean allow_options = TRUE;
  GError *real_err = NULL;
  gint real_argc;
  gchar **stripped_argv;
  
  g_return_val_if_fail (parser != NULL, FALSE);
  g_return_val_if_fail (argv != NULL, FALSE);

  /* FIXME set all already_seen flags to FALSE */
  
  /* FIXME call all pre callbacks */

  if (argc == NULL)
    {
      i = 0;
      while (argv[i])
        ++i;

      real_argc = i;
    }
  else
    real_argc = *argc;

  /* We strip a copy of argv, then copy it back
   * to the real argv, so that we leave argv
   * untouched if errors occur.
   */
  if ((flags & G_OPTION_STRIP) != 0)
    stripped_argv = g_new0 (gchar*, real_argc);
  else
    stripped_argv = NULL;
  
  i = 0;
  while (i < real_argc)
    {
      gchar *arg = (*argv)[i];

      if (allow_options && strcmp (arg, "--") == 0)
        {
          allow_options = FALSE;
          stripped_argv[i] = NULL;
        }
      else if (allow_options && arg[0] == '-' &&
               arg[1] != '\0')
        {
          GSList *entries;
          gchar *unhandled = NULL;
          OptionArgType arg_type;
          gchar *option_arg = NULL;
          
          entries = find_options (parser, arg,
                                  flags,
                                  &unhandled,
                                  &arg_type,
                                  &option_arg,
                                  &real_err);          

          /* put this here immediately so it will get
           * freed
           */
          if (stripped_argv)
            stripped_argv[i] = unhandled;

          if (real_err)
            {
              g_free (option_arg);
              g_slist_free (entries);
              goto error;
            }
          
          if (unhandled &&
              (flags & G_OPTION_ALLOW_UNKNOWN_OPTIONS) == 0)
            {
              g_set_error (&real_err,
                           G_OPTION_ERROR,
                           G_OPTION_ERROR_UNKNOWN_OPTION,
                           _("%s: unknown command line option '%s'"),
                           g_get_prgname (), unhandled);
              
              g_free (option_arg);
              g_slist_free (entries);
              goto error;
            }

          if (arg_type == OPTION_ARG_NEXT_REQUIRED ||
              arg_type == OPTION_ARG_NEXT_OPTIONAL)
            {
              g_assert (option_arg == NULL);
              
              if ((i + 1) < real_argc)
                {
                  option_arg = g_strdup ((*argv)[i + 1]);
                  ++i; /* eat the next arg */
                }
              else if (arg_type == OPTION_ARG_NEXT_REQUIRED)
                {
                  g_set_error (&real_err,
                               G_OPTION_ERROR,
                               G_OPTION_ERROR_MISSING_ARG,
                               _("%s: command line option '%s' requires an argument"),
                               g_get_prgname (), arg);
                  
                  g_slist_free (entries);
                  goto error;
                }
            }
          
          if (unhandled)
            unknowns = g_slist_prepend (unknowns, g_strdup (unhandled));
          
          if (entries)
            {
              GSList *tmp_list;
              
              tmp_list = entries;
              while (tmp_list != NULL)
                {
                  if (!process_option (parser, tmp_list->data,
                                       option_arg, &real_err))
                    {
                      g_free (option_arg);
                      g_slist_free (entries);
                      goto error;
                    }

                  tmp_list = g_slist_next (tmp_list);
                }
            }
        }
      else
        {
          if ((flags & G_OPTION_POSIX) != 0)
            allow_options = FALSE; /* no options after args */
          
          if ((flags & G_OPTION_OPTIONS_ONLY) != 0)
            {
              g_set_error (&real_err,
                           G_OPTION_ERROR,
                           G_OPTION_ERROR_EXTRA_ARGS,
                           _("%s: command line arguments are not allowed; problem argument is '%s'"),
                           g_get_prgname (), arg);

              goto error;
            }
          
          if (parser->arg_callback)
            (* parser->arg_callback) (parser,
                                      arg,
                                      parser->arg_callback_data,
                                      &real_err);

          if (real_err != NULL)
            goto error;

          if (args)
            arg_list = g_slist_prepend (arg_list, arg);
        }
      
      ++i;
    }
  
  if (stripped_argv)
    {
      i = 0;
      
      while (i < real_argc)
        {          
          if (stripped_argv[i])
            {
              g_assert (strlen ((*argv)[i]) <= strlen (stripped_argv[i]));
              strcpy ((*argv)[i], stripped_argv[i]);
            }
          else
            argv[i] = NULL;

          g_free (stripped_argv[i]);
          
          ++i;
        }

      strip_null_args (argc, argv);

      g_free (stripped_argv);
    }
  
  /* FIXME call all post callbacks */

  if (args)
    {
      arg_list = g_slist_reverse (arg_list);

      /* Fill in vector */
    }

  if (unknown_options)
    {
      unknowns = g_slist_reverse (unknowns);

      /* Fill in vector */
    }

  return TRUE;
  
 error:
  g_assert (real_err != NULL);
  
  g_slist_free (arg_list);
  g_slist_foreach (unknowns, (GFunc) g_free, NULL);
  g_slist_free (unknowns);

  if (stripped_argv)
    {
      i = 0;
      while (i < real_argc)
        {
          g_free (stripped_argv[i]);
          ++i;
        }
      g_free (stripped_argv);
    }
  
  if ((flags & G_OPTION_FATAL_ERRORS) != 0)
    {
      fprintf (stderr, "%s\n", real_err->message);
      exit (1);
    }
  else if ((flags & G_OPTION_PRINT_ERRORS) != 0)
    {
      fprintf (stderr, "%s\n", real_err->message);
    }
  
  g_propagate_error (err, real_err);
  
  return FALSE;
}

GQuark
g_option_error_quark (void)
{
  static GQuark eq = 0;
  if (eq == 0)
    eq = g_quark_from_static_string ("g-option-error-quark");

  return eq;
}

static void
auto_callback_BOOLEAN (GOptionParser    *parser,
                       const gchar      *long_name,
                       gchar             short_name,
                       const gchar      *option_arg,
                       gpointer          user_data,
                       GError          **error)
{
  gint i;
  gboolean *retloc;
  static const gchar *true_vals[] = { "1", "yes", "true", "y", "t" };
  static const gchar *false_vals[] = { "0", "no", "false", "n", "f" }; 

  retloc = user_data;
  
  i = 0;
  while (i < G_N_ELEMENTS (true_vals))
    {
      if (g_strcasecmp (option_arg, true_vals[i]) == 0)
        {
          *retloc = TRUE;
          return;
        }
      
      ++i;
    }

  i = 0;
  while (i < G_N_ELEMENTS (false_vals))
    {
      if (g_strcasecmp (option_arg, false_vals[i]) == 0)
        {
          *retloc = FALSE;
          return;
        }
      
      ++i;
    }
  
 parse_error:
  g_set_error (error,
               G_OPTION_ERROR,
               G_OPTION_ERROR_BAD_ARG,
               _("%s: '%s' is not a valid boolean value"),
               g_get_prgname (), option_arg);

}

static void
auto_callback_STRING (GOptionParser    *parser,
                      const gchar      *long_name,
                      gchar             short_name,
                      const gchar      *option_arg,
                      gpointer          user_data,
                      GError          **error)
{
  /* FIXME how does memory allocation work for the returned string? */
  
 parse_error:
  g_set_error (error,
               G_OPTION_ERROR,
               G_OPTION_ERROR_BAD_ARG,
               _("%s: '%s' is not a valid value"),
               g_get_prgname (), option_arg);
}

static void
auto_callback_INT (GOptionParser    *parser,
                   const gchar      *long_name,
                   gchar             short_name,
                   const gchar      *option_arg,
                   gpointer          user_data,
                   GError          **error)
{
  gchar *end = NULL;
  glong val;
  gint *retloc = user_data;

  errno = 0;
  val = strtol (option_arg, &end, 10);
  
  if (end == option_arg || errno == ERANGE)
    goto parse_error;

  if (val < (glong) - G_MAXINT || val > (glong) G_MAXINT)
    goto parse_error;

  *retloc = (gint) val;

  return;
  
 parse_error:
  g_set_error (error,
               G_OPTION_ERROR,
               G_OPTION_ERROR_BAD_ARG,
               _("%s: '%s' is not a valid integer value"),
               g_get_prgname (), option_arg);

}

static void
auto_callback_LONG (GOptionParser    *parser,
                    const gchar      *long_name,
                    gchar             short_name,
                    const gchar      *option_arg,
                    gpointer          user_data,
                    GError          **error)
{
  gchar *end = NULL;
  glong val;
  glong *retloc = user_data;

  errno = 0;
  val = strtol (option_arg, &end, 10);
  
  if (end == option_arg || errno == ERANGE)
    goto parse_error;

  *retloc = val;

  return;
  
 parse_error:
  g_set_error (error,
               G_OPTION_ERROR,
               G_OPTION_ERROR_BAD_ARG,
               _("%s: '%s' is not a valid integer value"),
               g_get_prgname (), option_arg);

}

static void
auto_callback_DOUBLE (GOptionParser    *parser,
                      const gchar      *long_name,
                      gchar             short_name,
                      const gchar      *option_arg,
                      gpointer          user_data,
                      GError          **error)
{
  gchar *end = NULL;
  gdouble val;
  gdouble *retloc = user_data;

  errno = 0;
  val = strtod (option_arg, &end);
  
  if (end == option_arg || errno == ERANGE)
    goto parse_error;

  *retloc = val;

  return;
  
 parse_error:
  g_set_error (error,
               G_OPTION_ERROR,
               G_OPTION_ERROR_BAD_ARG,
               _("%s: '%s' is not a valid floating point value"),
               g_get_prgname (), option_arg);

}


void
g_option_parser_add_auto_option (GOptionParser   *parser,
                                 const gchar     *long_name,
                                 gchar            short_name,
                                 GOptionType      type,
                                 GOptionFlags     flags,
                                 gpointer         address_of_value,
                                 const gchar     *description,
                                 const gchar     *arg_description,
                                 gboolean         is_static)
{
  GOptionCallback callback = NULL;
  
  switch (type)
    {      
    case G_OPTION_BOOLEAN:
      callback = auto_callback_BOOLEAN;
      break;
      
    case G_OPTION_STRING:
      callback = auto_callback_STRING;
      break;
      
    case G_OPTION_INT:
      callback = auto_callback_INT;
      break;
      
    case G_OPTION_LONG:
      callback = auto_callback_LONG;
      break;
      
    case G_OPTION_DOUBLE:
      callback = auto_callback_DOUBLE;
      break;

    default:
      g_warning ("%s: only the auto-arg-parser option types are allowed here, e.g. G_OPTION_BOOLEAN or G_OPTION_INT", G_STRLOC);
      break;
    }

  if (callback)
    {
      g_option_parser_add_option (parser,
                                  long_name,
                                  short_name,
                                  flags,
                                  callback,
                                  address_of_value,
                                  description,
                                  arg_description,
                                  is_static);
    }
}

void
g_option_parser_add_table (GOptionParser   *parser,
                           const GOptionTemplate   *option_table,
                           const gchar     *table_name,
                           gboolean         is_static)
{
  gint i;

  g_return_if_fail (parser != NULL);
  g_return_if_fail (option_table != NULL);
  
  i = 0;
  while (option_table[i].type != G_OPTION_TERMINATOR)
    {
      const GOptionTemplate *tmpl = &option_table[i];

      switch (tmpl->type)
        {  
        case G_OPTION_CALLBACK:
          /* FIXME */
          break;
          
        case G_OPTION_PRE_CALLBACK:
          /* FIXME */
          break;
          
        case G_OPTION_POST_CALLBACK:
          /* FIXME */
          break;
          
        case G_OPTION_TRANSLATION_DOMAIN:
          /* FIXME */
          break;
          
        case G_OPTION_ALIAS:
          {
            const gchar *target = tmpl->type_data;
            gboolean is_short;
              
            if (target == NULL)
              {
                g_warning ("Alias target in GOptionTemplate must be non-NULL");
                return;
              }
            
            is_short = strlen (target) < 2;
            
            g_option_parser_add_alias (parser,
                                       tmpl->long_name,
                                       tmpl->short_name,
                                       tmpl->flags,
                                       is_short ? NULL : target,
                                       is_short ? *target : '\0');
          }
          break;
          
        case G_OPTION_DATA:
          /* FIXME */
          
          break;
          
        case G_OPTION_BOOL:
        case G_OPTION_STRING:
        case G_OPTION_INT:
        case G_OPTION_LONG:
        case G_OPTION_DOUBLE:
          g_option_parser_add_auto_option (parser,
                                           tmpl->long_name,
                                           tmpl->short_name,
                                           tmpl->type,
                                           tmpl->flags,
                                           tmpl->type_data,
                                           tmpl->description,
                                           tmpl->argument_description,
                                           is_static);
          break;

        case G_OPTION_TERMINATOR:
          g_assert_not_reached ();
          break;
        }
  
      ++i;
    }

}










[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]