Click here to Skip to main content
15,885,278 members
Articles / Desktop Programming / MFC

MsAccess MdbTools with MFC and .NET

Rate me:
Please Sign up or sign in to vote.
4.82/5 (9 votes)
13 Jan 2012LGPL310 min read 69K   9.9K   49  
Viewer of MsAccess databases directly from MFC and .NET - Repair corrupt databases
/* goption.c - Option parser
 *
 *  Copyright (C) 1999, 2003 Red Hat Software
 *  Copyright (C) 2004       Anders Carlsson <andersca@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/**
 * SECTION:option
 * @Short_description: parses commandline options
 * @Title: Commandline option parser
 *
 * The GOption commandline parser is intended to be a simpler replacement
 * for the popt library. It supports short and long commandline options,
 * as shown in the following example:
 *
 * <literal>testtreemodel -r 1 --max-size 20 --rand --display=:1.0 -vb -- file1 file2</literal>
 *
 * The example demonstrates a number of features of the GOption
 * commandline parser
 * <itemizedlist><listitem><para>
 *   Options can be single letters, prefixed by a single dash. Multiple
 *   short options can be grouped behind a single dash.
 * </para></listitem><listitem><para>
 *   Long options are prefixed by two consecutive dashes.
 * </para></listitem><listitem><para>
 *   Options can have an extra argument, which can be a number, a string or
 *   a filename. For long options, the extra argument can be appended with
 *   an equals sign after the option name, which is useful if the extra
 *   argument starts with a dash, which would otherwise cause it to be
 *   interpreted as another option.
 * </para></listitem><listitem><para>
 *   Non-option arguments are returned to the application as rest arguments.
 * </para></listitem><listitem><para>
 *   An argument consisting solely of two dashes turns off further parsing,
 *   any remaining arguments (even those starting with a dash) are returned
 *   to the application as rest arguments.
 * </para></listitem></itemizedlist>
 *
 * Another important feature of GOption is that it can automatically
 * generate nicely formatted help output. Unless it is explicitly turned
 * off with g_option_context_set_help_enabled(), GOption will recognize
 * the <option>--help</option>, <option>-?</option>,
 * <option>--help-all</option> and
 * <option>--help-</option><replaceable>groupname</replaceable> options
 * (where <replaceable>groupname</replaceable> is the name of a
 * #GOptionGroup) and write a text similar to the one shown in the
 * following example to stdout.
 *
 * <informalexample><screen>
 * Usage:
 *   testtreemodel [OPTION...] - test tree model performance
 *  
 * Help Options:
 *   -h, --help               Show help options
 *   --help-all               Show all help options
 *   --help-gtk               Show GTK+ Options
 *  
 * Application Options:
 *   -r, --repeats=N          Average over N repetitions
 *   -m, --max-size=M         Test up to 2^M items
 *   --display=DISPLAY        X display to use
 *   -v, --verbose            Be verbose
 *   -b, --beep               Beep when done
 *   --rand                   Randomize the data
 * </screen></informalexample>
 *
 * GOption groups options in #GOptionGroup<!-- -->s, which makes it easy to
 * incorporate options from multiple sources. The intended use for this is
 * to let applications collect option groups from the libraries it uses,
 * add them to their #GOptionContext, and parse all options by a single call
 * to g_option_context_parse(). See gtk_get_option_group() for an example.
 *
 * If an option is declared to be of type string or filename, GOption takes
 * care of converting it to the right encoding; strings are returned in
 * UTF-8, filenames are returned in the GLib filename encoding. Note that
 * this only works if setlocale() has been called before
 * g_option_context_parse().
 *
 * Here is a complete example of setting up GOption to parse the example
 * commandline above and produce the example help output.
 *
 * <informalexample><programlisting>
 * static gint repeats = 2;
 * static gint max_size = 8;
 * static gboolean verbose = FALSE;
 * static gboolean beep = FALSE;
 * static gboolean rand = FALSE;
 *
 * static GOptionEntry entries[] =
 * {
 *   { "repeats", 'r', 0, G_OPTION_ARG_INT, &repeats, "Average over N repetitions", "N" },
 *   { "max-size", 'm', 0, G_OPTION_ARG_INT, &max_size, "Test up to 2^M items", "M" },
 *   { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL },
 *   { "beep", 'b', 0, G_OPTION_ARG_NONE, &beep, "Beep when done", NULL },
 *   { "rand", 0, 0, G_OPTION_ARG_NONE, &rand, "Randomize the data", NULL },
 *   { NULL }
 * };
 *
 * int
 * main (int argc, char *argv[])
 * {
 *   GError *error = NULL;
 *   GOptionContext *context;
 *
 *   context = g_option_context_new ("- test tree model performance");
 *   g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
 *   g_option_context_add_group (context, gtk_get_option_group (TRUE));
 *   if (!g_option_context_parse (context, &argc, &argv, &error))
 *     {
 *       g_print ("option parsing failed: %s\n", error->message);
 *       exit (1);
 *     }
 *
 *   /&ast; ... &ast;/
 *
 * }
 * </programlisting></informalexample>
 */

#include "config.h"

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "goption.h"

#include "gprintf.h"
#include "glibintl.h"

#define TRANSLATE(group, str) (((group)->translate_func ? (* (group)->translate_func) ((str), (group)->translate_data) : (str)))

#define NO_ARG(entry) ((entry)->arg == G_OPTION_ARG_NONE ||       \
                       ((entry)->arg == G_OPTION_ARG_CALLBACK &&  \
                        ((entry)->flags & G_OPTION_FLAG_NO_ARG)))

#define OPTIONAL_ARG(entry) ((entry)->arg == G_OPTION_ARG_CALLBACK &&  \
                       (entry)->flags & G_OPTION_FLAG_OPTIONAL_ARG)

typedef struct
{
  GOptionArg arg_type;
  gpointer arg_data;
  union
  {
    gboolean bool;
    gint integer;
    gchar *str;
    gchar **array;
    gdouble dbl;
    gint64 int64;
  } prev;
  union
  {
    gchar *str;
    struct
    {
      gint len;
      gchar **data;
    } array;
  } allocated;
} Change;

typedef struct
{
  gchar **ptr;
  gchar *value;
} PendingNull;

struct _GOptionContext
{
  GList           *groups;

  gchar           *parameter_string;
  gchar           *summary;
  gchar           *description;

  GTranslateFunc   translate_func;
  GDestroyNotify   translate_notify;
  gpointer         translate_data;

  guint            help_enabled   : 1;
  guint            ignore_unknown : 1;

  GOptionGroup    *main_group;

  /* We keep a list of change so we can revert them */
  GList           *changes;

  /* We also keep track of all argv elements
   * that should be NULLed or modified.
   */
  GList           *pending_nulls;
};

struct _GOptionGroup
{
  gchar           *name;
  gchar           *description;
  gchar           *help_description;

  GDestroyNotify   destroy_notify;
  gpointer         user_data;

  GTranslateFunc   translate_func;
  GDestroyNotify   translate_notify;
  gpointer         translate_data;

  GOptionEntry    *entries;
  gint             n_entries;

  GOptionParseFunc pre_parse_func;
  GOptionParseFunc post_parse_func;
  GOptionErrorFunc error_func;
};

static void free_changes_list (GOptionContext *context,
                               gboolean        revert);
static void free_pending_nulls (GOptionContext *context,
                                gboolean        perform_nulls);


static int
_g_unichar_get_width (gunichar c)
{
  if (G_UNLIKELY (g_unichar_iszerowidth (c)))
    return 0;

  /* we ignore the fact that we should call g_unichar_iswide_cjk() under
   * some locales (legacy East Asian ones) */
  if (g_unichar_iswide (c))
    return 2;

  return 1;
}

