/* 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. */ /** * 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: * * testtreemodel -r 1 --max-size 20 --rand --display=:1.0 -vb -- file1 file2 * * The example demonstrates a number of features of the GOption * commandline parser * * Options can be single letters, prefixed by a single dash. Multiple * short options can be grouped behind a single dash. * * Long options are prefixed by two consecutive dashes. * * 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. * * Non-option arguments are returned to the application as rest arguments. * * 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. * * * 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 , , * and * groupname options * (where groupname is the name of a * #GOptionGroup) and write a text similar to the one shown in the * following example to stdout. * * * 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 * * * GOption groups options in #GOptionGroups, 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. * * * 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); * } * * /* ... */ * * } * */ #include "config.h" #include #include #include #include #if defined __OpenBSD__ #include #include #include #include #endif #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) { glong len = 0; const gchar *start = p; g_return_val_if_fail (p != NULL, 0); while (*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 output, after the * usage summary * programname [OPTION...] * * 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_free_full (context->groups, (GDestroyNotify) g_option_group_free); 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 , %FALSE to disable it * * Enables or disables automatic generation of * output. By default, g_option_context_parse() recognizes * , , * , * and groupname 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 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 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 #GOptionEntrys * @translation_domain: (allow-none): a translation domain to use for translating * the 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); if (entry->short_name) len += 4; if (!NO_ARG (entry) && entry->arg_description) len += 1 + _g_utf8_strwidth (TRANSLATE (group, entry->arg_description)); 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)), "", 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_entries (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: (allow-none): the #GOptionGroup to create help for, or %NULL * * Returns a formatted, translated help text for the given context. * To obtain the text produced by , call * g_option_context_get_help (context, TRUE, NULL). * To obtain the text produced by , call * g_option_context_get_help (context, FALSE, NULL). * To obtain the help text for an option group, call * g_option_context_get_help (context, FALSE, group). * * 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"); if (list) { len = _g_utf8_strwidth ("--help-all"); 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- options */ len = _g_utf8_strwidth ("--help-") + _g_utf8_strwidth (g->name); 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_entries (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 (!OPTIONAL_ARG (&group->entries[j])) { 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 && OPTIONAL_ARG (&group->entries[j])) { 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; } /* Use a platform-specific mechanism to look up the first argument to * the current process. * Note if you implement this for other platforms, also add it to * tests/option-argv0.c */ static char * platform_get_argv0 (void) { #if defined __linux char *cmdline; char *base_arg0; gsize len; if (!g_file_get_contents ("/proc/self/cmdline", &cmdline, &len, NULL)) return NULL; /* Sanity check for a NUL terminator. */ if (!memchr (cmdline, 0, len)) return NULL; /* We could just return cmdline, but I think it's better * to hold on to a smaller malloc block; the arguments * could be large. */ base_arg0 = g_path_get_basename (cmdline); g_free (cmdline); return base_arg0; #elif defined __OpenBSD__ char **cmdline = NULL; char *base_arg0; gsize len = PATH_MAX; int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV }; cmdline = (char **) realloc (cmdline, len); if (sysctl (mib, G_N_ELEMENTS (mib), cmdline, &len, NULL, 0) == -1) { g_free (cmdline); return NULL; } /* We could just return cmdline, but I think it's better * to hold on to a smaller malloc block; the arguments * could be large. */ base_arg0 = g_path_get_basename (*cmdline); g_free (cmdline); return base_arg0; #endif return NULL; } /** * g_option_context_parse: * @context: a #GOptionContext * @argc: (inout) (allow-none): a pointer to the number of command line arguments * @argv: (inout) (array length=argc) (allow-none): 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 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 exit (0). * * Note that function depends on the * current locale 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()) { gchar *prgname; if (argc && argv && *argc) prgname = g_path_get_basename ((*argv)[0]); else prgname = platform_get_argv0 (); if (prgname) g_set_prgname (prgname); else g_set_prgname (""); g_free (prgname); } /* 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 ---@name * @description: a description for this group to be shown in * . This string is translated using the translation * domain or translation function of the group * @help_description: a description for the @name option. * This string is translated using the translation domain or translation function * of the group * @user_data: (allow-none): 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: (allow-none): 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 not * 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 #GOptionEntrys * * 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 == '-' || (c != 0 && !g_ascii_isprint (c))) { g_warning (G_STRLOC ": ignoring invalid short option '%c' (%d) in entry %s:%s", c, c, group->name, group->entries[i].long_name); group->entries[i].short_name = '\0'; } if (group->entries[i].arg != G_OPTION_ARG_NONE && (group->entries[i].flags & G_OPTION_FLAG_REVERSE) != 0) { g_warning (G_STRLOC ": ignoring reverse flag on option of arg-type %d in entry %s:%s", group->entries[i].arg, group->name, group->entries[i].long_name); group->entries[i].flags &= ~G_OPTION_FLAG_REVERSE; } if (group->entries[i].arg != G_OPTION_ARG_CALLBACK && (group->entries[i].flags & (G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_OPTIONAL_ARG|G_OPTION_FLAG_FILENAME)) != 0) { g_warning (G_STRLOC ": ignoring no-arg, optional-arg or filename flags (%d) on option of arg-type %d in entry %s:%s", group->entries[i].flags, group->entries[i].arg, group->name, group->entries[i].long_name); group->entries[i].flags &= ~(G_OPTION_FLAG_NO_ARG|G_OPTION_FLAG_OPTIONAL_ARG|G_OPTION_FLAG_FILENAME); } } group->n_entries += n_entries; } /** * g_option_group_set_parse_hooks: * @group: a #GOptionGroup * @pre_parse_func: (allow-none): a function to call before parsing, or %NULL * @post_parse_func: (allow-none): 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: (allow-none): the #GTranslateFunc, or %NULL * @data: (allow-none): user data to pass to @func, or %NULL * @destroy_notify: (allow-none): a function which gets called to free @data, or %NULL * * Sets the function which is used to translate user-visible * strings, for output. Different * groups can use different #GTranslateFuncs. 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: (allow-none): the #GTranslateFunc, or %NULL * @data: (allow-none): user data to pass to @func, or %NULL * @destroy_notify: (allow-none): a function which gets called to free @data, or %NULL * * Sets the function which is used to translate the contexts * user-visible strings, for 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: (allow-none): a string to be shown in output * before the list of options, or %NULL * * Adds a string to be displayed in 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 */ const 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: (allow-none): a string to be shown in output * after the list of options, or %NULL * * Adds a string to be displayed in 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 */ const gchar * g_option_context_get_description (GOptionContext *context) { g_return_val_if_fail (context != NULL, NULL); return context->description; }