Re: arguement parsing in glib



Jeff Rose <rosejn Colorado EDU> writes:
> I would like to work on integrating/renaming popt into glib.  Its on the
> task list, but I wanted to make sure someone else hasn't already done it.
> 

I'm a bit later than I said sending you this, but here are some code
doodles I had related to this. It doesn't compile; in fact the .c file
is total nonsense, half of it works one way and half of it works
another way, I didn't finish rewriting it. But the header might be a
starting point. It is intended to be roughly similar to popt to make
conversion possible.

The design perhaps makes the GnomeUIInfo/popt mistake, keeping the
convenience templates around as data structures. This results in pain
all around; but is an efficiency hack that could be worth it. I don't
know.

I wonder if G_OPTION_STRIP should be an option flag, maybe instead it
should be a flag passed to g_option_parser_parse.

There are no doubt countless other issues, I didn't finish this code.

Havoc

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

#define G_OPTION_ERROR g_option_error_quark ()

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
} GOptionError;

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

  G_OPTION_TRANSLATION_DOMAIN, /* type_data is a const char *
                                * translation domain
                                */

  G_OPTION_IGNORED,            /* type_data is ignored; this option is valid,
                                * but not used. The table callback if any is
                                * not called. For use with e.g. legacy options
                                * that no longer make sense.
                                */

  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. If an option has an argument,
   * it is passed to the callback as arg_text
   */

  G_OPTION_DATA,      /* type_data is a gpointer * to fill with GOption::user_data */
  
  G_OPTION_BOOL,     /* 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 enum
{
  G_OPTION_SINGLE_DASH  = 1 << 0,  /* allow single-dash long arg */
  G_OPTION_UNDOCUMENTED = 1 << 1,  /* don't include the option in
                                    * help output
                                    */ 
  G_OPTION_STRIP        = 1 << 2,  /* strip the option out of the argv
                                    * vector
                                    */
  G_OPTION_ARG_OPTIONAL = 1 << 3,  /* OK if the int/string/etc. arg
                                    * is not present. default to
                                    * 0/0.0/NULL
                                    */
  G_OPTION_CALLBACK_PRE = 1 << 4,  /* Invoke callback in preparse phase */
  G_OPTION_CALLBACK_POST = 1 << 5  /* Invoke callback in postparse phase */
} GOptionFlags;

typedef struct _GOption GOption;

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

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

  /* Exit with a message on error */
  G_OPTION_FATAL_ERRORS = 1 << 1,

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

  /* Allow unknown options without setting error */
  G_OPTION_ALLOW_UNKNOWN_OPTIONS = 1 << 3
  
} GOptionParseFlags;

typedef enum
{
  /* Before parsing any options */
  G_OPTION_PHASE_PRE  = 1 << 0,
  /* After parsing all options */
  G_OPTION_PHASE_POST = 1 << 1,
  /* Callback invoked on an option, while parsing
   * options.
   */
  G_OPTION_PHASE_DURING = 1 << 2
} GOptionParsePhase;

typedef void (* GOptionCallback) (GOptionParser    *parser,
                                  GOptionParsePhase phase,
                                  const GOption    *option,
                                  const char       *arg_text,
                                  /* user data from the callback's GOption */
                                  gpointer          callback_data,
                                  GError          **error);

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

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


/* If is_static, the table and all its contents are assumed to be
 * static.  "static" just means "will last longer than the arg parser
 * so don't bother making a copy"
 */

GOptionTable* g_option_parser_add_table  (GOptionParser             *parser,
                                          const GOption             *option_table,
                                          const gchar               *table_name,
                                          gboolean                   is_static);

/* If argc is NULL, assume argv is NULL-terminated.
 * 
 * argv gets shrunk if G_OPTION_STRIP options are seen and removed.
 * (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 unknown_options is non-NULL, return unknown option args
 */
gboolean g_option_parser_parse     (GOptionParser    *parser,
                                    GOptionParseFlags flags,
                                    gint             *argc,
                                    gchar          ***argv,
                                    gchar          ***args,
                                    gchar          ***unknown_options,
                                    GError          **err);


/* If you just have a simple program with a single table,
 * you can use this. Prints usage/help and exits if there's
 * an error; if you need flexible, do more typing. ;-)
 * Automatically does g_option_parser_add_help() 
 */