static glong
_g_utf8_strwidth (const gchar *p,
                  gssize       max)
{
  glong len = 0;
  const gchar *start = p;
  g_return_val_if_fail (p != NULL || max == 0, 0);

  if (max < 0)
    {
      while (*p)
        {
          len += _g_unichar_get_width (g_utf8_get_char (p));
          p = g_utf8_next_char (p);
        }
    }
  else
    {
      if (max == 0 || !*p)
        return 0;

      /* this case may not be quite correct */

      len += _g_unichar_get_width (g_utf8_get_char (p));
      p = g_utf8_next_char (p);

      while (p - start < max && *p)
        {
          len += _g_unichar_get_width (g_utf8_get_char (p));
          p = g_utf8_next_char (p);
        }
    }

  return len;
}


GQuark
g_option_error_quark (void)
{
  return g_quark_from_static_string ("g-option-context-error-quark");
}

/**
 * g_option_context_new:
 * @parameter_string: a string which is displayed in
 *    the first line of <option>--help</option> output, after the
 *    usage summary
 *    <literal><replaceable>programname</replaceable> [OPTION...]</literal>
 *
 * Creates a new option context.
 *
 * The @parameter_string can serve multiple purposes. It can be used
 * to add descriptions for "rest" arguments, which are not parsed by
 * the #GOptionContext, typically something like "FILES" or
 * "FILE1 FILE2...". If you are using #G_OPTION_REMAINING for
 * collecting "rest" arguments, GLib handles this automatically by
 * using the @arg_description of the corresponding #GOptionEntry in
 * the usage summary.
 *
 * Another usage is to give a short summary of the program
 * functionality, like " - frob the strings", which will be displayed
 * in the same line as the usage. For a longer description of the
 * program functionality that should be displayed as a paragraph
 * below the usage line, use g_option_context_set_summary().
 *
 * Note that the @parameter_string is translated using the
 * function set with g_option_context_set_translate_func(), so
 * it should normally be passed untranslated.
 *
 * Returns: a newly created #GOptionContext, which must be
 *    freed with g_option_context_free() after use.
 *
 * Since: 2.6
 */
GOptionContext *
g_option_context_new (const gchar *parameter_string)

{
  GOptionContext *context;

  context = g_new0 (GOptionContext, 1);

  context->parameter_string = g_strdup (parameter_string);
  context->help_enabled = TRUE;
  context->ignore_unknown = FALSE;

  return context;
}

/**
 * g_option_context_free:
 * @context: a #GOptionContext
 *
 * Frees context and all the groups which have been
 * added to it.
 *
 * Please note that parsed arguments need to be freed separately (see
 * #GOptionEntry).
 *
 * Since: 2.6
 */
void g_option_context_free (GOptionContext *context)
{
  g_return_if_fail (context != NULL);

  g_list_foreach (context->groups, (GFunc)g_option_group_free, NULL);
  g_list_free (context->groups);

  if (context->main_group)
    g_option_group_free (context->main_group);

  free_changes_list (context, FALSE);
  free_pending_nulls (context, FALSE);

  g_free (context->parameter_string);
  g_free (context->summary);
  g_free (context->description);

  if (context->translate_notify)
    (* context->translate_notify) (context->translate_data);

  g_free (context);
}


/**
 * g_option_context_set_help_enabled:
 * @context: a #GOptionContext
 * @help_enabled: %TRUE to enable <option>--help</option>, %FALSE to disable it
 *
 * Enables or disables automatic generation of <option>--help</option>
 * output. By default, g_option_context_parse() recognizes
 * <option>--help</option>, <option>-h</option>,
 * <option>-?</option>, <option>--help-all</option>
 * and <option>--help-</option><replaceable>groupname</replaceable> and creates
 * suitable output to stdout.
 *
 * Since: 2.6
 */
void g_option_context_set_help_enabled (GOptionContext *context,
                                        gboolean        help_enabled)

{
  g_return_if_fail (context != NULL);

  context->help_enabled = help_enabled;
}

/**
 * g_option_context_get_help_enabled:
 * @context: a #GOptionContext
 *
 * Returns whether automatic <option>--help</option> generation
 * is turned on for @context. See g_option_context_set_help_enabled().
 *
 * Returns: %TRUE if automatic help generation is turned on.
 *
 * Since: 2.6
 */
gboolean
g_option_context_get_help_enabled (GOptionContext *context)
{
  g_return_val_if_fail (context != NULL, FALSE);

  return context->help_enabled;
}

/**
 * g_option_context_set_ignore_unknown_options:
 * @context: a #GOptionContext
 * @ignore_unknown: %TRUE to ignore unknown options, %FALSE to produce
 *    an error when unknown options are met
 *
 * Sets whether to ignore unknown options or not. If an argument is
 * ignored, it is left in the @argv array after parsing. By default,
 * g_option_context_parse() treats unknown options as error.
 *
 * This setting does not affect non-option arguments (i.e. arguments
 * which don't start with a dash). But note that GOption cannot reliably
 * determine whether a non-option belongs to a preceding unknown option.
 *
 * Since: 2.6
 **/
void
g_option_context_set_ignore_unknown_options (GOptionContext *context,
                                             gboolean        ignore_unknown)
{
  g_return_if_fail (context != NULL);

  context->ignore_unknown = ignore_unknown;
}

/**
 * g_option_context_get_ignore_unknown_options:
 * @context: a #GOptionContext
 *
 * Returns whether unknown options are ignored or not. See
 * g_option_context_set_ignore_unknown_options().
 *
 * Returns: %TRUE if unknown options are ignored.
 *
 * Since: 2.6
 **/
gboolean
g_option_context_get_ignore_unknown_options (GOptionContext *context)
{
  g_return_val_if_fail (context != NULL, FALSE);

  return context->ignore_unknown;
}

/**
 * g_option_context_add_group:
 * @context: a #GOptionContext
 * @group: the group to add
 *
 * Adds a #GOptionGroup to the @context, so that parsing with @context
 * will recognize the options in the group. Note that the group will
 * be freed together with the context when g_option_context_free() is
 * called, so you must not free the group yourself after adding it
 * to a context.
 *
 * Since: 2.6
 **/
void
g_option_context_add_group (GOptionContext *context,
                            GOptionGroup   *group)
{
  GList *list;

  g_return_if_fail (context != NULL);
  g_return_if_fail (group != NULL);
  g_return_if_fail (group->name != NULL);
  g_return_if_fail (group->description != NULL);
  g_return_if_fail (group->help_description != NULL);

  for (list = context->groups; list; list = list->next)
    {
      GOptionGroup *g = (GOptionGroup *)list->data;

      if ((group->name == NULL && g->name == NULL) ||
          (group->name && g->name && strcmp (group->name, g->name) == 0))
        g_warning ("A group named \"%s\" is already part of this GOptionContext",
                   group->name);
    }

  context->groups = g_list_append (context->groups, group);
}

/**
 * g_option_context_set_main_group:
 * @context: a #GOptionContext
 * @group: the group to set as main group
 *
 * Sets a #GOptionGroup as main group of the @context.
 * This has the same effect as calling g_option_context_add_group(),
 * the only difference is that the options in the main group are
 * treated differently when generating <option>--help</option> output.
 *
 * Since: 2.6
 **/
void
g_option_context_set_main_group (GOptionContext *context,
                                 GOptionGroup   *group)
{
  g_return_if_fail (context != NULL);
  g_return_if_fail (group != NULL);

  if (context->main_group)
    {
      g_warning ("This GOptionContext already has a main group");

      return;
    }

  context->main_group = group;
}

/**
 * g_option_context_get_main_group:
 * @context: a #GOptionContext
 *
 * Returns a pointer to the main group of @context.
 *
 * Return value: the main group of @context, or %NULL if @context doesn't
 *  have a main group. Note that group belongs to @context and should
 *  not be modified or freed.
 *
 * Since: 2.6
 **/
