diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-10-07 21:58:50 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2016-10-07 21:58:50 +0300 |
commit | 87ef8884693eb0cdcdafa2365c86709f33e3a375 (patch) | |
tree | 3988c862973ebb3b5764af6feefc77aae223133a /libmailutils | |
parent | 8b8f29eb6485e5ab09e2215d595fbbf45c27c97b (diff) | |
download | mailutils-87ef8884693eb0cdcdafa2365c86709f33e3a375.tar.gz mailutils-87ef8884693eb0cdcdafa2365c86709f33e3a375.tar.bz2 |
Add generic library for option parsing.
* configure.ac: Build libmailutils/opt
* include/mailutils/Makefile.am: Add opt.h
* include/mailutils/opt.h: New file.
* include/mailutils/util.h (mu_c_type_t): New datatype.
(mu_str_to_c): New proto.
* libmailutils/Makefile.am: Add opt to subdirs.
* libmailutils/opt/Makefile.am: New file.
* libmailutils/opt/help.c: New file.
* libmailutils/opt/opt.c: New file.
* libmailutils/opt/progname.c: New file.
* libmailutils/string/Makefile.am: Add new files.
* libmailutils/string/str_to_c.c: New file.
* libmailutils/string/to_sn.c: New file.
* libmailutils/string/to_un.c: New file.
* libmailutils/tests/.gitignore: Update.
* libmailutils/tests/Makefile.am: Update.
* libmailutils/tests/parseopt.c: New file.
Diffstat (limited to 'libmailutils')
-rw-r--r-- | libmailutils/Makefile.am | 3 | ||||
-rw-r--r-- | libmailutils/opt/Makefile.am | 26 | ||||
-rw-r--r-- | libmailutils/opt/help.c | 337 | ||||
-rw-r--r-- | libmailutils/opt/opt.c | 625 | ||||
-rw-r--r-- | libmailutils/opt/progname.c | 43 | ||||
-rw-r--r-- | libmailutils/string/Makefile.am | 5 | ||||
-rw-r--r-- | libmailutils/string/str_to_c.c | 274 | ||||
-rw-r--r-- | libmailutils/string/to_sn.c | 37 | ||||
-rw-r--r-- | libmailutils/string/to_un.c | 34 | ||||
-rw-r--r-- | libmailutils/tests/.gitignore | 1 | ||||
-rw-r--r-- | libmailutils/tests/Makefile.am | 1 | ||||
-rw-r--r-- | libmailutils/tests/parseopt.c | 101 |
12 files changed, 1485 insertions, 2 deletions
diff --git a/libmailutils/Makefile.am b/libmailutils/Makefile.am index 9d0c3bfe6..7a5270c4c 100644 --- a/libmailutils/Makefile.am +++ b/libmailutils/Makefile.am @@ -18,7 +18,7 @@ SUBDIRS = \ auth base address list sockaddr cidr cfg diag\ - filter mailbox mailer mime msgset server string stream stdstream\ + filter mailbox mailer mime msgset opt server string stream stdstream\ property url imapio datetime . tests lib_LTLIBRARIES = libmailutils.la @@ -42,6 +42,7 @@ libmailutils_la_LIBADD = \ mailer/libmailer.la\ mime/libmime.la\ msgset/libmsgset.la\ + opt/libopt.la\ property/libproperty.la\ server/libserver.la\ string/libstring.la\ diff --git a/libmailutils/opt/Makefile.am b/libmailutils/opt/Makefile.am new file mode 100644 index 000000000..d229c4552 --- /dev/null +++ b/libmailutils/opt/Makefile.am @@ -0,0 +1,26 @@ +# GNU Mailutils -- a suite of utilities for electronic mail +# Copyright (C) 2016 Free Software Foundation, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General +# Public License along with this library. If not, see +# <http://www.gnu.org/licenses/>. + +noinst_LTLIBRARIES = libopt.la + +libopt_la_SOURCES = \ + opt.c\ + help.c\ + progname.c + +AM_CPPFLAGS = \ + @MU_LIB_COMMON_INCLUDES@ -I/libmailutils diff --git a/libmailutils/opt/help.c b/libmailutils/opt/help.c new file mode 100644 index 000000000..7ffc43ff9 --- /dev/null +++ b/libmailutils/opt/help.c @@ -0,0 +1,337 @@ +/* help.c -- general-purpose command line option parser + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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. + + GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <mailutils/alloc.h> +#include <mailutils/opt.h> +#include <mailutils/cctype.h> +#include <mailutils/nls.h> + +#define LMARGIN 2 +#define DESCRCOLUMN 30 +#define RMARGIN 79 +#define GROUPCOLUMN 2 +#define USAGECOLUMN 13 + +static void +indent (size_t start, size_t col) +{ + for (; start < col; start++) + putchar (' '); +} + +static void +print_option_descr (const char *descr, size_t lmargin, size_t rmargin) +{ + while (*descr) + { + size_t s = 0; + size_t i; + size_t width = rmargin - lmargin; + + for (i = 0; ; i++) + { + if (descr[i] == 0 || descr[i] == ' ' || descr[i] == '\t') + { + if (i > width) + break; + s = i; + if (descr[i] == 0) + break; + } + } + fwrite (descr, 1, s, stdout); + fputc ('\n', stdout); + descr += s; + if (*descr) + { + indent (0, lmargin); + descr++; + } + } +} + + +static size_t +print_option (struct mu_option **optbuf, size_t optcnt, size_t num, + int *argsused) +{ + struct mu_option *opt = optbuf[num]; + size_t next, i; + int delim; + int w; + + if (MU_OPTION_IS_GROUP_HEADER (opt)) + { + if (num) + putchar ('\n'); + indent (0, GROUPCOLUMN); + print_option_descr (gettext (opt->opt_doc), GROUPCOLUMN, RMARGIN); + putchar ('\n'); + return num + 1; + } + + /* count aliases */ + for (next = num + 1; + next < optcnt && optbuf[next]->opt_flags & MU_OPTION_ALIAS; + next++); + + if (opt->opt_flags & MU_OPTION_HIDDEN) + return next; + + w = 0; + for (i = num; i < next; i++) + { + if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i])) + { + if (w == 0) + { + indent (0, LMARGIN); + w = LMARGIN; + } + else + w += printf (", "); + w += printf ("-%c", optbuf[i]->opt_short); + delim = ' '; + } + } + + for (i = num; i < next; i++) + { + if (MU_OPTION_IS_VALID_LONG_OPTION (optbuf[i])) + { + if (w == 0) + { + indent (0, LMARGIN); + w = LMARGIN; + } + else + w += printf (", "); + w += printf ("--%s", optbuf[i]->opt_long); + delim = '='; + } + } + + if (opt->opt_arg) + { + *argsused = 1; + w += printf ("%c%s", delim, gettext (opt->opt_arg)); + } + if (w >= DESCRCOLUMN) + { + putchar ('\n'); + w = 0; + } + indent (w, DESCRCOLUMN); + print_option_descr (gettext (opt->opt_doc), DESCRCOLUMN, RMARGIN); + + return next; +} + +void +mu_option_describe_options (struct mu_option **optbuf, size_t optcnt) +{ + unsigned i; + int argsused = 0; + + for (i = 0; i < optcnt; ) + i = print_option (optbuf, optcnt, i, &argsused); + putchar ('\n'); + + if (argsused) + { + print_option_descr (_("Mandatory or optional arguments to long options are also mandatory or optional for any corresponding short options."), 0, RMARGIN); + putchar ('\n'); + } +} + +void +mu_program_help (struct mu_parseopt *po) +{ + printf ("%s", _("Usage:")); + if (po->po_prog_name) + printf (" %s", po->po_prog_name); + printf (" [%s]...", _("OPTION")); + if (po->po_prog_args) + printf (" %s", gettext (po->po_prog_args)); + putchar ('\n'); + + if (po->po_prog_doc) + print_option_descr (gettext (po->po_prog_doc), 0, RMARGIN); + putchar ('\n'); + + mu_option_describe_options (po->po_optv, po->po_optc); + + if (po->po_help_hook) + po->po_help_hook (stdout); + + if (po->po_bug_address) + /* 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). */ + printf (_("Report bugs to %s.\n"), po->po_bug_address); + + if (po->po_package_name && po->po_package_url) + printf (_("%s home page: <%s>\n"), + po->po_package_name, po->po_package_url); +} + +static struct mu_option **option_tab; + +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; +} + +static int +cmpidx_long (const void *a, const void *b) +{ + unsigned const *ai = (unsigned const *)a; + unsigned const *bi = (unsigned const *)b; + struct mu_option const *ap = option_tab[*ai]; + struct mu_option const *bp = option_tab[*bi]; + return strcmp (ap->opt_long, bp->opt_long); +} + +void +mu_program_usage (struct mu_parseopt *po) +{ + unsigned i; + unsigned n; + char buf[RMARGIN+1]; + unsigned *idxbuf; + unsigned nidx; + + struct mu_option **optbuf = po->po_optv; + size_t optcnt = po->po_optc; + +#define FLUSH \ + do \ + { \ + buf[n] = 0; \ + printf ("%s\n", buf); \ + n = USAGECOLUMN; \ + memset (buf, ' ', n); \ + } \ + while (0) +#define ADDC(c) \ + do \ + { \ + if (n == RMARGIN) FLUSH; \ + buf[n++] = c; \ + } \ + while (0) + + option_tab = optbuf; + + idxbuf = mu_calloc (optcnt, sizeof (idxbuf[0])); + + n = snprintf (buf, sizeof buf, "%s %s ", _("Usage:"), mu_progname); + + /* Print a list of short options without arguments. */ + for (i = nidx = 0; i < optcnt; i++) + if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && !optbuf[i]->opt_arg) + idxbuf[nidx++] = i; + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); + + ADDC ('['); + ADDC ('-'); + for (i = 0; i < nidx; i++) + { + ADDC (optbuf[idxbuf[i]]->opt_short); + } + ADDC (']'); + } + + /* Print a list of short options with arguments. */ + for (i = nidx = 0; i < optcnt; i++) + { + if (MU_OPTION_IS_VALID_SHORT_OPTION (optbuf[i]) && optbuf[i]->opt_arg) + idxbuf[nidx++] = i; + } + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_short); + + for (i = 0; i < nidx; i++) + { + struct mu_option *opt = optbuf[idxbuf[i]]; + const char *arg = gettext (opt->opt_arg); + size_t len = 5 + strlen (arg) + 1; + + if (n + len > RMARGIN) FLUSH; + buf[n++] = ' '; + buf[n++] = '['; + buf[n++] = '-'; + buf[n++] = opt->opt_short; + buf[n++] = ' '; + strcpy (&buf[n], arg); + n += strlen (arg); + buf[n++] = ']'; + } + } + + /* Print a list of long options */ + for (i = nidx = 0; i < optcnt; i++) + { + if (MU_OPTION_IS_VALID_LONG_OPTION (optbuf[i])) + idxbuf[nidx++] = i; + } + + if (nidx) + { + qsort (idxbuf, nidx, sizeof (idxbuf[0]), cmpidx_long); + + for (i = 0; i < nidx; i++) + { + struct mu_option *opt = optbuf[idxbuf[i]]; + const char *arg = opt->opt_arg ? gettext (opt->opt_arg) : NULL; + size_t len = 3 + strlen (opt->opt_long) + + (arg ? 1 + strlen (arg) : 0); + if (n + len > RMARGIN) FLUSH; + buf[n++] = ' '; + buf[n++] = '['; + buf[n++] = '-'; + buf[n++] = '-'; + strcpy (&buf[n], opt->opt_long); + n += strlen (opt->opt_long); + if (opt->opt_arg) + { + buf[n++] = '='; + strcpy (&buf[n], arg); + n += strlen (arg); + } + buf[n++] = ']'; + } + } + + FLUSH; + free (idxbuf); +} + diff --git a/libmailutils/opt/opt.c b/libmailutils/opt/opt.c new file mode 100644 index 000000000..bec74cda7 --- /dev/null +++ b/libmailutils/opt/opt.c @@ -0,0 +1,625 @@ +/* opt.c -- general-purpose command line option parser + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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. + + GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <mailutils/alloc.h> +#include <mailutils/opt.h> +#include <mailutils/nls.h> +#include <mailutils/errno.h> + +#define EXIT_SUCCESS 0 +#define EXIT_ERROR 1 + +/* Compare pointers to two option structs */ +static int +optcmp (const void *a, const void *b) +{ + struct mu_option const *ap = *(struct mu_option const **)a; + struct mu_option const *bp = *(struct mu_option const **)b; + + while (ap->opt_flags & MU_OPTION_ALIAS) + ap--; + while (bp->opt_flags & MU_OPTION_ALIAS) + bp--; + + if (MU_OPTION_IS_VALID_SHORT_OPTION (ap) + && MU_OPTION_IS_VALID_SHORT_OPTION (bp)) + return ap->opt_short - bp->opt_short; + if (MU_OPTION_IS_VALID_LONG_OPTION (ap) + && MU_OPTION_IS_VALID_LONG_OPTION (bp)) + return strcmp (ap->opt_long, bp->opt_long); + if (MU_OPTION_IS_VALID_LONG_OPTION (ap)) + return 1; + return -1; +} + +/* Sort a group of options in OPTBUF, starting at index START (first + option slot after a group header (if any). The group spans up to + next group header or end of options */ +static size_t +sort_group (struct mu_option **optbuf, size_t start) +{ + size_t i; + + for (i = start; optbuf[i] && !MU_OPTION_IS_GROUP_HEADER (optbuf[i]); i++) + ; + + qsort (&optbuf[start], i - start, sizeof (optbuf[0]), optcmp); + return i; +} + +/* Print help summary and exit. */ +static void +fn_help (struct mu_parseopt *po, struct mu_option *opt, char const *unused) +{ + mu_program_help (po); + exit (EXIT_SUCCESS); +} + +/* Print usage summary and exit. */ +static void +fn_usage (struct mu_parseopt *po, struct mu_option *opt, char const *unused) +{ + mu_program_usage (po); + exit (EXIT_SUCCESS); +} + +/* Default options */ +struct mu_option mu_default_options[] = { + MU_OPTION_GROUP(""), + { "help", 'h', NULL, MU_OPTION_IMMEDIATE, N_("give this help list"), + mu_c_string, NULL, fn_help }, + { "version", 'V', NULL, MU_OPTION_IMMEDIATE, N_("print program version"), + mu_c_string, NULL, /* FIXME: fn_version */ }, + { "usage", 0, NULL, MU_OPTION_IMMEDIATE, N_("give a short usage message"), + mu_c_string, NULL, fn_usage + }, + MU_OPTION_END +}; + +/* Output error message */ +static void +parse_error (struct mu_parseopt *po, char const *fmt, ...) +{ + va_list ap; + + if (po->po_flags & MU_PARSEOPT_IGNORE_ERRORS) + return; + + if (po->po_prog_name) + fprintf (stderr, "%s: ", po->po_prog_name); + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); +} + +static void +mu_option_cache_destroy (void *ptr) +{ + struct mu_option_cache *cache = ptr; + free (cache->cache_arg); + free (cache); +} + +static int parseopt_apply (void *item, void *data); + +/* If OPT is an immediate option, evaluate it right away. Otherwise, + add option OPT with argument ARG to the cache in PO. */ +void +add_option_cache (struct mu_parseopt *po, struct mu_option *opt, + char const *arg) +{ + struct mu_option_cache *cache = mu_alloc (sizeof (*cache)); + cache->cache_opt = opt; + cache->cache_arg = arg ? mu_strdup (arg) : NULL; + + if (opt->opt_flags & MU_OPTION_IMMEDIATE) + { + parseopt_apply (cache, po); + mu_option_cache_destroy (cache); + } + else + { + mu_list_append (po->po_optlist, cache); + } +} + +/* Find first option for which I is an alias */ +struct mu_option * +option_unalias (struct mu_parseopt *po, int i) +{ + while (i > 0 && po->po_optv[i]->opt_flags & MU_OPTION_ALIAS) + --i; + return po->po_optv[i]; +} + +/* Find a descriptor of short option CHR */ +struct mu_option * +find_short_option (struct mu_parseopt *po, int chr) +{ + size_t i; + + for (i = 0; i < po->po_optc; i++) + { + if (MU_OPTION_IS_VALID_SHORT_OPTION (po->po_optv[i]) + && po->po_optv[i]->opt_short == chr) + return option_unalias (po, i); + } + parse_error (po, _("unrecognized option '-%c'"), chr); + return NULL; +} + +/* Find a descriptor of long option OPTSTR. If it has argument, return + it in *ARGPTR. */ +struct mu_option * +find_long_option (struct mu_parseopt *po, char const *optstr, + char **argptr) +{ + size_t i; + size_t optlen; + size_t ind; + int found = 0; + + optlen = strcspn (optstr, "="); + + for (i = 0; i < po->po_optc; i++) + { + if (MU_OPTION_IS_VALID_LONG_OPTION (po->po_optv[i]) + && optlen <= strlen (po->po_optv[i]->opt_long) + && memcmp (po->po_optv[i]->opt_long, optstr, optlen) == 0) + { + switch (found) + { + case 0: + ind = i; + found++; + break; + + case 1: + if (po->po_flags & MU_PARSEOPT_IGNORE_ERRORS) + return NULL; + parse_error (po, + _("option '--%*.*s' is ambiguous; possibilities:"), + optlen, optlen, optstr); + fprintf (stderr, "--%s\n", po->po_optv[ind]->opt_long); + found++; + + case 2: + fprintf (stderr, "--%s\n", po->po_optv[i]->opt_long); + } + } + } + + switch (found) + { + case 0: + parse_error (po, _("unrecognized option '--%s'"), optstr); + break; + + case 1: + if (optstr[optlen]) + ++optlen; + *argptr = (char *)(optstr + optlen); + return option_unalias (po, ind); + + case 2: + break; + } + + return NULL; +} + + +/* Consume next option from PO. On success, update PO members as + described below and return 0. On end of options, return 1. + + If the consumed option is a short option, then + + po_chr keeps its option character, and + po_cur points to the next option character to be processed + + Otherwise, if the consumed option is a long one, then + + po_chr is 0 + po_cur points to the first character after -- +*/ +static int +next_opt (struct mu_parseopt *po) +{ + if (!*po->po_cur) + { + if (!(po->po_flags & MU_PARSEOPT_IN_ORDER) && po->po_arg_count) + { + /* Array to save arguments in */ + char *save[2]; + /* Number of arguments processed (at most two) */ + int n = po->po_ind - (po->po_arg_start + po->po_arg_count); + + if (n > 2) + abort (); + + /* Store the processed elements away */ + save[0] = po->po_argv[po->po_arg_start + po->po_arg_count]; + if (n == 2) + save[1] = po->po_argv[po->po_arg_start + po->po_arg_count + 1]; + + /* Shift the array */ + memmove (po->po_argv + po->po_arg_start + n, + po->po_argv + po->po_arg_start, + po->po_arg_count * sizeof (po->po_argv[0])); + + /* Place stored elements in the vacating slots */ + po->po_argv[po->po_arg_start] = save[0]; + if (n == 2) + po->po_argv[po->po_arg_start + 1] = save[1]; + + /* Fix up start index */ + po->po_arg_start += n; + } + + if (po->po_ind == po->po_argc) + return 1; + + while (1) + { + po->po_cur = po->po_argv[po->po_ind++]; + if (!po->po_cur) + return 1; + if (po->po_cur[0] == '-' && po->po_cur[1]) + break; + if (!(po->po_flags & MU_PARSEOPT_IN_ORDER)) + { + po->po_arg_count++; + continue; + } + else + return 1; + } + + if (*++po->po_cur == '-') + { + if (*++po->po_cur == 0) + /* End of options */ + return 1; + + /* It's a long option */ + po->po_chr = 0; + return 0; + } + } + + po->po_chr = *po->po_cur++; + + return 0; +} + +/* Parse options */ +static int +parse (struct mu_parseopt *po) +{ + int rc; + + rc = mu_list_create (&po->po_optlist); + if (rc) + return rc; + mu_list_set_destroy_item (po->po_optlist, mu_option_cache_destroy); + + po->po_ind = 0; + if (!(po->po_flags & MU_PARSEOPT_ARGV0)) + { + po->po_ind++; + if (!(po->po_flags & MU_PARSEOPT_PROG_NAME)) + { + char *p = strrchr (po->po_argv[0], '/'); + if (p) + po->po_prog_name = p + 1; + else + po->po_prog_name = (char*) po->po_argv[0]; + } + } + else if (!(po->po_flags & MU_PARSEOPT_PROG_NAME)) + po->po_prog_name = NULL; + + po->po_arg_start = po->po_ind; + po->po_arg_count = 0; + + po->po_cur = ""; + + po->po_opterr = -1; + + while (next_opt (po) == 0) + { + struct mu_option *opt; + char *long_opt; + + if (po->po_chr) + { + opt = find_short_option (po, po->po_chr); + long_opt = NULL; + } + else + { + long_opt = po->po_cur; + opt = find_long_option (po, long_opt, &po->po_cur); + } + + if (opt) + { + char *arg = NULL; + + if (opt->opt_arg) + { + if (po->po_cur[0]) + { + arg = po->po_cur; + po->po_cur = ""; + } + else if (opt->opt_flags & MU_OPTION_ARG_OPTIONAL) + /* ignore it */; + else if (po->po_ind < po->po_argc) + arg = po->po_argv[po->po_ind++]; + else + { + if (long_opt) + parse_error (po, + _("option '--%s' requires an argument"), + long_opt); + else + parse_error (po, + _("option '-%c' requires an argument"), + po->po_chr); + po->po_opterr = po->po_ind; + if (po->po_flags & MU_PARSEOPT_NO_ERREXIT) + { + if (!(po->po_flags & MU_PARSEOPT_IN_ORDER)) + po->po_arg_count++; + continue; + } + exit (EXIT_ERROR); + } + } + else + { + if (long_opt + && po->po_cur[0] + && !(po->po_flags & MU_OPTION_ARG_OPTIONAL)) + { + parse_error (po, + _("option '--%s' doesn't allow an argument"), + long_opt); + po->po_opterr = po->po_ind; + if (po->po_flags & MU_PARSEOPT_NO_ERREXIT) + { + if (!(po->po_flags & MU_PARSEOPT_IN_ORDER)) + po->po_arg_count++; + continue; + } + exit (EXIT_ERROR); + } + arg = NULL; + } + + add_option_cache (po, opt, arg); + } + else + { + po->po_opterr = po->po_ind; + if (po->po_flags & MU_PARSEOPT_NO_ERREXIT) + { + if (!(po->po_flags & MU_PARSEOPT_IN_ORDER)) + po->po_arg_count++; + continue; + } + exit (EXIT_ERROR); + } + } + + if (po->po_arg_count) + po->po_ind = po->po_arg_start; + return 0; +} + +/* Initialize structure mu_parseopt with given options and flags. */ +static int +parseopt_init (struct mu_parseopt *po, struct mu_option **options, + int flags) +{ + struct mu_option *opt; + size_t i, j; + + memset (po, 0, sizeof *po); + po->po_argc = 0; + po->po_argv = NULL; + po->po_optc = 0; + po->po_flags = flags; + + /* Fix up flags */ + if (flags & MU_PARSEOPT_IGNORE_ERRORS) + flags |= MU_PARSEOPT_NO_ERREXIT; + + if (!(flags & MU_PARSEOPT_PROG_DOC)) + po->po_prog_doc = NULL; + if (!(flags & MU_PARSEOPT_PROG_ARGS)) + po->po_prog_args = NULL; + if (!(flags & MU_PARSEOPT_BUG_ADDRESS)) + po->po_bug_address = NULL; + if (!(flags & MU_PARSEOPT_PACKAGE_NAME)) + po->po_package_name = NULL; + if (!(flags & MU_PARSEOPT_PACKAGE_URL)) + po->po_package_url = NULL; + if (!(flags & MU_PARSEOPT_PACKAGE_URL)) + po->po_data = NULL; + if (!(flags & MU_PARSEOPT_HELP_HOOK)) + po->po_help_hook = NULL; + + /* Count the options */ + po->po_optc = 0; + for (i = 0; options[i]; i++) + for (opt = options[i]; !MU_OPTION_IS_END (opt); opt++) + ++po->po_optc; + + if (!(flags & MU_PARSEOPT_NO_STDOPT)) + for (i = 0; !MU_OPTION_IS_END (&mu_default_options[i]); i++) + ++po->po_optc; + + /* Allocate the working buffer of option pointers */ + po->po_optv = mu_calloc (po->po_optc + 1, sizeof (*po->po_optv)); + if (!po->po_optv) + return -1; + + /* Fill in the array */ + j = 0; + for (i = 0; options[i]; i++) + for (opt = options[i]; !MU_OPTION_IS_END (opt); opt++, j++) + { + if (!opt->opt_set) + opt->opt_set = mu_option_set_value; + po->po_optv[j] = opt; + } + + if (!(flags & MU_PARSEOPT_NO_STDOPT)) + for (i = 0; !MU_OPTION_IS_END (&mu_default_options[i]); i++, j++) + po->po_optv[j] = &mu_default_options[i]; + + po->po_optv[j] = NULL; + + /* Ensure sane start of options. This is necessary, in particular, + because optcmp backs up until it finds an element with cleared + MU_OPTION_ALIAS bit. */ + po->po_optv[0]->opt_flags &= MU_OPTION_ALIAS; + if (!(flags & MU_PARSEOPT_NO_SORT)) + { + /* Sort the options */ + size_t start; + + for (start = 0; start < po->po_optc; ) + { + if (MU_OPTION_IS_GROUP_HEADER (po->po_optv[start])) + start = sort_group (po->po_optv, start + 1); + else + start = sort_group (po->po_optv, start); + } + } + return 0; +} + +/* Parse command line from ARGC/ARGV. Valid options are given in + OPTIONS. FLAGS control the parsing. */ +int +mu_parseopt (struct mu_parseopt *po, + int argc, char **argv, struct mu_option **options, + int flags) +{ + int rc; + + if (flags & MU_PARSEOPT_REUSE) + { + mu_list_clear (po->po_optlist); + po->po_flags = (po->po_flags & MU_PARSEOPT_IMMUTABLE_MASK) + | (flags & ~MU_PARSEOPT_IMMUTABLE_MASK); + } + else + { + rc = parseopt_init (po, options, flags); + if (rc) + return rc; + } + po->po_argc = argc; + po->po_argv = argv; + + rc = parse (po); + + if (rc == 0) + { + if (po->po_opterr >= 0) + rc = -1; + else + { + if (po->po_flags & MU_PARSEOPT_IMMEDIATE) + rc = mu_parseopt_apply (po); + } + } + + return rc; +} + +void +mu_parseopt_free (struct mu_parseopt *popt) +{ + free (popt->po_optv); + mu_list_destroy (&popt->po_optlist); +} + +static int +parseopt_apply (void *item, void *data) +{ + struct mu_option_cache *cp = item; + struct mu_parseopt *popt = data; + cp->cache_opt->opt_set (popt, cp->cache_opt, cp->cache_arg); + return 0; +} + +int +mu_parseopt_apply (struct mu_parseopt *popt) +{ + return mu_list_foreach (popt->po_optlist, parseopt_apply, popt); +} + +void +mu_option_set_value (struct mu_parseopt *po, struct mu_option *opt, + char const *arg) +{ + if (opt->opt_ptr) + { + char *errmsg; + int rc; + + if (arg == NULL) + { + if (opt->opt_arg == NULL) + arg = "1"; + else + { + *(void**)opt->opt_ptr = NULL; + return; + } + } + rc = mu_str_to_c (arg, opt->opt_type, opt->opt_ptr, &errmsg); + if (rc) + { + char const *errtext; + if (errmsg) + errtext = errmsg; + else + errtext = mu_strerror (rc); + + if (opt->opt_long) + parse_error (po, "--%s: %s", opt->opt_long, errtext); + else + parse_error (po, "-%c: %s", opt->opt_short, errtext); + free (errmsg); + + if (!(po->po_flags & MU_PARSEOPT_NO_ERREXIT)) + exit (EXIT_ERROR); + } + } +} diff --git a/libmailutils/opt/progname.c b/libmailutils/opt/progname.c new file mode 100644 index 000000000..5d9d46b5e --- /dev/null +++ b/libmailutils/opt/progname.c @@ -0,0 +1,43 @@ +/* help.c -- general-purpose command line option parser + Copyright (C) 2016 Free Software Foundation, Inc. + + GNU Mailutils 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. + + GNU Mailutils 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <string.h> +#include <mailutils/alloc.h> +#include <mailutils/opt.h> + +char *mu_progname; +char *mu_absprogname; + +void +mu_set_progname (char const *arg) +{ + char *p; + + free (mu_absprogname); + mu_absprogname = mu_strdup (arg); + + p = strrchr (arg, '/'); + if (p) + ++p; + else + p = (char*) arg; + free (mu_progname); + mu_progname = mu_strdup (p); +} diff --git a/libmailutils/string/Makefile.am b/libmailutils/string/Makefile.am index a9fbd5645..47e578e62 100644 --- a/libmailutils/string/Makefile.am +++ b/libmailutils/string/Makefile.am @@ -25,6 +25,7 @@ libstring_la_SOURCES = \ cstrupper.c\ hexstr.c\ stpcpy.c\ + str_to_c.c\ strltrim.c\ strskip.c\ stripws.c\ @@ -40,4 +41,6 @@ libstring_la_SOURCES = \ wordsplit.c\ xdecode.c -AM_CPPFLAGS = @MU_LIB_COMMON_INCLUDES@ -I/libmailutils +EXTRA_DIST=to_sn.c to_un.c |