void g_option_parse_simple (const gchar         *table_name,
                            const GOption       *table,
                            gint                *argc,
                            gchar             ***argv,
                            gchar             ***args);

/* 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 */
GOptionTable* g_option_parser_add_help (GOptionParser *parser);



#include "gargparser.h"

typedef struct _GOptionEntry GOptionEntry;
typedef struct _GOptionTable GOptionTable;

static GOptionTable* table_new        (const gchar         *name);
static GOptionEntry* table_add_option (GOptionTable        *table,
                                       gboolean             free_option,
                                       gchar               *translation_domain,
                                       const GOption       *callback,
                                       GOption             *single_option);
/* these can cast off const, sort of lame, but hey */
static void          table_add_domain (GOptionTable        *table,
                                       gboolean             make_copy,
                                       const gchar         *domain,
                                       gchar              **out);

static void parser_add_option (GOptionParser       *parser,
                               GOptionTable        *table,
                               gboolean             free_option,
                               gchar               *translation_domain,
                               const GOption       *callback,
                               GOption             *single_option);


struct _GOptionEntry
{
  GOption *option;
  const GOption *callback_option;
  const gchar *translation_domain;
  guint free_option : 1;
};

struct _GOptionTable
{
  gchar *name;

  /* Stored here so we can free them. */
  GSList *nonstatic_domains;

  GSList *entries;

};

struct _GOptionParser
{
  guint ref_count;

  GSList *tables;
  
  GHashTable *long_options;
  GHashTable *short_options;
};


static gint
g_unichar_equal (gconstpointer v1,
                 gconstpointer v2)
{
  return *((const gunichar*) v1) == *((const gunichar*) v2);
}

static guint
g_unichar_hash (gconstpointer v)
{
  return *(const gunichar*) v;
}

static gchar*
g_unichar_to_string (gunichar c, gchar *buf)
{
  gint len = g_unichar_to_utf8 (c, buf);
  buf[len] = '\0';
  return buf;
}

GOptionParser*
g_option_parser_new (void)
{
  GOptionParser *parser;

  parser = g_new (GOptionParser, 1);

  parser->ref_count = 1;

  parser->tables = g_hash_table_new (g_str_hash, g_str_equal);
  parser->long_options = g_hash_table_new (g_str_hash, g_str_equal);
  parser->short_options = g_hash_table_new (g_unichar_hash, g_unichar_equal);
  
  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 */
      
    }
}

static void
parser_add_option (GOptionParser       *parser,
                   GOptionTable        *table,
                   gboolean             free_option,
                   gchar               *translation_domain,
                   const GOption       *callback_option,
                   GOption             *single_option)
{
  GOptionEntry *entry;
  
  entry = table_add_option (table,
                            free_option,
                            translation_domain,
                            callback_option,
                            single_option);

  if (entry->option->long_name)
    {
#ifndef G_DISABLE_CHECKS
      if (g_hash_table_lookup (parser->long_options,
                               entry->option->long_name) != NULL)
        g_warning ("Option '%s' added twice", entry->option->long_name);
#endif
      
      g_hash_table_insert (parser->long_options,
                           entry->option->long_name,
                           entry);
    }

  if (entry->option->short_name)
    {
#ifndef G_DISABLE_CHECKS
      gchar buf[7];
      
      if (g_hash_table_lookup (parser->short_options,
                               &entry->option->short_name) != NULL)
        g_warning ("Option '%s' added twice",
                   g_unichar_to_string (entry->option->short_name, buf));
#endif

      g_hash_table_insert (parser->short_options,
                           &entry->option->short_name,
                           entry);
    }
}

GOptionTable*
g_option_parser_add_table (GOptionParser *parser,
                           const GOption *option_table,
                           const gchar   *table_name,
                           gboolean       is_static)
{
  const GOption *current_callback = NULL;
  gchar *current_domain = NULL;
  const GOption *current_option = NULL;
  GOptionTable *table;
  
  g_return_val_if_fail (parser != NULL, NULL);
  g_return_val_if_fail (table_name != NULL, NULL);

  table = table_new (table_name);

  parser->tables = g_slist_append (parser->tables, table);
  
  if (option_table)
    {
      current_option = option_table;
      while (current_option->type != G_OPTION_TERMINATOR)
        {
          switch (option->type)
            {
            case G_OPTION_TRANSLATION_DOMAIN:
              table_add_domain (table, !is_static,
                                current_option->type_data,
                                &current_domain);
              break;

            case G_OPTION_CALLBACK:

              /* FIXME must copy callback if necessary */

              g_warning ("FIXME");
              current_callback = current_option;
              break;

            default:
              parser_add_option (parser,
                                 table,
                                 !is_static,
                                 current_domain,
                                 current_callback,
                                 current_option);
              break;
            }

          ++current_option;
        }
    }
}

