From ae52ab3d1170a393b0b734e9b9b37c3f107a7c03 Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Mon, 27 Jan 2014 15:42:23 +0000 Subject: [PATCH] GOption: add strict posix mode Add a "posixly correct" mode to GOption to stop parsing arguments as soon as the first non-option argument is encountered. We determine the default value on the basis of duplicating the behaviour of the system getopt() implementation (which we directly check the behaviour of at runtime). On GNU systems this allows the user to modify our behaviour using POSIXLY_CORRECT. The user can change the value by g_option_context_set_strict_posix(), which might be useful for some usecases of GOptionContext (as mentioned in the doc string of this new function). https://bugzilla.gnome.org/show_bug.cgi?id=723160 --- glib/goption.c | 72 +++++++++++++++++++++++++++++++++++++++++++++ glib/goption.h | 6 ++++ glib/tests/option-context.c | 66 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+) diff --git a/glib/goption.c b/glib/goption.c index 6438281..019f54f 100644 --- a/glib/goption.c +++ b/glib/goption.c @@ -183,6 +183,7 @@ #include #include #include +#include #if defined __OpenBSD__ #include @@ -248,6 +249,7 @@ struct _GOptionContext guint help_enabled : 1; guint ignore_unknown : 1; guint strv_mode : 1; + guint strict_posix : 1; GOptionGroup *main_group; @@ -358,6 +360,14 @@ g_option_context_new (const gchar *parameter_string) context = g_new0 (GOptionContext, 1); context->parameter_string = g_strdup (parameter_string); + { + const char *argv[] = { "./a", "a", "-a", NULL }; + /* Check to see if getopt will parse the "-a" or not. If it finds + * no arguments then we are in strict POSIX mode. + */ + optind = 1; + context->strict_posix = getopt (3, (char **) argv, "a") != 'a'; + } context->help_enabled = TRUE; context->ignore_unknown = FALSE; @@ -484,6 +494,65 @@ g_option_context_get_ignore_unknown_options (GOptionContext *context) } /** + * g_option_context_set_strict_posix: + * @context: a #GoptionContext + * + * Sets strict POSIX mode. + * + * In strict POSIX mode, the first non-argument parameter encountered + * (eg: filename) terminates argument processing. Remaining arguments + * are treated as non-options and are not attempted to be parsed. + * + * If strict POSIX mode is disabled then parsing is done in the GNU way + * where option arguments can be freely mixed with non-options. + * + * As an example, consider "ls foo -l". With GNU style parsing, this + * will list "foo" in long mode. In strict POSIX style, this will list + * the files named "foo" and "-l". + * + * The default is system-dependent. In particular, on some systems, it + * may be modified by the POSIXLY_CORRECT environment variable. + * + * It may be useful to force strict POSIX mode when creating "verb + * style" command line tools. For example, the "gsettings" command line + * tool supports the global option "--schemadir" as well as many + * subcommands ("get", "set", etc.) which each have their own set of + * arguments. Using strict POSIX mode will allow parsing the global + * options up to the verb name while leaving the remaining options to be + * parsed by the relevant subcommand (which can be determined by + * examining the verb name, which should be present in argv[1] after + * parsing). + * + * Since: 2.44 + **/ +void +g_option_context_set_strict_posix (GOptionContext *context, + gboolean strict_posix) +{ + g_return_if_fail (context != NULL); + + context->strict_posix = strict_posix; +} + +/** + * g_option_context_get_strict_posix: + * @context: a #GoptionContext + * + * Returns whether strict POSIX code is enabled. + * + * See g_option_context_set_strict_posix() for more information. + * + * Since: 2.44 + **/ +gboolean +g_option_context_get_strict_posix (GOptionContext *context) +{ + g_return_val_if_fail (context != NULL, FALSE); + + return context->strict_posix; +} + +/** * g_option_context_add_group: * @context: a #GOptionContext * @group: the group to add @@ -2060,6 +2129,9 @@ g_option_context_parse (GOptionContext *context, } else { + if (context->strict_posix) + stop_parsing = TRUE; + /* Collect remaining args */ if (context->main_group && !parse_remaining_arg (context, context->main_group, &i, diff --git a/glib/goption.h b/glib/goption.h index ab12a27..205a484 100644 --- a/glib/goption.h +++ b/glib/goption.h @@ -310,6 +310,12 @@ void g_option_context_set_ignore_unknown_options (GOptionContext *context, GLIB_AVAILABLE_IN_ALL gboolean g_option_context_get_ignore_unknown_options (GOptionContext *context); +GLIB_AVAILABLE_IN_2_44 +void g_option_context_set_strict_posix (GOptionContext *context, + gboolean strict_posix); +GLIB_AVAILABLE_IN_2_44 +gboolean g_option_context_get_strict_posix (GOptionContext *context); + GLIB_AVAILABLE_IN_ALL void g_option_context_add_main_entries (GOptionContext *context, const GOptionEntry *entries, diff --git a/glib/tests/option-context.c b/glib/tests/option-context.c index 8cf77a6..0ca29ca 100644 --- a/glib/tests/option-context.c +++ b/glib/tests/option-context.c @@ -2300,6 +2300,71 @@ test_group_parse (void) g_option_context_free (context); } +static gint +option_context_parse_command_line (GOptionContext *context, + const gchar *command_line) +{ + gchar **argv; + guint argv_len, argv_new_len; + gboolean success; + + argv = split_string (command_line, NULL); + argv_len = g_strv_length (argv); + + success = g_option_context_parse_strv (context, &argv, NULL); + argv_new_len = g_strv_length (argv); + + g_strfreev (argv); + return success ? argv_len - argv_new_len : -1; +} + +static void +test_strict_posix (void) +{ + GOptionContext *context; + gboolean foo; + gboolean bar; + GOptionEntry entries[] = { + { "foo", 'f', 0, G_OPTION_ARG_NONE, &foo, NULL, NULL }, + { "bar", 'b', 0, G_OPTION_ARG_NONE, &bar, NULL, NULL }, + { NULL } + }; + gint n_parsed; + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, entries, NULL); + + foo = bar = FALSE; + g_option_context_set_strict_posix (context, FALSE); + n_parsed = option_context_parse_command_line (context, "program --foo command --bar"); + g_assert_cmpint (n_parsed, ==, 2); + g_assert (foo == TRUE); + g_assert (bar == TRUE); + + foo = bar = FALSE; + g_option_context_set_strict_posix (context, TRUE); + n_parsed = option_context_parse_command_line (context, "program --foo command --bar"); + g_assert_cmpint (n_parsed, ==, 1); + g_assert (foo == TRUE); + g_assert (bar == FALSE); + + foo = bar = FALSE; + g_option_context_set_strict_posix (context, TRUE); + n_parsed = option_context_parse_command_line (context, "program --foo --bar command"); + g_assert_cmpint (n_parsed, ==, 2); + g_assert (foo == TRUE); + g_assert (bar == TRUE); + + foo = bar = FALSE; + g_option_context_set_strict_posix (context, TRUE); + n_parsed = option_context_parse_command_line (context, "program command --foo --bar"); + g_assert_cmpint (n_parsed, ==, 0); + g_assert (foo == FALSE); + g_assert (bar == FALSE); + + g_option_context_free (context); +} + static void flag_reverse_string (void) { @@ -2454,6 +2519,7 @@ main (int argc, g_test_add_func ("/option/group/main", test_main_group); g_test_add_func ("/option/group/error-hook", test_error_hook); g_test_add_func ("/option/group/parse", test_group_parse); + g_test_add_func ("/option/strict-posix", test_strict_posix); /* Test that restoration on failure works */ g_test_add_func ("/option/restoration/int", error_test1); -- 2.7.4