GOptionContext: add some notes about encodings
[platform/upstream/glib.git] / glib / goption.c
index fa8c951..137ba31 100644 (file)
@@ -99,7 +99,7 @@
  * static gint max_size = 8;
  * static gboolean verbose = FALSE;
  * static gboolean beep = FALSE;
- * static gboolean rand = FALSE;
+ * static gboolean randomize = FALSE;
  *
  * static GOptionEntry entries[] =
  * {
  *   { "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 },
+ *   { "rand", 0, 0, G_OPTION_ARG_NONE, &randomize, "Randomize the data", NULL },
  *   { NULL }
  * };
  *
  *
  * }
  * </programlisting></informalexample>
+ *
+ * On UNIX systems, the argv that is passed to main() has no particular
+ * encoding, even to the extent that different parts of it may have
+ * different encodings.  In general, normal arguments and flags will be
+ * in the current locale and filenames should be considered to be opaque
+ * byte strings.  Proper use of %G_OPTION_ARG_FILENAME vs
+ * %G_OPTION_ARG_STRING is therefore important.
+ *
+ * Note that on Windows, filenames do have an encoding, but using
+ * #GOptionContext with the argv as passed to main() will result in a
+ * program that can only accept commandline arguments with characters
+ * from the system codepage.  This can cause problems when attempting to
+ * deal with filenames containing Unicode characters that fall outside
+ * of the codepage.
+ *
+ * A solution to this is to use g_win32_get_command_line() and
+ * g_option_context_parse_strv() which will properly handle full Unicode
+ * filenames.  If you are using #GApplication, this is done
+ * automatically for you.
+ *
+ * The following example shows how you can use #GOptionContext directly
+ * in order to correctly deal with Unicode filenames on Windows:
+ *
+ * |[
+ * int
+ * main (int argc, char **argv)
+ * {
+ *   GError *error = NULL;
+ *   GOptionContext *context;
+ *   gchar **args;
+ *
+ * #ifdef G_OS_WIN32
+ *   args = g_win32_get_command_line ();
+ * #else
+ *   args = g_strdupv (argv);
+ * #endif
+ *
+ *   /&ast; ... setup context ... &ast;/
+ *
+ *   if (!g_option_context_parse_strv (context, &args, &error))
+ *     {
+ *       /&ast; ... error ... &ast;/
+ *     }
+ *
+ *   /&ast; ... &ast;/
+ *
+ *   g_strfreev (args);
+ *
+ *   /&ast; ... &ast;/
+ * }
+ * ]|
  */
 
 #include "config.h"
@@ -204,6 +255,7 @@ struct _GOptionContext
 
   guint            help_enabled   : 1;
   guint            ignore_unknown : 1;
+  guint            strv_mode      : 1;
 
   GOptionGroup    *main_group;
 