gboolean
g_option_parser_parse (GOptionParser    *parser,
                       GOptionParseFlags flags,
                       gint             *argc,
                       gchar          ***argv,
                       gchar          ***args,
                       gchar          ***unknown_options,
                       GError          **err)
{



}

void
g_option_parse_simple (const gchar   *table_name,
                       const GOption *table,
                       gint          *argc,
                       gchar       ***argv,
                       gchar       ***args)
{



}

gchar*
g_option_parser_get_help (GOptionParser *parser)
{


}

gchar*
g_option_parser_get_usage (GOptionParser *parser)
{


}

enum
{
  OPTION_HELP,
  OPTION_USAGE
};

static void
help_callback (GOptionParser    *parser,
               GOptionParsePhase phase,
               const GOption    *option,
               const char       *arg_text,
               gpointer          callback_data,
               GError          **error)
{
  switch (GPOINTER_TO_INT (option->user_data))
    {
    case OPTION_HELP:
      break;

    case OPTION_USAGE:
      break;

    default:
      g_assert_not_reached ();
      break;
    }
}

static GOption help_options [] =
{
  { G_OPTION_TRANSLATION_DOMAIN,
    NULL, '\0',
    PACKAGE, NULL,
    NULL, NULL, 0 },
  
  { G_OPTION_CALLBACK, 
    NULL, '\0',
    &help_callback, NULL,
    NULL, NULL, 0 },
  
  { G_OPTION_BOOL, 
    "help", '?',
    NULL, GINT_TO_POINTER (OPTION_HELP),
    N_("Show this help message"), NULL, 0 },

  /* FIXME this is just for testing, we don't really
   * want to set up -h I don't think. (Or maybe popt
   * just skips -h for RPM's benefit, and we should
   * set up -h, I don't know)
   */
  { G_OPTION_ALIAS,
    NULL, 'h',
    "help", NULL,
    NULL, NULL, 0 },
  
  { G_OPTION_BOOL,
    "usage", '\0',
    NULL, GINT_TO_POINTER (OPTION_USAGE),
    N_("Display brief usage message"), NULL, 0 }

  { G_OPTION_TERMINATOR,
    NULL, '\0',
    NULL, NULL,
    NULL, NULL, 0 }
};

GOptionTable*
g_option_parser_add_help (GOptionParser *parser)
{
  g_return_val_if_fail (parser != NULL, NULL);
  
  return g_option_parser_add_table (parser,
                                    help_options,
                                    _("Help options"),
                                    TRUE);
}

static GOptionTable*
table_new (const gchar *name)
{
  GOptionTable *table;

  table = g_new0 (GOptionTable, 1);

  table->name = g_strdup (name);

  return table;
}

static GOptionEntry*
table_add_option (GOptionTable        *table,
                  gboolean             free_option,
                  gchar               *translation_domain,
                  const GOption       *callback,
                  GOption             *single_option)
{
  GOptionEntry *entry = g_new (GOptionEntry, 1);
  
  table->entries = g_slist_prepend (table->entries,
                                    entry);

  entry->free_option = free_option;
  entry->option = single_option;
  entry->callback_option = callback;
  entry->translation_domain = translation_domain;

  return entry;
}

static void
table_add_domain (GOptionTable        *table,
                  gboolean             make_copy,
                  const gchar         *domain,
                  gchar              **out)
{
  if (!make_copy)
    {
      *out = (gchar*) domain;
    }
  else
    {
      if (table->nonstatic_domains &&
          strcmp (table->nonstatic_domains->data, domain) == 0)
        ; /* We'll just recycle the last one - simple optimization
           * for a common case.
           */
      else
        {
          table->nonstatic_domains =
            g_slist_prepend (table->nonstatic_domains,
                             g_strdup (domain));
        }
      
      *out = table->nonstatic_domains->data;
    }
}













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