GOptionGroup *
g_option_context_get_main_group (GOptionContext *context)
{
  g_return_val_if_fail (context != NULL, NULL);

  return context->main_group;
}

/**
 * g_option_context_add_main_entries:
 * @context: a #GOptionContext
 * @entries: a %NULL-terminated array of #GOptionEntry<!-- -->s
 * @translation_domain: a translation domain to use for translating
 *    the <option>--help</option> output for the options in @entries
 *    with gettext(), or %NULL
 *
 * A convenience function which creates a main group if it doesn't
 * exist, adds the @entries to it and sets the translation domain.
 *
 * Since: 2.6
 **/
void
g_option_context_add_main_entries (GOptionContext      *context,
                                   const GOptionEntry  *entries,
                                   const gchar         *translation_domain)
{
  g_return_if_fail (entries != NULL);

  if (!context->main_group)
    context->main_group = g_option_group_new (NULL, NULL, NULL, NULL, NULL);

  g_option_group_add_entries (context->main_group, entries);
  g_option_group_set_translation_domain (context->main_group, translation_domain);
}

static gint
calculate_max_length (GOptionGroup *group)
{
  GOptionEntry *entry;
  gint i, len, max_length;

  max_length = 0;

  for (i = 0; i < group->n_entries; i++)
    {
      entry = &group->entries[i];

      if (entry->flags & G_OPTION_FLAG_HIDDEN)
        continue;

      len = _g_utf8_strwidth (entry->long_name, -1);

      if (entry->short_name)
        len += 4;

      if (!NO_ARG (entry) && entry->arg_description)
        len += 1 + _g_utf8_strwidth (TRANSLATE (group, entry->arg_description), -1);

      max_length = MAX (max_length, len);
    }

  return max_length;
}

static void
print_entry (GOptionGroup       *group,
             gint                max_length,
             const GOptionEntry *entry,
             GString            *string)
{
  GString *str;

  if (entry->flags & G_OPTION_FLAG_HIDDEN)
    return;

  if (entry->long_name[0] == 0)
    return;

  str = g_string_new (NULL);

  if (entry->short_name)
    g_string_append_printf (str, "  -%c, --%s", entry->short_name, entry->long_name);
  else
    g_string_append_printf (str, "  --%s", entry->long_name);

  if (entry->arg_description)
    g_string_append_printf (str, "=%s", TRANSLATE (group, entry->arg_description));

  g_string_append_printf (string, "%s%*s %s\n", str->str,
                          (int) (max_length + 4 - _g_utf8_strwidth (str->str, -1)), "",
                          entry->description ? TRANSLATE (group, entry->description) : "");
  g_string_free (str, TRUE);
}

static gboolean
group_has_visible_entries (GOptionContext *context,
                           GOptionGroup *group,
                           gboolean      main_entries)
{
  GOptionFlags reject_filter = G_OPTION_FLAG_HIDDEN;
  GOptionEntry *entry;
  gint i, l;
  gboolean main_group = group == context->main_group;

  if (!main_entries)
    reject_filter |= G_OPTION_FLAG_IN_MAIN;

  for (i = 0, l = (group ? group->n_entries : 0); i < l; i++)
    {
      entry = &group->entries[i];

      if (main_entries && !main_group && !(entry->flags & G_OPTION_FLAG_IN_MAIN))
        continue;
      if (!(entry->flags & reject_filter))
        return TRUE;
    }

  return FALSE;
}

static gboolean
group_list_has_visible_entires (GOptionContext *context,
                                GList          *group_list,
                                gboolean       main_entries)
{
  while (group_list)
    {
      if (group_has_visible_entries (context, group_list->data, main_entries))
        return TRUE;

      group_list = group_list->next;
    }

  return FALSE;
}

static gboolean
context_has_h_entry (GOptionContext *context)
{
  gsize i;
  GList *list;

  if (context->main_group)
    {
      for (i = 0; i < context->main_group->n_entries; i++)
        {
          if (context->main_group->entries[i].short_name == 'h')
            return TRUE;
        }
    }

  for (list = context->groups; list != NULL; list = g_list_next (list))
    {
     GOptionGroup *group;

      group = (GOptionGroup*)list->data;
      for (i = 0; i < group->n_entries; i++)
        {
          if (group->entries[i].short_name == 'h')
            return TRUE;
        }
    }
  return FALSE;
}

/**
 * g_option_context_get_help:
 * @context: a #GOptionContext
 * @main_help: if %TRUE, only include the main group
 * @group: the #GOptionGroup to create help for, or %NULL
 *
 * Returns a formatted, translated help text for the given context.
 * To obtain the text produced by <option>--help</option>, call
 * <literal>g_option_context_get_help (context, TRUE, NULL)</literal>.
 * To obtain the text produced by <option>--help-all</option>, call
 * <literal>g_option_context_get_help (context, FALSE, NULL)</literal>.
 * To obtain the help text for an option group, call
 * <literal>g_option_context_get_help (context, FALSE, group)</literal>.
 *
 * Returns: A newly allocated string containing the help text
 *
 * Since: 2.14
 */
