summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac1
-rw-r--r--include/mailutils/Makefile.am1
-rw-r--r--include/mailutils/opt.h168
-rw-r--r--include/mailutils/util.h30
-rw-r--r--libmailutils/Makefile.am3
-rw-r--r--libmailutils/opt/Makefile.am26
-rw-r--r--libmailutils/opt/help.c337
-rw-r--r--libmailutils/opt/opt.c625
-rw-r--r--libmailutils/opt/progname.c43
-rw-r--r--libmailutils/string/Makefile.am5
-rw-r--r--libmailutils/string/str_to_c.c274
-rw-r--r--libmailutils/string/to_sn.c37
-rw-r--r--libmailutils/string/to_un.c34
-rw-r--r--libmailutils/tests/.gitignore1
-rw-r--r--libmailutils/tests/Makefile.am1
-rw-r--r--libmailutils/tests/parseopt.c101
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