summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorAkim Demaille <akim.demaille@gmail.com>2019-04-30 08:01:14 +0200
committerAkim Demaille <akim.demaille@gmail.com>2019-06-21 18:35:01 +0200
commit5d00a2ec62270ded0438b5641a97b9ea0a1cd159 (patch)
tree2fb46e5cb4db958407e0302980d96e1177c67e79 /lib
parent13644a8180a084c4c682d54f27a2eaab01a92d7f (diff)
downloadgnulib-5d00a2ec62270ded0438b5641a97b9ea0a1cd159.tar.gz
argmatch: add support to generate the usage message
* lib/argmatch.c: Move some #includes and gettext support to... * lib/argmatch.h: here. (ARGMATCH_DEFINE_GROUP): New macro. * tests/test-argmatch.c (argmatch_backup_docs, argmatch_backup_args) (argmatch_backup_group): New. (CHECK): New. (main): Check argmatch_backup_value, argmatch_backup_xvalue, argmatch_backup_argument and argmatch_backup_usage. * modules/argmatch: We depend on c99. * doc/argmatch.texi (Recognizing Option Arguments): New. * doc/gnulib.texi: Use it.
Diffstat (limited to 'lib')
-rw-r--r--lib/argmatch.c4
-rw-r--r--lib/argmatch.h230
2 files changed, 226 insertions, 8 deletions
diff --git a/lib/argmatch.c b/lib/argmatch.c
index 7666ef4144..183a0ca941 100644
--- a/lib/argmatch.c
+++ b/lib/argmatch.c
@@ -29,12 +29,8 @@
#include <stdlib.h>
#include <string.h>
-#include "gettext.h"
-#define _(msgid) gettext (msgid)
-
#include "error.h"
#include "quotearg.h"
-#include "quote.h"
#include "getprogname.h"
#if USE_UNLOCKED_IO
diff --git a/lib/argmatch.h b/lib/argmatch.h
index 50de57f299..6f641aff4b 100644
--- a/lib/argmatch.h
+++ b/lib/argmatch.h
@@ -22,13 +22,21 @@
#ifndef ARGMATCH_H_
# define ARGMATCH_H_ 1
+# include <stdbool.h>
# include <stddef.h>
+# include <stdio.h>
+# include <string.h> /* memcmp */
+# include "gettext.h"
+# include "quote.h"
# include "verify.h"
-#ifdef __cplusplus
+# define _(Msgid) gettext (Msgid)
+# define N_(Msgid) (Msgid)
+
+# ifdef __cplusplus
extern "C" {
-#endif
+# endif
# define ARRAY_CARDINALITY(Array) (sizeof (Array) / sizeof *(Array))
@@ -104,8 +112,222 @@ char const *argmatch_to_argument (void const *value,
argmatch_to_argument (Value, Arglist, \
(void const *) (Vallist), sizeof *(Vallist))
-#ifdef __cplusplus
+# define ARGMATCH_DEFINE_GROUP(Name, Type) \
+ /* The type of the values of this group. */ \
+ typedef Type argmatch_##Name##_type; \
+ \
+ /* The size of the type of the values of this group. */ \
+ enum argmatch_##Name##_size_enum \
+ { \
+ argmatch_##Name##_size = sizeof (argmatch_##Name##_type) \
+ }; \
+ \
+ /* Documentation of this group. */ \
+ typedef struct \
+ { \
+ /* Argument (e.g., "simple"). */ \
+ const char const *arg; \
+ /* Documentation (e.g., N_("always make simple backups")). */ \
+ const char const *doc; \
+ } argmatch_##Name##_doc; \
+ \
+ /* Argument mapping of this group. */ \
+ typedef struct \
+ { \
+ /* Argument (e.g., "simple"). */ \
+ const char const *arg; \
+ /* Value (e.g., simple_backups). */ \
+ const argmatch_##Name##_type val; \
+ } argmatch_##Name##_arg; \
+ \
+ /* All the features of an argmatch group. */ \
+ typedef struct \
+ { \
+ const argmatch_##Name##_doc* docs; \
+ const argmatch_##Name##_arg* args; \
+ \
+ /* Printed before the usage message. */ \
+ const char *doc_pre; \
+ /* Printed after the usage message. */ \
+ const char *doc_post; \
+ } argmatch_##Name##_group_type; \
+ \
+ /* The structure the user must build. */ \
+ extern const argmatch_##Name##_group_type argmatch_##Name##_group; \
+ \
+ /* Print the documentation of this group. */ \
+ void argmatch_##Name##_usage (FILE *out); \
+ \
+ /* If nonnegative, the index I of ARG in ARGS, i.e, \
+ ARGS[I] == ARG. \
+ Return -1 for invalid argument, -2 for ambiguous argument. */ \
+ ptrdiff_t argmatch_##Name##_choice (const char *arg); \
+ \
+ /* A pointer to the corresponding value if it exists, or \
+ report an error and exit with failure if the argument was \
+ not recognized. */ \
+ const argmatch_##Name##_type* \
+ argmatch_##Name##_value (const char *context, const char *arg); \
+ \
+ /* The first argument in ARGS that matches this value, or NULL. */ \
+ const char * \
+ argmatch_##Name##_argument (const argmatch_##Name##_type *val); \
+ \
+ ptrdiff_t \
+ argmatch_##Name##_choice (const char *arg) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ size_t size = argmatch_##Name##_size; \
+ ptrdiff_t res = -1; /* Index of first nonexact match. */ \
+ bool ambiguous = false; /* Whether multiple nonexact match(es). */ \
+ size_t arglen = strlen (arg); \
+ \
+ /* Test all elements for either exact match or abbreviated \
+ matches. */ \
+ for (size_t i = 0; g->args[i].arg; i++) \
+ if (!strncmp (g->args[i].arg, arg, arglen)) \
+ { \
+ if (strlen (g->args[i].arg) == arglen) \
+ /* Exact match found. */ \
+ return i; \
+ else if (res == -1) \
+ /* First nonexact match found. */ \
+ res = i; \
+ else if (memcmp (&g->args[res].val, &g->args[i].val, size)) \
+ /* Second nonexact match found. */ \
+ /* There is a real ambiguity, or we could not \
+ disambiguate. */ \
+ ambiguous = true; \
+ } \
+ return ambiguous ? -2 : res; \
+ } \
+ \
+ const char * \
+ argmatch_##Name##_argument (const argmatch_##Name##_type *val) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ size_t size = argmatch_##Name##_size; \
+ for (size_t i = 0; g->args[i].arg; i++) \
+ if (!memcmp (val, &g->args[i].val, size)) \
+ return g->args[i].arg; \
+ return NULL; \
+ } \
+ \
+ /* List the valid values of this group. */ \
+ static void \
+ argmatch_##Name##_valid (FILE *out) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ size_t size = argmatch_##Name##_size; \
+ \
+ /* Try to put synonyms on the same line. Synonyms are expected \
+ to follow each other. */ \
+ fputs (_("Valid arguments are:"), out); \
+ for (int i = 0; g->args[i].arg; i++) \
+ if (i == 0 \
+ || memcmp (&g->args[i-1].val, &g->args[i].val, size)) \
+ fprintf (out, "\n - %s", quote (g->args[i].arg)); \
+ else \
+ fprintf (out, ", %s", quote (g->args[i].arg)); \
+ putc ('\n', out); \
+ } \
+ \
+ const argmatch_##Name##_type* \
+ argmatch_##Name##_value (const char *context, const char *arg) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ ptrdiff_t res = argmatch_##Name##_choice (arg); \
+ if (res < 0) \
+ { \
+ argmatch_invalid (context, arg, res); \
+ argmatch_##Name##_valid (stderr); \
+ argmatch_die (); \
+ } \
+ return &g->args[res].val; \
+ } \
+ \
+ /* The column in which the documentation is displayed. \
+ The leftmost possible, but no more than 20. */ \
+ static int \
+ argmatch_##Name##_doc_col (void) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ size_t size = argmatch_##Name##_size; \
+ int res = 0; \
+ for (int i = 0; g->docs[i].arg; ++i) \
+ { \
+ int col = 4; \
+ int ival = argmatch_##Name##_choice (g->docs[i].arg); \
+ if (ival < 0) \
+ /* Pseudo argument, display it. */ \
+ col += strlen (g->docs[i].arg); \
+ else \
+ /* Genuine argument, display it with its synonyms. */ \
+ for (int j = 0; g->args[j].arg; ++j) \
+ if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
+ col += (col == 4 ? 0 : 2) + strlen (g->args[j].arg); \
+ /* Ignore series that are too wide. */ \
+ if (col <= 20 && res <= col) \
+ res = col; \
+ } \
+ return res ? res : 20; \
+ } \
+ \
+ void \
+ argmatch_##Name##_usage (FILE *out) \
+ { \
+ const argmatch_##Name##_group_type *g = &argmatch_##Name##_group; \
+ size_t size = argmatch_##Name##_size; \
+ /* Width of the screen. Help2man does not seem to support \
+ arguments on several lines, so in that case pretend a very \
+ large width. */ \
+ const int screen_width = getenv ("HELP2MAN") ? INT_MAX : 80; \
+ if (g->doc_pre) \
+ fprintf (out, "%s\n", _(g->doc_pre)); \
+ int doc_col = argmatch_##Name##_doc_col (); \
+ for (int i = 0; g->docs[i].arg; ++i) \
+ { \
+ int col = 0; \
+ bool first = true; \
+ int ival = argmatch_##Name##_choice (g->docs[i].arg); \
+ if (ival < 0) \
+ /* Pseudo argument, display it. */ \
+ col += fprintf (out, " %s", g->docs[i].arg); \
+ else \
+ /* Genuine argument, display it with its synonyms. */ \
+ for (int j = 0; g->args[j].arg; ++j) \
+ if (! memcmp (&g->args[ival].val, &g->args[j].val, size)) \
+ { \
+ if (!first \
+ && screen_width < col + 2 + strlen (g->args[j].arg)) \
+ { \
+ fprintf (out, ",\n"); \
+ col = 0; \
+ first = true; \
+ } \
+ if (first) \
+ { \
+ col += fprintf (out, " "); \
+ first = false; \
+ } \
+ else \
+ col += fprintf (out, ","); \
+ col += fprintf (out, " %s", g->args[j].arg); \
+ } \
+ /* The doc. Separated by at least two spaces. */ \
+ if (doc_col < col + 2) \
+ { \
+ fprintf (out, "\n"); \
+ col = 0; \
+ } \
+ fprintf (out, "%*s%s\n", doc_col - col, "", _(g->docs[i].doc)); \
+ } \
+ if (g->doc_post) \
+ fprintf (out, "%s\n", _(g->doc_post)); \
+ }
+
+# ifdef __cplusplus
}
-#endif
+# endif
#endif /* ARGMATCH_H_ */