gchar *
g_option_context_get_help (GOptionContext *context,
                           gboolean        main_help,
                           GOptionGroup   *group)
{
  GList *list;
  gint max_length, len;
  gint i;
  GOptionEntry *entry;
  GHashTable *shadow_map;
  gboolean seen[256];
  const gchar *rest_description;
  GString *string;
  guchar token;

  string = g_string_sized_new (1024);

  rest_description = NULL;
  if (context->main_group)
    {

      for (i = 0; i < context->main_group->n_entries; i++)
        {
          entry = &context->main_group->entries[i];
          if (entry->long_name[0] == 0)
            {
              rest_description = TRANSLATE (context->main_group, entry->arg_description);
              break;
            }
        }
    }

  g_string_append_printf (string, "%s\n  %s %s",
                          _("Usage:"), g_get_prgname(), _("[OPTION...]"));

  if (rest_description)
    {
      g_string_append (string, " ");
      g_string_append (string, rest_description);
    }

  if (context->parameter_string)
    {
      g_string_append (string, " ");
      g_string_append (string, TRANSLATE (context, context->parameter_string));
    }

  g_string_append (string, "\n\n");

  if (context->summary)
    {
      g_string_append (string, TRANSLATE (context, context->summary));
      g_string_append (string, "\n\n");
    }

  memset (seen, 0, sizeof (gboolean) * 256);
  shadow_map = g_hash_table_new (g_str_hash, g_str_equal);

  if (context->main_group)
    {
      for (i = 0; i < context->main_group->n_entries; i++)
        {
          entry = &context->main_group->entries[i];
          g_hash_table_insert (shadow_map,
                               (gpointer)entry->long_name,
                               entry);

          if (seen[(guchar)entry->short_name])
            entry->short_name = 0;
          else
            seen[(guchar)entry->short_name] = TRUE;
        }
    }

  list = context->groups;
  while (list != NULL)
    {
      GOptionGroup *g = list->data;
      for (i = 0; i < g->n_entries; i++)
        {
          entry = &g->entries[i];
          if (g_hash_table_lookup (shadow_map, entry->long_name) &&
              !(entry->flags & G_OPTION_FLAG_NOALIAS))
            entry->long_name = g_strdup_printf ("%s-%s", g->name, entry->long_name);
          else
            g_hash_table_insert (shadow_map, (gpointer)entry->long_name, entry);

          if (seen[(guchar)entry->short_name] &&
              !(entry->flags & G_OPTION_FLAG_NOALIAS))
            entry->short_name = 0;
          else
            seen[(guchar)entry->short_name] = TRUE;
        }
      list = list->next;
    }

  g_hash_table_destroy (shadow_map);

  list = context->groups;

  max_length = _g_utf8_strwidth ("-?, --help", -1);

  if (list)
    {
      len = _g_utf8_strwidth ("--help-all", -1);
      max_length = MAX (max_length, len);
    }

  if (context->main_group)
    {
      len = calculate_max_length (context->main_group);
      max_length = MAX (max_length, len);
    }

  while (list != NULL)
    {
      GOptionGroup *g = list->data;

      /* First, we check the --help-<groupname> options */
      len = _g_utf8_strwidth ("--help-", -1) + _g_utf8_strwidth (g->name, -1);
      max_length = MAX (max_length, len);

      /* Then we go through the entries */
      len = calculate_max_length (g);
      max_length = MAX (max_length, len);

      list = list->next;
    }

  /* Add a bit of padding */
  max_length += 4;

  if (!group)
    {
      list = context->groups;

      token = context_has_h_entry (context) ? '?' : 'h';

      g_string_append_printf (string, "%s\n  -%c, --%-*s %s\n",
                              _("Help Options:"), token, max_length - 4, "help",
                              _("Show help options"));

      /* We only want --help-all when there are groups */
      if (list)
        g_string_append_printf (string, "  --%-*s %s\n",
                                max_length, "help-all",
                                _("Show all help options"));

      while (list)
        {
          GOptionGroup *g = list->data;

          if (group_has_visible_entries (context, g, FALSE))
            g_string_append_printf (string, "  --help-%-*s %s\n",
                                    max_length - 5, g->name,
                                    TRANSLATE (g, g->help_description));

          list = list->next;
        }

      g_string_append (string, "\n");
    }

  if (group)
    {
      /* Print a certain group */

      if (group_has_visible_entries (context, group, FALSE))
        {
          g_string_append (string, TRANSLATE (group, group->description));
          g_string_append (string, "\n");
          for (i = 0; i < group->n_entries; i++)
            print_entry (group, max_length, &group->entries[i], string);
          g_string_append (string, "\n");
        }
    }
  else if (!main_help)
    {
      /* Print all groups */

      list = context->groups;

      while (list)
        {
          GOptionGroup *g = list->data;

          if (group_has_visible_entries (context, g, FALSE))
            {
              g_string_append (string, g->description);
              g_string_append (string, "\n");
              for (i = 0; i < g->n_entries; i++)
                if (!(g->entries[i].flags & G_OPTION_FLAG_IN_MAIN))
                  print_entry (g, max_length, &g->entries[i], string);

              g_string_append (string, "\n");
            }

          list = list->next;
        }
    }

  /* Print application options if --help or --help-all has been specified */
  if ((main_help || !group) &&
      (group_has_visible_entries (context, context->main_group, TRUE) ||
       group_list_has_visible_entires (context, context->groups, TRUE)))
    {
      list = context->groups;

      g_string_append (string,  _("Application Options:"));
      g_string_append (string, "\n");
      if (context->main_group)
        for (i = 0; i < context->main_group->n_entries; i++)
          print_entry (context->main_group, max_length,
                       &context->main_group->entries[i], string);

      while (list != NULL)
        {
          GOptionGroup *g = list->data;

          /* Print main entries from other groups */
          for (i = 0; i < g->n_entries; i++)
            if (g->entries[i].flags & G_OPTION_FLAG_IN_MAIN)
              print_entry (g, max_length, &g->entries[i], string);

          list = list->next;
        }

      g_string_append (string, "\n");
    }

  if (context->description)
    {
      g_string_append (string, TRANSLATE (context, context->description));
      g_string_append (string, "\n");
    }

  return g_string_free (string, FALSE);
}

G_GNUC_NORETURN
static void
print_help (GOptionContext *context,
            gboolean        main_help,
            GOptionGroup   *group)
{
  gchar *help;

  help = g_option_context_get_help (context, main_help, group);
  g_print ("%s", help);
  g_free (help);

  exit (0);
}

static gboolean
parse_int (const gchar *arg_name,
           const gchar *arg,
           gint        *result,
           GError     **error)
{
  gchar *end;
  glong tmp;

  errno = 0;
  tmp = strtol (arg, &end, 0);

  if (*arg == '\0' || *end != '\0')
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Cannot parse integer value '%s' for %s"),
                   arg, arg_name);
      return FALSE;
    }

  *result = tmp;
  if (*result != tmp || errno == ERANGE)
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Integer value '%s' for %s out of range"),
                   arg, arg_name);
      return FALSE;
    }

  return TRUE;
}


static gboolean
parse_double (const gchar *arg_name,
           const gchar *arg,
           gdouble        *result,
           GError     **error)
{
  gchar *end;
  gdouble tmp;

  errno = 0;
  tmp = g_strtod (arg, &end);

  if (*arg == '\0' || *end != '\0')
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Cannot parse double value '%s' for %s"),
                   arg, arg_name);
      return FALSE;
    }
  if (errno == ERANGE)
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Double value '%s' for %s out of range"),
                   arg, arg_name);
      return FALSE;
    }

  *result = tmp;

  return TRUE;
}


static gboolean
parse_int64 (const gchar *arg_name,
             const gchar *arg,
             gint64      *result,
             GError     **error)
{
  gchar *end;
  gint64 tmp;

  errno = 0;
  tmp = g_ascii_strtoll (arg, &end, 0);

  if (*arg == '\0' || *end != '\0')
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Cannot parse integer value '%s' for %s"),
                   arg, arg_name);
      return FALSE;
    }
  if (errno == ERANGE)
    {
      g_set_error (error,
                   G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                   _("Integer value '%s' for %s out of range"),
                   arg, arg_name);
      return FALSE;
    }

  *result = tmp;

  return TRUE;
}


static Change *
get_change (GOptionContext *context,
            GOptionArg      arg_type,
            gpointer        arg_data)
{
  GList *list;
  Change *change = NULL;

  for (list = context->changes; list != NULL; list = list->next)
    {
      change = list->data;

      if (change->arg_data == arg_data)
        goto found;
    }

  change = g_new0 (Change, 1);
  change->arg_type = arg_type;
  change->arg_data = arg_data;

  context->changes = g_list_prepend (context->changes, change);

 found:

  return change;
}

static void
add_pending_null (GOptionContext *context,
                  gchar         **ptr,
                  gchar          *value)
{
  PendingNull *n;

  n = g_new0 (PendingNull, 1);
  n->ptr = ptr;
  n->value = value;

  context->pending_nulls = g_list_prepend (context->pending_nulls, n);
}

static gboolean
parse_arg (GOptionContext *context,
           GOptionGroup   *group,
           GOptionEntry   *entry,
           const gchar    *value,
           const gchar    *option_name,
           GError        **error)

{
  Change *change;

  g_assert (value || OPTIONAL_ARG (entry) || NO_ARG (entry));

  switch (entry->arg)
    {
    case G_OPTION_ARG_NONE:
      {
        change = get_change (context, G_OPTION_ARG_NONE,
                             entry->arg_data);

        *(gboolean *)entry->arg_data = !(entry->flags & G_OPTION_FLAG_REVERSE);
        break;
      }
    case G_OPTION_ARG_STRING:
      {
        gchar *data;

        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);

        if (!data)
          return FALSE;

        change = get_change (context, G_OPTION_ARG_STRING,
                             entry->arg_data);
        g_free (change->allocated.str);

        change->prev.str = *(gchar **)entry->arg_data;
        change->allocated.str = data;

        *(gchar **)entry->arg_data = data;
        break;
      }
    case G_OPTION_ARG_STRING_ARRAY:
      {
        gchar *data;

        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);

        if (!data)
          return FALSE;

        change = get_change (context, G_OPTION_ARG_STRING_ARRAY,
                             entry->arg_data);

        if (change->allocated.array.len == 0)
          {
            change->prev.array = *(gchar ***)entry->arg_data;
            change->allocated.array.data = g_new (gchar *, 2);
          }
        else
          change->allocated.array.data =
            g_renew (gchar *, change->allocated.array.data,
                     change->allocated.array.len + 2);

        change->allocated.array.data[change->allocated.array.len] = data;
        change->allocated.array.data[change->allocated.array.len + 1] = NULL;

        change->allocated.array.len ++;

        *(gchar ***)entry->arg_data = change->allocated.array.data;

        break;
      }

    case G_OPTION_ARG_FILENAME:
      {
        gchar *data;

#ifdef G_OS_WIN32
        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);

        if (!data)
          return FALSE;
