Hey Bruno, > Le 19 mai 2019 à 14:14, Akim Demaille <akim.demai...@gmail.com> a écrit : > >> Le 19 mai 2019 à 14:01, Bruno Haible <br...@clisp.org> a écrit : > >> So I would prefer a macro with one argument, the type of the first field. > > That sounds good.
Well, it is still quite messy. I had hope to keep the implementation in argmatch.c, but there are too many problems with tracking the offset of the different members of the structure. Eventually, I went for something much simpler, much safer, and easier to maintain. What do you think of something like this? commit 4cca4c589eaf17756a779ab91776bc68315edf4e Author: Akim Demaille <akim.demai...@gmail.com> Date: Tue Apr 30 08:01:14 2019 +0200 WIP: argmatch: provide support for documentation diff --git a/lib/argmatch.h b/lib/argmatch.h index 50de57f29..5607def49 100644 --- a/lib/argmatch.h +++ b/lib/argmatch.h @@ -23,6 +23,7 @@ # define ARGMATCH_H_ 1 # include <stddef.h> +# include <stdio.h> # include "verify.h" @@ -104,6 +105,89 @@ char const *argmatch_to_argument (void const *value, argmatch_to_argument (Value, Arglist, \ (void const *) (Vallist), sizeof *(Vallist)) +/* A type that groups together all the feature of an argmatch group. */ +# define ARGMATCH_DEFINE_GROUP(Name, Type) \ + typedef Type argmatch_##Name##_type; \ + \ + typedef enum \ + { \ + size = sizeof (argmatch_##Name##_type) \ + } argmatch_##Name##_size; \ + \ + typedef struct \ + { \ + /* Value that we document. */ \ + const argmatch_##Name##_type val; \ + /* The documentation of each documented value. \ + This dictates the order in which values are documented. \ + docs[i] documents doc_vals[i]. */ \ + const char const *doc; \ + } argmatch_##Name##_doc; \ + \ + typedef struct \ + { \ + /* Arguments denoting a value. */ \ + const char const *arg; \ + /* vals[i] is the value for args[i]. */ \ + const argmatch_##Name##_type val; \ + } argmatch_##Name##_arg; \ + \ + typedef struct \ + { \ + /* Printed before the usage message. */ \ + const char *doc_pre; \ + /* Printed after the usage message. */ \ + const char *doc_post; \ + \ + const argmatch_##Name##_doc* docs; \ + const argmatch_##Name##_arg* args; \ + } argmatch_##Name##_group_type; \ + \ + static inline void \ + argmatch_usage (const argmatch_##Name##_group_type *g, FILE *out) \ + { \ + /* 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)); \ + for (int i = 0; g->docs[i].doc; ++i) \ + { \ + int col = 0; \ + bool first = true; \ + for (int j = 0; g->args[j].arg; ++j) \ + if (! memcmp (&g->docs[i].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. Must be on column 20 separated by at least two \ + spaces. */ \ + if (20 < col + 2) \ + { \ + fprintf (out, "\n"); \ + col = 0; \ + } \ + fprintf (out, "%*s%s\n", 20 - col, "", _(g->docs[i].doc)); \ + } \ + if (g->doc_post) \ + fprintf (out, "%s\n", _(g->doc_post)); \ + } + #ifdef __cplusplus } #endif diff --git a/tests/test-argmatch.c b/tests/test-argmatch.c index 9335adf55..de28dd1e2 100644 --- a/tests/test-argmatch.c +++ b/tests/test-argmatch.c @@ -21,10 +21,16 @@ #include "argmatch.h" +#include <gettext.h> +#include <stdbool.h> #include <stdlib.h> +#include <string.h> /* memcmp */ #include "macros.h" +#define _(Msgid) gettext (Msgid) +#define N_(Msgid) (Msgid) + /* Some packages define ARGMATCH_DIE and ARGMATCH_DIE_DECL in <config.h>, and thus must link with a definition of that function. Provide it here. */ #ifdef ARGMATCH_DIE_DECL @@ -59,6 +65,45 @@ static const enum backup_type backup_vals[] = numbered_backups, numbered_backups, numbered_backups }; +ARGMATCH_DEFINE_GROUP(backup, enum backup_type); + +static const argmatch_backup_doc argmatch_backup_docs[] = +{ + { no_backups, N_("never make backups (even if --backup is given)") }, + { simple_backups, N_("make numbered backups") }, + { numbered_existing_backups, N_("numbered if numbered backups exist, simple otherwise") }, + { numbered_backups, N_("always make simple backups") }, + { no_backups, NULL } +}; + +static const argmatch_backup_arg argmatch_backup_args[] = +{ + { "no", no_backups }, + { "none", no_backups }, + { "off", no_backups }, + { "simple", simple_backups }, + { "never", simple_backups }, + { "single", simple_backups }, + { "existing", numbered_existing_backups }, + { "nil", numbered_existing_backups }, + { "numbered-existing", numbered_existing_backups }, + { "numbered", numbered_backups }, + { "t", numbered_backups }, + { "newstyle", numbered_backups }, + { NULL, no_backups } +}; + +argmatch_backup_group_type argmatch_backup_group = +{ + N_("\ +The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.\n\ +The version control method may be selected via the --backup option or through\n\ +the VERSION_CONTROL environment variable. Here are the values:\n"), + NULL, + argmatch_backup_docs, + argmatch_backup_args +}; + int main (int argc, char *argv[]) { @@ -90,9 +135,11 @@ main (int argc, char *argv[]) /* Ambiguous abbreviated. */ ASSERT (ARGMATCH ("ne", backup_args, backup_vals) == -2); - /* Ambiguous abbreviated, but same value. */ + /* Ambiguous abbreviated, but same value ("single" and "simple"). */ ASSERT (ARGMATCH ("si", backup_args, backup_vals) == 3); ASSERT (ARGMATCH ("s", backup_args, backup_vals) == 3); + argmatch_usage (&argmatch_backup_group, stdout); + return 0; } When run, it gives: > $ /tmp/gnutest/gltests/test-argmatch > The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX. > The version control method may be selected via the --backup option or through > the VERSION_CONTROL environment variable. Here are the values: > > no, none, off never make backups (even if --backup is given) > simple, never, single > make numbered backups > existing, nil, numbered-existing > numbered if numbered backups exist, simple otherwise > numbered, t, newstyle > always make simple backups which is what help2man expects.