2005-08-08 Roland McGrath <roland@redhat.com>
[kopensolaris-gnu/glibc.git] / argp / argp-help.c
index 5d7df54..abd59c1 100644 (file)
 /* Hierarchial argument parsing help output
-   Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc.
+   Copyright (C) 1995-2003, 2004, 2005 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
    Written by Miles Bader <miles@gnu.ai.mit.edu>.
 
    The GNU C 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.
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
 
    The GNU C 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.
+   Lesser General Public License for more details.
 
-   You should have received a copy of the GNU Library General Public
-   License along with the GNU C Library; see the file COPYING.LIB.  If not,
-   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-   Boston, MA 02111-1307, USA.  */
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE   1
+#endif
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
+/* AIX requires this to be the first thing in the file.  */
+#ifndef __GNUC__
+# if HAVE_ALLOCA_H || defined _LIBC
+#  include <alloca.h>
+# else
+#  ifdef _AIX
+#pragma alloca
+#  else
+#   ifndef alloca /* predefined by HP cc +Olibcalls */
+char *alloca ();
+#   endif
+#  endif
+# endif
+#endif
+
+#include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
 #include <stdarg.h>
-#include <malloc.h>
 #include <ctype.h>
+#include <limits.h>
+#ifdef USE_IN_LIBIO
+# include <wchar.h>
+#endif
 
 #ifndef _
-/* This is for other GNU distributions with internationalized messages.
-   When compiling libc, the _ macro is predefined.  */
-#ifdef HAVE_LIBINTL_H
-# include <libintl.h>
-# define _(msgid)       gettext (msgid)
-#else
-# define _(msgid)       (msgid)
-# define gettext(msgid) (msgid)
+/* This is for other GNU distributions with internationalized messages.  */
+# if defined HAVE_LIBINTL_H || defined _LIBC
+#  include <libintl.h>
+#  ifdef _LIBC
+#   undef dgettext
+#   define dgettext(domain, msgid) \
+  INTUSE(__dcgettext) (domain, msgid, LC_MESSAGES)
+#  endif
+# else
+#  define dgettext(domain, msgid) (msgid)
+# endif
 #endif
+
+#ifndef _LIBC
+# if HAVE_STRERROR_R
+#  if !HAVE_DECL_STRERROR_R
+char *strerror_r (int errnum, char *buf, size_t buflen);
+#  endif
+# else
+#  if !HAVE_DECL_STRERROR
+char *strerror (int errnum);
+#  endif
+# endif
 #endif
 
 #include "argp.h"
 #include "argp-fmtstream.h"
 #include "argp-namefrob.h"
 
+#ifndef SIZE_MAX
+# define SIZE_MAX ((size_t) -1)
+#endif
+\f
+/* User-selectable (using an environment variable) formatting parameters.
+
+   These may be specified in an environment variable called `ARGP_HELP_FMT',
+   with a contents like:  VAR1=VAL1,VAR2=VAL2,BOOLVAR2,no-BOOLVAR2
+   Where VALn must be a positive integer.  The list of variables is in the
+   UPARAM_NAMES vector, below.  */
+
+/* Default parameters.  */
+#define DUP_ARGS      0                /* True if option argument can be duplicated. */
+#define DUP_ARGS_NOTE 1                /* True to print a note about duplicate args. */
 #define SHORT_OPT_COL 2                /* column in which short options start */
 #define LONG_OPT_COL  6                /* column in which long options start */
 #define DOC_OPT_COL   2                /* column in which doc options start */
 #define USAGE_INDENT 12                /* indentation of wrapped usage lines */
 #define RMARGIN      79                /* right margin used for wrapping */
 
+/* User-selectable (using an environment variable) formatting parameters.
+   They must all be of type `int' for the parsing code to work.  */
+struct uparams
+{
+  /* If true, arguments for an option are shown with both short and long
+     options, even when a given option has both, e.g. `-x ARG, --longx=ARG'.
+     If false, then if an option has both, the argument is only shown with
+     the long one, e.g., `-x, --longx=ARG', and a message indicating that
+     this really means both is printed below the options.  */
+  int dup_args;
+
+  /* This is true if when DUP_ARGS is false, and some duplicate arguments have
+     been suppressed, an explanatory message should be printed.  */
+  int dup_args_note;
+
+  /* Various output columns.  */
+  int short_opt_col;
+  int long_opt_col;
+  int doc_opt_col;
+  int opt_doc_col;
+  int header_col;
+  int usage_indent;
+  int rmargin;
+
+  int valid;                   /* True when the values in here are valid.  */
+};
+
+/* This is a global variable, as user options are only ever read once.  */
+static struct uparams uparams = {
+  DUP_ARGS, DUP_ARGS_NOTE,
+  SHORT_OPT_COL, LONG_OPT_COL, DOC_OPT_COL, OPT_DOC_COL, HEADER_COL,
+  USAGE_INDENT, RMARGIN,
+  0
+};
+
+/* A particular uparam, and what the user name is.  */
+struct uparam_name
+{
+  const char *name;            /* User name.  */
+  int is_bool;                 /* Whether it's `boolean'.  */
+  size_t uparams_offs;         /* Location of the (int) field in UPARAMS.  */
+};
+
+/* The name-field mappings we know about.  */
+static const struct uparam_name uparam_names[] =
+{
+  { "dup-args",       1, offsetof (struct uparams, dup_args) },
+  { "dup-args-note",  1, offsetof (struct uparams, dup_args_note) },
+  { "short-opt-col",  0, offsetof (struct uparams, short_opt_col) },
+  { "long-opt-col",   0, offsetof (struct uparams, long_opt_col) },
+  { "doc-opt-col",    0, offsetof (struct uparams, doc_opt_col) },
+  { "opt-doc-col",    0, offsetof (struct uparams, opt_doc_col) },
+  { "header-col",     0, offsetof (struct uparams, header_col) },
+  { "usage-indent",   0, offsetof (struct uparams, usage_indent) },
+  { "rmargin",        0, offsetof (struct uparams, rmargin) },
+  { 0 }
+};
+
+/* Read user options from the environment, and fill in UPARAMS appropiately.  */
+static void
+fill_in_uparams (const struct argp_state *state)
+{
+  const char *var = getenv ("ARGP_HELP_FMT");
+
+#define SKIPWS(p) do { while (isspace (*p)) p++; } while (0);
+
+  if (var)
+    /* Parse var. */
+    while (*var)
+      {
+       SKIPWS (var);
+
+       if (isalpha (*var))
+         {
+           size_t var_len;
+           const struct uparam_name *un;
+           int unspec = 0, val = 0;
+           const char *arg = var;
+
+           while (isalnum (*arg) || *arg == '-' || *arg == '_')
+             arg++;
+           var_len = arg - var;
+
+           SKIPWS (arg);
+
+           if (*arg == '\0' || *arg == ',')
+             unspec = 1;
+           else if (*arg == '=')
+             {
+               arg++;
+               SKIPWS (arg);
+             }
+
+           if (unspec)
+             {
+               if (var[0] == 'n' && var[1] == 'o' && var[2] == '-')
+                 {
+                   val = 0;
+                   var += 3;
+                   var_len -= 3;
+                 }
+               else
+                 val = 1;
+             }
+           else if (isdigit (*arg))
+             {
+               val = atoi (arg);
+               while (isdigit (*arg))
+                 arg++;
+               SKIPWS (arg);
+             }
+
+           for (un = uparam_names; un->name; un++)
+             if (strlen (un->name) == var_len
+                 && strncmp (var, un->name, var_len) == 0)
+               {
+                 if (unspec && !un->is_bool)
+                   __argp_failure (state, 0, 0,
+                                   dgettext (state->root_argp->argp_domain, "\
+%.*s: ARGP_HELP_FMT parameter requires a value"),
+                                   (int) var_len, var);
+                 else
+                   *(int *)((char *)&uparams + un->uparams_offs) = val;
+                 break;
+               }
+           if (! un->name)
+             __argp_failure (state, 0, 0,
+                             dgettext (state->root_argp->argp_domain, "\
+%.*s: Unknown ARGP_HELP_FMT parameter"),
+                             (int) var_len, var);
+
+           var = arg;
+           if (*var == ',')
+             var++;
+         }
+       else if (*var)
+         {
+           __argp_failure (state, 0, 0,
+                           dgettext (state->root_argp->argp_domain,
+                                     "Garbage in ARGP_HELP_FMT: %s"), var);
+           break;
+         }
+      }
+}
+\f
 /* Returns true if OPT hasn't been marked invisible.  Visibility only affects
    whether OPT is displayed or used in sorting, not option shadowing.  */
 #define ovisible(opt) (! ((opt)->flags & OPTION_HIDDEN))
