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
This commit is contained in:
Ryan Lortie 2014-01-27 15:42:23 +00:00 committed by Lars Uebernickel
parent f2786908a8
commit ae52ab3d11
3 changed files with 144 additions and 0 deletions

View File

@ -183,6 +183,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#if defined __OpenBSD__
#include <unistd.h>
@ -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;
@ -483,6 +493,65 @@ g_option_context_get_ignore_unknown_options (GOptionContext *context)
return context->ignore_unknown;
}
/**
* 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
@ -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,

View File

@ -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,

View File

@ -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);