diff --git a/ChangeLog b/ChangeLog index c3d7d2a76..803183296 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,14 @@ +2004-08-02 Anders Carlsson + + * glib/Makefile.am: + * glib/glib.h: + * glib/goption.c: + * glib/goption.h: + * tests/.cvsignore: + * tests/Makefile.am: + * tests/option-test.c: + Add GOption. + 2004-08-02 Matthias Clasen * glib/gmacros.h: Add a G_GNUC_INTERNAL macro to mark function diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index c3d7d2a76..803183296 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,14 @@ +2004-08-02 Anders Carlsson + + * glib/Makefile.am: + * glib/glib.h: + * glib/goption.c: + * glib/goption.h: + * tests/.cvsignore: + * tests/Makefile.am: + * tests/option-test.c: + Add GOption. + 2004-08-02 Matthias Clasen * glib/gmacros.h: Add a G_GNUC_INTERNAL macro to mark function diff --git a/ChangeLog.pre-2-12 b/ChangeLog.pre-2-12 index c3d7d2a76..803183296 100644 --- a/ChangeLog.pre-2-12 +++ b/ChangeLog.pre-2-12 @@ -1,3 +1,14 @@ +2004-08-02 Anders Carlsson + + * glib/Makefile.am: + * glib/glib.h: + * glib/goption.c: + * glib/goption.h: + * tests/.cvsignore: + * tests/Makefile.am: + * tests/option-test.c: + Add GOption. + 2004-08-02 Matthias Clasen * glib/gmacros.h: Add a G_GNUC_INTERNAL macro to mark function diff --git a/ChangeLog.pre-2-6 b/ChangeLog.pre-2-6 index c3d7d2a76..803183296 100644 --- a/ChangeLog.pre-2-6 +++ b/ChangeLog.pre-2-6 @@ -1,3 +1,14 @@ +2004-08-02 Anders Carlsson + + * glib/Makefile.am: + * glib/glib.h: + * glib/goption.c: + * glib/goption.h: + * tests/.cvsignore: + * tests/Makefile.am: + * tests/option-test.c: + Add GOption. + 2004-08-02 Matthias Clasen * glib/gmacros.h: Add a G_GNUC_INTERNAL macro to mark function diff --git a/ChangeLog.pre-2-8 b/ChangeLog.pre-2-8 index c3d7d2a76..803183296 100644 --- a/ChangeLog.pre-2-8 +++ b/ChangeLog.pre-2-8 @@ -1,3 +1,14 @@ +2004-08-02 Anders Carlsson + + * glib/Makefile.am: + * glib/glib.h: + * glib/goption.c: + * glib/goption.h: + * tests/.cvsignore: + * tests/Makefile.am: + * tests/option-test.c: + Add GOption. + 2004-08-02 Matthias Clasen * glib/gmacros.h: Add a G_GNUC_INTERNAL macro to mark function diff --git a/glib/Makefile.am b/glib/Makefile.am index a8cb5c754..827a1f3ab 100644 --- a/glib/Makefile.am +++ b/glib/Makefile.am @@ -63,6 +63,7 @@ libglib_2_0_la_SOURCES = \ gmem.c \ gmessages.c \ gnode.c \ + goption.c \ gpattern.c \ gprimes.c \ gqsort.c \ @@ -133,6 +134,7 @@ glibsubinclude_HEADERS = \ gmem.h \ gmessages.h \ gnode.h \ + goption.h \ gpattern.h \ gprimes.h \ gqsort.h \ diff --git a/glib/glib.h b/glib/glib.h index b8a720ee3..b267fe90e 100644 --- a/glib/glib.h +++ b/glib/glib.h @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/goption.c b/glib/goption.c new file mode 100644 index 000000000..24944b624 --- /dev/null +++ b/glib/goption.c @@ -0,0 +1,1195 @@ +/* goption.c - Option parser + * + * Copyright (C) 1999, 2003 Red Hat Software + * Copyright (C) 2004 Anders Carlsson + * + * 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. + */ + +#include "goption.h" + +#include "glib.h" +#include "gi18n.h" + +#include +#include +#include + +#define TRANSLATE(group, str) (((group)->translate_func ? (* (group)->translate_func) ((str), (group)->translate_data) : (str))) + +typedef struct { + GOptionArg arg_type; + gpointer arg_data; + union { + gboolean bool; + gint integer; + gchar *str; + gchar **array; + } prev; + union { + gchar *str; + struct { + int len; + gchar **data; + } array; + } allocated; +} Change; + +typedef struct +{ + gchar **ptr; + gchar *value; +} PendingNull; + +struct _GOptionContext +{ + GList *groups; + + gchar *parameter_string; + + gboolean help_enabled; + gboolean ignore_unknown; + + 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); + +GQuark +g_option_context_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) + q = g_quark_from_static_string ("g-option-context-error-quark"); + + return q; +} + +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; + + return context; +} + +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); + + 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); +} + +void +g_option_context_set_help_enabled (GOptionContext *context, + gboolean help_enabled) + +{ + g_return_if_fail (context != NULL); + + context->help_enabled = help_enabled; +} + +gboolean +g_option_context_get_help_enabled (GOptionContext *context) +{ + g_return_val_if_fail (context != NULL, FALSE); + + return context->help_enabled; +} + +void +g_option_context_set_ignore_unknown_options (GOptionContext *context, + gboolean ignore_unknown) +{ + g_return_if_fail (context != NULL); + + context->ignore_unknown = ignore_unknown; +} + +gboolean +g_option_context_get_ignore_unknown_options (GOptionContext *context) +{ + g_return_val_if_fail (context != NULL, FALSE); + + return context->ignore_unknown; +} + +void +g_option_context_add_group (GOptionContext *context, + GOptionGroup *group) +{ + 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); + + context->groups = g_list_prepend (context->groups, group); +} + +void +g_option_context_set_main_group (GOptionContext *context, + GOptionGroup *group) +{ + g_return_if_fail (context != NULL); + g_return_if_fail (group != NULL); + + context->main_group = group; +} + +GOptionGroup * +g_option_context_get_main_group (GOptionContext *context) +{ + g_return_val_if_fail (context != NULL, NULL); + + return context->main_group; +} + +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 void +print_entry (GOptionGroup *group, + gint max_length, + const GOptionEntry *entry) +{ + GString *str; + + if (entry->flags & G_OPTION_FLAG_HIDDEN) + 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_print ("%-*s %s\n", max_length + 4, str->str, + entry->description ? TRANSLATE (group, entry->description) : ""); + g_string_free (str, TRUE); +} + +static void +print_help (GOptionContext *context, + gboolean main_help, + GOptionGroup *group) +{ + GList *list; + gint max_length, len; + gint i; + + g_print ("Usage:\n"); + g_print (" %s [OPTION...] %s\n\n", g_get_prgname (), + context->parameter_string ? context->parameter_string : ""); + + list = context->groups; + + max_length = g_utf8_strlen ("--help, -?", -1); + + if (list) + { + len = g_utf8_strlen ("--help-all", -1); + max_length = MAX (max_length, len); + } + + while (list != NULL) + { + GOptionGroup *group = list->data; + + /* First, we check the --help- options */ + len = g_utf8_strlen ("--help-", -1) + g_utf8_strlen (group->name, -1); + max_length = MAX (max_length, len); + + /* Then we go through the entries */ + for (i = 0; i < group->n_entries; i++) + { + len = g_utf8_strlen (group->entries[i].long_name, -1); + + if (group->entries[i].short_name) + len += 4; + + if (group->entries[i].arg != G_OPTION_ARG_NONE && + group->entries[i].arg_description) + len += 1 + g_utf8_strlen (TRANSLATE (group, group->entries[i].arg_description), -1); + + max_length = MAX (max_length, len); + } + + list = list->next; + } + + /* Add a bit of padding */ + max_length += 4; + + list = context->groups; + + g_print ("Help Options:\n"); + g_print (" --%-*s %s\n", max_length, "help", "Show help options"); + + /* We only want --help-all when there are groups */ + if (list) + g_print (" --%-*s %s\n", max_length, "help-all", "Show all help options"); + + while (list) + { + GOptionGroup *group = list->data; + + g_print (" --help-%-*s %s\n", max_length - 5, group->name, TRANSLATE (group, group->help_description)); + + list = list->next; + } + + g_print ("\n"); + + if (group) + { + /* Print a certain group */ + + g_print ("%s\n", TRANSLATE (group, group->description)); + for (i = 0; i < group->n_entries; i++) + print_entry (group, max_length, &group->entries[i]); + g_print ("\n"); + } + else if (!main_help) + { + /* Print all groups */ + + list = context->groups; + + while (list) + { + GOptionGroup *group = list->data; + + g_print ("%s\n", group->description); + + for (i = 0; i < group->n_entries; i++) + if (!(group->entries[i].flags & G_OPTION_FLAG_IN_MAIN)) + print_entry (group, max_length, &group->entries[i]); + + g_print ("\n"); + list = list->next; + } + } + + /* Print application options if --help or --help-all has been specified */ + if (main_help || !group) + { + list = context->groups; + + g_print ("Application Options\n"); + + for (i = 0; i < context->main_group->n_entries; i++) + print_entry (context->main_group, max_length, &context->main_group->entries[i]); + + while (list != NULL) + { + GOptionGroup *group = list->data; + + /* Print main entries from other groups */ + for (i = 0; i < group->n_entries; i++) + if (group->entries[i].flags & G_OPTION_FLAG_IN_MAIN) + print_entry (group, max_length, &group->entries[i]); + + list = list->next; + } + + g_print ("\n"); + } + + + exit (0); +} + +static gboolean +parse_int (const char *arg_name, + const char *arg, + gint *result, + GError **error) +{ + gchar *end; + glong tmp = strtol (arg, &end, 0); + + errno = 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 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) + break; + } + + if (!change) + { + change = g_new0 (Change, 1); + change->arg_type = arg_type; + change->arg_data = arg_data; + + context->changes = g_list_prepend (context->changes, change); + } + + 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; + + switch (entry->arg) + { + case G_OPTION_ARG_NONE: + { + change = get_change (context, G_OPTION_ARG_NONE, + entry->arg_data); + + *(gboolean *)entry->arg_data = TRUE; + 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 = 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; + + data = g_strdup (value); + + 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; + + break; + } + + case G_OPTION_ARG_FILENAME_ARRAY: + { + gchar *data; + + data = g_strdup (value); + + change = get_change (context, G_OPTION_ARG_STRING_ARRAY, + entry->arg_data); + + if (change->allocated.array.len == 0) + { + change->prev.array = 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: + { + int 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 *tmp; + gboolean retval; + + tmp = g_locale_to_utf8 (value, -1, NULL, NULL, error); + + if (!value) + return FALSE; + + retval = (* (GOptionArgFunc) entry->arg_data) (option_name, tmp, group->user_data, error); + + g_free (tmp); + + return retval; + + break; + } + default: + g_assert_not_reached (); + } + + return TRUE; +} + +static gboolean +parse_short_option (GOptionContext *context, + GOptionGroup *group, + gint index, + gint *new_index, + gchar arg, + gint *argc, + gchar ***argv, + GError **error, + gboolean *parsed) +{ + int j; + + for (j = 0; j < group->n_entries; j++) + { + if (arg == group->entries[j].short_name) + { + if (group->entries[j].arg == G_OPTION_ARG_NONE) + { + parse_arg (context, group, &group->entries[j], + NULL, NULL, error); + *parsed = TRUE; + } + else + { + gchar *value = NULL; + gchar *option_name; + + if (*new_index > index) + { + g_warning ("FIXME: figure out the correct error here"); + + return FALSE; + } + + if (index < *argc - 1) + { + value = (*argv)[index + 1]; + add_pending_null (context, &((*argv)[index + 1]), NULL); + *new_index = index + 1; + } + else + value = ""; + + + option_name = g_strdup_printf ("-%c", group->entries[j].short_name); + + 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 *index, + gchar *arg, + gint *argc, + gchar ***argv, + GError **error, + gboolean *parsed) +{ + int j; + + for (j = 0; j < group->n_entries; j++) + { + if (*index >= *argc) + return TRUE; + + if (group->entries[j].arg == G_OPTION_ARG_NONE && + strcmp (arg, group->entries[j].long_name) == 0) + { + parse_arg (context, group, &group->entries[j], + NULL, NULL, error); + + add_pending_null (context, &((*argv)[*index]), NULL); + } + 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)[*index + 1]), NULL); + + if (arg[len] == '=') + value = arg + len + 1; + else if (*index < *argc - 1) + { + value = (*argv)[*index + 1]; + add_pending_null (context, &((*argv)[*index + 1]), NULL); + (*index)++; + } + else + value = ""; + + option_name = g_strconcat ("--", group->entries[j].long_name, NULL); + + 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 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) + { + /* Free any allocated data */ + g_free (change->allocated.str); + g_strfreev (change->allocated.array.data); + + 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: + *(gchar **)change->arg_data = change->prev.str; + break; + case G_OPTION_ARG_STRING_ARRAY: + case G_OPTION_ARG_FILENAME_ARRAY: + *(gchar ***)change->arg_data = change->prev.array; + 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; +} + +gboolean +g_option_context_parse (GOptionContext *context, + gint *argc, + gchar ***argv, + GError **error) +{ + gint i, j, k; + GList *list; + + /* 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->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) + { + for (i = 1; i < *argc; i++) + { + gchar *arg; + gboolean parsed = FALSE; + + if ((*argv)[i][0] == '-') + { + if ((*argv)[i][1] == '-') + { + /* -- option */ + + arg = (*argv)[i] + 2; + + /* '--' terminates list of arguments */ + if (*arg == 0) + { + add_pending_null (context, &((*argv)[i]), NULL); + break; + } + + /* 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) + { + GList *list; + + 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 (!parse_long_option (context, context->main_group, &i, arg, + 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, + argc, argv, error, &parsed)) + goto fail; + + if (parsed) + break; + + list = list->next; + } + + if (context->ignore_unknown) + continue; + } + else + { + gint new_i, j; + gboolean *nulled_out = NULL; + + arg = (*argv)[i] + 1; + + new_i = i; + + if (context->ignore_unknown) + nulled_out = g_new0 (gboolean, strlen (arg)); + + for (j = 0; j < strlen (arg); j++) + { + parsed = FALSE; + + if (!parse_short_option (context, context->main_group, + i, &new_i, arg[j], + argc, argv, error, &parsed)) + { + + g_free (nulled_out); + 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) + { + if (parsed) + nulled_out[j] = TRUE; + else + continue; + } + + if (!parsed) + break; + } + + if (context->ignore_unknown) + { + gchar *new_arg = NULL; + int arg_index = 0; + + for (j = 0; j < strlen (arg); j++) + { + if (!nulled_out[j]) + { + if (!new_arg) + new_arg = g_malloc (strlen (arg)); + 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 && !context->ignore_unknown) + { + g_set_error (error, + G_OPTION_ERROR, G_OPTION_ERROR_UNKNOWN_OPTION, + _("Unknown option %s"), (*argv)[i]); + goto fail; + } + } + } + + /* 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->post_parse_func) + { + if (!(* context->main_group->post_parse_func) (context, context->main_group, + context->main_group->user_data, error)) + goto fail; + } + + 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]; + *argc -= k; + } + } + + } + + return TRUE; + + fail: + + /* Call error hooks */ + list = context->groups; + while (list) + { + GOptionGroup *group = list->data; + + if (group->error_func) + (* group->post_parse_func) (context, group, + group->user_data, error); + + list = list->next; + } + + if (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; +} + + +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; +} + +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); +} + + +void +g_option_group_add_entries (GOptionGroup *group, + const GOptionEntry *entries) +{ + int n_entries; + int i; + + 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); + + group->n_entries += n_entries; +} + +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; +} + +void +g_option_group_set_error_hook (GOptionGroup *group, + GOptionErrorFunc error_func) +{ + g_return_if_fail (group != NULL); + + group->error_func = error_func; +} + + +void +g_option_group_set_translate_func (GOptionGroup *group, + GTranslateFunc func, + gpointer data, + GDestroyNotify 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 = notify; +} + +static gchar * +dgettext_swapped (const gchar *msgid, + const gchar *domainname) +{ + return dgettext (domainname, msgid); +} + +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); +} + diff --git a/glib/goption.h b/glib/goption.h new file mode 100644 index 000000000..2921253bd --- /dev/null +++ b/glib/goption.h @@ -0,0 +1,144 @@ +/* goption.h - Option parser + * + * Copyright (C) 2004 Anders Carlsson + * + * 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. + */ + +#ifndef __G_OPTION_H__ +#define __G_OPTION_H__ + +#include +#include + +G_BEGIN_DECLS + +/* Should go into gtypes.h */ +typedef const gchar * (*GTranslateFunc) (const gchar *str, + gpointer data); + +typedef struct _GOptionContext GOptionContext; +typedef struct _GOptionGroup GOptionGroup; +typedef struct _GOptionEntry GOptionEntry; + +typedef enum +{ + G_OPTION_FLAG_HIDDEN = 1 << 0, + G_OPTION_FLAG_IN_MAIN = 1 << 1, +} GOptionFlags; + +typedef enum +{ + G_OPTION_ARG_NONE, + G_OPTION_ARG_STRING, + G_OPTION_ARG_INT, + G_OPTION_ARG_CALLBACK, + G_OPTION_ARG_FILENAME, + G_OPTION_ARG_STRING_ARRAY, + G_OPTION_ARG_FILENAME_ARRAY, +} GOptionArg; + +typedef gboolean (*GOptionArgFunc) (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error); + +typedef gboolean (*GOptionParseFunc) (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error); + +typedef void (*GOptionErrorFunc) (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error); + +#define G_OPTION_ERROR (g_option_context_error_quark ()) + +typedef enum +{ + G_OPTION_ERROR_UNKNOWN_OPTION, + G_OPTION_ERROR_BAD_VALUE, + G_OPTION_ERROR_FAILED +} GOptionError; + +GQuark g_option_context_error_quark (void) G_GNUC_CONST; + + +struct _GOptionEntry +{ + const char *long_name; + char short_name; + int flags; + + GOptionArg arg; + gpointer arg_data; + + const char *description; + const char *arg_description; +}; + +GOptionContext *g_option_context_new (const gchar *parameter_string); +void g_option_context_free (GOptionContext *context); +void g_option_context_set_help_enabled (GOptionContext *context, + gboolean help_enabled); +gboolean g_option_context_get_help_enabled (GOptionContext *context); +void g_option_context_set_ignore_unknown_options (GOptionContext *context, + gboolean ignore_unknown); +gboolean g_option_context_get_ignore_unknown_options (GOptionContext *context); + +void g_option_context_add_main_entries (GOptionContext *context, + const GOptionEntry *entries, + const gchar *translation_domain); +gboolean g_option_context_parse (GOptionContext *context, + gint *argc, + gchar ***argv, + GError **error); + +void g_option_context_add_group (GOptionContext *context, + GOptionGroup *group); +void g_option_context_set_main_group (GOptionContext *context, + GOptionGroup *group); +GOptionGroup *g_option_context_get_main_group (GOptionContext *context); + + +GOptionGroup *g_option_group_new (const gchar *name, + const gchar *description, + const char *help_description, + gpointer user_data, + GDestroyNotify destroy); +void g_option_group_set_parse_hooks (GOptionGroup *group, + GOptionParseFunc pre_parse_func, + GOptionParseFunc post_parse_func); +void g_option_group_set_error_hook (GOptionGroup *group, + GOptionErrorFunc error_func); +void g_option_group_free (GOptionGroup *group); +void g_option_group_add_entries (GOptionGroup *group, + const GOptionEntry *entries); +void g_option_group_set_translate_func (GOptionGroup *group, + GTranslateFunc func, + gpointer data, + GDestroyNotify destroy_notify); +void g_option_group_set_translation_domain (GOptionGroup *group, + const gchar *domain); + + + + + +G_END_DECLS + +#endif /* __G_OPTION_H__ */ diff --git a/tests/.cvsignore b/tests/.cvsignore index 782e5ed00..9ebb7d479 100644 --- a/tests/.cvsignore +++ b/tests/.cvsignore @@ -33,6 +33,7 @@ markup-test markup-escape-test module-test node-test +option-test printf-test queue-test qsort-test diff --git a/tests/Makefile.am b/tests/Makefile.am index 94b29c2fa..2b8872bdc 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -79,6 +79,7 @@ test_programs = \ markup-escape-test \ module-test \ node-test \ + option-test \ patterntest \ printf-test \ queue-test \ @@ -132,6 +133,7 @@ markup_escape_test_LDADD = $(progs_ldadd) module_test_LDADD = $(module_ldadd) $(module_test_exp) module_test_LDFLAGS = $(G_MODULE_LDFLAGS) node_test_LDADD = $(progs_ldadd) +option_test_LDADD = $(progs_ldadd) printf_test_LDADD = $(progs_ldadd) queue_test_LDADD = $(progs_ldadd) qsort_test_LDADD = $(progs_ldadd) diff --git a/tests/option-test.c b/tests/option-test.c new file mode 100644 index 000000000..370ad6617 --- /dev/null +++ b/tests/option-test.c @@ -0,0 +1,438 @@ +#include +#include "goption.h" +#include + +int error_test1_int; +char *error_test2_string; +gboolean error_test3_boolean; + +int arg_test1_int; +gchar *arg_test2_string; + +gchar **array_test1_array; + +gboolean ignore_test1_boolean; +gboolean ignore_test2_boolean; + +gchar ** +split_string (const char *str, int *argc) +{ + gchar **argv; + int len; + + argv = g_strsplit (str, " ", 0); + + for (len = 0; argv[len] != NULL; len++); + + if (argc) + *argc = len; + + return argv; +} + +gchar * +join_stringv (int argc, char **argv) +{ + int i; + GString *str; + + str = g_string_new (NULL); + + for (i = 0; i < argc; i++) + { + g_string_append (str, argv[i]); + + if (i < argc - 1) + g_string_append_c (str, ' '); + } + + return g_string_free (str, FALSE); +} + +/* Performs a shallow copy */ +char ** +copy_stringv (char **argv, int argc) +{ + return g_memdup (argv, sizeof (char *) * (argc + 1)); +} + + +static gboolean +error_test1_pre_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (error_test1_int == 0x12345678); + + return TRUE; +} + +static gboolean +error_test1_post_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (error_test1_int == 20); + + /* Set an error in the post hook */ + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, NULL); + + return FALSE; +} + +void +error_test1 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionGroup *main_group; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_INT, &error_test1_int, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Set pre and post parse hooks */ + main_group = g_option_context_get_main_group (context); + g_option_group_set_parse_hooks (main_group, + error_test1_pre_parse, error_test1_post_parse); + + /* Now try parsing */ + argv = split_string ("program --test 20", &argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval == FALSE); + + /* On failure, values should be reset */ + g_assert (error_test1_int == 0x12345678); + + g_strfreev (argv); + g_option_context_free (context); + +} + +static gboolean +error_test2_pre_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (strcmp (error_test2_string, "foo") == 0); + + return TRUE; +} + +static gboolean +error_test2_post_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (strcmp (error_test2_string, "bar") == 0); + + /* Set an error in the post hook */ + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, NULL); + + return FALSE; +} + +void +error_test2 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionGroup *main_group; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_STRING, &error_test2_string, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Set pre and post parse hooks */ + main_group = g_option_context_get_main_group (context); + g_option_group_set_parse_hooks (main_group, + error_test2_pre_parse, error_test2_post_parse); + + /* Now try parsing */ + argv = split_string ("program --test bar", &argc); + retval = g_option_context_parse (context, &argc, &argv, &error); + + g_error_free (error); + g_assert (retval == FALSE); + + g_assert (strcmp (error_test2_string, "foo") == 0); + + g_strfreev (argv); + g_option_context_free (context); +} + +static gboolean +error_test3_pre_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (!error_test3_boolean); + + return TRUE; +} + +static gboolean +error_test3_post_parse (GOptionContext *context, + GOptionGroup *group, + gpointer data, + GError **error) +{ + g_assert (error_test3_boolean); + + /* Set an error in the post hook */ + g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, NULL); + + return FALSE; +} + +void +error_test3 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionGroup *main_group; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_NONE, &error_test3_boolean, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Set pre and post parse hooks */ + main_group = g_option_context_get_main_group (context); + g_option_group_set_parse_hooks (main_group, + error_test3_pre_parse, error_test3_post_parse); + + /* Now try parsing */ + argv = split_string ("program --test", &argc); + retval = g_option_context_parse (context, &argc, &argv, &error); + + g_error_free (error); + g_assert (retval == FALSE); + + g_assert (!error_test3_boolean); + + g_strfreev (argv); + g_option_context_free (context); +} + +void +arg_test1 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_INT, &arg_test1_int, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Now try parsing */ + argv = split_string ("program --test 20 --test 30", &argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval); + + /* Last arg specified is the one that should be stored */ + g_assert (arg_test1_int == 30); + + g_strfreev (argv); + g_option_context_free (context); +} + +void +arg_test2 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_STRING, &arg_test2_string, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Now try parsing */ + argv = split_string ("program --test foo --test bar", &argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval); + + /* Last arg specified is the one that should be stored */ + g_assert (strcmp (arg_test2_string, "bar") == 0); + + g_free (arg_test2_string); + + g_strfreev (argv); + g_option_context_free (context); +} + +void +ignore_test1 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv, **argv_copy; + int argc; + gchar *arg; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_NONE, &ignore_test1_boolean, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_set_ignore_unknown_options (context, TRUE); + g_option_context_add_main_entries (context, entries, NULL); + + /* Now try parsing */ + argv = split_string ("program --test --hello", &argc); + argv_copy = copy_stringv (argv, argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval); + + /* Check array */ + arg = join_stringv (argc, argv); + g_assert (strcmp (arg, "program --hello") == 0); + + g_free (arg); + g_strfreev (argv_copy); + g_free (argv); + g_option_context_free (context); +} + +void +ignore_test2 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + gchar *arg; + GOptionEntry entries [] = + { { "test", 't', 0, G_OPTION_ARG_NONE, &ignore_test2_boolean, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_set_ignore_unknown_options (context, TRUE); + g_option_context_add_main_entries (context, entries, NULL); + + /* Now try parsing */ + argv = split_string ("program -test", &argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval); + + /* Check array */ + arg = join_stringv (argc, argv); + g_assert (strcmp (arg, "program -es") == 0); + + g_free (arg); + g_strfreev (argv); + g_option_context_free (context); +} + +void +array_test1 (void) +{ + GOptionContext *context; + gboolean retval; + GError *error = NULL; + gchar **argv; + int argc; + GOptionEntry entries [] = + { { "test", 0, 0, G_OPTION_ARG_STRING_ARRAY, &array_test1_array, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + /* Now try parsing */ + argv = split_string ("program --test foo --test bar", &argc); + + retval = g_option_context_parse (context, &argc, &argv, &error); + g_assert (retval); + + /* Check array */ + g_assert (strcmp (array_test1_array[0], "foo") == 0); + g_assert (strcmp (array_test1_array[1], "bar") == 0); + g_assert (array_test1_array[2] == NULL); + + g_strfreev (array_test1_array); + + g_strfreev (argv); + g_option_context_free (context); +} + +void +add_test1 (void) +{ + GOptionContext *context; + + GOptionEntry entries1 [] = + { { "test1", 0, 0, G_OPTION_ARG_STRING_ARRAY, NULL, NULL, NULL }, + { NULL } }; + GOptionEntry entries2 [] = + { { "test2", 0, 0, G_OPTION_ARG_STRING_ARRAY, NULL, NULL, NULL }, + { NULL } }; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries1, NULL); + g_option_context_add_main_entries (context, entries2, NULL); + + g_option_context_free (context); +} + +int +main (int argc, char **argv) +{ + /* Test that restoration on failure works */ + error_test1_int = 0x12345678; + error_test1 (); + error_test2_string = "foo"; + error_test2 (); + error_test3_boolean = FALSE; + error_test3 (); + + /* Test that special argument parsing works */ + arg_test1 (); + arg_test2 (); + + /* Test string arrays */ + array_test1 (); + + /* Test ignoring options */ + ignore_test1 (); + ignore_test2 (); + + add_test1 (); + + return 0; +}