#else
        data = g_strdup (value);
#endif
        change = get_change (context, G_OPTION_ARG_FILENAME,
                             entry->arg_data);
        g_free (change->allocated.str);

        change->prev.str = *(gchar **)entry->arg_data;
        change->allocated.str = data;

        *(gchar **)entry->arg_data = data;
        break;
      }

    case G_OPTION_ARG_FILENAME_ARRAY:
      {
        gchar *data;

#ifdef G_OS_WIN32
        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);

        if (!data)
          return FALSE;
#else
        data = g_strdup (value);
#endif
        change = get_change (context, G_OPTION_ARG_STRING_ARRAY,
                             entry->arg_data);

        if (change->allocated.array.len == 0)
          {
            change->prev.array = *(gchar ***)entry->arg_data;
            change->allocated.array.data = g_new (gchar *, 2);
          }
        else
          change->allocated.array.data =
            g_renew (gchar *, change->allocated.array.data,
                     change->allocated.array.len + 2);

        change->allocated.array.data[change->allocated.array.len] = data;
        change->allocated.array.data[change->allocated.array.len + 1] = NULL;

        change->allocated.array.len ++;

        *(gchar ***)entry->arg_data = change->allocated.array.data;

        break;
      }

    case G_OPTION_ARG_INT:
      {
        gint data;

        if (!parse_int (option_name, value,
                        &data,
                        error))
          return FALSE;

        change = get_change (context, G_OPTION_ARG_INT,
                             entry->arg_data);
        change->prev.integer = *(gint *)entry->arg_data;
        *(gint *)entry->arg_data = data;
        break;
      }
    case G_OPTION_ARG_CALLBACK:
      {
        gchar *data;
        gboolean retval;

        if (!value && entry->flags & G_OPTION_FLAG_OPTIONAL_ARG)
          data = NULL;
        else if (entry->flags & G_OPTION_FLAG_NO_ARG)
          data = NULL;
        else if (entry->flags & G_OPTION_FLAG_FILENAME)
          {
#ifdef G_OS_WIN32
            data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
#else
            data = g_strdup (value);
#endif
          }
        else
          data = g_locale_to_utf8 (value, -1, NULL, NULL, error);

        if (!(entry->flags & (G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_OPTIONAL_ARG)) &&
            !data)
          return FALSE;

        retval = (* (GOptionArgFunc) entry->arg_data) (option_name, data, group->user_data, error);

        if (!retval && error != NULL && *error == NULL)
          g_set_error (error,
                       G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
                       _("Error parsing option %s"), option_name);

        g_free (data);

        return retval;

        break;
      }
    case G_OPTION_ARG_DOUBLE:
      {
        gdouble data;

        if (!parse_double (option_name, value,
                        &data,
                        error))
          {
            return FALSE;
          }

        change = get_change (context, G_OPTION_ARG_DOUBLE,
                             entry->arg_data);
        change->prev.dbl = *(gdouble *)entry->arg_data;
        *(gdouble *)entry->arg_data = data;
        break;
      }
    case G_OPTION_ARG_INT64:
      {
        gint64 data;

        if (!parse_int64 (option_name, value,
                         &data,
                         error))
          {
            return FALSE;
          }

        change = get_change (context, G_OPTION_ARG_INT64,
                             entry->arg_data);
        change->prev.int64 = *(gint64 *)entry->arg_data;
        *(gint64 *)entry->arg_data = data;
        break;
      }
    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static gboolean
parse_short_option (GOptionContext *context,
                    GOptionGroup   *group,
                    gint            idx,
                    gint           *new_idx,
                    gchar           arg,
                    gint           *argc,
                    gchar        ***argv,
                    GError        **error,
                    gboolean       *parsed)
{
  gint j;

  for (j = 0; j < group->n_entries; j++)
    {
      if (arg == group->entries[j].short_name)
        {
          gchar *option_name;
          gchar *value = NULL;

          option_name = g_strdup_printf ("-%c", group->entries[j].short_name);

          if (NO_ARG (&group->entries[j]))
            value = NULL;
          else
            {
              if (*new_idx > idx)
                {
                  g_set_error (error,
                               G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
                               _("Error parsing option %s"), option_name);
                  g_free (option_name);
                  return FALSE;
                }

              if (idx < *argc - 1)
                {
                  if (!OPTIONAL_ARG (&group->entries[j]))
                    {
                      value = (*argv)[idx + 1];
                      add_pending_null (context, &((*argv)[idx + 1]), NULL);
                      *new_idx = idx + 1;
                    }
                  else
                    {
                      if ((*argv)[idx + 1][0] == '-')
                        value = NULL;
                      else
                        {
                          value = (*argv)[idx + 1];
                          add_pending_null (context, &((*argv)[idx + 1]), NULL);
                          *new_idx = idx + 1;
                        }
                    }
                }
              else if (idx >= *argc - 1 && OPTIONAL_ARG (&group->entries[j]))
                value = NULL;
              else
                {
                  g_set_error (error,
                               G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                               _("Missing argument for %s"), option_name);
                  g_free (option_name);
                  return FALSE;
                }
            }

          if (!parse_arg (context, group, &group->entries[j],
                          value, option_name, error))
            {
              g_free (option_name);
              return FALSE;
            }

          g_free (option_name);
          *parsed = TRUE;
        }
    }

  return TRUE;
}

static gboolean
parse_long_option (GOptionContext *context,
                   GOptionGroup   *group,
                   gint           *idx,
                   gchar          *arg,
                   gboolean        aliased,
                   gint           *argc,
                   gchar        ***argv,
                   GError        **error,
                   gboolean       *parsed)
{
  gint j;

  for (j = 0; j < group->n_entries; j++)
    {
      if (*idx >= *argc)
        return TRUE;

      if (aliased && (group->entries[j].flags & G_OPTION_FLAG_NOALIAS))
        continue;

      if (NO_ARG (&group->entries[j]) &&
          strcmp (arg, group->entries[j].long_name) == 0)
        {
          gchar *option_name;
          gboolean retval;

          option_name = g_strconcat ("--", group->entries[j].long_name, NULL);
          retval = parse_arg (context, group, &group->entries[j],
                              NULL, option_name, error);
          g_free (option_name);

          add_pending_null (context, &((*argv)[*idx]), NULL);
          *parsed = TRUE;

          return retval;
        }
      else
        {
          gint len = strlen (group->entries[j].long_name);

          if (strncmp (arg, group->entries[j].long_name, len) == 0 &&
              (arg[len] == '=' || arg[len] == 0))
            {
              gchar *value = NULL;
              gchar *option_name;

              add_pending_null (context, &((*argv)[*idx]), NULL);
              option_name = g_strconcat ("--", group->entries[j].long_name, NULL);

              if (arg[len] == '=')
                value = arg + len + 1;
              else if (*idx < *argc - 1)
                {
                  if (!(group->entries[j].flags & G_OPTION_FLAG_OPTIONAL_ARG))
                    {
                      value = (*argv)[*idx + 1];
                      add_pending_null (context, &((*argv)[*idx + 1]), NULL);
                      (*idx)++;
                    }
                  else
                    {
                      if ((*argv)[*idx + 1][0] == '-')
                        {
                          gboolean retval;
                          retval = parse_arg (context, group, &group->entries[j],
                                              NULL, option_name, error);
                          *parsed = TRUE;
                          g_free (option_name);
                          return retval;
                        }
                      else
                        {
                          value = (*argv)[*idx + 1];
                          add_pending_null (context, &((*argv)[*idx + 1]), NULL);
                          (*idx)++;
                        }
                    }
                }
              else if (*idx >= *argc - 1 &&
                       group->entries[j].flags & G_OPTION_FLAG_OPTIONAL_ARG)
                {
                    gboolean retval;
                    retval = parse_arg (context, group, &group->entries[j],
                                        NULL, option_name, error);
                    *parsed = TRUE;
                    g_free (option_name);
                    return retval;
                }
              else
                {
                  g_set_error (error,
                               G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
                               _("Missing argument for %s"), option_name);
                  g_free (option_name);
                  return FALSE;
                }

              if (!parse_arg (context, group, &group->entries[j],
                              value, option_name, error))
                {
                  g_free (option_name);
                  return FALSE;
                }

              g_free (option_name);
              *parsed = TRUE;
            }
        }
    }

  return TRUE;
}

