t_packtestu64_SOURCES = t/packtestu64.c src/pack.c src/tap.c
t_packtests64_SOURCES = t/packtests64.c src/pack.c src/tap.c
+if HAVE_STRUCT_OPTION
+check_PROGRAMS += t/helpdesc t/helpopt
+endif
+
+t_helpopt_SOURCES = t/helpopt.c src/help.c src/tap.c
+t_helpdesc_SOURCES = t/helpdesc.c src/help.c src/tap.c
+
DISTCLEANFILES =
EXTRA_DIST =
SUFFIXES =
DX_PROG_AUTOTEST
AM_CONDITIONAL([HAVE_AUTOTEST], [test x"$dx_cv_autotest_works" = x"yes"])
+AC_CACHE_CHECK([for struct option in <getopt.h>], [dx_cv_have_struct_option],
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([#include <getopt.h>],
+[[struct option opt = { "aaaa", 2, (void *)0, 'a' };
+return opt.name[opt.flag ? opt.val : opt.has_arg];]])],
+ [dx_cv_have_struct_option=yes], [dx_cv_have_struct_option=no])])
+AM_CONDITIONAL([HAVE_STRUCT_OPTION],
+ [test x"$dx_cv_have_struct_option" = x"yes"])
+
AC_CONFIG_FILES([Makefile atlocal])
AC_OUTPUT
--- /dev/null
+/*
+ * Copyright © 2021 Nick Bowler
+ *
+ * Helper functions for formatting --help program output.
+ *
+ * In order to support localized output, this depends on the Gnulib gettext-h
+ * and mbswidth modules. However, if ENABLE_NLS is not defined (or defined to
+ * 0) then these modules are not required.
+ *
+ * License WTFPL2: Do What The Fuck You Want To Public License, version 2.
+ * This is free software: you are free to do what the fuck you want to.
+ * There is NO WARRANTY, to the extent permitted by law.
+ */
+
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+#include <getopt.h>
+
+#include "help.h"
+
+#ifndef ENABLE_NLS
+# define ENABLE_NLS 0
+#endif
+
+#if ENABLE_NLS
+# include <gettext.h>
+# include <mbswidth.h>
+#else
+# define gettext(s) (s)
+# define pgettext_expr(c, s) (s)
+# define mbsnwidth(a, b, c) (assert(0), 0)
+#endif
+
+#define _(s) gettext(s)
+
+/* Returns a single numeric value depending on the type of option:
+ *
+ * 6 - if the option has a short option character and an optional argument
+ * 5 - if the option has a short option character and a mandatory argument
+ * 4 - if the option has a short option charater and no argument
+ * 3 - N/A
+ * 2 - if the option has no short option character and an optional argument
+ * 1 - if the option has no short option character and a mandatory argument
+ * 0 - if the option has no short option character and no argument
+ */
+static int option_type(const struct option *opt)
+{
+ return ((opt->val <= CHAR_MAX) << 2) | (opt->has_arg & 3);
+}
+
+enum {
+ OPT_SHORT_WITH_OPTIONAL_ARG = 6,
+ OPT_SHORT_WITH_MANDATORY_ARG = 5,
+ OPT_SHORT_WITHOUT_ARG = 4,
+ OPT_LONG_WITH_OPTIONAL_ARG = 2,
+ OPT_LONG_WITH_MANDATORY_ARG = 1,
+ OPT_LONG_WITHOUT_ARG = 0,
+};
+
+int help_print_optstring(const struct option *opt, const char *argname, int l)
+{
+ char optstring[100];
+ int w;
+
+ if (!ENABLE_NLS)
+ goto no_translate;
+
+ switch (option_type(opt)) {
+ case OPT_SHORT_WITH_OPTIONAL_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" -%c, --%s[=%s]"), opt->val, opt->name,
+ pgettext_expr(opt->name, argname));
+ break;
+ case OPT_LONG_WITH_OPTIONAL_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" --%s[=%s]"), opt->name,
+ pgettext_expr(opt->name, argname));
+ break;
+ case OPT_SHORT_WITH_MANDATORY_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" -%c, --%s=%s"), opt->val, opt->name,
+ pgettext_expr(opt->name, argname));
+ break;
+ case OPT_LONG_WITH_MANDATORY_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" --%s=%s"), opt->name,
+ pgettext_expr(opt->name, argname));
+ break;
+ case OPT_SHORT_WITHOUT_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" -%c, --%s"), opt->val, opt->name);
+ break;
+ case OPT_LONG_WITHOUT_ARG:
+ w = snprintf(optstring, sizeof optstring,
+ _(" --%s"), opt->name);
+ break;
+ default:
+ assert(0);
+ }
+
+ if (w < 0)
+ goto no_translate;
+
+ w = mbsnwidth(optstring, w, 0);
+ printf("%s", optstring);
+ goto out;
+
+no_translate:
+ switch (option_type(opt)) {
+ case OPT_SHORT_WITH_OPTIONAL_ARG:
+ w = printf(" -%c, --%s[=%s]", opt->val, opt->name, argname);
+ break;
+ case OPT_LONG_WITH_OPTIONAL_ARG:
+ w = printf(" --%s[=%s]", opt->name, argname);
+ break;
+ case OPT_SHORT_WITH_MANDATORY_ARG:
+ w = printf(" -%c, --%s=%s", opt->val, opt->name, argname);
+ break;
+ case OPT_LONG_WITH_MANDATORY_ARG:
+ w = printf(" --%s=%s", opt->name, argname);
+ break;
+ case OPT_SHORT_WITHOUT_ARG:
+ w = printf(" -%c, --%s", opt->val, opt->name);
+ break;
+ case OPT_LONG_WITHOUT_ARG:
+ w = printf(" --%s", opt->name);
+ break;
+ default:
+ assert(0);
+ }
+out:
+ if (w < 0 || w > l) {
+ putchar('\n');
+ return 0;
+ }
+
+ return w;
+}
+
+void help_print_desc(const struct option *opt, const char *s, int i, int w)
+{
+ for (s = pgettext_expr(opt->name, s); *s; w = 0) {
+ const char *nl = strchr(s, '\n');
+ int n = (nl ? nl-s : -1);
+
+ printf("%*s%.*s\n", i-w, "", n, s);
+ if (!nl)
+ break;
+
+ s = nl+1;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2021 Nick Bowler
+ *
+ * Helper functions for formatting --help program output.
+ *
+ * License WTFPL2: Do What The Fuck You Want To Public License, version 2.
+ * This is free software: you are free to do what the fuck you want to.
+ * There is NO WARRANTY, to the extent permitted by law.
+ */
+
+#ifndef DX_HELP_H_
+#define DX_HELP_H_
+
+struct option;
+
+/*
+ * Print an option string describing the short option character (if any),
+ * the long option name, and the argument name (if applicable). The argument
+ * name is localized (if NLS is enabled). If the string width is more than
+ * l columns, a newline is printed and 0 is returned. Otherwise, a newline
+ * is not printed and the string width (in columns) is returned.
+ */
+int help_print_optstring(const struct option *opt, const char *argname, int l);
+
+/*
+ * Print an option description with each line indented. The string is first
+ * localized (if NLS is enabled). The first line will be indented by i-w
+ * spaces (to account for the cursor being in some other column), all other
+ * lines are indented by i spaces.
+ */
+void help_print_desc(const struct option *opt, const char *desc, int i, int w);
+
+static inline void help_print_option(const struct option *opt,
+ const char *argname, const char *desc,
+ int w)
+{
+ if (w < 2)
+ w = 2;
+
+ help_print_desc(opt, desc, w,
+ help_print_optstring(opt, argname, w-2));
+}
+
+#endif
-packtest[su]
-packtest[su]64
+/helpdesc
+/helpopt
+/packtest[su]
+/packtest[su]64
--- /dev/null
+/*
+ * Read some text from standard input and format it with help_print_desc,
+ * for testing. Each pair of program arguments is converted to an int and
+ * passed as the two integer arguments to help_print_desc.
+ */
+#include "help.h"
+#include "tap.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+static char buf[1000];
+
+int arg_to_int(const char *s)
+{
+ char *end;
+ long val;
+
+ errno = 0;
+ val = strtol(s, &end, 0);
+ if (*end != 0)
+ tap_bail_out("%s: numeric argument expected", s);
+ else if (val < INT_MIN || val > INT_MAX || errno == ERANGE)
+ tap_bail_out("%s: %s", s, strerror(ERANGE));
+ else if (errno)
+ tap_bail_out("%s: %s", s, strerror(errno));
+
+ return val;
+}
+
+int main(int argc, char **argv)
+{
+ long a, b;
+ size_t len;
+ int i;
+
+ len = fread(buf, 1, sizeof buf - 1, stdin);
+ if (len == sizeof buf - 1)
+ tap_bail_out("too much input text");
+ if (ferror(stdin))
+ tap_bail_out("error reading from stdin: %s", strerror(errno));
+
+ for (i = 1; i < argc; i += 2) {
+ int indent = arg_to_int(argv[i]);
+ int sub = i+1 < argc ? arg_to_int(argv[i+1]) : 0;
+
+ help_print_desc(NULL, buf, indent, sub);
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * Read some text from standard input and format it with help_print_desc,
+ * for testing. Each pair of program arguments is converted to an int and
+ * passed as the two integer arguments to help_print_desc.
+ */
+#include "help.h"
+#include "tap.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <getopt.h>
+
+static char buf[1000];
+
+int arg_to_int(const char *s)
+{
+ char *end;
+ long val;
+
+ errno = 0;
+ val = strtol(s, &end, 0);
+ if (*end != 0)
+ tap_bail_out("%s: numeric argument expected", s);
+ else if (val < INT_MIN || val > INT_MAX || errno == ERANGE)
+ tap_bail_out("%s: %s", s, strerror(ERANGE));
+ else if (errno)
+ tap_bail_out("%s: %s", s, strerror(errno));
+
+ return val;
+}
+
+void print_opt(struct option *opt, const char *argname, int w)
+{
+ w = help_print_optstring(opt, argname, w);
+ printf("\t%d\n", w);
+}
+
+int main(int argc, char **argv)
+{
+ struct option opt = {0};
+ const char *argname = 0;
+ int i, w = 20;
+
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] == '-' && argv[i][1] == '-') {
+ if (opt.name)
+ print_opt(&opt, argname, w);
+ opt.val = UCHAR_MAX+1;
+ opt.has_arg = 0;
+ opt.name = argv[i]+2;
+ } else if (argv[i][0] == '-') {
+ opt.val = argv[i][1];
+ } else if (argv[i][0] == '[') {
+ char *c;
+
+ argname = argv[i]+1;
+ if ((c = strchr(argname, ']')))
+ *c = 0;
+ opt.has_arg = 2;
+ } else if (argv[i][0] >= '0' && argv[i][0] <= '9') {
+ w = arg_to_int(argv[i]);
+ } else {
+ argname = argv[i];
+ opt.has_arg = 1;
+ }
+ }
+
+ if (opt.name)
+ print_opt(&opt, argname, w);
+
+ return 0;
+}
-dnl Copyright © 2015 Nick Bowler
+dnl Copyright © 2015, 2021 Nick Bowler
dnl
dnl License WTFPL2: Do What The Fuck You Want To Public License, version 2.
dnl This is free software: you are free to do what the fuck you want to.
TEST_TAP_SIMPLE([unsigned unpacking], [packtestu], [], [pack])
TEST_TAP_SIMPLE([64-bit signed unpacking], [packtests64], [], [pack])
TEST_TAP_SIMPLE([64-bit unsigned unpacking], [packtestu64], [], [pack])
+
+AT_BANNER([Help formatting functions])
+
+AT_SETUP([help_print_desc])
+
+AT_SKIP_IF([test ! -x "$builddir/t/helpdesc"])
+
+AT_DATA([test.txt],
+[[this is the first line
+this is the second line
+this is the third line
+and so on
+]])
+
+sed -e '5,$s/^/ /' -e '6,$s/^/ /' \
+ -e '10,$s/^/ /' \
+ -e '13s/^ *//' -e '14,$s/^/ /' \
+ test.txt test.txt test.txt test.txt >expout
+
+AT_CHECK(["$builddir/t/helpdesc" 0 0 10 5 30 20 40 40 <test.txt],
+ [0], [expout])
+
+AT_CLEANUP
+
+AT_SETUP([help_print_optstring])
+
+AT_SKIP_IF([test ! -x "$builddir/t/helpopt"])
+
+AT_CHECK([m4_join([ ],
+ ["$builddir/t/helpopt"],
+ [--foo],
+ [--bar -b],
+ [--baz ARG],
+ [--baz -B ARG],
+ [--quux '@<:@ARG@:>@'],
+ [--quux -q '@<:@ARG@:>@'],
+ [--hello-this-is-a-very-long-option 20],
+ [--hello-this-is-a-very-long-option 50],
+ [--not-long 12])], [0],
+[[ --foo 7
+ -b, --bar 11
+ --baz=ARG 11
+ -B, --baz=ARG 15
+ --quux[=ARG] 14
+ -q, --quux[=ARG] 18
+ --hello-this-is-a-very-long-option
+ 0
+ --hello-this-is-a-very-long-option 36
+ --not-long 12
+]])
+
+AT_CLEANUP