/* This file is part of GDBM, the GNU data base manager. Copyright (C) 2011-2023 Free Software Foundation, Inc. GDBM is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GDBM 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GDBM. If not, see . */ # include "autoconf.h" # include "gdbm.h" # include "gdbmapp.h" # include "gdbmdefs.h" # include # include # include # include # include # ifdef HAVE_GETOPT_H # include # endif static int argc; static char **argv; static struct gdbm_option *option_tab; static size_t option_count; static size_t option_max; static char *short_options; static size_t short_option_count; static size_t short_option_max; #ifdef HAVE_GETOPT_LONG static struct option *long_options; static size_t long_option_count; static size_t long_option_max; #endif #define OPT_USAGE -2 struct gdbm_option parseopt_default_options[] = { { 0, NULL, NULL, "" }, { 'h', "help", NULL, N_("give this help list") }, { 'V', "version", NULL, N_("print program version") }, { OPT_USAGE, "usage", NULL, N_("give a short usage message") }, { 0 } }; #define OPT_END(opt) \ ((opt)->opt_short == 0 && (opt)->opt_long == 0 && (opt)->opt_descr == NULL) #define IS_OPTION(opt) \ ((opt)->opt_short || (opt)->opt_long) #define IS_GROUP_HEADER(opt) \ (!IS_OPTION(opt) && (opt)->opt_descr) #define IS_VALID_SHORT_OPTION(opt) \ ((opt)->opt_short > 0 && (opt)->opt_short < 127 && \ isalnum ((opt)->opt_short)) #define IS_VALID_LONG_OPTION(opt) \ ((opt)->opt_long != NULL) static int optcmp (const void *a, const void *b) { struct gdbm_option const *ap = (struct gdbm_option const *)a; struct gdbm_option const *bp = (struct gdbm_option const *)b; while (ap->opt_flags & PARSEOPT_ALIAS) ap--; while (bp->opt_flags & PARSEOPT_ALIAS) bp--; if (IS_VALID_SHORT_OPTION(ap) && IS_VALID_SHORT_OPTION(bp)) return ap->opt_short - bp->opt_short; if (IS_VALID_LONG_OPTION(ap) && IS_VALID_LONG_OPTION(bp)) return strcmp (ap->opt_long, bp->opt_long); if (IS_VALID_LONG_OPTION(ap)) return 1; return -1; } static void sort_options (int start, int count) { qsort (option_tab + start, count, sizeof (option_tab[0]), optcmp); } static size_t sort_group (size_t start) { size_t i; for (i = start; i < option_count && !IS_GROUP_HEADER (&option_tab[i]); i++) ; sort_options (start, i - start); return i + 1; } static void sort_all_options (void) { size_t start; /* Ensure sane start of options. This is necessary because optcmp backs up until it finds an element with cleared PARSEOPT_ALIAS flag bit. */ option_tab[0].opt_flags &= PARSEOPT_ALIAS; for (start = 0; start < option_count; ) { if (IS_GROUP_HEADER (&option_tab[start])) start = sort_group (start + 1); else start = sort_group (start); } } static void add_options (struct gdbm_option *options) { size_t optcnt = 0; size_t argcnt = 0; size_t count = 0; struct gdbm_option *opt; for (opt = options; !OPT_END(opt); opt++) { count++; if (IS_OPTION(opt)) { optcnt++; if (opt->opt_arg) argcnt++; } } if (option_count + count + 1 > option_max) { option_max = option_count + count + 1; option_tab = erealloc (option_tab, sizeof (option_tab[0]) * option_max); } #ifdef HAVE_GETOPT_LONG if (long_option_count + optcnt + 1 > long_option_max) { long_option_max = long_option_count + optcnt + 1; long_options = erealloc (long_options, sizeof (long_options[0]) * long_option_max); } #endif if (short_option_count + optcnt + argcnt + 1 > short_option_max) { short_option_max = short_option_count + optcnt + argcnt + 1; short_options = erealloc (short_options, sizeof (short_options[0]) * short_option_max); } for (opt = options; !OPT_END(opt); opt++) { option_tab[option_count++] = *opt; if (!IS_OPTION (opt)) continue; if (IS_VALID_SHORT_OPTION (opt)) { short_options[short_option_count++] = opt->opt_short; if (opt->opt_arg) short_options[short_option_count++] = ':'; } #ifdef HAVE_GETOPT_LONG if (IS_VALID_LONG_OPTION (opt)) { long_options[long_option_count].name = opt->opt_long; long_options[long_option_count].has_arg = opt->opt_arg != NULL; long_options[long_option_count].flag = NULL; long_options[long_option_count].val = opt->opt_short; long_option_count++; } #endif } short_options[short_option_count] = 0; #ifdef HAVE_GETOPT_LONG memset (&long_options[long_option_count], 0, sizeof long_options[long_option_count]); #endif } void parseopt_free (void) { free (option_tab); option_tab = NULL; free (short_options); short_options = NULL; short_option_count = short_option_max = 0; #ifdef HAVE_GETOPT_LONG free (long_options); long_options = NULL; long_option_count = long_option_max = 0; #endif } int parseopt_first (int pc, char **pv, struct gdbm_option *opts) { parseopt_free (); add_options (opts); add_options (parseopt_default_options); opterr = 0; argc = pc; argv = pv; return parseopt_next (); } static unsigned short_opt_col = 2; static unsigned long_opt_col = 6; static unsigned doc_opt_col = 2; /* FIXME: Not used: there are no doc options in this implementation */ static unsigned header_col = 1; static unsigned opt_doc_col = 29; static unsigned usage_indent = 12; static unsigned rmargin = 79; static unsigned dup_args = 0; static unsigned dup_args_note = 1; enum usage_var_type { usage_var_column, usage_var_bool }; struct usage_var_def { char *name; unsigned *valptr; enum usage_var_type type; }; static struct usage_var_def usage_var[] = { { "short-opt-col", &short_opt_col, usage_var_column }, { "header-col", &header_col, usage_var_column }, { "opt-doc-col", &opt_doc_col, usage_var_column }, { "usage-indent", &usage_indent, usage_var_column }, { "rmargin", &rmargin, usage_var_column }, { "dup-args", &dup_args, usage_var_bool }, { "dup-args-note", &dup_args_note, usage_var_bool }, { "long-opt-col", &long_opt_col, usage_var_column }, { "doc-opt-col", &doc_opt_col, usage_var_column }, { NULL } }; static void set_usage_var (char const *text, char **end) { struct usage_var_def *p; int boolval = 1; char const *prog_name = parseopt_program_name ? parseopt_program_name : progname; size_t len = strcspn (text, ",="); char *endp; if (len > 3 && memcmp (text, "no-", 3) == 0) { text += 3; len -= 3; boolval = 0; } for (p = usage_var; p->name; p++) { if (strlen (p->name) == len && memcmp (p->name, text, len) == 0) break; } endp = (char*) text + len; if (p) { if (p->type == usage_var_bool) { if (*endp == '=') { if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("error in ARGP_HELP_FMT: improper usage of [no-]%s\n"), p->name); endp = strchr (text + len, ','); } else *p->valptr = boolval; } else if (*endp == '=') { unsigned long val; errno = 0; val = strtoul (text + len + 1, &endp, 10); if (errno || (*endp && *endp != ',')) { if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("error in ARGP_HELP_FMT: bad value for %s"), p->name); if (endp) { fprintf (stderr, _(" (near %s)"), endp); } fputc ('\n', stderr); } else if (val > UINT_MAX) { if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("error in ARGP_HELP_FMT: %s value is out of range\n"), p->name); } else *p->valptr = val; } else { if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("%s: ARGP_HELP_FMT parameter requires a value\n"), p->name); } } else { if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("%s: Unknown ARGP_HELP_FMT parameter\n"), text); } *end = endp; } static void init_usage_vars (void) { char *fmt, *p; fmt = getenv ("ARGP_HELP_FMT"); if (!fmt || !*fmt) return; while (1) { set_usage_var (fmt, &p); if (*p == 0) break; else if (*p == ',') p++; else { char const *prog_name = parseopt_program_name ? parseopt_program_name : progname; if (prog_name) fprintf (stderr, "%s: ", prog_name); fprintf (stderr, _("ARGP_HELP_FMT: missing delimiter near %s\n"), p); break; } fmt = p; } } char *parseopt_program_name; const char *program_bug_address = "<" PACKAGE_BUGREPORT ">"; void (*parseopt_help_hook) (FILE *stream); static int argsused; static int print_arg (WORDWRAP_FILE wf, struct gdbm_option *opt, int delim) { if (opt->opt_arg) { argsused = 1; return wordwrap_printf (wf, "%c%s", delim, opt->opt_arg[0] ? gettext (opt->opt_arg) : ""); } return 0; } size_t print_option (WORDWRAP_FILE wf, size_t num) { struct gdbm_option *opt = option_tab + num; size_t next, i; int delim; int w; if (IS_GROUP_HEADER (opt)) { wordwrap_set_left_margin (wf, header_col); wordwrap_set_right_margin (wf, rmargin); if (opt->opt_descr[0]) { wordwrap_putc (wf, '\n'); wordwrap_puts (wf, gettext (opt->opt_descr)); wordwrap_putc (wf, '\n'); } wordwrap_putc (wf, '\n'); return num + 1; } /* count aliases */ for (next = num + 1; next < option_count && option_tab[next].opt_flags & PARSEOPT_ALIAS; next++); if (opt->opt_flags & PARSEOPT_HIDDEN) return next; wordwrap_set_left_margin (wf, short_opt_col); w = 0; for (i = num; i < next; i++) { if (IS_VALID_SHORT_OPTION (&option_tab[i])) { if (w) wordwrap_write (wf, ", ", 2); wordwrap_printf (wf, "-%c", option_tab[i].opt_short); delim = ' '; if (dup_args) print_arg (wf, opt, delim); w = 1; } } #ifdef HAVE_GETOPT_LONG for (i = num; i < next; i++) { if (IS_VALID_LONG_OPTION (&option_tab[i])) { if (w) wordwrap_write (wf, ", ", 2); wordwrap_set_left_margin (wf, long_opt_col); w = 0; break; } } for (; i < next; i++) { if (IS_VALID_LONG_OPTION (&option_tab[i])) { if (w) wordwrap_write (wf, ", ", 2); wordwrap_printf (wf, "--%s", option_tab[i].opt_long); delim = '='; if (dup_args) print_arg (wf, opt, delim); w = 1; } } #endif if (!dup_args) print_arg (wf, opt, delim); wordwrap_set_left_margin (wf, opt_doc_col); if (opt->opt_descr[0]) wordwrap_puts (wf, gettext (opt->opt_descr)); return next; } void parseopt_print_help (void) { unsigned i; WORDWRAP_FILE wf; argsused = 0; init_usage_vars (); wf = wordwrap_fdopen (1); wordwrap_printf (wf, "%s %s [%s]... %s\n", _("Usage:"), parseopt_program_name ? parseopt_program_name : progname, _("OPTION"), (parseopt_program_args && parseopt_program_args[0]) ? gettext (parseopt_program_args) : ""); wordwrap_set_right_margin (wf, rmargin); if (parseopt_program_doc && parseopt_program_doc[0]) wordwrap_puts (wf, gettext (parseopt_program_doc)); wordwrap_para (wf); sort_all_options (); for (i = 0; i < option_count; ) { i = print_option (wf, i); } wordwrap_para (wf); #ifdef HAVE_GETOPT_LONG if (argsused && dup_args_note) { wordwrap_set_left_margin (wf, 0); wordwrap_set_right_margin (wf, rmargin); wordwrap_puts (wf, _("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options.")); wordwrap_para (wf); } #endif if (parseopt_help_hook) parseopt_help_hook (stdout);//FIXME wordwrap_set_left_margin (wf, 0); wordwrap_set_right_margin (wf, rmargin); /* TRANSLATORS: The placeholder indicates the bug-reporting address for this package. Please add _another line_ saying "Report translation bugs to <...>\n" with the address for translation bugs (typically your translation team's web or email address). */ wordwrap_printf (wf, _("Report bugs to %s.\n"), program_bug_address); #ifdef PACKAGE_URL wordwrap_printf (wf, _("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL); #endif } static int cmpidx_short (const void *a, const void *b) { unsigned const *ai = (unsigned const *)a; unsigned const *bi = (unsigned const *)b; return option_tab[*ai].opt_short - option_tab[*bi].opt_short; } #ifdef HAVE_GETOPT_LONG static int cmpidx_long (const void *a, const void *b) { unsigned const *ai = (unsigned const *)a; unsigned const *bi = (unsigned const *)b; struct gdbm_option const *ap = option_tab + *ai; struct gdbm_option const *bp = option_tab + *bi; return strcmp (ap->opt_long, bp->opt_long); } #endif void print_usage (void) { WORDWRAP_FILE wf; unsigned i; unsigned *idxbuf; unsigned nidx; init_usage_vars (); idxbuf = ecalloc (option_count, sizeof (idxbuf[0])); wf = wordwrap_fdopen (1); wordwrap_set_right_margin (wf, rmargin); wordwrap_printf (wf, "%s %s ", _("Usage:"), parseopt_program_name ? parseopt_program_name : progname); wordwrap_next_left_margin (wf, usage_indent); /* Print a list of short options without arguments. */ for (i = nidx = 0; i < option_count; i++) if (IS_VALID_SHORT_OPTION (&option_tab[i]) && !option_tab[i].opt_arg) idxbuf[nidx++] = i; if (nidx) { qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); wordwrap_puts (wf, "[-"); for (i = 0; i < nidx; i++) { wordwrap_putc (wf, option_tab[idxbuf[i]].opt_short); } wordwrap_putc (wf, ']'); } /* Print a list of short options with arguments. */ for (i = nidx = 0; i < option_count; i++) { if (IS_VALID_SHORT_OPTION (&option_tab[i]) && option_tab[i].opt_arg) idxbuf[nidx++] = i; } if (nidx) { qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); for (i = 0; i < nidx; i++) { struct gdbm_option *opt = option_tab + idxbuf[i]; const char *arg = gettext (opt->opt_arg); wordwrap_word_start (wf); wordwrap_puts (wf, " [-"); wordwrap_putc (wf, opt->opt_short); wordwrap_putc (wf, ' '); wordwrap_puts (wf, arg); wordwrap_putc (wf, ']'); wordwrap_word_end (wf); } } #ifdef HAVE_GETOPT_LONG /* Print a list of long options */ for (i = nidx = 0; i < option_count; i++) { if (IS_VALID_LONG_OPTION (&option_tab[i])) idxbuf[nidx++] = i; } if (nidx) { qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_long); for (i = 0; i < nidx; i++) { struct gdbm_option *opt = option_tab + idxbuf[i]; const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL; wordwrap_word_start (wf); wordwrap_write (wf, " [--", 4); wordwrap_puts (wf, opt->opt_long); if (opt->opt_arg) { wordwrap_putc (wf, '='); wordwrap_write (wf, arg, strlen (arg)); } wordwrap_putc (wf, ']'); wordwrap_word_end (wf); } } #endif wordwrap_close (wf); free (idxbuf); } const char version_etc_copyright[] = /* Do *not* mark this string for translation. First %s is a copyright symbol suitable for this locale, and second %s are the copyright years. */ "Copyright %s %s Free Software Foundation, Inc"; const char license_text[] = "License GPLv3+: GNU GPL version 3 or later \n" "This is free software: you are free to change and redistribute it.\n" "There is NO WARRANTY, to the extent permitted by law."; void print_version_only (void) { printf ("%s (%s) %s\n", parseopt_program_name ? parseopt_program_name : progname, PACKAGE_NAME, PACKAGE_VERSION); /* TRANSLATORS: Translate "(C)" to the copyright symbol (C-in-a-circle), if this symbol is available in the user's locale. Otherwise, do not translate "(C)"; leave it as-is. */ printf (version_etc_copyright, _("(C)"), "2011-2023"); putchar ('\n'); puts (license_text); putchar ('\n'); } static int handle_option (int c) { switch (c) { case 'h': parseopt_print_help (); exit (0); case 'V': print_version_only (); exit (0); case OPT_USAGE: print_usage (); exit (0); default: break; } return 0; } int parseopt_next (void) { int rc; do { #ifdef HAVE_GETOPT_LONG rc = getopt_long (argc, argv, short_options, long_options, NULL); #else rc = getopt (argc, argv, short_options); #endif } while (handle_option (rc)); return rc; }