diff options
-rw-r--r-- | configure.ac | 1 | ||||
-rw-r--r-- | include/mailutils/Makefile.am | 1 | ||||
-rw-r--r-- | include/mailutils/opt.h | 168 | ||||
-rw-r--r-- | include/mailutils/util.h | 30 | ||||
-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 |
16 files changed, 1684 insertions, 3 deletions
diff --git a/configure.ac b/configure.ac index d473f802b..7bd364118 100644 --- a/configure.ac +++ b/configure.ac @@ -1519,6 +1519,7 @@ AC_CONFIG_FILES([ libmailutils/mailer/Makefile libmailutils/mime/Makefile libmailutils/msgset/Makefile + libmailutils/opt/Makefile libmailutils/property/Makefile libmailutils/server/Makefile libmailutils/string/Makefile diff --git a/include/mailutils/Makefile.am b/include/mailutils/Makefile.am index 361ebdca2..ba87486ce 100644 --- a/include/mailutils/Makefile.am +++ b/include/mailutils/Makefile.am @@ -79,6 +79,7 @@ pkginclude_HEADERS = \ nntp.h\ observer.h\ opool.h\ + opt.h\ pam.h\ parse822.h\ pop3.h\ diff --git a/include/mailutils/opt.h b/include/mailutils/opt.h new file mode 100644 index 000000000..945c69e97 --- /dev/null +++ b/include/mailutils/opt.h @@ -0,0 +1,168 @@ +/* opt.h -- 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/>. +*/ + +#ifndef _MAILUTILS_OPT_H +#define _MAILUTILS_OPT_H +#include <stdio.h> +#include <mailutils/types.h> +#include <mailutils/list.h> +#include <mailutils/util.h> +#include <mailutils/cctype.h> + +extern char *mu_progname; +extern char *mu_absprogname; + +void mu_set_progname (char const *arg); + +#define MU_OPTION_DEFAULT 0 +#define MU_OPTION_ARG_OPTIONAL 0x01 +#define MU_OPTION_HIDDEN 0x02 +#define MU_OPTION_ALIAS 0x04 +#define MU_OPTION_IMMEDIATE 0x08 + +struct mu_parseopt; + +struct mu_option +{ + char *opt_long; /* Long option name */ + int opt_short; /* Short option character */ + char *opt_arg; /* Argument name */ + int opt_flags; /* Flags (see above) */ + char *opt_doc; /* Human-readable documentation */ + mu_c_type_t opt_type; /* Option type */ + void *opt_ptr; /* Data pointer */ + void (*opt_set) (struct mu_parseopt *, struct mu_option *, char const *); + /* Function to set the option */ +}; + +#define MU_OPTION_GROUP(text) { NULL, 0, NULL, 0, text } +#define MU_OPTION_END { NULL, 0, NULL, 0, NULL } + +#define MU_OPTION_IS_END(opt) \ + (!(opt)->opt_long && !(opt)->opt_short && !(opt)->opt_doc) + +#define MU_OPTION_IS_OPTION(opt) \ + ((opt)->opt_short || (opt)->opt_long) +#define MU_OPTION_IS_GROUP_HEADER(opt) \ + (!MU_OPTION_IS_OPTION(opt) && (opt)->opt_doc) +#define MU_OPTION_IS_VALID_SHORT_OPTION(opt) \ + ((opt)->opt_short > 0 && (opt)->opt_short < 127 && \ + mu_isalnum ((opt)->opt_short)) +#define MU_OPTION_IS_VALID_LONG_OPTION(opt) \ + ((opt)->opt_long != NULL) + +typedef struct mu_option_cache *mu_option_cache_ptr_t; + +struct mu_option_cache +{ + struct mu_option *cache_opt; + char *cache_arg; +}; + +#define MU_PARSEOPT_DEFAULT 0 +/* Don't ignore the first element of ARGV. By default it is the program + name */ +#define MU_PARSEOPT_ARGV0 0x00000001 +/* Ignore command line errors. */ +#define MU_PARSEOPT_IGNORE_ERRORS 0x00000002 +/* Don't order arguments so that options come first. */ +#define MU_PARSEOPT_IN_ORDER 0x00000004 +/* Don't provide standard options: -h, --help, --usage, --version */ +#define MU_PARSEOPT_NO_STDOPT 0x00000008 +/* Don't exit on errors */ +#define MU_PARSEOPT_NO_ERREXIT 0x00000010 +/* Apply all options immediately */ +#define MU_PARSEOPT_IMMEDIATE 0x00000020 + +/* Don't sort options */ +#define MU_PARSEOPT_NO_SORT 0x00001000 + +#define MU_PARSEOPT_PROG_NAME 0x00002000 +#define MU_PARSEOPT_PROG_DOC 0x00004000 +#define MU_PARSEOPT_PROG_ARGS 0x00008000 +#define MU_PARSEOPT_BUG_ADDRESS 0x00010000 +#define MU_PARSEOPT_PACKAGE_NAME 0x00020000 +#define MU_PARSEOPT_PACKAGE_URL 0x00040000 +#define MU_PARSEOPT_DATA 0x00080000 +#define MU_PARSEOPT_HELP_HOOK 0x00100000 + +/* Reuse mu_parseopt struct initialized previously */ +#define MU_PARSEOPT_REUSE 0x80000000 +/* Mask for immutable flag bits */ +#define MU_PARSEOPT_IMMUTABLE_MASK 0xFFFFF000 + + +struct mu_parseopt +{ + /* Input data: */ + int po_argc; /* Number of argiments */ + char **po_argv; /* Array of arguments */ + size_t po_optc; /* Number of elements in optv */ + struct mu_option **po_optv; /* Array of ptrs to option structures */ + int po_flags; + + char *po_data; /* Call-specific data */ + + /* Informational: */ + char const *po_prog_name; + char const *po_prog_doc; + char const *po_prog_args; + char const *po_bug_address; + char const *po_package_name; + char const *po_package_url; + + void (*po_help_hook) (FILE *stream); /* FIXME: should take mu_Stream_t ?*/ + + /* Output data */ + int po_ind; /* Index of the next option */ + int po_opterr; /* Index of the element in po_argv that + caused last error, or -1 if no errors */ + mu_list_t po_optlist; + + /* Auxiliary data */ + char *po_cur; /* Points to the next character */ + int po_chr; /* Single-char option */ + + /* The following two keep the position of the first non-optional argument + and the number of contiguous non-optional arguments after it. + Obviously, the following holds true: + + arg_start + arg_count == opt_ind + + If permutation is not allowed (MU_OPTION_PARSE_IN_ORDER flag is set), + arg_count is always 0. + */ + int po_arg_start; + int po_arg_count; + +}; + +int mu_parseopt (struct mu_parseopt *p, + int argc, char **argv, struct mu_option **optv, + int flags); + +int mu_parseopt_apply (struct mu_parseopt *p); +void mu_parseopt_free (struct mu_parseopt *p); + +void mu_option_describe_options (struct mu_option **optbuf, size_t optcnt); +void mu_program_help (struct mu_parseopt *p); +void mu_program_usage (struct mu_parseopt *p); + +void mu_option_set_value (struct mu_parseopt *po, struct mu_option *opt, + char const *arg); + +#endif diff --git a/include/mailutils/util.h b/include/mailutils/util.h index b5bc303ea..8c7082c28 100644 --- a/include/mailutils/util.h +++ b/include/mailutils/util.h @@ -152,6 +152,35 @@ int mu_getpass (mu_stream_t in, mu_stream_t out, const char *prompt, char **passptr); /* ----------------------- */ + /* String conversions. */ + /* ----------------------- */ + +enum mu_c_type + { + mu_c_string, + mu_c_short, + mu_c_ushort, + mu_c_int, + mu_c_uint, + mu_c_long, + mu_c_ulong, + mu_c_size, + mu_c_off, + mu_c_time, + mu_c_bool, + mu_c_ipv4, + mu_c_cidr, + mu_c_host, + mu_c_incr, /* C int value, incremented each time mu_str_to_c is + invoked */ + }; + +typedef enum mu_c_type mu_c_type_t; + +int mu_str_to_c (char const *string, mu_c_type_t type, void *tgt, + char **errmsg); + + /* ----------------------- */ /* Assorted functions. */ /* ----------------------- */ int mu_getmaxfd (void); @@ -208,7 +237,6 @@ int mu_file_safety_compose (int *res, const char *name, int defval); int mu_file_mode_to_safety_criteria (int mode); int mu_safety_criteria_to_file_mode (int crit); - #ifdef __cplusplus } #endif 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++; + conti |