@@ -276,7 +328,7 @@ G_DEFINE_QUARK (g-option-context-error-quark, g_option_error)
 
 /**
  * g_option_context_new:
- * @parameter_string: a string which is displayed in
+ * @parameter_string: (allow-none): a string which is displayed in
  *    the first line of <option>--help</option> output, after the
  *    usage summary
  *    <literal><replaceable>programname</replaceable> [OPTION...]</literal>
@@ -557,10 +609,12 @@ g_option_context_add_main_entries (GOptionContext      *context,
 }
 
 static gint
-calculate_max_length (GOptionGroup *group)
+calculate_max_length (GOptionGroup *group,
+                      GHashTable   *aliases)
 {
   GOptionEntry *entry;
   gint i, len, max_length;
+  const gchar *long_name;
 
   max_length = 0;
 
@@ -571,7 +625,10 @@ calculate_max_length (GOptionGroup *group)
       if (entry->flags & G_OPTION_FLAG_HIDDEN)
         continue;
 
-      len = _g_utf8_strwidth (entry->long_name);
+      long_name = g_hash_table_lookup (aliases, &entry->long_name);
+      if (!long_name)
+        long_name = entry->long_name;
+      len = _g_utf8_strwidth (long_name);
 
       if (entry->short_name)
         len += 4;
@@ -589,9 +646,11 @@ static void
 print_entry (GOptionGroup       *group,
              gint                max_length,
              const GOptionEntry *entry,
-             GString            *string)
+             GString            *string,
+             GHashTable         *aliases)
 {
   GString *str;
+  const gchar *long_name;
 
   if (entry->flags & G_OPTION_FLAG_HIDDEN)
     return;
@@ -599,12 +658,16 @@ print_entry (GOptionGroup       *group,
   if (entry->long_name[0] == 0)
     return;
 
+  long_name = g_hash_table_lookup (aliases, &entry->long_name);
+  if (!long_name)
+    long_name = entry->long_name;
+
   str = g_string_new (NULL);
 
   if (entry->short_name)
-    g_string_append_printf (str, "  -%c, --%s", entry->short_name, entry->long_name);
+    g_string_append_printf (str, "  -%c, --%s", entry->short_name, long_name);
   else
-    g_string_append_printf (str, "  --%s", entry->long_name);
+    g_string_append_printf (str, "  --%s", long_name);
 
   if (entry->arg_description)
     g_string_append_printf (str, "=%s", TRANSLATE (group, entry->arg_description));
@@ -712,10 +775,11 @@ g_option_context_get_help (GOptionContext *context,
                            GOptionGroup   *group)
 {
   GList *list;
-  gint max_length, len;
+  gint max_length = 0, len;
   gint i;
   GOptionEntry *entry;
   GHashTable *shadow_map;
+  GHashTable *aliases;
   gboolean seen[256];
   const gchar *rest_description;
   GString *string;
@@ -763,6 +827,7 @@ g_option_context_get_help (GOptionContext *context,
 
   memset (seen, 0, sizeof (gboolean) * 256);
   shadow_map = g_hash_table_new (g_str_hash, g_str_equal);
+  aliases = g_hash_table_new_full (NULL, NULL, NULL, g_free);
 
   if (context->main_group)
     {
@@ -789,7 +854,10 @@ g_option_context_get_help (GOptionContext *context,
           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);
+            {
+              g_hash_table_insert (aliases, &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);
 
@@ -806,17 +874,20 @@ g_option_context_get_help (GOptionContext *context,
 
   list = context->groups;
 
-  max_length = _g_utf8_strwidth ("-?, --help");
-
-  if (list)
+  if (context->help_enabled)
     {
-      len = _g_utf8_strwidth ("--help-all");
-      max_length = MAX (max_length, len);
+      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);
+      len = calculate_max_length (context->main_group, aliases);
       max_length = MAX (max_length, len);
     }
 
@@ -824,12 +895,15 @@ g_option_context_get_help (GOptionContext *context,
     {
       GOptionGroup *g = list->data;
 
-      /* First, we check the --help-<groupname> options */
-      len = _g_utf8_strwidth ("--help-") + _g_utf8_strwidth (g->name);
-      max_length = MAX (max_length, len);
+      if (context->help_enabled)
+        {
+          /* First, we check the --help-<groupname> 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);
+      len = calculate_max_length (g, aliases);
       max_length = MAX (max_length, len);
 
       list = list->next;
@@ -838,7 +912,7 @@ g_option_context_get_help (GOptionContext *context,
   /* Add a bit of padding */
   max_length += 4;
 
-  if (!group)
+  if (!group && context->help_enabled)
     {
       list = context->groups;
 
@@ -878,7 +952,7 @@ g_option_context_get_help (GOptionContext *context,
           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);
+            print_entry (group, max_length, &group->entries[i], string, aliases);
           g_string_append (string, "\n");
         }
     }
@@ -898,7 +972,7 @@ g_option_context_get_help (GOptionContext *context,
               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);
+                  print_entry (g, max_length, &g->entries[i], string, aliases);
 
               g_string_append (string, "\n");
             }
@@ -919,7 +993,7 @@ g_option_context_get_help (GOptionContext *context,
       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);