static gboolean
parse_remaining_arg (GOptionContext *context,
                     GOptionGroup   *group,
                     gint           *idx,
                     gint           *argc,
                     gchar        ***argv,
                     GError        **error,
                     gboolean       *parsed)
{
  gint j;

  for (j = 0; j < group->n_entries; j++)
    {
      if (*idx >= *argc)
        return TRUE;

      if (group->entries[j].long_name[0])
        continue;

      g_return_val_if_fail (group->entries[j].arg == G_OPTION_ARG_CALLBACK ||
                            group->entries[j].arg == G_OPTION_ARG_STRING_ARRAY ||
                            group->entries[j].arg == G_OPTION_ARG_FILENAME_ARRAY, FALSE);

      add_pending_null (context, &((*argv)[*idx]), NULL);

      if (!parse_arg (context, group, &group->entries[j], (*argv)[*idx], "", error))
        return FALSE;

      *parsed = TRUE;
      return TRUE;
    }

  return TRUE;
}

static void
free_changes_list (GOptionContext *context,
                   gboolean        revert)
{
  GList *list;

  for (list = context->changes; list != NULL; list = list->next)
    {
      Change *change = list->data;

      if (revert)
        {
          switch (change->arg_type)
            {
            case G_OPTION_ARG_NONE:
              *(gboolean *)change->arg_data = change->prev.bool;
              break;
            case G_OPTION_ARG_INT:
              *(gint *)change->arg_data = change->prev.integer;
              break;
            case G_OPTION_ARG_STRING:
            case G_OPTION_ARG_FILENAME:
              g_free (change->allocated.str);
              *(gchar **)change->arg_data = change->prev.str;
              break;
            case G_OPTION_ARG_STRING_ARRAY:
            case G_OPTION_ARG_FILENAME_ARRAY:
              g_strfreev (change->allocated.array.data);
              *(gchar ***)change->arg_data = change->prev.array;
              break;
            case G_OPTION_ARG_DOUBLE:
              *(gdouble *)change->arg_data = change->prev.dbl;
              break;
            case G_OPTION_ARG_INT64:
              *(gint64 *)change->arg_data = change->prev.int64;
              break;
            default:
              g_assert_not_reached ();
            }
        }

      g_free (change);
    }

  g_list_free (context->changes);
  context->changes = NULL;
}

static void
free_pending_nulls (GOptionContext *context,
                    gboolean        perform_nulls)
{
  GList *list;

  for (list = context->pending_nulls; list != NULL; list = list->next)
    {
      PendingNull *n = list->data;

      if (perform_nulls)
        {
          if (n->value)
            {
              /* Copy back the short options */
              *(n->ptr)[0] = '-';
              strcpy (*n->ptr + 1, n->value);
            }
          else
            *n->ptr = NULL;
        }

      g_free (n->value);
      g_free (n);
    }

  g_list_free (context->pending_nulls);
  context->pending_nulls = NULL;
}

/**
 * g_option_context_parse:
 * @context: a #GOptionContext
 * @argc: a pointer to the number of command line arguments
 * @argv: a pointer to the array of command line arguments
 * @error: a return location for errors
 *
 * Parses the command line arguments, recognizing options
 * which have been added to @context. A side-effect of
 * calling this function is that g_set_prgname() will be
 * called.
 *
 * If the parsing is successful, any parsed arguments are
 * removed from the array and @argc and @argv are updated
 * accordingly. A '--' option is stripped from @argv
 * unless there are unparsed options before and after it,
 * or some of the options after it start with '-'. In case
 * of an error, @argc and @argv are left unmodified.
 *
 * If automatic <option>--help</option> support is enabled
 * (see g_option_context_set_help_enabled()), and the
 * @argv array contains one of the recognized help options,
 * this function will produce help output to stdout and
 * call <literal>exit (0)</literal>.
 *
 * Note that function depends on the
 * <link linkend="setlocale">current locale</link> for
 * automatic character set conversion of string and filename
 * arguments.
 *
 * Return value: %TRUE if the parsing was successful,
 *               %FALSE if an error occurred
 *
 * Since: 2.6
 **/