@@ -169,6 +365,9 @@ struct hol_entry
 
   /* The cluster of options this entry belongs to, or 0 if none.  */
   struct hol_cluster *cluster;
+
+  /* The argp from which this option came.  */
+  const struct argp *argp;
 };
 
 /* A cluster of entries to reflect the argp tree structure.  */
@@ -178,7 +377,7 @@ struct hol_cluster
   const char *header;
 
   /* Used to order clusters within the same group with the same parent,
-     according to the order in which they occured in the parent argp's child
+     according to the order in which they occurred in the parent argp's child
      list.  */
   int index;
 
@@ -190,6 +389,9 @@ struct hol_cluster
      level.  */
   struct hol_cluster *parent;
 
+  /* The argp from which this cluster is (eventually) derived.  */
+  const struct argp *argp;
+
   /* The distance this cluster is from the root.  */
   int depth;
 
@@ -215,13 +417,14 @@ struct hol
   struct hol_cluster *clusters;
 };
 \f
-/* Create a struct hol from an array of struct argp_option.  CLUSTER is the
+/* Create a struct hol from the options in ARGP.  CLUSTER is the
    hol_cluster in which these entries occur, or 0, if at the root.  */
 static struct hol *
-make_hol (const struct argp_option *opt, struct hol_cluster *cluster)
+make_hol (const struct argp *argp, struct hol_cluster *cluster)
 {
   char *so;
   const struct argp_option *o;
+  const struct argp_option *opts = argp->options;
   struct hol_entry *entry;
   unsigned num_short_options = 0;
   struct hol *hol = malloc (sizeof (struct hol));
@@ -231,15 +434,15 @@ make_hol (const struct argp_option *opt, struct hol_cluster *cluster)
   hol->num_entries = 0;
   hol->clusters = 0;
 
-  if (opt)
+  if (opts)
     {
       int cur_group = 0;
 
       /* The first option must not be an alias.  */
-      assert (! oalias (opt));
+      assert (! oalias (opts));
 
       /* Calculate the space needed.  */
-      for (o = opt; ! oend (o); o++)
+      for (o = opts; ! oend (o); o++)
        {
          if (! oalias (o))
            hol->num_entries++;
@@ -251,10 +454,13 @@ make_hol (const struct argp_option *opt, struct hol_cluster *cluster)
       hol->short_options = malloc (num_short_options + 1);
 
       assert (hol->entries && hol->short_options);
+#if SIZE_MAX <= UINT_MAX
+      assert (hol->num_entries <= SIZE_MAX / sizeof (struct hol_entry));
+#endif
 
       /* Fill in the entries.  */
       so = hol->short_options;
-      for (o = opt, entry = hol->entries; ! oend (o); entry++)
+      for (o = opts, entry = hol->entries; ! oend (o); entry++)
        {
          entry->opt = o;
          entry->num = 0;
@@ -266,6 +472,7 @@ make_hol (const struct argp_option *opt, struct hol_cluster *cluster)
               ? cur_group + 1
               : cur_group);
          entry->cluster = cluster;
+         entry->argp = argp;
 
          do
            {
@@ -285,10 +492,10 @@ make_hol (const struct argp_option *opt, struct hol_cluster *cluster)
 \f
 /* Add a new cluster to HOL, with the given GROUP and HEADER (taken from the
    associated argp child list entry), INDEX, and PARENT, and return a pointer
-   to it.  */
+   to it.  ARGP is the argp that this cluster results from.  */
 static struct hol_cluster *
 hol_add_cluster (struct hol *hol, int group, const char *header, int index,
-                struct hol_cluster *parent)
+                struct hol_cluster *parent, const struct argp *argp)
 {
   struct hol_cluster *cl = malloc (sizeof (struct hol_cluster));
   if (cl)
@@ -298,6 +505,8 @@ hol_add_cluster (struct hol *hol, int group, const char *header, int index,
 
       cl->index = index;
       cl->parent = parent;
+      cl->argp = argp;
+      cl->depth = parent ? parent->depth + 1 : 0;
 
       cl->next = hol->clusters;
       hol->clusters = cl;
@@ -327,12 +536,12 @@ hol_free (struct hol *hol)
   free (hol);
 }
 \f
-static inline int
+static int
 hol_entry_short_iterate (const struct hol_entry *entry,
                         int (*func)(const struct argp_option *opt,
                                     const struct argp_option *real,
-                                    void *cookie),
-                        void *cookie)
+                                    const char *domain, void *cookie),
+                        const char *domain, void *cookie)
 {
   unsigned nopts;
   int val = 0;
@@ -345,7 +554,7 @@ hol_entry_short_iterate (const struct hol_entry *entry,
        if (!oalias (opt))
          real = opt;
        if (ovisible (opt))
-         val = (*func)(opt, real, cookie);
+         val = (*func)(opt, real, domain, cookie);
        so++;
       }
 
@@ -353,11 +562,12 @@ hol_entry_short_iterate (const struct hol_entry *entry,
 }
 
 static inline int
+__attribute__ ((always_inline))
 hol_entry_long_iterate (const struct hol_entry *entry,
                        int (*func)(const struct argp_option *opt,
                                    const struct argp_option *real,
-                                   void *cookie),
-                       void *cookie)
+                                   const char *domain, void *cookie),
+                       const char *domain, void *cookie)
 {
   unsigned nopts;
   int val = 0;
@@ -369,7 +579,7 @@ hol_entry_long_iterate (const struct hol_entry *entry,
        if (!oalias (opt))
          real = opt;
        if (ovisible (opt))
-         val = (*func)(opt, real, cookie);
+         val = (*func)(opt, real, domain, cookie);
       }
 
   return val;
@@ -378,7 +588,7 @@ hol_entry_long_iterate (const struct hol_entry *entry,
 /* Iterator that returns true for the first short option.  */
 static inline int
 until_short (const struct argp_option *opt, const struct argp_option *real,
-            void *cookie)
+            const char *domain, void *cookie)
 {
   return oshort (opt) ? opt->key : 0;
 }
@@ -387,7 +597,8 @@ until_short (const struct argp_option *opt, const struct argp_option *real,
 static char
 hol_entry_first_short (const struct hol_entry *entry)
 {
-  return hol_entry_short_iterate (entry, until_short, 0);
+  return hol_entry_short_iterate (entry, until_short,
+                                 entry->argp->argp_domain, 0);
 }
 
 /* Returns the first valid long option in ENTRY, or 0 if there is none.  */
@@ -498,12 +709,12 @@ canon_doc_option (const char **name)
 {
   int non_opt;
   /* Skip initial whitespace.  */
-  while (isspace (*name))
+  while (isspace (**name))
     (*name)++;
   /* Decide whether this looks like an option (leading `-') or not.  */
   non_opt = (**name != '-');
   /* Skip until part of name used for sorting.  */
-  while (**name && !isalnum (*name))
+  while (**name && !isalnum (**name))
     (*name)++;
   return non_opt;
 }
@@ -511,27 +722,30 @@ canon_doc_option (const char **name)
 /* Order ENTRY1 & ENTRY2 by the order which they should appear in a help
    listing.  */
 static int
-hol_entry_cmp (const struct hol_entry *entry1, const struct hol_entry *entry2)
+hol_entry_cmp (const struct hol_entry *entry1,
+              const struct hol_entry *entry2)
 {
   /* The group numbers by which the entries should be ordered; if either is
      in a cluster, then this is just the group within the cluster.  */
   int group1 = entry1->group, group2 = entry2->group;
 
   if (entry1->cluster != entry2->cluster)
-    /* The entries are not within the same cluster, so we can't compare them
-       directly, we have to use the appropiate clustering level too.  */
-    if (! entry1->cluster)
-      /* ENTRY1 is at the `base level', not in a cluster, so we have to
-        compare it's group number with that of the base cluster in which
-        ENTRY2 resides.  Note that if they're in the same group, the
-        clustered option always comes laster.  */
-      return group_cmp (group1, hol_cluster_base (entry2->cluster)->group, -1);
-    else if (! entry2->cluster)
-      /* Likewise, but ENTRY2's not in a cluster.  */
-      return group_cmp (hol_cluster_base (entry1->cluster)->group, group2, 1);
-    else
-      /* Both entries are in clusters, we can just compare the clusters.  */
-      return hol_cluster_cmp (entry1->cluster, entry2->cluster);
+    {
+      /* The entries are not within the same cluster, so we can't compare them
+        directly, we have to use the appropiate clustering level too.  */
+      if (! entry1->cluster)
+       /* ENTRY1 is at the `base level', not in a cluster, so we have to
+          compare it's group number with that of the base cluster in which
+          ENTRY2 resides.  Note that if they're in the same group, the
+          clustered option always comes laster.  */
+       return group_cmp (group1, hol_cluster_base (entry2->cluster)->group, -1);
+      else if (! entry2->cluster)
+       /* Likewise, but ENTRY2's not in a cluster.  */
+       return group_cmp (hol_cluster_base (entry1->cluster)->group, group2, 1);
+      else
+       /* Both entries are in clusters, we can just compare the clusters.  */
+       return hol_cluster_cmp (entry1->cluster, entry2->cluster);
+    }
   else if (group1 == group2)
     /* The entries are both in the same cluster and group, so compare them
        alphabetically.  */
@@ -564,7 +778,11 @@ hol_entry_cmp (const struct hol_entry *entry1, const struct hol_entry *entry2)
        {
          char first1 = short1 ? short1 : long1 ? *long1 : 0;
          char first2 = short2 ? short2 : long2 ? *long2 : 0;
+#ifdef _tolower
+         int lower_cmp = _tolower (first1) - _tolower (first2);
+#else
          int lower_cmp = tolower (first1) - tolower (first2);
+#endif
          /* Compare ignoring case, except when the options are both the
             same letter, in which case lower-case always comes first.  */
          return lower_cmp ? lower_cmp : first2 - first1;
@@ -609,73 +827,81 @@ hol_append (struct hol *hol, struct hol *more)
 
   /* Merge entries.  */
   if (more->num_entries > 0)
-    if (hol->num_entries == 0)
-      {
-       hol->num_entries = more->num_entries;
-       hol->entries = more->entries;
-       hol->short_options = more->short_options;
-       more->num_entries = 0;  /* Mark MORE's fields as invalid.  */
-      }
-    else
-      /* append the entries in MORE to those in HOL, taking care to only add
-        non-shadowed SHORT_OPTIONS values.  */
-      {
-       unsigned left;
-       char *so, *more_so;
-       struct hol_entry *e;
-       unsigned num_entries = hol->num_entries + more->num_entries;
-       struct hol_entry *entries =
-         malloc (num_entries * sizeof (struct hol_entry));
-       unsigned hol_so_len = strlen (hol->short_options);
-       char *short_options =
-         malloc (hol_so_len + strlen (more->short_options) + 1);
-
-       memcpy (entries, hol->entries,
-               hol->num_entries * sizeof (struct hol_entry));
-       memcpy (entries + hol->num_entries, more->entries,
-               more->num_entries * sizeof (struct hol_entry));
-
-       memcpy (short_options, hol->short_options, hol_so_len);
-
-       /* Fix up the short options pointers from HOL.  */
-       for (e = entries, left = hol->num_entries; left > 0; e++, left--)
-         e->short_options += (short_options - hol->short_options);
-
-       /* Now add the short options from MORE, fixing up its entries too.  */
-       so = short_options + hol_so_len;
-       more_so = more->short_options;
-       for (left = more->num_entries; left > 0; e++, left--)
-         {
-           int opts_left;
-           const struct argp_option *opt;
+    {
+      if (hol->num_entries == 0)
+       {
+         hol->num_entries = more->num_entries;
+         hol->entries = more->entries;
+         hol->short_options = more->short_options;
+         more->num_entries = 0;        /* Mark MORE's fields as invalid.  */
+       }
+      else
+       /* Append the entries in MORE to those in HOL, taking care to only add
+          non-shadowed SHORT_OPTIONS values.  */
+       {
+         unsigned left;
+         char *so, *more_so;
+         struct hol_entry *e;
+         unsigned num_entries = hol->num_entries + more->num_entries;
+         struct hol_entry *entries =
+           malloc (num_entries * sizeof (struct hol_entry));
+         unsigned hol_so_len = strlen (hol->short_options);
+         char *short_options =
+           malloc (hol_so_len + strlen (more->short_options) + 1);
+
+         assert (entries && short_options);
+#if SIZE_MAX <= UINT_MAX
+         assert (num_entries <= SIZE_MAX / sizeof (struct hol_entry));
+#endif
 
-           e->short_options = so;
+         __mempcpy (__mempcpy (entries, hol->entries,
+                               hol->num_entries * sizeof (struct hol_entry)),
+                    more->entries,
+                    more->num_entries * sizeof (struct hol_entry));
 
-           for (opts_left = e->num, opt = e->opt; opts_left; opt++, opts_left--)
-             {
-               int ch = *more_so;
-               if (oshort (opt) && ch == opt->key)
-                 /* The next short option in MORE_SO, CH, is from OPT.  */
-                 {
-                   if (! find_char (ch,
-                                    short_options, short_options + hol_so_len))
-                     /* The short option CH isn't shadowed by HOL's options,
-                        so add it to the sum.  */
-                     *so++ = ch;
-                   more_so++;
-                 }
-             }
-         }
+         __mempcpy (short_options, hol->short_options, hol_so_len);
 
-       *so = '\0';
+         /* Fix up the short options pointers from HOL.  */
+         for (e = entries, left = hol->num_entries; left > 0; e++, left--)
+           e->short_options += (short_options - hol->short_options);
 
-       free (hol->entries);
-       free (hol->short_options);
+         /* Now add the short options from MORE, fixing up its entries
+            too.  */
+         so = short_options + hol_so_len;
+         more_so = more->short_options;
+         for (left = more->num_entries; left > 0; e++, left--)
+           {
+             int opts_left;
+             const struct argp_option *opt;
+
+             e->short_options = so;
+
+             for (opts_left = e->num, opt = e->opt; opts_left; opt++, opts_left--)
+               {
+                 int ch = *more_so;
+                 if (oshort (opt) && ch == opt->key)
+                   /* The next short option in MORE_SO, CH, is from OPT.  */
+                   {
+                     if (! find_char (ch, short_options,
+                                      short_options + hol_so_len))
+                       /* The short option CH isn't shadowed by HOL's options,
+                          so add it to the sum.  */
+                       *so++ = ch;
+                     more_so++;
+                   }
+               }
+           }
 
-       hol->entries = entries;
-       hol->num_entries = num_entries;
-       hol->short_options = short_options;
-      }
+         *so = '\0';
+
+         free (hol->entries);
+         free (hol->short_options);
+
+         hol->entries = entries;
+         hol->num_entries = num_entries;
+         hol->short_options = short_options;
+       }
+    }
 
   hol_free (more);
 }
@@ -689,22 +915,53 @@ indent_to (argp_fmtstream_t stream, unsigned col)
     __argp_fmtstream_putc (stream, ' ');
 }
 
+/* Output to STREAM either a space, or a newline if there isn't room for at
+   least ENSURE characters before the right margin.  */
+static void
+space (argp_fmtstream_t stream, size_t ensure)
+{
+  if (__argp_fmtstream_point (stream) + ensure
+      >= __argp_fmtstream_rmargin (stream))
+    __argp_fmtstream_putc (stream, '\n');
+  else
+    __argp_fmtstream_putc (stream, ' ');
+}
+
 /* If the option REAL has an argument, we print it in using the printf
    format REQ_FMT or OPT_FMT depending on whether it's a required or
    optional argument.  */
 static void
 arg (const struct argp_option *real, const char *req_fmt, const char *opt_fmt,
-     argp_fmtstream_t stream)
+     const char *domain, argp_fmtstream_t stream)
 {
   if (real->arg)
-    if (real->flags & OPTION_ARG_OPTIONAL)
-      __argp_fmtstream_printf (stream, opt_fmt, _(real->arg));
-    else
-      __argp_fmtstream_printf (stream, req_fmt, _(real->arg));
+    {
+      if (real->flags & OPTION_ARG_OPTIONAL)
+       __argp_fmtstream_printf (stream, opt_fmt,
+                                dgettext (domain, real->arg));
+      else
+       __argp_fmtstream_printf (stream, req_fmt,
+                                dgettext (domain, real->arg));
+    }
 }
 \f
 /* Helper functions for hol_entry_help.  */
 
+/* State used during the execution of hol_help.  */
+struct hol_help_state
+{
+  /* PREV_ENTRY should contain the previous entry printed, or 0.  */
+  struct hol_entry *prev_entry;
+
+  /* If an entry is in a different group from the previous one, and SEP_GROUPS
+     is true, then a blank line will be printed before any output. */
+  int sep_groups;
+
+  /* True if a duplicate option argument was suppressed (only ever set if
+     UPARAMS.dup_args is false).  */
+  int suppressed_dup_arg;
+};
+
 /* Some state used while printing a help entry (used to communicate with
    helper functions).  See the doc for hol_entry_help for more info, as most
    of the fields are copied from its arguments.  */
@@ -712,164 +969,224 @@ struct pentry_state
 {
   const struct hol_entry *entry;
   argp_fmtstream_t stream;
-  struct hol_entry **prev_entry;
-  int *sep_groups;
+  struct hol_help_state *hhstate;
+
+  /* True if nothing's been printed so far.  */
+  int first;
 
-  int first;                   /* True if nothing's been printed so far.  */
+  /* If non-zero, the state that was used to print this help.  */
+  const struct argp_state *state;
 };
 
+/* If a user doc filter should be applied to DOC, do so.  */
+static const char *
+filter_doc (const char *doc, int key, const struct argp *argp,
+           const struct argp_state *state)
+{
+  if (argp->help_filter)
+    /* We must apply a user filter to this output.  */
+    {
+      void *input = __argp_input (argp, state);
+      return (*argp->help_filter) (key, doc, input);
+    }
+  else
+    /* No filter.  */
+    return doc;
+}
+
 /* Prints STR as a header line, with the margin lines set appropiately, and
-   notes the fact that groups should be separated with a blank line.  Note
+   notes the fact that groups should be separated with a blank line.  ARGP is
+   the argp that should dictate any user doc filtering to take place.  Note
    that the previous wrap margin isn't restored, but the left margin is reset
    to 0.  */
 static void
-print_header (const char *str, struct pentry_state *st)
+print_header (const char *str, const struct argp *argp,
+             struct pentry_state *pest)
 {
-  if (*str)
+  const char *tstr = dgettext (argp->argp_domain, str);
+  const char *fstr = filter_doc (tstr, ARGP_KEY_HELP_HEADER, argp, pest->state);
+
+  if (fstr)
     {
-      if (st->prev_entry && *st->prev_entry)
-       __argp_fmtstream_putc (st->stream, '\n'); /* Precede with a blank line.  */
-      indent_to (st->stream, HEADER_COL);
-      __argp_fmtstream_set_lmargin (st->stream, HEADER_COL);
-      __argp_fmtstream_set_wmargin (st->stream, HEADER_COL);
-      __argp_fmtstream_puts (st->stream, str);
-      __argp_fmtstream_set_lmargin (st->stream, 0);
+      if (*fstr)
+       {
+         if (pest->hhstate->prev_entry)
+           /* Precede with a blank line.  */
+           __argp_fmtstream_putc (pest->stream, '\n');
+         indent_to (pest->stream, uparams.header_col);
+         __argp_fmtstream_set_lmargin (pest->stream, uparams.header_col);
+         __argp_fmtstream_set_wmargin (pest->stream, uparams.header_col);
+         __argp_fmtstream_puts (pest->stream, fstr);
+         __argp_fmtstream_set_lmargin (pest->stream, 0);
+         __argp_fmtstream_putc (pest->stream, '\n');
+       }
+
+      pest->hhstate->sep_groups = 1; /* Separate subsequent groups. */
     }
 
-  if (st->sep_groups)
-    *st->sep_groups = 1;       /* Separate subsequent groups. */
+  if (fstr != tstr)
+    free ((char *) fstr);
 }
 
 /* Inserts a comma if this isn't the first item on the line, and then makes
-   sure we're at least to column COL.  Also clears FIRST.  */
+   sure we're at least to column COL.  If this *is* the first item on a line,
+   prints any pending whitespace/headers that should precede this line. Also
+   clears FIRST.  */
 static void
-comma (unsigned col, struct pentry_state *st)
+comma (unsigned col, struct pentry_state *pest)
 {
-  if (st->first)
+  if (pest->first)
     {
-      const struct hol_entry *pe = st->prev_entry ? *st->prev_entry : 0;
-      const struct hol_cluster *cl = st->entry->cluster;
+      const struct hol_entry *pe = pest->hhstate->prev_entry;
+      const struct hol_cluster *cl = pest->entry->cluster;
 
-      if (st->sep_groups && *st->sep_groups
-         && pe && st->entry->group != pe->group)
-       __argp_fmtstream_putc (st->stream, '\n');
+      if (pest->hhstate->sep_groups && pe && pest->entry->group != pe->group)
+       __argp_fmtstream_putc (pest->stream, '\n');
 
-      if (pe && cl && pe->cluster != cl && cl->header && *cl->header
-         && !hol_cluster_is_child (pe->cluster, cl))
+      if (cl && cl->header && *cl->header
+         && (!pe
+             || (pe->cluster != cl
+                 && !hol_cluster_is_child (pe->cluster, cl))))
        /* If we're changing clusters, then this must be the start of the
           ENTRY's cluster unless that is an ancestor of the previous one
           (in which case we had just popped into a sub-cluster for a bit).
           If so, then print the cluster's header line.  */
        {
-         int old_wm = __argp_fmtstream_wmargin (st->stream);
-         print_header (cl->header, st);
-         __argp_fmtstream_putc (st->stream, '\n');
-         __argp_fmtstream_set_wmargin (st->stream, old_wm);
+         int old_wm = __argp_fmtstream_wmargin (pest->stream);
+         print_header (cl->header, cl->argp, pest);
+         __argp_fmtstream_set_wmargin (pest->stream, old_wm);
        }
 
-      st->first = 0;
+      pest->first = 0;
     }
   else
-    __argp_fmtstream_puts (st->stream, ", ");
+    __argp_fmtstream_puts (pest->stream, ", ");
 
-  indent_to (st->stream, col);
+  indent_to (pest->stream, col);
 }
 \f
-/* Print help for ENTRY to STREAM.  *PREV_ENTRY should contain the last entry
-   printed before this, or null if it's the first, and if ENTRY is in a
-   different group, and *SEP_GROUPS is true, then a blank line will be
-   printed before any output.  *SEP_GROUPS is also set to true if a
-   user-specified group header is printed.  */
+/* Print help for ENTRY to STREAM.  */
 static void
-hol_entry_help (struct hol_entry *entry, argp_fmtstream_t stream,
-               struct hol_entry **prev_entry, int *sep_groups)
+hol_entry_help (struct hol_entry *entry, const struct argp_state *state,
+               argp_fmtstream_t stream, struct hol_help_state *hhstate)
 {
   unsigned num;
   const struct argp_option *real = entry->opt, *opt;
   char *so = entry->short_options;
+  int have_long_opt = 0;       /* We have any long options.  */
+  /* Saved margins.  */
   int old_lm = __argp_fmtstream_set_lmargin (stream, 0);
   int old_wm = __argp_fmtstream_wmargin (stream);
-  struct pentry_state pest = { entry, stream, prev_entry, sep_groups, 1 };
+  /* PEST is a state block holding some of our variables that we'd like to
+     share with helper functions.  */
+  struct pentry_state pest = { entry, stream, hhstate, 1, state };
+
+  if (! odoc (real))
+    for (opt = real, num = entry->num; num > 0; opt++, num--)
+      if (opt->name && ovisible (opt))
+       {
+         have_long_opt = 1;
+         break;
+       }
 
   /* First emit short options.  */
-  __argp_fmtstream_set_wmargin (stream, SHORT_OPT_COL); /* For truly bizarre cases. */
+  __argp_fmtstream_set_wmargin (stream, uparams.short_opt_col); /* For truly bizarre cases. */
   for (opt = real, num = entry->num; num > 0; opt++, num--)
     if (oshort (opt) && opt->key == *so)
       /* OPT has a valid (non shadowed) short option.  */
       {
        if (ovisible (opt))
          {
-           comma (SHORT_OPT_COL, &pest);
+           comma (uparams.short_opt_col, &pest);
            __argp_fmtstream_putc (stream, '-');
            __argp_fmtstream_putc (stream, *so);
-           arg (real, " %s", "[%s]", stream);
+           if (!have_long_opt || uparams.dup_args)
+             arg (real, " %s", "[%s]", state->root_argp->argp_domain, stream);
+           else if (real->arg)
+             hhstate->suppressed_dup_arg = 1;
          }
        so++;
       }
 
   /* Now, long options.  */
   if (odoc (real))
-    /* Really a `documentation' option.  */
+    /* A `documentation' option.  */
     {
-      __argp_fmtstream_set_wmargin (stream, DOC_OPT_COL);
+      __argp_fmtstream_set_wmargin (stream, uparams.doc_opt_col);
       for (opt = real, num = entry->num; num > 0; opt++, num--)
        if (opt->name && ovisible (opt))
          {
-           comma (DOC_OPT_COL, &pest);
+           comma (uparams.doc_opt_col, &pest);
            /* Calling gettext here isn't quite right, since sorting will
               have been done on the original; but documentation options
               should be pretty rare anyway...  */
-           __argp_fmtstream_puts (stream, _(opt->name));
+           __argp_fmtstream_puts (stream,
+                                  dgettext (state->root_argp->argp_domain,
+                                            opt->name));
          }
     }
   else
-    /* A realy long option.  */
+    /* A real long option.  */
     {
-      __argp_fmtstream_set_wmargin (stream, LONG_OPT_COL);
+      int first_long_opt = 1;
+
+      __argp_fmtstream_set_wmargin (stream, uparams.long_opt_col);
       for (opt = real, num = entry->num; num > 0; opt++, num--)
        if (opt->name && ovisible (opt))
          {
-           comma (LONG_OPT_COL, &pest);
+           comma (uparams.long_opt_col, &pest);
            __argp_fmtstream_printf (stream, "--%s", opt->name);
-           arg (real, "=%s", "[=%s]", stream);
+           if (first_long_opt || uparams.dup_args)
+             arg (real, "=%s", "[=%s]", state->root_argp->argp_domain,
+                  stream);
+           else if (real->arg)
+             hhstate->suppressed_dup_arg = 1;
          }
     }
 
+  /* Next, documentation strings.  */
   __argp_fmtstream_set_lmargin (stream, 0);
+
   if (pest.first)
-    /* Didn't print any switches, what's up?  */
-    if (!oshort (real) && !real->name && real->doc)
-      /* This is a group header, print it nicely.  */
-      print_header (real->doc, &pest);
-    else
-      /* Just a totally shadowed option or null header; print nothing.  */
-      goto cleanup;            /* Just return, after cleaning up.  */
-  else if (real->doc)
-    /* Now the option documentation.  */
     {
-      unsigned col = __argp_fmtstream_point (stream);
-      const char *doc = real->doc;
+      /* Didn't print any switches, what's up?  */
+      if (!oshort (real) && !real->name)
+       /* This is a group header, print it nicely.  */
+       print_header (real->doc, entry->argp, &pest);
+      else
+       /* Just a totally shadowed option or null header; print nothing.  */
+       goto cleanup;           /* Just return, after cleaning up.  */
+    }
+  else
+    {
+      const char *tstr = real->doc ? dgettext (state->root_argp->argp_domain,
+                                              real->doc) : 0;
+      const char *fstr = filter_doc (tstr, real->key, entry->argp, state);
+      if (fstr && *fstr)
+       {
+         unsigned int col = __argp_fmtstream_point (stream);
 
-      __argp_fmtstream_set_lmargin (stream, OPT_DOC_COL);
-      __argp_fmtstream_set_wmargin (stream, OPT_DOC_COL);
+         __argp_fmtstream_set_lmargin (stream, uparams.opt_doc_col);
+         __argp_fmtstream_set_wmargin (stream, uparams.opt_doc_col);
 
-      if (col > OPT_DOC_COL + 3)
-       __argp_fmtstream_putc (stream, '\n');
-      else if (col >= OPT_DOC_COL)
-       __argp_fmtstream_puts (stream, "   ");
-      else
-       indent_to (stream, OPT_DOC_COL);
+         if (col > (unsigned int) (uparams.opt_doc_col + 3))
+           __argp_fmtstream_putc (stream, '\n');
+         else if (col >= (unsigned int) uparams.opt_doc_col)
+           __argp_fmtstream_puts (stream, "   ");
+         else
+           indent_to (stream, uparams.opt_doc_col);
 
-      __argp_fmtstream_puts (stream, doc);
+         __argp_fmtstream_puts (stream, fstr);
+       }
+      if (fstr && fstr != tstr)
+       free ((char *) fstr);
 
       /* Reset the left margin.  */
       __argp_fmtstream_set_lmargin (stream, 0);
+      __argp_fmtstream_putc (stream, '\n');
     }
 
-  __argp_fmtstream_putc (stream, '\n');
-
-  if (prev_entry)
-    *prev_entry = entry;
+  hhstate->prev_entry = entry;
 
 cleanup:
   __argp_fmtstream_set_lmargin (stream, old_lm);
@@ -878,15 +1195,32 @@ cleanup:
 \f
 /* Output a long help message about the options in HOL to STREAM.  */
 static void
-hol_help (struct hol *hol, argp_fmtstream_t stream)
+hol_help (struct hol *hol, const struct argp_state *state,
+         argp_fmtstream_t stream)
 {
   unsigned num;
   struct hol_entry *entry;
-  struct hol_entry *last_entry = 0;
-  int sep_groups = 0;          /* True if we should separate different
-                                  sections with blank lines.   */
+  struct hol_help_state hhstate = { 0, 0, 0 };
+
   for (entry = hol->entries, num = hol->num_entries; num > 0; entry++, num--)
-    hol_entry_help (entry, stream, &last_entry, &sep_groups);
+    hol_entry_help (entry, state, stream, &hhstate);
+
+  if (hhstate.suppressed_dup_arg && uparams.dup_args_note)
+    {
+      const char *tstr = dgettext (state->root_argp->argp_domain, "\
+Mandatory or optional arguments to long options are also mandatory or \
+optional for any corresponding short options.");
+      const char *fstr = filter_doc (tstr, ARGP_KEY_HELP_DUP_ARGS_NOTE,
+                                    state ? state->root_argp : 0, state);
+      if (fstr && *fstr)
+       {
+         __argp_fmtstream_putc (stream, '\n');
+         __argp_fmtstream_puts (stream, fstr);
+         __argp_fmtstream_putc (stream, '\n');
+       }
+      if (fstr && fstr != tstr)
+       free ((char *) fstr);
+    }
 }
 \f
 /* Helper functions for hol_usage.  */
@@ -896,10 +1230,11 @@ hol_help (struct hol *hol, argp_fmtstream_t stream)
 static int
 add_argless_short_opt (const struct argp_option *opt,
                       const struct argp_option *real,
-                      void *cookie)
+                      const char *domain, void *cookie)
 {
   char **snao_end = cookie;
-  if (! (opt->arg || real->arg))
+  if (!(opt->arg || real->arg)
+      && !((opt->flags | real->flags) & OPTION_NO_USAGE))
     *(*snao_end)++ = opt->key;
   return 0;
 }
@@ -909,29 +1244,26 @@ add_argless_short_opt (const struct argp_option *opt,
 static int
 usage_argful_short_opt (const struct argp_option *opt,
                        const struct argp_option *real,
-                       void *cookie)
+                       const char *domain, void *cookie)
 {
   argp_fmtstream_t stream = cookie;
   const char *arg = opt->arg;
+  int flags = opt->flags | real->flags;
 
   if (! arg)
     arg = real->arg;
 
-  if (arg)
+  if (arg && !(flags & OPTION_NO_USAGE))
     {
-      arg = _(arg);
+      arg = dgettext (domain, arg);
 
-      if ((opt->flags | real->flags) & OPTION_ARG_OPTIONAL)
+      if (flags & OPTION_ARG_OPTIONAL)
        __argp_fmtstream_printf (stream, " [-%c[%s]]", opt->key, arg);
       else
        {
          /* Manually do line wrapping so that it (probably) won't
             get wrapped at the embedded space.  */
-         if (__argp_fmtstream_point (stream) + 6 + strlen (arg)
-             >= __argp_fmtstream_rmargin (stream))
-           __argp_fmtstream_putc (stream, '\n');
-         else
-           __argp_fmtstream_putc (stream, ' ');
+         space (stream, 6 + strlen (arg));
          __argp_fmtstream_printf (stream, "[-%c %s]", opt->key, arg);
        }
     }
@@ -944,24 +1276,28 @@ usage_argful_short_opt (const struct argp_option *opt,
 static int
 usage_long_opt (const struct argp_option *opt,
                const struct argp_option *real,
-               void *cookie)
+               const char *domain, void *cookie)
 {
   argp_fmtstream_t stream = cookie;
   const char *arg = opt->arg;
+  int flags = opt->flags | real->flags;
 
   if (! arg)
     arg = real->arg;
 
-  if (arg)
+  if (! (flags & OPTION_NO_USAGE))
     {
-      arg = gettext (arg);
-      if ((opt->flags | real->flags) & OPTION_ARG_OPTIONAL)
-       __argp_fmtstream_printf (stream, " [--%s[=%s]]", opt->name, arg);
+      if (arg)
+       {
+         arg = dgettext (domain, arg);
+         if (flags & OPTION_ARG_OPTIONAL)
+           __argp_fmtstream_printf (stream, " [--%s[=%s]]", opt->name, arg);
+         else
+           __argp_fmtstream_printf (stream, " [--%s=%s]", opt->name, arg);
+       }
       else
-       __argp_fmtstream_printf (stream, " [--%s=%s]", opt->name, arg);
+       __argp_fmtstream_printf (stream, " [--%s]", opt->name);
     }
-  else
-    __argp_fmtstream_printf (stream, " [--%s]", opt->name);
 
   return 0;
 }
@@ -981,7 +1317,8 @@ hol_usage (struct hol *hol, argp_fmtstream_t stream)
       for (entry = hol->entries, nentries = hol->num_entries
           ; nentries > 0
           ; entry++, nentries--)
-       hol_entry_short_iterate (entry, add_argless_short_opt, &snao_end);
+       hol_entry_short_iterate (entry, add_argless_short_opt,
+                                entry->argp->argp_domain, &snao_end);
       if (snao_end > short_no_arg_opts)
        {
          *snao_end++ = 0;
@@ -992,13 +1329,15 @@ hol_usage (struct hol *hol, argp_fmtstream_t stream)
       for (entry = hol->entries, nentries = hol->num_entries
           ; nentries > 0
           ; entry++, nentries--)
-       hol_entry_short_iterate (entry, usage_argful_short_opt, stream);
+       hol_entry_short_iterate (entry, usage_argful_short_opt,
+                                entry->argp->argp_domain, stream);
 
       /* Finally, a list of long options (whew!).  */
       for (entry = hol->entries, nentries = hol->num_entries
           ; nentries > 0
           ; entry++, nentries--)
-       hol_entry_long_iterate (entry, usage_long_opt, stream);
+       hol_entry_long_iterate (entry, usage_long_opt,
+                               entry->argp->argp_domain, stream);
     }
 }
 \f
@@ -1008,7 +1347,7 @@ static struct hol *
 argp_hol (const struct argp *argp, struct hol_cluster *cluster)
 {
   const struct argp_child *child = argp->children;
-  struct hol *hol = make_hol (argp->options, cluster);
+  struct hol *hol = make_hol (argp, cluster);
   if (child)
     while (child->argp)
       {
@@ -1016,7 +1355,7 @@ argp_hol (const struct argp *argp, struct hol_cluster *cluster)
          ((child->group || child->header)
           /* Put CHILD->argp within its own cluster.  */
           ? hol_add_cluster (hol, child->group, child->header,
-                             child - argp->children, cluster)
+                             child - argp->children, cluster, argp)
           /* Just merge it into the parent's cluster.  */
           : cluster);
        hol_append (hol, argp_hol (child->argp, child_cluster)) ;
@@ -1049,56 +1388,56 @@ argp_args_levels (const struct argp *argp)
    updated by this routine for the next call if ADVANCE is true.  True is
    returned as long as there are more patterns to output.  */
 static int
-argp_args_usage (const struct argp *argp, char **levels, int advance,
-                argp_fmtstream_t stream)
+argp_args_usage (const struct argp *argp, const struct argp_state *state,
+                char **levels, int advance, argp_fmtstream_t stream)
 {
   char *our_level = *levels;
   int multiple = 0;
   const struct argp_child *child = argp->children;
-  const char *doc = _(argp->args_doc), *nl = 0;
+  const char *tdoc = dgettext (argp->argp_domain, argp->args_doc), *nl = 0;
+  const char *fdoc = filter_doc (tdoc, ARGP_KEY_HELP_ARGS_DOC, argp, state);
 
-  if (doc)
+  if (fdoc)
     {
-      nl = strchr (doc, '\n');
-      if (nl)
+      const char *cp = fdoc;
+      nl = __strchrnul (cp, '\n');
+      if (*nl != '\0')
        /* This is a `multi-level' args doc; advance to the correct position
           as determined by our state in LEVELS, and update LEVELS.  */
        {
          int i;
          multiple = 1;
          for (i = 0; i < *our_level; i++)
-           doc = nl + 1, nl = strchr (doc, '\n');
+           cp = nl + 1, nl = __strchrnul (cp, '\n');
          (*levels)++;
        }
-      if (! nl)
-       nl = doc + strlen (doc);
 
       /* Manually do line wrapping so that it (probably) won't get wrapped at
         any embedded spaces.  */
-      if (__argp_fmtstream_point (stream) + 1 + nl - doc
-         >= __argp_fmtstream_rmargin (stream))
-       __argp_fmtstream_putc (stream, '\n');
-      else
-       __argp_fmtstream_putc (stream, ' ');
+      space (stream, 1 + nl - cp);
 
-      __argp_fmtstream_write (stream, doc, nl - doc);
+      __argp_fmtstream_write (stream, cp, nl - cp);
     }
+  if (fdoc && fdoc != tdoc)
+    free ((char *)fdoc);       /* Free user's modified doc string.  */
 
   if (child)
     while (child->argp)
-      advance = !argp_args_usage ((child++)->argp, levels, advance, stream);
+      advance = !argp_args_usage ((child++)->argp, state, levels, advance, stream);
 
   if (advance && multiple)
-    /* Need to increment our level.  */
-    if (*nl)
-      /* There's more we can do here.  */
-      {
-       (*our_level)++;
-       advance = 0;            /* Our parent shouldn't advance also. */
-      }
-    else if (*our_level > 0)
-      /* We had multiple levels, but used them up; reset to zero.  */
-      *our_level = 0;
+    {
+      /* Need to increment our level.  */
+      if (*nl)
+       /* There's more we can do here.  */
+       {
+         (*our_level)++;
+         advance = 0;          /* Our parent shouldn't advance also. */
+       }
+      else if (*our_level > 0)
+       /* We had multiple levels, but used them up; reset to zero.  */
+       *our_level = 0;
+    }
 
   return !advance;
 }
@@ -1109,48 +1448,100 @@ argp_args_usage (const struct argp *argp, char **levels, int advance,
    following the `\v' character (nothing for strings without).  Each separate
    bit of documentation is separated a blank line, and if PRE_BLANK is true,
    then the first is as well.  If FIRST_ONLY is true, only the first
-   occurance is output.  Returns true if anything was output.  */
+   occurrence is output.  Returns true if anything was output.  */
 static int
-argp_doc (const struct argp *argp, int post, int pre_blank, int first_only,
+argp_doc (const struct argp *argp, const struct argp_state *state,
+         int post, int pre_blank, int first_only,
          argp_fmtstream_t stream)
 {
-  const struct argp_child *child = argp->children;
-  const char *doc = argp->doc;
+  const char *text;
+  const char *inp_text;
+  void *input = 0;
   int anything = 0;
+  size_t inp_text_limit = 0;
+  const char *doc = dgettext (argp->argp_domain, argp->doc);
+  const struct argp_child *child = argp->children;
 
   if (doc)
     {
       char *vt = strchr (doc, '\v');
+      inp_text = post ? (vt ? vt + 1 : 0) : doc;
+      inp_text_limit = (!post && vt) ? (vt - doc) : 0;
+    }
+  else
+    inp_text = 0;
 
-      if (pre_blank && (vt || !post))
+  if (argp->help_filter)
+    /* We have to filter the doc strings.  */
+    {
+      if (inp_text_limit)
+       /* Copy INP_TEXT so that it's nul-terminated.  */
+       inp_text = __strndup (inp_text, inp_text_limit);
+      input = __argp_input (argp, state);
+      text =
+       (*argp->help_filter) (post
+                             ? ARGP_KEY_HELP_POST_DOC
+                             : ARGP_KEY_HELP_PRE_DOC,
+                             inp_text, input);
+    }
+  else
+    text = (const char *) inp_text;
+
+  if (text)
+    {
+      if (pre_blank)
        __argp_fmtstream_putc (stream, '\n');
 
-      if (vt)
-       if (post)
-         __argp_fmtstream_puts (stream, vt + 1);
-       else
-         __argp_fmtstream_write (stream, doc, vt - doc);
+      if (text == inp_text && inp_text_limit)
+       __argp_fmtstream_write (stream, inp_text, inp_text_limit);
       else
-       if (! post)
-         __argp_fmtstream_puts (stream, doc);
+       __argp_fmtstream_puts (stream, text);
+
       if (__argp_fmtstream_point (stream) > __argp_fmtstream_lmargin (stream))
        __argp_fmtstream_putc (stream, '\n');
 
       anything = 1;
     }
+
+  if (text && text != inp_text)
+    free ((char *) text);      /* Free TEXT returned from the help filter.  */
+  if (inp_text && inp_text_limit && argp->help_filter)
+    free ((char *) inp_text);  /* We copied INP_TEXT, so free it now.  */
+
+  if (post && argp->help_filter)
+    /* Now see if we have to output a ARGP_KEY_HELP_EXTRA text.  */
+    {
+      text = (*argp->help_filter) (ARGP_KEY_HELP_EXTRA, 0, input);
+      if (text)
+       {
+         if (anything || pre_blank)
+           __argp_fmtstream_putc (stream, '\n');
+         __argp_fmtstream_puts (stream, text);
+         free ((char *) text);
+         if (__argp_fmtstream_point (stream)
+             > __argp_fmtstream_lmargin (stream))
+           __argp_fmtstream_putc (stream, '\n');
+         anything = 1;
+       }
+    }
+
   if (child)
     while (child->argp && !(first_only && anything))
       anything |=
-       argp_doc ((child++)->argp, post, anything || pre_blank, first_only,
+       argp_doc ((child++)->argp, state,
+                 post, anything || pre_blank, first_only,
                  stream);
 
   return anything;
 }
 \f
-/* Output a usage message for ARGP to STREAM.  FLAGS are from the set
-   ARGP_HELP_*.  NAME is what to use wherever a `program name' is needed. */
-void __argp_help (const struct argp *argp, FILE *stream,
-                 unsigned flags, char *name)
+/* Output a usage message for ARGP to STREAM.  If called from
+   argp_state_help, STATE is the relevent parsing state.  FLAGS are from the
+   set ARGP_HELP_*.  NAME is what to use wherever a `program name' is
+   needed. */
+static void
+_help (const struct argp *argp, const struct argp_state *state, FILE *stream,
+       unsigned flags, char *name)
 {
   int anything = 0;            /* Whether we've output anything.  */
   struct hol *hol = 0;
@@ -1159,9 +1550,21 @@ void __argp_help (const struct argp *argp, FILE *stream,
   if (! stream)
     return;
 
-  fs = __argp_make_fmtstream (stream, 0, RMARGIN, 0);
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+  __flockfile (stream);
+#endif
+
+  if (! uparams.valid)
+    fill_in_uparams (state);
+
+  fs = __argp_make_fmtstream (stream, 0, uparams.rmargin, 0);
   if (! fs)
-    return;
+    {
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+      __funlockfile (stream);
+#endif
+      return;
+    }
 
   if (flags & (ARGP_HELP_USAGE | ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG))
     {
@@ -1186,21 +1589,28 @@ void __argp_help (const struct argp *argp, FILE *stream,
       do
        {
          int old_lm;
-         int old_wm = __argp_fmtstream_set_wmargin (fs, USAGE_INDENT);
+         int old_wm = __argp_fmtstream_set_wmargin (fs, uparams.usage_indent);
          char *levels = pattern_levels;
 
-         __argp_fmtstream_printf (fs, "%s %s",
-                                  first_pattern ? "Usage:" : "  or: ", name);
+         if (first_pattern)
+           __argp_fmtstream_printf (fs, "%s %s",
+                                    dgettext (argp->argp_domain, "Usage:"),
+                                    name);
+         else
+           __argp_fmtstream_printf (fs, "%s %s",
+                                    dgettext (argp->argp_domain, "  or: "),
+                                    name);
 
          /* We set the lmargin as well as the wmargin, because hol_usage
             manually wraps options with newline to avoid annoying breaks.  */
-         old_lm = __argp_fmtstream_set_lmargin (fs, USAGE_INDENT);
+         old_lm = __argp_fmtstream_set_lmargin (fs, uparams.usage_indent);
 
          if (flags & ARGP_HELP_SHORT_USAGE)
            /* Just show where the options go.  */
            {
              if (hol->num_entries > 0)
-               __argp_fmtstream_puts (fs, " [OPTION...]");
+               __argp_fmtstream_puts (fs, dgettext (argp->argp_domain,
+                                                    " [OPTION...]"));
            }
          else
            /* Actually print the options.  */
@@ -1209,7 +1619,7 @@ void __argp_help (const struct argp *argp, FILE *stream,
              flags |= ARGP_HELP_SHORT_USAGE; /* But only do so once.  */
            }
 
-         more_patterns = argp_args_usage (argp, &levels, 1, fs);
+         more_patterns = argp_args_usage (argp, state, &levels, 1, fs);
 
          __argp_fmtstream_set_wmargin (fs, old_wm);
          __argp_fmtstream_set_lmargin (fs, old_lm);
@@ -1223,13 +1633,13 @@ void __argp_help (const struct argp *argp, FILE *stream,
     }
 
   if (flags & ARGP_HELP_PRE_DOC)
-    anything |= argp_doc (argp, 0, 0, 1, fs);
+    anything |= argp_doc (argp, state, 0, 0, 1, fs);
 
   if (flags & ARGP_HELP_SEE)
     {
-      __argp_fmtstream_printf (fs,
-              "Try `%s --help' or `%s --usage' for more information.\n",
-              name, name);
+      __argp_fmtstream_printf (fs, dgettext (argp->argp_domain, "\
+Try `%s --help' or `%s --usage' for more information.\n"),
+                              name, name);
       anything = 1;
     }
 
@@ -1241,49 +1651,89 @@ void __argp_help (const struct argp *argp, FILE *stream,
        {
          if (anything)
            __argp_fmtstream_putc (fs, '\n');
-         hol_help (hol, fs);
+         hol_help (hol, state, fs);
          anything = 1;
        }
     }
 
   if (flags & ARGP_HELP_POST_DOC)
     /* Print any documentation strings at the end.  */
-    anything |= argp_doc (argp, 1, anything, 0, fs);
+    anything |= argp_doc (argp, state, 1, anything, 0, fs);
 
   if ((flags & ARGP_HELP_BUG_ADDR) && argp_program_bug_address)
     {
       if (anything)
        __argp_fmtstream_putc (fs, '\n');
-      __argp_fmtstream_printf (fs, "Report bugs to %s.\n", argp_program_bug_address);
+      __argp_fmtstream_printf (fs, dgettext (argp->argp_domain,
+                                            "Report bugs to %s.\n"),
+                              argp_program_bug_address);
       anything = 1;
     }
 
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+  __funlockfile (stream);
+#endif
+
   if (hol)
     hol_free (hol);
 
   __argp_fmtstream_free (fs);
 }
+\f
+/* Output a usage message for ARGP to STREAM.  FLAGS are from the set
+   ARGP_HELP_*.  NAME is what to use wherever a `program name' is needed. */
+void __argp_help (const struct argp *argp, FILE *stream,
+                 unsigned flags, char *name)
+{
+  _help (argp, 0, stream, flags, name);
+}
 #ifdef weak_alias
 weak_alias (__argp_help, argp_help)
 #endif
 
+#ifndef _LIBC
+char *__argp_basename (char *name)
+{
+  char *short_name = strrchr (name, '/');
+  return short_name ? short_name + 1 : name;
+}
+
+char *
+__argp_short_program_name (void)
+{
+# if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME
+  return program_invocation_short_name;
+# elif HAVE_DECL_PROGRAM_INVOCATION_NAME
+  return __argp_basename (program_invocation_name);
+# else
+  /* FIXME: What now? Miles suggests that it is better to use NULL,
+     but currently the value is passed on directly to fputs_unlocked,
+     so that requires more changes. */
+# if __GNUC__
+#  warning No reasonable value to return
+# endif /* __GNUC__ */
+  return "";
+# endif
+}
+#endif
+
 /* Output, if appropriate, a usage message for STATE to STREAM.  FLAGS are
    from the set ARGP_HELP_*.  */
 void
-__argp_state_help (struct argp_state *state, FILE *stream, unsigned flags)
+__argp_state_help (const struct argp_state *state, FILE *stream, unsigned flags)
 {
   if ((!state || ! (state->flags & ARGP_NO_ERRS)) && stream)
     {
       if (state && (state->flags & ARGP_LONG_ONLY))
        flags |= ARGP_HELP_LONG_ONLY;
 
-      __argp_help (state ? state->argp : 0, stream, flags,
-                  state ? state->name : program_invocation_name);
+      _help (state ? state->root_argp : 0, state, stream, flags,
+            state ? state->name : __argp_short_program_name ());
 
       if (!state || ! (state->flags & ARGP_NO_EXIT))
        {
          if (flags & ARGP_HELP_EXIT_ERR)
-           exit (1);
+           exit (argp_err_exit_status);
          if (flags & ARGP_HELP_EXIT_OK)
            exit (0);
        }
@@ -1297,7 +1747,7 @@ weak_alias (__argp_state_help, argp_state_help)
    by the program name and `:', to stderr, and followed by a `Try ... --help'
    message, then exit (1).  */
 void
-__argp_error (struct argp_state *state, const char *fmt, ...)
+__argp_error (const struct argp_state *state, const char *fmt, ...)
 {
   if (!state || !(state->flags & ARGP_NO_ERRS))
     {
@@ -1307,17 +1757,40 @@ __argp_error (struct argp_state *state, const char *fmt, ...)
        {
          va_list ap;
 
-         fputs (state ? state->name : program_invocation_name, stream);
-         putc (':', stream);
-         putc (' ', stream);
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+         __flockfile (stream);
+#endif
 
          va_start (ap, fmt);
+
+#ifdef _LIBC
+         char *buf;
+
+         if (vasprintf (&buf, fmt, ap) < 0)
+           buf = NULL;
+
+         __fxprintf (stream, "%s: %s\n",
+                     state ? state->name : __argp_short_program_name (), buf);
+
+         free (buf);
+#else
+         fputs_unlocked (state ? state->name : __argp_short_program_name (),
+                         stream);
+         putc_unlocked (':', stream);
+         putc_unlocked (' ', stream);
+
          vfprintf (stream, fmt, ap);
-         va_end (ap);
 
-         putc ('\n', stream);
+         putc_unlocked ('\n', stream);
+#endif
 
          __argp_state_help (state, stream, ARGP_HELP_STD_ERR);
+
+         va_end (ap);
+
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+         __funlockfile (stream);
+#endif
        }
     }
 }
@@ -1334,8 +1807,8 @@ weak_alias (__argp_error, argp_error)
    *parsing errors*, and the former is for other problems that occur during
    parsing but don't reflect a (syntactic) problem with the input.  */
 void
-__argp_failure (struct argp_state *state, int status, int errnum,
-             const char *fmt, ...)
+__argp_failure (const struct argp_state *state, int status, int errnum,
+               const char *fmt, ...)
 {
   if (!state || !(state->flags & ARGP_NO_ERRS))
     {
@@ -1343,30 +1816,72 @@ __argp_failure (struct argp_state *state, int status, int errnum,
 
       if (stream)
        {
-         fputs (state ? state->name : program_invocation_name, stream);
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+         __flockfile (stream);
+#endif
+
+#ifdef _LIBC
+         __fxprintf (stream, "%s",
+                     state ? state->name : __argp_short_program_name ());
+#else
+         fputs_unlocked (state ? state->name : __argp_short_program_name (),
+                         stream);
+#endif
 
          if (fmt)
            {
              va_list ap;
 
-             putc (':', stream);
-             putc (' ', stream);
-
              va_start (ap, fmt);
+#ifdef _LIBC
+             char *buf;
+
+             if (vasprintf (&buf, fmt, ap) < 0)
+               buf = NULL;
+
+             __fxprintf (stream, ": %s", buf);
+
+             free (buf);
+#else
+             putc_unlocked (':', stream);
+             putc_unlocked (' ', stream);
+
              vfprintf (stream, fmt, ap);
+#endif
+
              va_end (ap);
            }
 
          if (errnum)
            {
-             putc (':', stream);
-             putc (' ', stream);
+             char buf[200];
+
+#ifdef _LIBC
+             __fxprintf (stream, ": %s",
+                         __strerror_r (errnum, buf, sizeof (buf)));
+#else
+             putc_unlocked (':', stream);
+             putc_unlocked (' ', stream);
+# ifdef HAVE_STRERROR_R
+             fputs (__strerror_r (errnum, buf, sizeof (buf)), stream);
+# else
              fputs (strerror (errnum), stream);
+# endif
+#endif
            }
 
-         putc ('\n', stream);
+#ifdef USE_IN_LIBIO
+         if (_IO_fwide (stream, 0) > 0)
+           putwc_unlocked (L'\n', stream);
+         else
+#endif
+           putc_unlocked ('\n', stream);
+
+#if _LIBC || (HAVE_FLOCKFILE && HAVE_FUNLOCKFILE)
+         __funlockfile (stream);
+#endif
 
-         if (status)
+         if (status && (!state || !(state->flags & ARGP_NO_EXIT)))
            exit (status);
        }
     }