summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libshouldbeinlibc/argp-help.c471
1 files changed, 328 insertions, 143 deletions
diff --git a/libshouldbeinlibc/argp-help.c b/libshouldbeinlibc/argp-help.c
index 20a51bc0..4f68bdf3 100644
--- a/libshouldbeinlibc/argp-help.c
+++ b/libshouldbeinlibc/argp-help.c
@@ -111,6 +111,8 @@ find_char (char ch, char *beg, char *end)
return 0;
}
+struct hol_cluster; /* fwd decl */
+
struct hol_entry
{
/* First option. */
@@ -130,23 +132,59 @@ struct hol_entry
1, 2, ..., n, 0, -m, ..., -2, -1
and then alphabetically within each group. The default is 0. */
int group;
+
+ /* The cluster of options this entry belongs to, or 0 if none. */
+ struct hol_cluster *cluster;
+};
+
+/* A cluster of entries to reflect the argp tree structure. */
+struct hol_cluster
+{
+ /* A descriptive header printed before options in this 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
+ list. */
+ int index;
+
+ /* How to sort this cluster with respect to options and other clusters at the
+ same depth (clusters always follow options in the same group). */
+ int group;
+
+ /* The cluster to which this cluster belongs, or 0 if it's at the base
+ level. */
+ struct hol_cluster *parent;
+
+ /* The distance this cluster is from the root. */
+ int depth;
+
+ /* Clusters in a given hol are kept in a linked list, to make freeing them
+ possible. */
+ struct hol_cluster *next;
};
/* A list of options for help. */
struct hol
{
+ /* An array of hol_entry's. */
+ struct hol_entry *entries;
/* The number of entries in this hol. If this field is zero, the others
are undefined. */
unsigned num_entries;
- /* An array of hol_entry's. */
- struct hol_entry *entries;
+
/* A string containing all short options in this HOL. Each entry contains
pointers into this string, so the order can't be messed with blindly. */
char *short_options;
+
+ /* Clusters of entries in this hol. */
+ struct hol_cluster *clusters;
};
-/* Create a struct hol from an array of struct argp_option. */
-struct hol *make_hol (const struct argp_option *opt)
+/* Create a struct hol from an array of struct argp_option. CLUSTER is the
+ hol_cluster in which these entries occur, or 0, if at the root. */
+struct hol *make_hol (const struct argp_option *opt,
+ struct hol_cluster *cluster)
{
char *so;
const struct argp_option *o;
@@ -187,6 +225,7 @@ struct hol *make_hol (const struct argp_option *opt)
entry->num = 0;
entry->short_options = so;
entry->group = cur_group = o->group ?: cur_group;
+ entry->cluster = cluster;
do
{
@@ -203,16 +242,48 @@ struct hol *make_hol (const struct argp_option *opt)
return hol;
}
+
+/* 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. */
+static struct hol_cluster *
+hol_add_cluster (struct hol *hol, int group, const char *header, int index,
+ struct hol_cluster *parent)
+{
+ struct hol_cluster *cl = malloc (sizeof (struct hol_cluster));
+ if (cl)
+ {
+ cl->group = group;
+ cl->header = header;
+
+ cl->index = index;
+ cl->parent = parent;
+ cl->next = hol->clusters;
+ hol->clusters = cl;
+ }
+ return cl;
+}
+
/* Free HOL and any resources it uses. */
static void
hol_free (struct hol *hol)
{
+ struct hol_cluster *cl = hol->clusters;
+
+ while (cl)
+ {
+ struct hol_cluster *next = cl->next;
+ free (cl);
+ cl = next;
+ }
+
if (hol->num_entries > 0)
{
free (hol->entries);
free (hol->short_options);
}
+
free (hol);
}
@@ -317,6 +388,115 @@ hol_set_group (struct hol *hol, char *name, int group)
if (entry)
entry->group = group;
}
+
+/* Order by group: 1, 2, ..., n, 0, -m, ..., -2, -1.
+ EQ is what to return if GROUP1 and GROUP2 are the same. */
+static int
+group_cmp (int group1, int group2, int eq)
+{
+ if (group1 == group2)
+ return eq;
+ else if ((group1 < 0 && group2 < 0) || (group1 > 0 && group2 > 0))
+ return group1 - group2;
+ else
+ return group2 - group1;
+}
+
+/* Compare clusters CL1 & CL2 by the order that they should appear in
+ output. */
+static int
+hol_cluster_cmp (const struct hol_cluster *cl1, const struct hol_cluster *cl2)
+{
+ /* If one cluster is deeper than the other, use its ancestor at the same
+ level, so that finding the common ancestor is straightforward. */
+ while (cl1->depth < cl2->depth)
+ cl1 = cl1->parent;
+ while (cl2->depth < cl1->depth)
+ cl2 = cl2->parent;
+
+ /* Now reduce both clusters to their ancestors at the point where both have
+ a common parent; these can be directly compared. */
+ while (cl1->parent != cl2->parent)
+ cl1 = cl1->parent, cl2 = cl2->parent;
+
+ return group_cmp (cl1->group, cl2->group, cl2->index - cl1->index);
+}
+
+/* Return the ancestor of CL that's just below the root (i.e., has a parent
+ of 0). */
+static struct hol_cluster *
+hol_cluster_base (struct hol_cluster *cl)
+{
+ while (cl->parent)
+ cl = cl->parent;
+ return cl;
+}
+
+/* Return true if CL1 is a child of CL2. */
+static int
+hol_cluster_is_child (const struct hol_cluster *cl1,
+ const struct hol_cluster *cl2)
+{
+ while (cl1 && cl1 != cl2)
+ cl1 = cl1->parent;
+ return cl1 == cl2;
+}
+
+/* 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)
+{
+ /* 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);
+ else if (group1 == group2)
+ /* The entries are both in the same cluster and group, so compare them
+ alphabetically. */
+ {
+ int short1 = hol_entry_first_short (entry1);
+ int short2 = hol_entry_first_short (entry2);
+ const char *long1 = hol_entry_first_long (entry1);
+ const char *long2 = hol_entry_first_long (entry2);
+
+ if (!short1 && !short2 && long1 && long2)
+ /* Only long options. */
+ return strcasecmp (long1, long2);
+ else
+ /* Compare short/short, long/short, short/long, using the first
+ character of long options. Entries without *any* valid
+ options (such as options with OPTION_HIDDEN set) will be put
+ first, but as they're not displayed, it doesn't matter where
+ they are. */
+ {
+ char first1 = short1 ?: long1 ? *long1 : 0;
+ char first2 = short2 ?: long2 ? *long2 : 0;
+ /* Compare ignoring case, except when the options are both the
+ same letter, in which case lower-case always comes first. */
+ return (tolower (first1) - tolower (first2)) ?: first2 - first1;
+ }
+ }
+ else
+ /* Within the same cluster, but not the same group, so just compare
+ groups. */
+ return group_cmp (group1, group2, 0);
+}
/* Sort HOL by group and alphabetically by option name (with short options
taking precedence over long). Since the sorting is for display purposes
@@ -324,47 +504,12 @@ hol_set_group (struct hol *hol, char *name, int group)
static void
hol_sort (struct hol *hol)
{
- int entry_cmp (const void *entry1_v, const void *entry2_v)
+ int cmp (const void *entry1_v, const void *entry2_v)
{
- const struct hol_entry *entry1 = entry1_v, *entry2 = entry2_v;
- int group1 = entry1->group, group2 = entry2->group;
-
- if (group1 == group2)
- /* Normal comparison. */
- {
- int short1 = hol_entry_first_short (entry1);
- int short2 = hol_entry_first_short (entry2);
- const char *long1 = hol_entry_first_long (entry1);
- const char *long2 = hol_entry_first_long (entry2);
-
- if (!short1 && !short2 && long1 && long2)
- /* Only long options. */
- return strcasecmp (long1, long2);
- else
- /* Compare short/short, long/short, short/long, using the first
- character of long options. Entries without *any* valid
- options (such as options with OPTION_HIDDEN set) will be put
- first, but as they're not displayed, it doesn't matter where
- they are. */
- {
- char first1 = short1 ?: long1 ? *long1 : 0;
- char first2 = short2 ?: long2 ? *long2 : 0;
- /* Compare ignoring case, except when the options are both the
- same letter, in which case lower-case always comes first. */
- return (tolower (first1) - tolower (first2)) ?: first2 - first1;
- }
- }
- else
- /* Order by group: 1, 2, ..., n, 0, -m, ..., -2, -1 */
- if ((group1 < 0 && group2 < 0) || (group1 > 0 && group2 > 0))
- return group1 - group2;
- else
- return group2 - group1;
+ return hol_entry_cmp (entry1_v, entry2_v);
}
-
if (hol->num_entries > 0)
- qsort (hol->entries, hol->num_entries, sizeof (struct hol_entry),
- entry_cmp);
+ qsort (hol->entries, hol->num_entries, sizeof (struct hol_entry), cmp);
}
/* Append MORE to HOL, destroying MORE in the process. Options in HOL shadow
@@ -372,78 +517,85 @@ hol_sort (struct hol *hol)
static void
hol_append (struct hol *hol, struct hol *more)
{
- if (more->num_entries == 0)
- hol_free (more);
- else if (hol->num_entries == 0)
- {
- hol->num_entries = more->num_entries;
- hol->entries = more->entries;
- hol->short_options = more->short_options;
- /* We've stolen everything MORE from more. Destroy the empty shell. */
- free (more);
- }
- 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);
-
- bcopy (hol->entries, entries,
- hol->num_entries * sizeof (struct hol_entry));
- bcopy (more->entries, entries + hol->num_entries,
- more->num_entries * sizeof (struct hol_entry));
-
- bcopy (hol->short_options, 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;
+ struct hol_cluster **cl_end = &hol->clusters;
- e->short_options = so;
+ /* Steal MORE's cluster list, and add it to the end of HOL's. */
+ while (*cl_end)
+ cl_end = &(*cl_end)->next;
+ *cl_end = more->clusters;
+ more->clusters = 0;
- 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++;
- }
- }
- }
+ /* 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);
+
+ bcopy (hol->entries, entries,
+ hol->num_entries * sizeof (struct hol_entry));
+ bcopy (more->entries, entries + hol->num_entries,
+ more->num_entries * sizeof (struct hol_entry));
+
+ bcopy (hol->short_options, 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;
- *so = '\0';
+ e->short_options = so;
- free (hol->entries);
- free (hol->short_options);
+ 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';
- hol_free (more);
- }
+ free (hol->entries);
+ free (hol->short_options);
+
+ hol->entries = entries;
+ hol->num_entries = num_entries;
+ hol->short_options = short_options;
+ }
+
+ hol_free (more);
}
/* Inserts enough spaces to make sure STREAM is at column COL. */
@@ -455,7 +607,7 @@ indent_to (FILE *stream, unsigned col)
putc (' ', stream);
}
-/* Print help for ENTRY to STREAM. *LAST_ENTRY should contain the last entry
+/* 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
@@ -467,9 +619,31 @@ hol_entry_help (struct hol_entry *entry, FILE *stream,
unsigned num;
int first = 1; /* True if nothing's been printed so far. */
const struct argp_option *real = entry->opt, *opt;
+ const struct hol_entry *pe = prev_entry ? *prev_entry : 0;
+ const struct hol_cluster *cl = entry->cluster;
char *so = entry->short_options;
int old_lm = line_wrap_set_lmargin (stream, 0);
- int old_wm = line_wrap_set_wmargin (stream, 0);
+ int old_wm = line_wrap_wmargin (stream);
+
+ /* 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
+ that the previous wrap margin isn't restored, but the left margin is reset
+ to 0. */
+ void print_header (const char *str)
+ {
+ if (*str)
+ {
+ if (pe)
+ putc ('\n', stream); /* Precede with a blank line. */
+ indent_to (stream, HEADER_COL);
+ line_wrap_set_lmargin (stream, HEADER_COL);
+ line_wrap_set_wmargin (stream, HEADER_COL);
+ fputs (str, stream);
+ line_wrap_set_lmargin (stream, 0);
+ }
+ if (sep_groups)
+ *sep_groups = 1; /* Separate subsequent groups. */
+ }
/* 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. */
@@ -477,10 +651,20 @@ hol_entry_help (struct hol_entry *entry, FILE *stream,
{
if (first)
{
- if (sep_groups && *sep_groups
- && prev_entry && *prev_entry
- && entry->group != (*prev_entry)->group)
+ if (sep_groups && *sep_groups && pe && entry->group != pe->group)
putc ('\n', stream);
+ if (pe && cl && pe->cluster != cl && cl->header && *cl->header
+ && !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 = line_wrap_wmargin (stream);
+ print_header (cl->header);
+ putc ('\n', stream);
+ line_wrap_set_wmargin (stream, old_wm);
+ }
first = 0;
}
else
@@ -531,19 +715,7 @@ hol_entry_help (struct hol_entry *entry, FILE *stream,
/* Didn't print any switches, what's up? */
if (!oshort (real) && !real->name && real->doc)
/* This is a group header, print it nicely. */
- {
- if (*real->doc)
- {
- if (prev_entry && *prev_entry)
- putc ('\n', stream); /* Precede with a blank line. */
- indent_to (stream, HEADER_COL);
- line_wrap_set_lmargin (stream, HEADER_COL);
- line_wrap_set_wmargin (stream, HEADER_COL);
- fputs (real->doc, stream);
- }
- if (sep_groups)
- *sep_groups = 1; /* Separate subsequent groups. */
- }
+ print_header (real->doc);
else
/* Just a totally shadowed option or null header; print nothing. */
goto cleanup; /* Just return, after cleaning up. */
@@ -564,9 +736,11 @@ hol_entry_help (struct hol_entry *entry, FILE *stream,
indent_to (stream, OPT_DOC_COL);
fputs (doc, stream);
+
+ /* Reset the left margin. */
+ line_wrap_set_lmargin (stream, 0);
}
- line_wrap_set_lmargin (stream, 0); /* Don't follow the nl with spaces. */
putc ('\n', stream);
if (prev_entry)
@@ -674,15 +848,26 @@ hol_usage (struct hol *hol, FILE *stream)
}
}
-/* Make a HOL containing all levels of options in ARGP. */
+/* Make a HOL containing all levels of options in ARGP. CLUSTER is the
+ cluster in which ARGP's entries should be clustered, or 0. */
static struct hol *
-argp_hol (const struct argp *argp)
+argp_hol (const struct argp *argp, struct hol_cluster *cluster)
{
- const struct argp **children = argp->children;
- struct hol *hol = make_hol (argp->options);
- if (children)
- while (*children)
- hol_append (hol, argp_hol (*children++));
+ const struct argp_child *child = argp->children;
+ struct hol *hol = make_hol (argp->options, cluster);
+ if (child)
+ while (child->argp)
+ {
+ struct hol_cluster *child_cluster =
+ ((child->group || child->header)
+ /* Put CHILD->argp within its own cluster. */
+ ? hol_add_cluster (hol, child->group, child->header,
+ child - argp->children, cluster)
+ /* Just merge it into the parent's cluster. */
+ : cluster);
+ hol_append (hol, argp_hol (child->argp, child_cluster)) ;
+ child++;
+ }
return hol;
}
@@ -691,7 +876,7 @@ argp_hol (const struct argp *argp)
static void
argp_args_usage (const struct argp *argp, FILE *stream)
{
- const struct argp **children = argp->children;
+ const struct argp_child *child = argp->children;
const char *doc = argp->args_doc;
if (doc)
{
@@ -704,9 +889,9 @@ argp_args_usage (const struct argp *argp, FILE *stream)
putc (' ', stream);
fputs (doc, stream);
}
- if (children)
- while (*children)
- argp_args_usage (*children++, stream);
+ if (child)
+ while (child->argp)
+ argp_args_usage ((child++)->argp, stream);
}
/* Print the documentation for ARGP to STREAM; if POST is false, then
@@ -720,7 +905,7 @@ static int
argp_doc (const struct argp *argp, int post, int pre_blank, int first_only,
FILE *stream)
{
- const struct argp **children = argp->children;
+ const struct argp_child *child = argp->children;
const char *doc = argp->doc;
int anything = 0;
@@ -744,10 +929,10 @@ argp_doc (const struct argp *argp, int post, int pre_blank, int first_only,
anything = 1;
}
- if (children)
- while (*children && !(first_only && anything))
+ if (child)
+ while (child->argp && !(first_only && anything))
anything |=
- argp_doc (*children++, post, anything || pre_blank, first_only,
+ argp_doc ((child++)->argp, post, anything || pre_blank, first_only,
stream);
return anything;
@@ -769,7 +954,7 @@ void argp_help (const struct argp *argp, FILE *stream,
if (flags & (ARGP_HELP_USAGE | ARGP_HELP_SHORT_USAGE | ARGP_HELP_LONG))
{
- hol = argp_hol (argp);
+ hol = argp_hol (argp, 0);
/* If present, these options always come last. */
hol_set_group (hol, "help", -1);