gboolean
g_option_context_parse (GOptionContext   *context,
                        gint             *argc,
                        gchar          ***argv,
                        GError          **error)
{
  gint i, j, k;
  GList *list;

  /* Set program name */
  if (!g_get_prgname())
    {
      if (argc && argv && *argc)
        {
          gchar *prgname;

          prgname = g_path_get_basename ((*argv)[0]);
          g_set_prgname (prgname);
          g_free (prgname);
        }
      else
        g_set_prgname ("<unknown>");
    }

  /* Call pre-parse hooks */
  list = context->groups;
  while (list)
    {
      GOptionGroup *group = list->data;

      if (group->pre_parse_func)
        {
          if (!(* group->pre_parse_func) (context, group,
                                          group->user_data, error))
            goto fail;
        }

      list = list->next;
    }

  if (context->main_group && context->main_group->pre_parse_func)
    {
      if (!(* context->main_group->pre_parse_func) (context, context->main_group,
                                                    context->main_group->user_data, error))
        goto fail;
    }

  if (argc && argv)
    {
      gboolean stop_parsing = FALSE;
      gboolean has_unknown = FALSE;
      gint separator_pos = 0;

      for (i = 1; i < *argc; i++)
        {
          gchar *arg, *dash;
          gboolean parsed = FALSE;

          if ((*argv)[i][0] == '-' && (*argv)[i][1] != '\0' && !stop_parsing)
            {
              if ((*argv)[i][1] == '-')
                {
                  /* -- option */

                  arg = (*argv)[i] + 2;

                  /* '--' terminates list of arguments */
                  if (*arg == 0)
                    {
                      separator_pos = i;
                      stop_parsing = TRUE;
                      continue;
                    }

                  /* Handle help options */
                  if (context->help_enabled)
                    {
                      if (strcmp (arg, "help") == 0)
                        print_help (context, TRUE, NULL);
                      else if (strcmp (arg, "help-all") == 0)
                        print_help (context, FALSE, NULL);
                      else if (strncmp (arg, "help-", 5) == 0)
                        {
                          list = context->groups;

                          while (list)
                            {
                              GOptionGroup *group = list->data;

                              if (strcmp (arg + 5, group->name) == 0)
                                print_help (context, FALSE, group);

                              list = list->next;
                            }
                        }
                    }

                  if (context->main_group &&
                      !parse_long_option (context, context->main_group, &i, arg,
                                          FALSE, argc, argv, error, &parsed))
                    goto fail;

                  if (parsed)
                    continue;

                  /* Try the groups */
                  list = context->groups;
                  while (list)
                    {
                      GOptionGroup *group = list->data;

                      if (!parse_long_option (context, group, &i, arg,
                                              FALSE, argc, argv, error, &parsed))
                        goto fail;

                      if (parsed)
                        break;

                      list = list->next;
                    }

                  if (parsed)
                    continue;

                  /* Now look for --<group>-<option> */
                  dash = strchr (arg, '-');
                  if (dash)
                    {
                      /* Try the groups */
                      list = context->groups;
                      while (list)
                        {
                          GOptionGroup *group = list->data;

                          if (strncmp (group->name, arg, dash - arg) == 0)
                            {
                              if (!parse_long_option (context, group, &i, dash + 1,
                                                      TRUE, argc, argv, error, &parsed))
                                goto fail;

                              if (parsed)
                                break;
                            }

                          list = list->next;
                        }
                    }

                  if (context->ignore_unknown)
                    continue;
                }
              else
                { /* short option */
                  gint new_i = i, arg_length;
                  gboolean *nulled_out = NULL;
                  gboolean has_h_entry = context_has_h_entry (context);
                  arg = (*argv)[i] + 1;
                  arg_length = strlen (arg);
                  nulled_out = g_newa (gboolean, arg_length);
                  memset (nulled_out, 0, arg_length * sizeof (gboolean));
                  for (j = 0; j < arg_length; j++)
                    {
                      if (context->help_enabled && (arg[j] == '?' ||
                        (arg[j] == 'h' && !has_h_entry)))
                        print_help (context, TRUE, NULL);
                      parsed = FALSE;
                      if (context->main_group &&
                          !parse_short_option (context, context->main_group,
                                               i, &new_i, arg[j],
                                               argc, argv, error, &parsed))
                        goto fail;
                      if (!parsed)
                        {
                          /* Try the groups */
                          list = context->groups;
                          while (list)
                            {
                              GOptionGroup *group = list->data;
                              if (!parse_short_option (context, group, i, &new_i, arg[j],
                                                       argc, argv, error, &parsed))
                                goto fail;
                              if (parsed)
                                break;
                              list = list->next;
                            }
                        }

                      if (context->ignore_unknown && parsed)
                        nulled_out[j] = TRUE;
                      else if (context->ignore_unknown)
                        continue;
                      else if (!parsed)
                        break;
                      /* !context->ignore_unknown && parsed */
                    }
                  if (context->ignore_unknown)
                    {
                      gchar *new_arg = NULL;
                      gint arg_index = 0;
                      for (j = 0; j < arg_length; j++)
                        {
                          if (!nulled_out[j])
                            {
                              if (!new_arg)
                                new_arg = g_malloc (arg_length + 1);
                              new_arg[arg_index++] = arg[j];
                            }
                        }
                      if (new_arg)
                        new_arg[arg_index] = '\0';
                      add_pending_null (context, &((*argv)[i]), new_arg);
                    }
                  else if (parsed)
                    {
                      add_pending_null (context, &((*argv)[i]), NULL);
                      i = new_i;
                    }
                }

              if (!parsed)
                has_unknown = TRUE;

              if (!parsed && !context->ignore_unknown)
                {
                  g_set_error (error,
                               G_OPTION_ERROR, G_OPTION_ERROR_UNKNOWN_OPTION,
                                   _("Unknown option %s"), (*argv)[i]);
                  goto fail;
                }
            }
          else
            {
              /* Collect remaining args */
              if (context->main_group &&
                  !parse_remaining_arg (context, context->main_group, &i,
                                        argc, argv, error, &parsed))
                goto fail;

              if (!parsed && (has_unknown || (*argv)[i][0] == '-'))
                separator_pos = 0;
            }
        }

      if (separator_pos > 0)
        add_pending_null (context, &((*argv)[separator_pos]), NULL);

    }

  /* Call post-parse hooks */
  list = context->groups;
  while (list)
    {
      GOptionGroup *group = list->data;

      if (group->post_parse_func)
        {
          if (!(* group->post_parse_func) (context, group,
                                           group->user_data, error))
            goto fail;
        }

      list = list->next;
    }

  if (context->main_group && context->main_group->post_parse_func)
    {
      if (!(* context->main_group->post_parse_func) (context, context->main_group,
                                                     context->main_group->user_data, error))
        goto fail;
    }

  if (argc && argv)
    {
      free_pending_nulls (context, TRUE);

      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];
                  (*argv)[j] = NULL;
                }
              *argc -= k;
            }
        }
    }

  return TRUE;

 fail:

  /* Call error hooks */
  list = context->groups;
  while (list)
    {
      GOptionGroup *group = list->data;

      if (group->error_func)
        (* group->error_func) (context, group,
                               group->user_data, error);

      list = list->next;
    }

  if (context->main_group && context->main_group->error_func)
    (* context->main_group->error_func) (context, context->main_group,
                                         context->main_group->user_data, error);

  free_changes_list (context, TRUE);
  free_pending_nulls (context, FALSE);

  return FALSE;
}

/**
 * g_option_group_new:
 * @name: the name for the option group, this is used to provide
 *   help for the options in this group with <option>--help-</option>@name
 * @description: a description for this group to be shown in
 *   <option>--help</option>. This string is translated using the translation
 *   domain or translation function of the group
 * @help_description: a description for the <option>--help-</option>@name option.
 *   This string is translated using the translation domain or translation function
 *   of the group
 * @user_data: user data that will be passed to the pre- and post-parse hooks,
 *   the error hook and to callbacks of %G_OPTION_ARG_CALLBACK options, or %NULL
 * @destroy: a function that will be called to free @user_data, or %NULL
 *
 * Creates a new #GOptionGroup.
 *
 * Return value: a newly created option group. It should be added
 *   to a #GOptionContext or freed with g_option_group_free().
 *
 * Since: 2.6
 **/
GOptionGroup *
g_option_group_new (const gchar    *name,
                    const gchar    *description,
                    const gchar    *help_description,
                    gpointer        user_data,
                    GDestroyNotify  destroy)

{
  GOptionGroup *group;

  group = g_new0 (GOptionGroup, 1);
  group->name = g_strdup (name);
  group->description = g_strdup (description);
  group->help_description = g_strdup (help_description);
  group->user_data = user_data;
  group->destroy_notify = destroy;

  return group;
}


/**
 * g_option_group_free:
 * @group: a #GOptionGroup
 *
 * Frees a #GOptionGroup. Note that you must <emphasis>not</emphasis>
 * free groups which have been added to a #GOptionContext.
 *
 * Since: 2.6
 **/
void
g_option_group_free (GOptionGroup *group)
{
  g_return_if_fail (group != NULL);

  g_free (group->name);
  g_free (group->description);
  g_free (group->help_description);

  g_free (group->entries);

  if (group->destroy_notify)
    (* group->destroy_notify) (group->user_data);

  if (group->translate_notify)
    (* group->translate_notify) (group->translate_data);

  g_free (group);
}


/**
 * g_option_group_add_entries:
 * @group: a #GOptionGroup
 * @entries: a %NULL-terminated array of #GOptionEntry<!-- -->s
 *
 * Adds the options specified in @entries to @group.
 *
 * Since: 2.6
 **/
