perl-Glib r1052 - in trunk: . t



Author: tsch
Date: Sat Nov 22 14:21:54 2008
New Revision: 1052
URL: http://svn.gnome.org/viewvc/perl-Glib?rev=1052&view=rev

Log:
Add Glib::OptionContext and Glib::OptionGroup, wrapping glib's command line
option parser.


Added:
   trunk/GOption.xs
   trunk/t/options.t
Modified:
   trunk/ChangeLog
   trunk/Glib.xs
   trunk/MANIFEST
   trunk/Makefile.PL
   trunk/gperl.h
   trunk/typemap

Added: trunk/GOption.xs
==============================================================================
--- (empty file)
+++ trunk/GOption.xs	Sat Nov 22 14:21:54 2008
@@ -0,0 +1,892 @@
+/*
+ * Copyright (c) 2005-2008 by the gtk2-perl team (see the file AUTHORS)
+ *
+ * Licensed under the LGPL, see LICENSE file for more information.
+ *
+ * $Id$
+ */
+
+#include "gperl.h"
+
+/* ------------------------------------------------------------------------- */
+
+/* This hash table is used to store option groups that have been handed to
+ * GOptionContext.
+ */
+static GHashTable *transferred_groups = NULL;
+
+static GOptionGroup *
+gperl_option_group_transfer (GOptionGroup *group)
+{
+	if (!transferred_groups)
+		transferred_groups =
+			g_hash_table_new (g_direct_hash, g_direct_equal);
+
+	g_hash_table_insert (transferred_groups, group, group);
+
+	return group;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* Define custom types for GOptionContext, GOptionGroup, GOptionFlags, and
+ * GOptionArg since glib doesn't provide them.
+ */
+
+static gpointer
+no_copy_for_you (gpointer boxed)
+{
+	croak ("copying Glib::OptionContext and Glib::OptionGroup isn't supported");
+	return boxed;
+}
+
+/* glib assumes ownership of option groups it gets, and there's no copy
+ * function.  So we need a custom free function here that checks if the group
+ * was transferred to glib already before freeing it.
+ */
+static void
+gperl_option_group_free (GOptionGroup *group)
+{
+	if (!g_hash_table_lookup (transferred_groups, group))
+		g_option_group_free (group);
+}
+
+GType
+gperl_option_context_get_type (void)
+{
+	static GType t = 0;
+	if (!t)
+		t = g_boxed_type_register_static ("GOptionContext",
+		      (GBoxedCopyFunc) no_copy_for_you,
+		      (GBoxedFreeFunc) g_option_context_free);
+	return t;
+}
+
+GType
+gperl_option_group_get_type (void)
+{
+	static GType t = 0;
+	if (!t)
+		t = g_boxed_type_register_static ("GOptionGroup",
+		      (GBoxedCopyFunc) no_copy_for_you,
+		      (GBoxedFreeFunc) gperl_option_group_free);
+	return t;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define GPERL_TYPE_OPTION_FLAGS (g_option_flags_get_type ())
+
+static GType
+g_option_flags_get_type (void)
+{
+	static GType t = 0;
+	if (t == 0) {
+		static const GFlagsValue values[] = {
+			{G_OPTION_FLAG_HIDDEN,	     "G_OPTION_FLAG_HIDDEN",       "hidden"},
+			{G_OPTION_FLAG_IN_MAIN,	     "G_OPTION_FLAG_IN_MAIN",      "in-main"},
+			{G_OPTION_FLAG_REVERSE,	     "G_OPTION_FLAG_REVERSE",      "reverse"},
+#if GLIB_CHECK_VERSION (2, 8, 0)
+			{G_OPTION_FLAG_NO_ARG,       "G_OPTION_FLAG_NO_ARG",       "no-arg"},
+			{G_OPTION_FLAG_FILENAME,     "G_OPTION_FLAG_FILENAME",     "filename"},
+			{G_OPTION_FLAG_OPTIONAL_ARG, "G_OPTION_FLAG_OPTIONAL_ARG", "optional-arg"},
+			{G_OPTION_FLAG_NOALIAS,      "G_OPTION_FLAG_NOALIAS",      "noalias"},
+#endif
+			{0, NULL, NULL}
+		};
+		t = g_flags_register_static ("GOptionFlags", values);
+	}
+	return t;
+}
+
+#if 0
+static SV *
+newSVGOptionFlags (GOptionFlags flags)
+{
+	return gperl_convert_back_flags (GPERL_TYPE_OPTION_FLAGS, flags);
+}
+#endif
+
+static GOptionFlags
+SvGOptionFlags (SV *sv)
+{
+	return gperl_convert_flags (GPERL_TYPE_OPTION_FLAGS, sv);
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define GPERL_TYPE_OPTION_ARG (g_option_arg_get_type ())
+
+static GType
+g_option_arg_get_type (void)
+{
+	static GType t = 0;
+	if (t == 0) {
+		static const GEnumValue values[] = {
+			{G_OPTION_ARG_NONE,		"G_OPTION_ARG_NONE",		"none"},
+			{G_OPTION_ARG_STRING,		"G_OPTION_ARG_STRING",		"string"},
+			{G_OPTION_ARG_INT,		"G_OPTION_ARG_INT",		"int"},
+			/* Not supported:
+			{G_OPTION_ARG_CALLBACK,		"G_OPTION_ARG_CALLBACK",	"callback"}, */
+			{G_OPTION_ARG_FILENAME,		"G_OPTION_ARG_FILENAME",	"filename"},
+			{G_OPTION_ARG_STRING_ARRAY,	"G_OPTION_ARG_STRING_ARRAY",	"string-array"},
+			{G_OPTION_ARG_FILENAME_ARRAY,	"G_OPTION_ARG_FILENAME_ARRAY",	"filename-array"},
+#if GLIB_CHECK_VERSION (2, 12, 0)
+			{G_OPTION_ARG_DOUBLE,		"G_OPTION_ARG_DOUBLE",		"double"},
+			{G_OPTION_ARG_INT64,		"G_OPTION_ARG_INT64",		"int64"},
+#endif
+			{0, NULL, NULL}
+		};
+		t = g_enum_register_static ("GOptionArg", values);
+	}
+	return t;
+}
+
+#if 0
+static SV *
+newSVGOptionArg (GOptionArg arg)
+{
+	return gperl_convert_back_enum (GPERL_TYPE_OPTION_ARG, arg);
+}
+#endif
+
+static GOptionArg
+SvGOptionArg (SV *sv)
+{
+	return gperl_convert_enum (GPERL_TYPE_OPTION_ARG, sv);
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct {
+	GOptionArg arg;
+	gpointer   arg_data;
+} GPerlArgInfo;
+
+static GPerlArgInfo *
+gperl_arg_info_new (GOptionArg arg, gpointer arg_data)
+{
+	GPerlArgInfo *info = g_new0 (GPerlArgInfo, 1);
+	info->arg = arg;
+	info->arg_data = arg_data;
+	return info;
+}
+
+static void
+gperl_arg_info_destroy (GPerlArgInfo *info)
+{
+	g_free (info->arg_data); /* NULL-safe */
+	g_free (info);
+}
+
+typedef struct {
+	GHashTable *scalar_to_info;
+	GSList *allocated_strings;
+} GPerlArgInfoTable;
+
+static GPerlArgInfoTable *
+gperl_arg_info_table_new (void)
+{
+	GPerlArgInfoTable *table = g_new0 (GPerlArgInfoTable, 1);
+	table->scalar_to_info =
+		g_hash_table_new_full (g_direct_hash,
+				       g_direct_equal,
+				       NULL,
+		      (GDestroyNotify) gperl_arg_info_destroy);
+	table->allocated_strings = NULL;
+	return table;
+}
+
+static void
+gperl_arg_info_table_destroy (GPerlArgInfoTable *table)
+{
+	g_hash_table_destroy (table->scalar_to_info);
+
+	/* These are NULL-safe. */
+	g_slist_foreach (table->allocated_strings, (GFunc) g_free, NULL);
+	g_slist_free (table->allocated_strings);
+
+	g_free (table);
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define INSTALL_POINTER(type)						\
+{									\
+	type *pointer = g_new0 (type, 1);				\
+	g_hash_table_insert (scalar_to_info, 				\
+			     ref,					\
+			     gperl_arg_info_new (entry->arg, pointer));	\
+	entry->arg_data = pointer;					\
+}
+
+static void
+handle_arg_data (GOptionEntry *entry, SV *ref, GHashTable *scalar_to_info)
+{
+	if (!SvROK (ref))
+		croak ("encountered non-reference variable for the arg_value "
+		       "field");
+
+	switch (entry->arg) {
+	    case G_OPTION_ARG_NONE:
+		INSTALL_POINTER (gboolean);
+		break;
+
+	    case G_OPTION_ARG_STRING:
+	    case G_OPTION_ARG_FILENAME:
+	        INSTALL_POINTER (gchar *);
+		break;
+
+	    case G_OPTION_ARG_INT:
+	        INSTALL_POINTER (gint);
+		break;
+
+	    case G_OPTION_ARG_CALLBACK:
+		croak ("unhandled arg type G_OPTION_ARG_CALLBACK encountered");
+		break;
+
+	    case G_OPTION_ARG_STRING_ARRAY:
+	    case G_OPTION_ARG_FILENAME_ARRAY:
+	        INSTALL_POINTER (gchar **);
+		break;
+
+#if GLIB_CHECK_VERSION (2, 12, 0)
+	    case G_OPTION_ARG_DOUBLE:
+	        INSTALL_POINTER (gdouble);
+		break;
+
+	    case G_OPTION_ARG_INT64:
+	        INSTALL_POINTER (gint64);
+		break;
+#endif
+	}
+}
+
+static gchar *
+copy_string (gchar *src, GPerlArgInfoTable *table)
+{
+	if (!src)
+		return NULL;
+	gchar *result = g_strdup (src);
+	table->allocated_strings =
+		g_slist_prepend (table->allocated_strings, result);
+	return result;
+}
+
+static GOptionEntry *
+sv_to_option_entry (SV *sv, GPerlArgInfoTable *table)
+{
+	SV *long_name = NULL,
+	   *short_name = NULL,
+	   *flags = NULL,
+	   *description = NULL,
+	   *arg_description = NULL,
+	   *arg_type = NULL,
+	   *arg_value = NULL;
+	GOptionEntry *entry;
+
+	if (!gperl_sv_is_hash_ref (sv) && !gperl_sv_is_array_ref (sv))
+		croak ("an option entry must be either a hash or an array "
+		       "reference");
+
+	if (gperl_sv_is_hash_ref (sv)) {
+		HV *hv = (HV *) SvRV (sv);
+		SV **value;
+
+		value = hv_fetch (hv, "long_name", 9, 0);
+		if (value) long_name = *value;
+
+		value = hv_fetch (hv, "short_name", 10, 0);
+		if (value) short_name = *value;
+
+		value = hv_fetch (hv, "flags", 5, 0);
+		if (value) flags = *value;
+
+		value = hv_fetch (hv, "description", 11, 0);
+		if (value) description = *value;
+
+		value = hv_fetch (hv, "arg_description", 15, 0);
+		if (value) arg_description = *value;
+
+		value = hv_fetch (hv, "arg_type", 8, 0);
+		if (value) arg_type = *value;
+
+		value = hv_fetch (hv, "arg_value", 9, 0);
+		if (value) arg_value = *value;
+	} else {
+		AV *av = (AV *) SvRV (sv);
+		SV **value;
+
+		if (4 != av_len (av) + 1)
+			croak ("an option entry array reference must contain "
+			       "four values: long_name, short_name, arg_type, "
+			       "and arg_value");
+
+		value = av_fetch (av, 0, 0);
+		if (value) long_name = *value;
+
+		value = av_fetch (av, 1, 0);
+		if (value) short_name = *value;
+
+		value = av_fetch (av, 2, 0);
+		if (value) arg_type = *value;
+
+		value = av_fetch (av, 3, 0);
+		if (value) arg_value = *value;
+	}
+
+	if (!gperl_sv_is_defined (long_name) ||
+	    !gperl_sv_is_defined (arg_type) ||
+	    !gperl_sv_is_defined (arg_value))
+		croak ("in an option entry, the fields long_name, arg_type, and "
+		       "arg_value must be specified");
+
+	entry = gperl_alloc_temp (sizeof (GOptionEntry));
+
+	entry->long_name       = copy_string (SvGChar (long_name), table);
+	entry->arg             = SvGOptionArg (arg_type);
+	entry->arg_data        = NULL;
+	handle_arg_data (entry, arg_value, table->scalar_to_info);
+
+	entry->short_name      = gperl_sv_is_defined (short_name)
+	                       ? (SvGChar (short_name))[0]
+	                       : 0;
+	entry->flags           = gperl_sv_is_defined (flags)
+	                       ? SvGOptionFlags (flags)
+                               : 0;
+	entry->description     = gperl_sv_is_defined (description)
+	                       ? copy_string (SvGChar (description), table)
+	                       : NULL;
+	entry->arg_description = gperl_sv_is_defined (arg_description)
+	                       ? copy_string (SvGChar (arg_description), table)
+	                       : NULL;
+
+	return entry;
+}
+
+static GOptionEntry *
+sv_to_option_entries (SV *sv, GPerlArgInfoTable *table)
+{
+	GOptionEntry *entries;
+	AV *av;
+	int length, i;
+	SV **value;
+
+	if (!gperl_sv_is_array_ref (sv))
+		croak ("option entries must be an array reference containing hash references");
+
+	av = (AV *) SvRV (sv);
+	length = av_len (av) + 1;
+
+	/* Allocating length + 1 entries here because the list is supposed to
+	 * be NULL-terminated. */
+	entries = gperl_alloc_temp (sizeof (GOptionEntry) * (length + 1));
+
+	for (i = 0; i < length; i++) {
+		value = av_fetch (av, i, 0);
+		if (value && gperl_sv_is_defined (*value))
+			entries[i] = *(sv_to_option_entry (*value, table));
+	}
+
+	return entries;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static gchar **
+strings_from_sv (SV *sv)
+{
+	AV *av;
+	gint n_strings, i;
+	gchar **result;
+
+	if (!gperl_sv_is_array_ref (sv))
+		return NULL;
+
+	av = (AV *) SvRV (sv);
+	n_strings = av_len (av) + 1;
+	if (n_strings <= 0)
+		return NULL;
+
+	/* NULL-terminated */
+	result = gperl_alloc_temp (sizeof (gchar *) * (n_strings + 1));
+	for (i = 0; i < n_strings; i++) {
+		SV **string_sv = av_fetch (av, i, 0);
+		result[i] = string_sv ? SvGChar (*string_sv) : NULL;
+	}
+
+	return result;
+}
+
+static gchar **
+filenames_from_sv (SV *sv)
+{
+	AV *av;
+	gint n_filenames, i;
+	gchar **result;
+
+	if (!gperl_sv_is_array_ref (sv))
+		return NULL;
+
+	av = (AV *) SvRV (sv);
+	n_filenames = av_len (av) + 1;
+	if (n_filenames <= 0)
+		return NULL;
+
+	/* NULL-terminated */
+	result = gperl_alloc_temp (sizeof (gchar *) * (n_filenames + 1));
+	for (i = 0; i < n_filenames; i++) {
+		SV **string_sv = av_fetch (av, i, 0);
+		result[i] = string_sv ? SvPV_nolen (*string_sv) : NULL;
+	}
+
+	return result;
+}
+
+#define INITIALIZE_POINTER(type, converter)			\
+{								\
+	SV *sv = SvRV (ref);					\
+	if (gperl_sv_is_defined (sv))				\
+		*((type *) info->arg_data) = converter (sv);	\
+}
+
+static void
+initialize_scalar (gpointer key,
+		   gpointer value,
+		   gpointer data)
+{
+	SV *ref = key;
+	GPerlArgInfo *info = value;
+
+	switch (info->arg) {
+	    case G_OPTION_ARG_NONE:
+		INITIALIZE_POINTER (gboolean, sv_2bool);
+		break;
+
+	    case G_OPTION_ARG_STRING:
+		INITIALIZE_POINTER (gchar *, SvGChar);
+		break;
+
+	    case G_OPTION_ARG_INT:
+		INITIALIZE_POINTER (gint, SvIV);
+		break;
+
+	    case G_OPTION_ARG_CALLBACK:
+		croak ("unhandled arg type G_OPTION_ARG_CALLBACK encountered");
+		break;
+
+	    case G_OPTION_ARG_FILENAME:
+		/* FIXME: Is this the correct converter? */
+		INITIALIZE_POINTER (gchar *, SvPV_nolen);
+		break;
+
+	    case G_OPTION_ARG_STRING_ARRAY:
+		INITIALIZE_POINTER (gchar **, strings_from_sv);
+		break;
+
+	    case G_OPTION_ARG_FILENAME_ARRAY:
+		INITIALIZE_POINTER (gchar **, filenames_from_sv);
+		break;
+
+#if GLIB_CHECK_VERSION (2, 12, 0)
+	    case G_OPTION_ARG_DOUBLE:
+		INITIALIZE_POINTER (gdouble, SvNV);
+		break;
+
+	    case G_OPTION_ARG_INT64:
+		INITIALIZE_POINTER (gint64, SvGInt64);
+		break;
+#endif
+	}
+}
+
+static gboolean
+initialize_scalars (GOptionContext *context,
+		    GOptionGroup *group,
+		    gpointer data,
+		    GError **error)
+{
+	GPerlArgInfoTable *table = data;
+	g_hash_table_foreach (table->scalar_to_info, initialize_scalar, NULL);
+	return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static SV *
+sv_from_strings (gchar **strings)
+{
+	AV *av;
+	gint i;
+
+	if (!strings)
+		return &PL_sv_undef;
+
+	av = newAV ();
+	for (i = 0; strings[i] != NULL; i++) {
+		av_push (av, newSVGChar (strings[i]));
+	}
+
+	return newRV_noinc ((SV *) av);
+}
+
+static SV *
+sv_from_filenames (gchar **filenames)
+{
+	AV *av;
+	gint i;
+
+	if (!filenames)
+		return &PL_sv_undef;
+
+	av = newAV ();
+	for (i = 0; filenames[i] != NULL; i++) {
+		/* FIXME: Is this the correct converter? */
+		av_push (av, newSVpv (filenames[i], PL_na));
+	}
+
+	return newRV_noinc ((SV *) av);
+}
+
+#define READ_POINTER(type) (*((type *) info->arg_data))
+
+static void
+fill_in_scalar (gpointer key,
+	        gpointer value,
+		gpointer data)
+{
+	SV *ref = key;
+	GPerlArgInfo *info = value;
+	SV *sv = SvRV (ref);
+
+	switch (info->arg) {
+	    case G_OPTION_ARG_NONE:
+		sv_setsv (sv, boolSV (READ_POINTER (gboolean)));
+		break;
+
+	    case G_OPTION_ARG_STRING:
+		sv_setpv (sv, READ_POINTER (gchar *));
+		SvUTF8_on (sv);
+		break;
+
+	    case G_OPTION_ARG_INT:
+	        sv_setiv (sv, READ_POINTER (gint));
+		break;
+
+	    case G_OPTION_ARG_CALLBACK:
+		croak ("unhandled arg type G_OPTION_ARG_CALLBACK encountered");
+		break;
+
+	    case G_OPTION_ARG_FILENAME:
+		/* FIXME: Is this the correct converter? */
+		sv_setpv (sv, READ_POINTER (gchar *));
+		break;
+
+	    case G_OPTION_ARG_STRING_ARRAY:
+		sv_setsv (sv, sv_from_strings (READ_POINTER (gchar **)));
+		break;
+
+	    case G_OPTION_ARG_FILENAME_ARRAY:
+		sv_setsv (sv, sv_from_filenames (READ_POINTER (gchar **)));
+		break;
+
+#if GLIB_CHECK_VERSION (2, 12, 0)
+	    case G_OPTION_ARG_DOUBLE:
+	        sv_setnv (sv, READ_POINTER (gdouble));
+		break;
+
+	    case G_OPTION_ARG_INT64:
+		sv_setsv (sv, newSVGInt64 (READ_POINTER (gint64)));
+		break;
+#endif
+	}
+}
+
+static gboolean
+fill_in_scalars (GOptionContext *context,
+		 GOptionGroup *group,
+		 gpointer data,
+		 GError **error)
+{
+	GPerlArgInfoTable *table = data;
+	g_hash_table_foreach (table->scalar_to_info, fill_in_scalar, NULL);
+	return TRUE;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static GPerlCallback *
+gperl_translate_func_create (SV *func, SV *data)
+{
+	GType param_types [1];
+	param_types[0] = G_TYPE_STRING;
+	return gperl_callback_new (func, data, G_N_ELEMENTS (param_types),
+				   param_types, G_TYPE_STRING);
+}
+
+static const gchar *
+gperl_translate_func (const gchar *str, gpointer data)
+{
+	GPerlCallback *callback = (GPerlCallback *) data;
+	GValue value = {0,};
+	const gchar *retval;
+
+	/* FIXME: This leaks but I've no idea how to make sure the string
+         * survives. */
+	g_value_init (&value, callback->return_type);
+	gperl_callback_invoke (callback, &value, str);
+	retval = g_value_dup_string (&value);
+	g_value_unset (&value);
+
+	return retval;
+}
+
+/* ------------------------------------------------------------------------- */
+
+MODULE = Glib::Option	PACKAGE = Glib::OptionContext	PREFIX = g_option_context_
+
+BOOT:
+	gperl_register_boxed (GPERL_TYPE_OPTION_CONTEXT, "Glib::OptionContext", NULL);
+	gperl_register_boxed (GPERL_TYPE_OPTION_GROUP, "Glib::OptionGroup", NULL);
+	gperl_register_fundamental (GPERL_TYPE_OPTION_ARG, "Glib::OptionArg");
+	gperl_register_fundamental (GPERL_TYPE_OPTION_FLAGS, "Glib::OptionFlags");
+
+=for position SYNOPSIS
+
+=head1 SYNOPSIS
+
+  my ($verbose, $source, $filenames) = ('', undef, []);
+
+  my $entries = [
+    { long_name => 'verbose',
+      short_name => 'v',
+      arg_type => 'none',
+      arg_value => \$verbose,
+      description => 'be verbose' },
+
+    { long_name => 'source',
+      short_name => 's',
+      arg_type => 'string',
+      arg_value => \$source,
+      description => 'set the source',
+      arg_description => 'source' },
+
+    [ 'filenames', 'f', 'filename-array', \$filenames ],
+  ];
+
+  my $context = Glib::OptionContext->new ('- urgsify your life');
+  $context->add_main_entries ($entries, 'C');
+  $context->parse ();
+
+  # $verbose, $source, and $filenames are now updated according to the
+  # command line options given
+
+=cut
+
+##  GOptionContext * g_option_context_new (const gchar *parameter_string);
+GOptionContext_own *
+g_option_context_new (class, parameter_string);
+	const gchar *parameter_string
+    C_ARGS:
+	parameter_string
+
+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);
+=for signature
+=arg entries reference to an array of option entries
+=cut
+void
+g_option_context_add_main_entries (GOptionContext *context, SV *entries, const gchar *translation_domain)
+    PREINIT:
+	GPerlArgInfoTable *table;
+	GOptionGroup *group;
+	GOptionEntry *real_entries;
+    CODE:
+	table = gperl_arg_info_table_new ();
+	group = g_option_group_new (NULL, NULL, NULL,
+				    table,
+		   (GDestroyNotify) gperl_arg_info_table_destroy);
+	g_option_group_set_parse_hooks (group, initialize_scalars,
+	                                fill_in_scalars);
+
+	real_entries = sv_to_option_entries (entries, table);
+	if (real_entries)
+		g_option_group_add_entries (group, real_entries);
+	g_option_group_set_translation_domain (group, translation_domain);
+
+	/* context assumes ownership of group */
+	g_option_context_set_main_group (context, group);
+
+##  gboolean g_option_context_parse (GOptionContext *context, gint *argc, gchar ***argv, GError **error);
+=for apidoc __gerror__
+This method works directly on I<@ARGV>.
+=cut
+gboolean
+g_option_context_parse (context)
+	GOptionContext *context
+    PREINIT:
+	GPerlArgv *pargv;
+	GError *error = NULL;
+    CODE:
+	pargv = gperl_argv_new ();
+	RETVAL = g_option_context_parse (context, &pargv->argc, &pargv->argv, &error);
+
+	if (error) {
+		gperl_argv_free (pargv);
+		gperl_croak_gerror (NULL, error);
+	}
+
+	gperl_argv_update (pargv);
+	gperl_argv_free (pargv);
+    OUTPUT:
+	RETVAL
+
+# Groups that belong to a context will be destroyed when that context goes
+# away, so we need to mark the group to ensure it doesn't get freed by our
+# boxed wrappers.
+
+##  void g_option_context_add_group (GOptionContext *context, GOptionGroup *group);
+void
+g_option_context_add_group (context, group)
+	GOptionContext *context
+	GOptionGroup *group
+    C_ARGS:
+	context, gperl_option_group_transfer (group)
+
+##  void g_option_context_set_main_group (GOptionContext *context, GOptionGroup *group);
+void
+g_option_context_set_main_group (context, group);
+	GOptionContext *context
+	GOptionGroup *group
+    C_ARGS:
+	context, gperl_option_group_transfer (group)
+
+GOptionGroup * g_option_context_get_main_group (GOptionContext *context);
+
+# --------------------------------------------------------------------------- #
+
+MODULE = Glib::Option	PACKAGE = Glib::OptionGroup	PREFIX = g_option_group_
+
+=for enum Glib::OptionFlags
+=cut
+
+=for enum Glib::OptionArg
+=cut
+
+##  GOptionGroup * g_option_group_new (const gchar *name, const gchar *description, const gchar *help_description, gpointer user_data, GDestroyNotify destroy);
+##  void g_option_group_add_entries (GOptionGroup *group, const GOptionEntry *entries);
+##  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);
+=for apidoc
+=for signature optiongroup = Glib::OptionGroup->new (key => value, ...)
+=for arg ... (__hide__)
+
+Creates a new option group from the given key-value pairs.  The valid keys are
+name, description, help_description, and entries.  The first three specify
+strings while the last one, entries, specifies an array reference of option
+entries.  Example:
+
+  my $group = Glib::OptionGroup->new (
+                name => 'urgs',
+                description => 'Urgs Urgs Urgs',
+                help_description => 'Help with Urgs',
+                entries => \ entries);
+
+An option entry is a hash reference like this:
+
+  { long_name => 'verbose',
+    short_name => 'v',
+    flags => [qw/reverse hidden in-main/],
+    arg_type => 'none',
+    arg_value => \$verbose,
+    description => 'verbose desc.',
+    arg_description => 'verbose arg desc.' }
+
+Of those keys only long_name, arg_type, and arg_value are required.  So this is
+a valid option entry too:
+
+  { long_name => 'package-names',
+    arg_type => 'string-array',
+    arg_value => \$package_names }
+
+For convenience, option entries can also be specified as array references
+containing long_name, short_name, arg_type, and arg_value:
+
+  [ 'filenames', 'f', 'filename-array', \$filenames ]
+
+If you don't want an option to have a short name, specify undef for it:
+
+  [ 'filenames', undef, 'filename-array', \$filenames ]
+
+=cut
+GOptionGroup_own *
+g_option_group_new (class, ...)
+    PREINIT:
+	int i;
+	gchar *name = NULL;
+	gchar *description = NULL;
+	gchar *help_description = NULL;
+	SV *entries = NULL;
+	GPerlArgInfoTable *table;
+	GOptionEntry *real_entries = NULL;
+    CODE:
+	if ((items - 1) % 2 != 0)
+		croak ("even number of arguments expected: key => value, ...");
+
+	for (i = 1; i < items; i += 2) {
+		char *key = SvPV_nolen (ST (i));
+		SV *value = ST (i + 1);
+
+		if (strEQ (key, "name"))
+			name = SvGChar (value);
+		else if (strEQ (key, "description"))
+			description = SvGChar (value);
+		else if (strEQ (key, "help_description"))
+			help_description = SvGChar (value);
+		else if (strEQ (key, "entries"))
+			entries = value;
+		else
+			warn ("unknown key `%s encountered; ignoring", key);
+	}
+
+	table = gperl_arg_info_table_new ();
+	if (entries)
+		real_entries = sv_to_option_entries (entries, table);
+
+	RETVAL = g_option_group_new (name,
+				     description,
+				     help_description,
+				     table,
+		    (GDestroyNotify) gperl_arg_info_table_destroy);
+
+	g_option_group_set_parse_hooks (RETVAL, initialize_scalars, fill_in_scalars);
+
+	if (real_entries)
+		g_option_group_add_entries (RETVAL, real_entries);
+    OUTPUT:
+	RETVAL
+
+##  void g_option_group_set_translate_func (GOptionGroup *group, GTranslateFunc func, gpointer data, GDestroyNotify destroy_notify);
+void
+g_option_group_set_translate_func (group, func, data=NULL);
+	GOptionGroup *group
+	SV *func
+	SV *data
+    PREINIT:
+	GPerlCallback *callback;
+    CODE:
+	callback = gperl_translate_func_create (func, data);
+	g_option_group_set_translate_func (group,
+	                                   gperl_translate_func,
+	                                   callback,
+	                                   (GDestroyNotify)
+	                                     gperl_callback_destroy);
+
+void g_option_group_set_translation_domain (GOptionGroup *group, const gchar *domain);

Modified: trunk/Glib.xs
==============================================================================
--- trunk/Glib.xs	(original)
+++ trunk/Glib.xs	Sat Nov 22 14:21:54 2008
@@ -386,6 +386,7 @@
 	GPERL_CALL_BOOT (boot_Glib__IO__Channel);
 #if GLIB_CHECK_VERSION (2, 6, 0)
 	GPERL_CALL_BOOT (boot_Glib__KeyFile);
+	GPERL_CALL_BOOT (boot_Glib__Option);
 #endif /* GLIB_CHECK_VERSION (2, 6, 0) */
 #if GLIB_CHECK_VERSION (2, 12, 0)
 	GPERL_CALL_BOOT (boot_Glib__BookmarkFile);

Modified: trunk/MANIFEST
==============================================================================
--- trunk/MANIFEST	(original)
+++ trunk/MANIFEST	Sat Nov 22 14:21:54 2008
@@ -18,6 +18,7 @@
 GLog.xs
 GMainLoop.xs
 GObject.xs
+GOption.xs
 GParamSpec.xs
 gperl-gtypes.c
 gperl-gtypes.h
@@ -59,6 +60,7 @@
 t/h.t
 t/lazy_loader.t
 t/make_helper.t
+t/options.t
 t/signal_emission_hooks.t
 t/signal_query.t
 t/tied_definedness.t

Modified: trunk/Makefile.PL
==============================================================================
--- trunk/Makefile.PL	(original)
+++ trunk/Makefile.PL	Sat Nov 22 14:21:54 2008
@@ -72,6 +72,7 @@
 # Check version before including
 if (ExtUtils::PkgConfig->atleast_version ('glib-2.0', '2.6.0')) {
 	push @xs_files, 'GKeyFile.xs';
+	push @xs_files, 'GOption.xs';
 }
 
 if (ExtUtils::PkgConfig->atleast_version ('glib-2.0', '2.12.0')) {

Modified: trunk/gperl.h
==============================================================================
--- trunk/gperl.h	(original)
+++ trunk/gperl.h	Sat Nov 22 14:21:54 2008
@@ -376,6 +376,32 @@
 GBookmarkFile * SvGBookmarkFile (SV * sv);
 #endif /* GLIB_CHECK_VERSION (2, 12, 0) */
 
+#if GLIB_CHECK_VERSION (2, 6, 0)
+
+/*
+ * GOption.xs
+ */
+
+typedef GOptionContext GOptionContext_own;
+
+#define GPERL_TYPE_OPTION_CONTEXT (gperl_option_context_get_type ())
+GType gperl_option_context_get_type (void);
+
+#define SvGOptionContext(sv)		(gperl_get_boxed_check ((sv), GPERL_TYPE_OPTION_CONTEXT))
+#define newSVGOptionContext(val)	(gperl_new_boxed ((gpointer) (val), GPERL_TYPE_OPTION_CONTEXT, FALSE))
+#define newSVGOptionContext_own(val)	(gperl_new_boxed ((gpointer) (val), GPERL_TYPE_OPTION_CONTEXT, TRUE))
+
+typedef GOptionGroup GOptionGroup_own;
+
+#define GPERL_TYPE_OPTION_GROUP (gperl_option_group_get_type ())
+GType gperl_option_group_get_type (void);
+
+#define SvGOptionGroup(sv)		(gperl_get_boxed_check ((sv), GPERL_TYPE_OPTION_GROUP))
+#define newSVGOptionGroup(val)		(gperl_new_boxed ((gpointer) (val), GPERL_TYPE_OPTION_GROUP, FALSE))
+#define newSVGOptionGroup_own(val)	(gperl_new_boxed ((gpointer) (val), GPERL_TYPE_OPTION_GROUP, TRUE))
+
+#endif /* 2.6.0 */
+
 /*
  * gutils.h / GUtils.xs
  */

Added: trunk/t/options.t
==============================================================================
--- (empty file)
+++ trunk/t/options.t	Sat Nov 22 14:21:54 2008
@@ -0,0 +1,204 @@
+#!/usr/bin/perl
+# $Id$
+use strict;
+use warnings;
+use utf8;
+use Test::More;
+use Glib qw(TRUE FALSE);
+
+unless (Glib -> CHECK_VERSION (2, 6, 0)) {
+  plan skip_all => 'the option stuff is new in 2.6';
+} else {
+  plan tests => 29;
+}
+
+# --------------------------------------------------------------------------- #
+
+my ($none, $string, $int, $filename, $string_array, $filename_array);
+
+my $entries = [
+  { long_name => 'none',
+    short_name => 'n',
+    flags => [qw/reverse in-main/],
+    arg_type => 'none',
+    arg_value => \$none,
+    description => 'none desc.',
+    arg_description => 'none arg desc.' },
+
+  { long_name => 'string',
+    short_name => 's',
+    arg_type => 'string',
+    arg_value => \$string },
+
+  { long_name => 'int',
+    short_name => 'i',
+    arg_type => 'int',
+    arg_value => \$int },
+
+  { long_name => 'filename',
+    short_name => undef,
+    arg_type => 'filename',
+    arg_value => \$filename },
+
+  { long_name => 'string-array',
+    arg_type => 'string-array',
+    arg_value => \$string_array },
+
+  [ 'filename-array', undef, 'filename-array', \$filename_array ],
+];
+
+# --------------------------------------------------------------------------- #
+
+# Misc. non-parse API.
+{
+  my $context = Glib::OptionContext -> new('- urgsify your life');
+  isa_ok($context, 'Glib::OptionContext');
+
+  $context -> set_help_enabled(TRUE);
+  is($context -> get_help_enabled(), TRUE);
+
+  $context -> set_ignore_unknown_options(TRUE);
+  is($context -> get_ignore_unknown_options(), TRUE);
+
+  my $group = Glib::OptionGroup -> new(name => 'urgs',
+                                       description => 'Urgs Urgs Urgs',
+                                       help_description => 'Help with Urgs',
+                                       entries => $entries);
+  isa_ok($group, 'Glib::OptionGroup');
+
+  $context -> set_main_group($group);
+  isa_ok($context -> get_main_group(), 'Glib::OptionGroup');
+}
+
+# --------------------------------------------------------------------------- #
+
+# Translation stuff.  Commented out since it aborts the program.
+{
+  my $context = Glib::OptionContext -> new('- urgsify your life');
+  my $group = Glib::OptionGroup -> new(name => 'urgs',
+                                       description => 'Urgs Urgs Urgs',
+                                       help_description => 'Help with Urgs',
+                                       entries => $entries);
+
+  $group -> set_translation_domain('de_DE');
+  $group -> set_translate_func(sub {
+    my ($string, $data) = @_;
+
+    warn $string;
+    warn $data;
+
+    return reverse $string;
+  }, 'atad');
+
+  $context -> add_group($group);
+
+  # ARGV = qw(--help);
+  # ARGV = qw(--help-urgs);
+  #$context -> parse();
+}
+
+# --------------------------------------------------------------------------- #
+
+# Parsing.
+{
+  my $context = Glib::OptionContext -> new('- urgsify your life');
+  $context -> add_main_entries($entries, 'de_DE');
+
+  # Test that undef is preserved.
+  {
+    @ARGV = qw();
+    $context -> parse();
+
+    is ($none, FALSE); # FIXME?
+    is ($string, undef);
+    is ($int, 0); # FIXME?
+    is ($filename, undef);
+    is ($string_array, undef);
+    is ($filename_array, undef);
+  }
+
+  # Test that existing values are not overwritten.
+  {
+    $none = TRUE;
+    $string = 'Ãrgs';
+    $int = 23;
+    $filename = $^X;
+    $string_array = [qw/Ã Ã Ã Ã Ã/];
+    $filename_array = [$^X, $0];
+
+    @ARGV = qw();
+    $context -> parse();
+
+    is ($none, TRUE);
+    is ($string, 'Ãrgs');
+    is ($int, 23);
+    is ($filename, $^X);
+    is_deeply ($string_array, [qw/Ã Ã Ã Ã Ã/]);
+    is_deeply ($filename_array, [$^X, $0]);
+  }
+
+  # Test actual parsing.
+  {
+    @ARGV = qw(-n
+               -s blÃ
+               -i 42
+               --filename ~/Foo
+               --string-array ÃÃÃ --string-array ÃÃÃ
+               --filename-array /usr/bin/bla --filename-array ./harness);
+    $context -> parse();
+
+    is ($none, FALSE);
+    is ($string, 'blÃ');
+    is ($int, 42);
+    is ($filename, '~/Foo');
+    is_deeply ($string_array, [qw/ÃÃÃ ÃÃÃ/]);
+    is_deeply ($filename_array, [qw(/usr/bin/bla ./harness)]);
+  }
+}
+
+# --------------------------------------------------------------------------- #
+
+SKIP: {
+  skip 'new 2.12 stuff', 6
+    unless Glib->CHECK_VERSION(2, 12, 0);
+
+  my ($double, $int64);
+
+  my $entries = [
+    [ 'double', 'd', 'double', \$double ],
+    [ 'int64',  'i', 'int64',  \$int64 ],
+  ];
+  my $context = Glib::OptionContext -> new('- urgsify your life');
+  $context -> add_main_entries($entries, 'de_DE');
+
+  # Test that undef is preserved.
+  {
+    @ARGV = qw();
+    $context -> parse();
+
+    is ($double, 0); # FIXME?
+    is ($int64, 0); # FIXME?
+  }
+
+  # Test that existing values are not overwritten.
+  {
+    $double = 0.23;
+    $int64 = 23;
+
+    @ARGV = qw();
+    $context -> parse();
+
+    ok ($double - 0.23 < 1e-6);
+    is ($int64, 23);
+  }
+
+  # Test actual parsing.
+  {
+    @ARGV = qw(-d 0.42
+               -i 42);
+    $context -> parse();
+
+    ok ($double - 0.42 < 1e-6);
+    is ($int64, 42);
+  }
+}

Modified: trunk/typemap
==============================================================================
--- trunk/typemap	(original)
+++ trunk/typemap	Sat Nov 22 14:21:54 2008
@@ -1,4 +1,4 @@
-# Copyright (C) 2003-2004 by the gtk2-perl team (see the file AUTHORS for
+# Copyright (C) 2003-2005 by the gtk2-perl team (see the file AUTHORS for
 # the full list)
 # 
 # This library is free software; you can redistribute it and/or modify it under
@@ -82,6 +82,11 @@
 GPerlFilename_own	T_GPERL_FILENAME_OWN
 GPerlFilename_ornull	T_GPERL_FILENAME_ORNULL
 
+GOptionContext *	T_GPERL_GENERIC_WRAPPER
+GOptionContext_own *	T_GPERL_GENERIC_WRAPPER
+GOptionGroup *		T_GPERL_GENERIC_WRAPPER
+GOptionGroup_own *	T_GPERL_GENERIC_WRAPPER
+
 GUserDirectory	T_GPERL_GENERIC_WRAPPER
 
 #####



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]