+                       &context->main_group->entries[i], string, aliases);
 
       while (list != NULL)
         {
@@ -928,7 +1002,7 @@ g_option_context_get_help (GOptionContext *context,
           /* 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);
+              print_entry (g, max_length, &g->entries[i], string, aliases);
 
           list = list->next;
         }
@@ -942,6 +1016,8 @@ g_option_context_get_help (GOptionContext *context,
       g_string_append (string, "\n");
     }
 
+  g_hash_table_destroy (aliases);
+
   return g_string_free (string, FALSE);
 }
 
@@ -1133,7 +1209,14 @@ parse_arg (GOptionContext *context,
       {
         gchar *data;
 
+#if G_OS_WIN32
+        if (!context->strv_mode)
+          data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        else
+          data = g_strdup (value);
+#else
         data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+#endif
 
         if (!data)
           return FALSE;
@@ -1152,7 +1235,14 @@ parse_arg (GOptionContext *context,
       {
         gchar *data;
 
+#if G_OS_WIN32
+        if (!context->strv_mode)
+          data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        else
+          data = g_strdup (value);
+#else
         data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+#endif
 
         if (!data)
           return FALSE;
@@ -1185,7 +1275,10 @@ parse_arg (GOptionContext *context,
         gchar *data;
 
 #ifdef G_OS_WIN32
-        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        if (!context->strv_mode)
+          data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        else
+          data = g_strdup (value);
 
         if (!data)
           return FALSE;
@@ -1208,7 +1301,10 @@ parse_arg (GOptionContext *context,
         gchar *data;
 
 #ifdef G_OS_WIN32
-        data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        if (!context->strv_mode)
+          data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+        else
+          data = g_strdup (value);
 
         if (!data)
           return FALSE;
@@ -1265,7 +1361,10 @@ parse_arg (GOptionContext *context,
         else if (entry->flags & G_OPTION_FLAG_FILENAME)
           {
 #ifdef G_OS_WIN32
-            data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+            if (!context->strv_mode)
+              data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
+            else
+              data = g_strdup (value);
 #else
             data = g_strdup (value);
 #endif
@@ -1621,6 +1720,9 @@ free_pending_nulls (GOptionContext *context,
 
       if (perform_nulls)
         {
+          if (context->strv_mode)
+            g_free (*n->ptr);
+
           if (n->value)
             {
               /* Copy back the short options */
@@ -2440,3 +2542,48 @@ g_option_context_get_description (GOptionContext *context)
 
   return context->description;
 }
+
+/**
+ * g_option_context_parse_strv:
+ * @context: a #GOptionContext
+ * @arguments: (inout) (array null-terminated=1): a pointer to the
+ *    command line arguments (which must be in UTF-8 on Windows)
+ * @error: a return location for errors
+ *
+ * Parses the command line arguments.
+ *
+ * This function is similar to g_option_context_parse() except that it
+ * respects the normal memory rules when dealing with a strv instead of
+ * assuming that the passed-in array is the argv of the main function.
+ *
+ * In particular, strings that are removed from the arguments list will
+ * be freed using g_free().
+ *
+ * On Windows, the strings are expected to be in UTF-8.  This is in
+ * contrast to g_option_context_parse() which expects them to be in the
+ * system codepage, which is how they are passed as @argv to main().
+ * See g_win32_get_command_line() for a solution.
+ *
+ * This function is useful if you are trying to use #GOptionContext with
+ * #GApplication.
+ *
+ * Returns: %TRUE if the parsing was successful,
+ *          %FALSE if an error occurred
+ *
+ * Since: 2.40
+ **/
+gboolean
+g_option_context_parse_strv (GOptionContext   *context,
+                             gchar          ***arguments,
+                             GError          **error)
+{
+  gboolean success;
+  gint argc;
+
+  context->strv_mode = TRUE;
+  argc = g_strv_length (*arguments);
+  success = g_option_context_parse (context, &argc, arguments, error);
+  context->strv_mode = FALSE;
+
+  return success;
+}