void
g_option_group_add_entries (GOptionGroup       *group,
                            const GOptionEntry *entries)
{
  gint i, n_entries;

  g_return_if_fail (entries != NULL);

  for (n_entries = 0; entries[n_entries].long_name != NULL; n_entries++) ;

  group->entries = g_renew (GOptionEntry, group->entries, group->n_entries + n_entries);

  memcpy (group->entries + group->n_entries, entries, sizeof (GOptionEntry) * n_entries);

  for (i = group->n_entries; i < group->n_entries + n_entries; i++)
    {
      gchar c = group->entries[i].short_name;

      if (c)
        {
          if (c == '-' || !g_ascii_isprint (c))
            {
              g_warning (G_STRLOC": ignoring invalid short option '%c' (%d)", c, c);
              group->entries[i].short_name = 0;
            }
        }
    }

  group->n_entries += n_entries;
}

/**
 * g_option_group_set_parse_hooks:
 * @group: a #GOptionGroup
 * @pre_parse_func: a function to call before parsing, or %NULL
 * @post_parse_func: a function to call after parsing, or %NULL
 *
 * Associates two functions with @group which will be called
 * from g_option_context_parse() before the first option is parsed
 * and after the last option has been parsed, respectively.
 *
 * Note that the user data to be passed to @pre_parse_func and
 * @post_parse_func can be specified when constructing the group
 * with g_option_group_new().
 *
 * Since: 2.6
 **/
void
g_option_group_set_parse_hooks (GOptionGroup     *group,
                                GOptionParseFunc  pre_parse_func,
                                GOptionParseFunc  post_parse_func)
{
  g_return_if_fail (group != NULL);

  group->pre_parse_func = pre_parse_func;
  group->post_parse_func = post_parse_func;
}

/**
 * g_option_group_set_error_hook:
 * @group: a #GOptionGroup
 * @error_func: a function to call when an error occurs
 *
 * Associates a function with @group which will be called
 * from g_option_context_parse() when an error occurs.
 *
 * Note that the user data to be passed to @error_func can be
 * specified when constructing the group with g_option_group_new().
 *
 * Since: 2.6
 **/
void
g_option_group_set_error_hook (GOptionGroup     *group,
                               GOptionErrorFunc  error_func)
{
  g_return_if_fail (group != NULL);

  group->error_func = error_func;
}


/**
 * g_option_group_set_translate_func:
 * @group: a #GOptionGroup
 * @func: the #GTranslateFunc, or %NULL
 * @data: user data to pass to @func, or %NULL
 * @destroy_notify: a function which gets called to free @data, or %NULL
 *
 * Sets the function which is used to translate user-visible
 * strings, for <option>--help</option> output. Different
 * groups can use different #GTranslateFunc<!-- -->s. If @func
 * is %NULL, strings are not translated.
 *
 * If you are using gettext(), you only need to set the translation
 * domain, see g_option_group_set_translation_domain().
 *
 * Since: 2.6
 **/
void
g_option_group_set_translate_func (GOptionGroup   *group,
                                   GTranslateFunc  func,
                                   gpointer        data,
                                   GDestroyNotify  destroy_notify)
{
  g_return_if_fail (group != NULL);

  if (group->translate_notify)
    group->translate_notify (group->translate_data);

  group->translate_func = func;
  group->translate_data = data;
  group->translate_notify = destroy_notify;
}

static const gchar *
dgettext_swapped (const gchar *msgid,
                  const gchar *domainname)
{
  return g_dgettext (domainname, msgid);
}

/**
 * g_option_group_set_translation_domain:
 * @group: a #GOptionGroup
 * @domain: the domain to use
 *
 * A convenience function to use gettext() for translating
 * user-visible strings.
 *
 * Since: 2.6
 **/
void
g_option_group_set_translation_domain (GOptionGroup *group,
                                       const gchar  *domain)
{
  g_return_if_fail (group != NULL);

  g_option_group_set_translate_func (group,
                                     (GTranslateFunc)dgettext_swapped,
                                     g_strdup (domain),
                                     g_free);
}

/**
 * g_option_context_set_translate_func:
 * @context: a #GOptionContext
 * @func: the #GTranslateFunc, or %NULL
 * @data: user data to pass to @func, or %NULL
 * @destroy_notify: a function which gets called to free @data, or %NULL
 *
 * Sets the function which is used to translate the contexts
 * user-visible strings, for <option>--help</option> output.
 * If @func is %NULL, strings are not translated.
 *
 * Note that option groups have their own translation functions,
 * this function only affects the @parameter_string (see g_option_context_new()),
 * the summary (see g_option_context_set_summary()) and the description
 * (see g_option_context_set_description()).
 *
 * If you are using gettext(), you only need to set the translation
 * domain, see g_option_context_set_translation_domain().
 *
 * Since: 2.12
 **/
void
g_option_context_set_translate_func (GOptionContext *context,
                                     GTranslateFunc func,
                                     gpointer       data,
                                     GDestroyNotify destroy_notify)
{
  g_return_if_fail (context != NULL);

  if (context->translate_notify)
    context->translate_notify (context->translate_data);

  context->translate_func = func;
  context->translate_data = data;
  context->translate_notify = destroy_notify;
}

/**
 * g_option_context_set_translation_domain:
 * @context: a #GOptionContext
 * @domain: the domain to use
 *
 * A convenience function to use gettext() for translating
 * user-visible strings.
 *
 * Since: 2.12
 **/
void
g_option_context_set_translation_domain (GOptionContext *context,
                                         const gchar     *domain)
{
  g_return_if_fail (context != NULL);

  g_option_context_set_translate_func (context,
                                       (GTranslateFunc)dgettext_swapped,
                                       g_strdup (domain),
                                       g_free);
}

/**
 * g_option_context_set_summary:
 * @context: a #GOptionContext
 * @summary: a string to be shown in <option>--help</option> output
 *  before the list of options, or %NULL
 *
 * Adds a string to be displayed in <option>--help</option> output
 * before the list of options. This is typically a summary of the
 * program functionality.
 *
 * Note that the summary is translated (see
 * g_option_context_set_translate_func() and
 * g_option_context_set_translation_domain()).
 *
 * Since: 2.12
 */
void
g_option_context_set_summary (GOptionContext *context,
                              const gchar    *summary)
{
  g_return_if_fail (context != NULL);

  g_free (context->summary);
  context->summary = g_strdup (summary);
}


/**
 * g_option_context_get_summary:
 * @context: a #GOptionContext
 *
 * Returns the summary. See g_option_context_set_summary().
 *
 * Returns: the summary
 *
 * Since: 2.12
 */
G_CONST_RETURN gchar *
g_option_context_get_summary (GOptionContext *context)
{
  g_return_val_if_fail (context != NULL, NULL);

  return context->summary;
}

/**
 * g_option_context_set_description:
 * @context: a #GOptionContext
 * @description: a string to be shown in <option>--help</option> output
 *   after the list of options, or %NULL
 *
 * Adds a string to be displayed in <option>--help</option> output
 * after the list of options. This text often includes a bug reporting
 * address.
 *
 * Note that the summary is translated (see
 * g_option_context_set_translate_func()).
 *
 * Since: 2.12
 */
void
g_option_context_set_description (GOptionContext *context,
                                  const gchar    *description)
{
  g_return_if_fail (context != NULL);

  g_free (context->description);
  context->description = g_strdup (description);
}


/**
 * g_option_context_get_description:
 * @context: a #GOptionContext
 *
 * Returns the description. See g_option_context_set_description().
 *
 * Returns: the description
 *
 * Since: 2.12
 */
G_CONST_RETURN gchar *
g_option_context_get_description (GOptionContext *context)
{
  g_return_val_if_fail (context != NULL, NULL);

  return context->description;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer
Argentina Argentina
System developer from Argentina.

Programmed in VB 5,6,.NET, C#, Java, PL-SQL, Transac-SQL, C, C++ and even some "calculator" language.

Love to build small, useful applications.
Usually building big and complicated apps based on solid, reliable components.

Hobbies: reading, photography, chess, paddle, running.

Comments and Discussions