]> git.tue.mpg.de Git - lopsub.git/commitdiff
The long option parser for subcommands (lopsub). v1.0.0
authorAndre Noll <maan@tuebingen.mpg.de>
Sat, 22 Oct 2016 13:47:12 +0000 (15:47 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sun, 23 Oct 2016 12:16:46 +0000 (14:16 +0200)
This library was under development for over a year and is now ready
for prime time. The code is believed to be mature and no bugs are
known at this time. The API is fully documented and stable, and no
new features are planned. Future fixes and enhancements will take
backwards compatibility into account.

The code used to be part of the paraslash package, but has been
moved to its own repository. All prior commits have been discarded,
so this repository contains only the final result as a single commit.

The input files for the lopsubgen utility and the config file parser
are based on flex. The API documentation is created with gendoc.m4,
a simple m4 file which is part of this repository.

Documentation includes the three man pages lopsub.7, lopsubgen.1 and
lopsub.suite.5 which describe different aspects of the library. There
is also an example application called lopsubex which illustrates
various features of the library.

20 files changed:
.gitignore [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
config_file.l [new file with mode: 0644]
gendoc/gendoc.m4 [new file with mode: 0644]
lopsub-internal.h [new file with mode: 0644]
lopsub-suite.5.m4 [new file with mode: 0644]
lopsub.7.m4 [new file with mode: 0644]
lopsub.c [new file with mode: 0644]
lopsub.h.m4 [new file with mode: 0644]
lopsubex.c [new file with mode: 0644]
lopsubex.suite [new file with mode: 0644]
lopsubgen.l [new file with mode: 0644]
lopsubgen.suite [new file with mode: 0644]
lsg.c [new file with mode: 0644]
lsg.h [new file with mode: 0644]
version-gen.sh [new file with mode: 0755]
web/footer.html [new file with mode: 0644]
web/header.html [new file with mode: 0644]
web/lopsub.png [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..be0f3e3
--- /dev/null
@@ -0,0 +1,12 @@
+**/*.o
+*.lsg.*
+*.[1-9]
+config_file.c
+lopsubgen.c
+version.c
+lopsub.h
+liblopsub.a
+lopsubex
+lopsubgen
+lopsubgen-stage1
+web/*.html
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..4278505
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,113 @@
+# Implicit rules are implemented in make as suffix rules. The following rule
+# empties the suffix list to disable the predefined implicit rules. This
+# increases performance and avoids hard-to-debug behaviour.
+.SUFFIXES:
+MAKEFLAGS += -Rr
+ifeq ("$(origin CC)", "default")
+        CC := cc
+endif
+
+PREFIX ?= /usr/local
+M4 := m4
+LN := ln -f
+LEX := lex
+RM := rm -f
+AR := ar
+GROFF := groff
+CP := cp
+INSTALL := install
+
+DATE := $(shell date '+%B %Y')
+GIT_VERSION := $(shell ./version-gen.sh)
+
+m4_man_pages := lopsub-suite.5 lopsub.7
+all := $(m4_man_pages) liblopsub.a lopsubgen lopsubgen.1 \
+       lopsubex lopsubex.1
+all: $(all)
+
+# deps
+lopsubgen.o: lsg.h
+lopsub.o lsg.o: lopsub.h lopsub-internal.h
+lsg.o: lopsubgen.lsg.h lopsub-internal.h
+lopsubex.o: lopsubex.lsg.h lopsub.h
+config_file.c: lopsub-internal.h lopsub.h
+version.o: version.c
+
+# m4 stuff
+gendoc := gendoc/gendoc.m4
+%.h: %.h.m4 $(gendoc)
+       $(M4) -DOUTPUT_MODE=C $(gendoc) $< > $@
+$(m4_man_pages): %: %.m4 version.c
+       $(M4) -DGIT_VERSION=$(GIT_VERSION) -DDATE="$(DATE)" $< > $@
+
+# flex
+%.c: %.l
+       $(LEX) -o $@ $<
+
+# lopsubgen
+lopsubgen.lsg.c lopsubgen.lsg.h: lopsubgen.suite lopsubgen-stage1 \
+               lopsub-internal.h
+       ./lopsubgen-stage1 < $<
+%.lsg.c: %.suite lopsubgen
+       ./lopsubgen --gen-c < $<
+%.lsg.h: %.suite lopsubgen
+       ./lopsubgen --gen-header < $<
+%.1: %.suite lopsubgen
+       ./lopsubgen --gen-man=$@ --version-string $(GIT_VERSION) < $<
+
+# compiling
+lsg1_objs := lopsubgen.o lsg1.o version.o
+lsg_objs := lopsubgen.o lsg.o lopsubgen.lsg.o lopsub.o version.o
+liblopsub_objs := config_file.o lopsub.o version.o
+lopsubex_objs := lopsubex.o lopsubex.lsg.o version.o
+
+$(lsg_objs) $(liblopsub_objs) $(lopsubex_objs): %.o: %.c
+lopsubgen.o config_file.o:
+       $(CC) -g -c -o $@ ${@:.o=.c}
+lsg1.o: lsg.c lsg.h
+       $(CC) -g -DSTAGE1 -Wall -g -c $< -o $@
+%.o: %.c
+       $(CC) -Wall -I. -g -c -o $@ $<
+
+# linking
+lopsubgen-stage1: $(lsg1_objs)
+       $(CC) -Wall -g $(lsg1_objs) -o $@
+lopsubgen: $(lsg_objs)
+       $(CC) -Wall -g -o $@ $(lsg_objs)
+liblopsub.a: $(liblopsub_objs)
+       $(AR) -rcs $@ $^
+lopsubex: $(lopsubex_objs) liblopsub.a
+       $(CC) -Wall -g -o $@ $^
+
+# web
+html := $(addprefix web/, $(addsuffix .html, \
+       index lopsub-api lopsubgen.1 lopsubex.1 $(m4_man_pages)))
+$(html): $(addprefix web/, $(addsuffix .html, header footer))
+
+www: $(html)
+
+web/lopsub-api.html: lopsub.h.m4
+       $(M4) -DOUTPUT_MODE=HTML web/header.html $(gendoc) \
+               $< web/footer.html > $@
+web/index.html: web/lopsub.7.html
+       $(LN) -s $(notdir $<) $@
+web/%.html: %
+       $(CP) web/header.html $@
+       $(GROFF) -m man -Thtml $< | sed -e '1,/^<body>/d' >> $@
+
+install: liblopsub.a lopsub.7
+       $(INSTALL) -d $(PREFIX)/lib $(PREFIX)/include $(PREFIX)/man/man1 \
+                $(PREFIX)/man/man5 $(PREFIX)/man/man7 $(PREFIX)/bin
+       $(INSTALL) -m 755 liblopsub.a $(PREFIX)/lib
+       $(INSTALL) -m 755 lopsubgen $(PREFIX)/bin
+       $(INSTALL) -m 644 lopsub.h $(PREFIX)/include
+       $(INSTALL) -m 644 lopsub-internal.h $(PREFIX)/include
+       $(INSTALL) -m 644 lopsubgen.1 $(PREFIX)/man/man1
+       $(INSTALL) -m 644 lopsub-suite.5 $(PREFIX)/man/man5
+       $(INSTALL) -m 644 lopsub.7 $(PREFIX)/man/man7
+
+clean:
+       $(RM) $(all) $(html) *.o *.man *.lsg.c *.lsg.h \
+               lopsubgen.c config_file.c lopsubgen-stage1 \
+               lopsub.h lopsub.7 lopsub-suite.5 version.c
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..a6ffa0b
--- /dev/null
+++ b/README
@@ -0,0 +1,12 @@
+Lopsub, the long option parser for subcommands
+----------------------------------------------
+
+Documentation is available at
+
+       http://people.tuebingen.mpg.de/maan/lopsub
+
+Alternatively, run
+
+       man -l ./lopsub.7.m4
+
+to show build and installation instructions.
diff --git a/config_file.l b/config_file.l
new file mode 100644 (file)
index 0000000..38691b7
--- /dev/null
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
+ */
+
+%option noyywrap
+%option stack
+%option never-interactive
+%option yylineno
+
+%x SC_ARG
+%s SC_SCANNING
+
+IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]*
+EQUALS [[:space:]]*=[[:space:]]*
+OPTION [a-zA-Z]+[a-zA-Z0-9_-]*
+
+%{
+       #include <stdlib.h>
+       #include <ctype.h>
+       #include <assert.h>
+       #include <stdbool.h>
+       #include <inttypes.h>
+       #include "lopsub-internal.h"
+       #include "lopsub.h"
+
+       static int rargc;
+       static char **rargv;
+       static const char *subcommand;
+
+       static int expand_result(void)
+       {
+               int nargc = rargc + 1;
+               char **nrargv = realloc(rargv, (nargc + 1) * sizeof(char *));
+
+               if (!nrargv)
+                       return -E_LLS_NOMEM;
+               rargc = nargc;
+               rargv = nrargv;
+               rargv[rargc] = NULL;
+               return 1;
+       }
+
+       static int add_option(void)
+       {
+               int ret;
+               unsigned n;
+
+               for (n = 0; n < yyleng; n++)
+                       if (!isalnum(yytext[n]) && !(yytext[n] == '-'))
+                               break;
+               assert(n > 0);
+               ret = expand_result();
+               if (ret < 0)
+                       return ret;
+               rargv[rargc - 1] = malloc(n + 2 + 1);
+               if (!rargv[rargc - 1])
+                       return -E_LLS_NOMEM;
+               rargv[rargc - 1][0] = rargv[rargc - 1][1] = '-';
+               memcpy(rargv[rargc - 1] + 2, yytext, n);
+               rargv[rargc - 1][n + 2] = '\0';
+               return 1;
+       }
+
+       static int parse_arg(char **result)
+       {
+               bool backslash = false, quote = false;
+               const char *in;
+               char *out;
+               int ret;
+
+               *result = malloc(yyleng + 1);
+               if (!*result)
+                       return -E_LLS_NOMEM;
+               for (in = yytext, out = *result; *in; in++) {
+                       if (*in == '\\') {
+                               if (!backslash) {
+                                       backslash = true;
+                                       continue;
+                               }
+                       } else if (*in == 'n' || *in == 't') {
+                               if (backslash) { /* \n or \t */
+                                       *out++ = (*in == 'n')? '\n' : '\t';
+                                       backslash = false;
+                                       continue;
+                               }
+                       } else if (*in == '"') {
+                               if (!backslash) {
+                                       quote = !quote;
+                                       continue;
+                               }
+                       } else if (isspace(*in)) {
+                               if (!backslash && !quote)
+                                       break;
+                       }
+                       /* copy the character */
+                       *out++ = *in;
+                       backslash = false;
+               }
+               ret = -E_LLS_TRAILING_BACKSLASH;
+               if (backslash)
+                       goto fail;
+               ret = -E_LLS_UNMATCHED_QUOTE;
+               if (quote)
+                       goto fail;
+               /* look at first non-space character */
+               for (; *in; in++) {
+                       if (isspace(*in))
+                               continue;
+                       if (*in == '#')
+                               break;
+                       ret = -E_LLS_TRAILING_GARBAGE;
+                       goto fail;
+               }
+               /* success */
+               *out = '\0';
+               return out - *result;
+       fail:
+               assert(ret < 0);
+               free(*result);
+               *result = NULL;
+               return ret;
+       }
+%}
+
+%%
+
+ /* skip comments and whitespace */
+^[[:space:]]*#.*\n ;
+[[:space:]]|\n+ ;
+
+<INITIAL,SC_SCANNING>\[[[:space:]]*{IDENTIFIER}[[:space:]]*\][[:space:]]*\n {
+       int i, j;
+
+       assert(yytext[0] == '[');
+       if (!subcommand)
+               return 0;
+       for (i = 1; i < yyleng; i++)
+               if (!isspace(yytext[i]))
+                       break;
+       for (j = i; j < yyleng; j++)
+               if (yytext[j] == ']' || isspace(yytext[j]))
+                       break;
+       assert(j < yyleng);
+       yytext[j] = '\0';
+       if (strcmp(yytext + i, subcommand))
+               BEGIN(INITIAL);
+       else
+               BEGIN(SC_SCANNING);
+}
+
+<SC_SCANNING>{OPTION}[[:space:]]*\n add_option();
+
+<SC_SCANNING>{OPTION}({EQUALS}|[[:space:]]+) {
+       int ret = add_option();
+
+       if (ret < 0)
+               return ret;
+       BEGIN(SC_ARG);
+}
+
+<SC_ARG>.*\n {
+       const char *opt = rargv[rargc - 1];
+       char *arg, *result;
+       size_t opt_len = strlen(opt), arg_len;
+       int ret = parse_arg(&arg);
+
+       if (ret < 0)
+               return ret;
+       arg_len = ret;
+       result = malloc(opt_len + arg_len + 2);
+       if (!result)
+               return -E_LLS_NOMEM;
+       strcpy(result, opt);
+       result[opt_len] = '=';
+       strcpy(result + opt_len + 1, arg);
+       free(arg);
+       result[opt_len + arg_len + 1] = '\0';
+       free(rargv[rargc - 1]);
+       rargv[rargc - 1] = result;
+       BEGIN(SC_SCANNING);
+}
+
+<INITIAL>.*\n {}
+
+ /* This rule runs iff none of the above patterns matched */
+. {return -1;}
+%%
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+
+int lls_convert_config(const char *buf, size_t nbytes, const char *subcmd,
+                char ***result, char **errctx)
+{
+       int ret;
+       YY_BUFFER_STATE yybs;
+
+       *result = NULL;
+       if (errctx)
+               *errctx = NULL;
+       subcommand = subcmd;
+       if (!subcmd)
+               BEGIN(SC_SCANNING);
+       else
+               BEGIN(INITIAL);
+       yybs = yy_scan_bytes(buf, nbytes);
+       if (!yybs)
+               return -E_LLS_YY_SCAN;
+       rargc = 1;
+       rargv = malloc((rargc + 1) * sizeof(char *));
+       if (!rargv)
+               return -E_LLS_NOMEM;
+       rargv[0] = strdup(__FUNCTION__);
+       if (!rargv[0]) {
+               free(rargv);
+               return -E_LLS_NOMEM;
+       }
+       rargv[1] = NULL;
+       ret = yylex();
+       yy_delete_buffer(yybs);
+       if (ret >= 0) {
+               *result = rargv;
+               return rargc;
+       }
+       if (errctx) {
+               *errctx = malloc(100);
+               if (*errctx)
+                       sprintf(*errctx, "error at line %d", yyget_lineno());
+       }
+       for (; rargc >= 0; rargc--)
+               free(rargv[rargc]);
+       free(rargv);
+       *result = NULL;
+       return -E_LLS_YY_LEX;
+}
+
+void lls_free_argv(char **argv)
+{
+       int i;
+
+       if (!argv)
+               return;
+       for (i = 0; argv[i]; i++)
+               free(argv[i]);
+       free(argv);
+}
+#if 0
+int main(void)
+{
+       char buf[100 * 1024];
+       int ret, len, i, argc;
+       char **argv;
+
+       ret = read(STDIN_FILENO, buf, sizeof(buf));
+       if (ret <= 0)
+               exit(EXIT_FAILURE);
+       len = ret;
+       ret = lls_convert_config(buf, len, NULL, &argv, NULL);
+       if (ret < 0)
+               exit(EXIT_FAILURE);
+       argc = ret;
+       for (i = 0; i < argc; i++)
+               printf("argv[%d]: %s\n", i, rargv[i]);
+       return EXIT_SUCCESS;
+}
+#endif
diff --git a/gendoc/gendoc.m4 b/gendoc/gendoc.m4
new file mode 100644 (file)
index 0000000..6c3099c
--- /dev/null
@@ -0,0 +1,265 @@
+changequote(`«', `»')dnl
+define(«IFNBLANK», «ifelse(translit(««$1»», «
+        »), «», «», «$2»)»)dnl
+define(«DECL_ARGLIST», «ifelse(«$#», «2», «$1»,
+       «$1, DECL_ARGLIST(shift(shift($@)))»)»)dnl
+define(«LINK_COUNT», 0)
+define(«STRIP_LEFT», «patsubst(«$1», «^\s+», «»)»)dnl
+define(«STRIP_RIGHT», «patsubst(«$1», «\s+$», «»)»)dnl
+define(«STRIP», «STRIP_LEFT(«STRIP_RIGHT(«$1»)»)»)dnl
+ifelse(OUTPUT_MODE, «C», «dnl --------- C output
+define(«COMPOUND_MEMBERS», «ifelse(«$#», «1», «»,
+       «      $1; /* STRIP_RIGHT(«$2») */
+COMPOUND_MEMBERS(shift(shift($@)))»)»)dnl
+define(«DECL_ARG_TEXT», «ifelse(«$#», «1», «», «IFNBLANK(«$2», «
+               $1: $2«»DECL_ARG_TEXT(shift(shift($@)))»)»)»)dnl
+define(«DECLARE_FUNCTION», «/* $2 */
+$6 $1(DECL_ARGLIST($4));
+/*dnl
+$3«»dnl
+DECL_ARG_TEXT($4)dnl
+IFNBLANK(«$5», «$5»)dnl
+IFNBLANK(«$7», «
+               Return ($6): STRIP_RIGHT(«$7»)»
+)dnl
+IFNBLANK(«$8», «$8»)dnl
+*/»)dnl
+define(«STATEMENT», «/* $2 */
+$1;
+IFNBLANK(«$3», «/*STRIP_RIGHT(«$3»)
+*/dnl
+»)dnl
+»)dnl
+define(«DECLARE_COMPOUND», «/* $2 */
+$1 {
+IFNBLANK(«$3», «/*STRIP_RIGHT(«$3»)
+*/
+»)dnl
+COMPOUND_MEMBERS($4)dnl
+};
+IFNBLANK(«$5», «/*STRIP_RIGHT(«$5»)
+*/
+»)dnl
+»)dnl
+define(«VERBATIM_C», «$1»)dnl
+», OUTPUT_MODE, «HTML», «dnl --------- HTML output
+       define(«FIXUP_LT», «patsubst(«$1», «<», «&lt;»)»)
+       define(«FIXUP_GT», «patsubst(«$1», «>», «&gt;»)»)
+       define(«FIXUP_AMPERSAND», «patsubst(«$1», «&», «&amp;»)»)
+       define(«HANDLE_EMPTY_LINES», «patsubst(«$1», «^\s*$», «</p><p>»)»)
+       define(«FIXUP_HTML», «<p> FIXUP_AMPERSAND(«FIXUP_LT(«FIXUP_GT(
+               «HANDLE_EMPTY_LINES(«$1»)»)»)») </p>»)
+       define(«FORMAT_LIST_HTML», «ifelse(«$#», «1», «», «
+               IFNBLANK(«$2», «<li> <tt>$1</tt>: $2 </li>
+               FORMAT_LIST_HTML(shift(shift($@)))»)»
+       )»)
+       define(«ANCHOR», «
+               <a name = "link_«»LINK_COUNT" </a>
+       »)
+       define(«HREF», «
+               define(«LINK_COUNT», incr(LINK_COUNT))
+               <a href = "«#»link_«»LINK_COUNT"> $1 </a>
+       »)
+       define(«DECLARE_FUNCTION», «
+               divert
+               <tr> <td> <tt> HREF(«$1») </tt> </td> <td> $2 </td> </tr>
+               divert(«1»)
+               ANCHOR
+               <h1> <tt> $6 $1(DECL_ARGLIST($4)) </tt> </h1>
+               <strong> FIXUP_HTML(«$2») </strong>
+               FIXUP_HTML(«$3»)
+               <ul> FORMAT_LIST_HTML($4) </ul>
+               FIXUP_HTML(«$5»)
+               IFNBLANK(«$7», «Return (<tt>$6</tt>):
+                       <ul><li> FIXUP_HTML(«$7») </lu></ul>»)
+               FIXUP_HTML(«$8»)
+               <hr>
+       »)
+       define(«STATEMENT», «
+               divert
+               <tr> <td> <tt> HREF(«$1») </tt> </td> <td> $2 </td>
+               divert(«1»)
+               ANCHOR
+               <h1> <tt> $1 </tt> </h1>
+               <strong> FIXUP_HTML(«$2») </strong>
+               <p> FIXUP_HTML(«$3») </p>
+               <hr>
+       »)
+       define(«DECLARE_COMPOUND», «
+               divert
+               <tr> <td> <tt> HREF(«$1») </tt> </td> <td> $2 </td>
+               divert(«1»)
+               ANCHOR
+               <h1> <tt> $1 </tt> </h1>
+               <strong> FIXUP_HTML(«$2») </strong>
+               FIXUP_HTML(«$3»)
+               Members:
+               <ul> FORMAT_LIST_HTML($4) </ul>
+               FIXUP_HTML(«$5»)
+               <hr>
+       »)
+       define(«VERBATIM_C»)
+       divert(«1») </table> <hr> divert
+       <center> <h1> API Reference </h1> </center>
+       <table>
+», OUTPUT_MODE, «ROFF»,«dnl ---------- ROFF output
+.TH
+.SH SYNOPSIS
+define(«VERBATIM_C»)
+define(«FORMAT_LIST_ROFF», «ifelse(«$#», «1», «», «
+IFNBLANK(«$2», «
+.B "STRIP(«$1»)"
+\- STRIP(«$2»)
+.br dnl
+FORMAT_LIST_ROFF(shift(shift($@)))»dnl
+»))»)
+define(«STATEMENT», «dnl
+divert
+.B "$1"
+\- «$2»
+.br dnl
+divert(«1»)
+.SS "«$1»"
+STRIP_LEFT(«$2»)
+STRIP_LEFT(«$3»)dnl
+»)
+define(«DECLARE_FUNCTION», «
+divert
+.BR "«$1»" ()
+\- «$2»
+.br dnl
+divert(«1»)
+.SS "STRIP_RIGHT(«$6») $1(DECL_ARGLIST($4))"
+STRIP(«$2»)
+STRIP(«$3»)
+.IP
+FORMAT_LIST_ROFF($4)
+.P
+STRIP(«$5»)dnl
+IFNBLANK(«$7», «
+.IP
+Return
+.RB ( "$6" ):
+STRIP(«$7»)»
+.P
+)
+STRIP_LEFT(«$8»)
+.P
+»)
+divert(«1»)
+.P
+.SH DESCRIPTION
+divert
+»)dnl
+ifdef(«EXAMPLES», «
+dnl
+dnl
+dnl    Args
+dnl    ~~~~
+dnl    (1) name, (2) one-line summary, (3) prolog, (4) args,
+dnl    (5) arg doc, (6) return type, (7) one-line return text, (8) epilog
+DECLARE_FUNCTION(
+       «main»,
+       «The function that is executed on startup.»,
+       1. This function must not be static, it's always linked in.
+
+       Each executable needs to define exactly one main function.
+», «
+       «int argc»,   «usual argument count»,
+       «char **argv»,        «usual argument vector»
+», «
+       2. The arg list may optionally contain an env pointer. In any case
+       argc > 0 and argc < INT_MAX.
+»,
+       «int», «EXIT_SUCCESS or EXIT_FAILURE»,
+       3. On most systems EXIT_FAILURE is 1 and EXIT_SUCCESS is 0.
+»)
+
+DECLARE_FUNCTION(
+       «sync»,
+       «Commit buffer cache to disk.»,
+       Causes all buffered modifications to file metadata and data
+       to be written to the underlying filesystems.
+», «
+       «void»,
+», «
+»,
+       «void»,
+)
+
+DECLARE_FUNCTION(
+       «getchar»,
+       «Input of characters and strings.»,
+       Reads the next character from stdin.
+», «
+       «void»,
+», «
+»,
+       «int», «the character read»,
+       On success the return value is an unsigned char cast to
+       an int. On end of file or error, EOF is returned.
+)
+
+DECLARE_FUNCTION(
+       «free»,
+       «Free dynamic memory.»,
+       The memory space must have been returned by a previous call to
+       malloc(), calloc(), or realloc().
+», «
+       «void *ptr»,  «free the memory space pointed to by ptr»
+», «
+       If ptr is NULL, no operation is performed.
+»,
+       «void»,
+)
+
+dnl    Args
+dnl    ~~~~
+dnl    (1) name, (2) one-line summary, (3) prolog, (4) member list, (5) epilog
+DECLARE_COMPOUND(
+       «struct complex»,
+       «Describes a complex number»,
+», «
+       «float re», «real part»,
+       «float im», «imaginary part»,
+)
+DECLARE_COMPOUND(
+       «struct iovec»,
+       «According to POSIX, the <sys/uio.h> header shall define the iovec structure.»,
+       This structure is employed for the readv() and writev() system calls.
+
+       For example, the readv() system call reads into buffers which is
+       described by an array of iovec structures.
+», «
+       «void *iov_base»,     «start address»,
+       «size_t iov_len»,     «number of bytes to transfer»
+», «
+       Buffers are processed in array order.
+)
+dnl    Args
+dnl    ~~~~
+dnl    (1) name, (2) one-line summary, (3) description
+STATEMENT(
+       «struct foo_handle»,
+       «Opaque structure that describes one connection to the foo subsystem»,
+       The compiler considers this structure an incomplete type.
+
+       Applications can still declare pointer variables or arrays of pointers
+       to this structure, and compare pointers for equality. However, they
+       will not be able to de-reference a pointer, and can only change the
+       pointer's content by calling some procedure of the library.
+)
+»)
diff --git a/lopsub-internal.h b/lopsub-internal.h
new file mode 100644 (file)
index 0000000..99366d3
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
+ */
+
+#include <inttypes.h>
+
+#define LLS_ABI_VERSION 1
+
+/*
+ * Concat "lls_abi_version" and the ABI version number to produce the name of a
+ * variable. To concat the *expansion* of the macro arguments, we have to use
+ * two levels of macros since otherwise the arguments are not macro-expanded
+ * first.
+ */
+#define LLS_CONCAT_EXPAND(_prefix, _suffix) LLS_CONCAT(_prefix, _suffix)
+#define LLS_CONCAT(_prefix, _suffix) _prefix ## _suffix
+#define LLS_ABI_VERSION_VAR LLS_CONCAT_EXPAND(lls_abi_version, LLS_ABI_VERSION)
+
+/*
+ * Declare the variable. It is defined in lopsub.c, which is part of the
+ * library. The single mission of this variable is to cause an error at link
+ * time on ABI version mismatches. The actual value of the variable is
+ * irrelevant. When the lopsubgen utility translates a suite into a .c file,
+ * the generated C code references this variable, using the *lopsubgen* ABI
+ * version for the reference. Therefore, if the lopsubgen ABI version differs
+ * from the ABI version of the library, the symbol can not be resolved,
+ * resulting in a link error.
+ */
+extern const unsigned LLS_ABI_VERSION_VAR;
+
+/*
+ * Options may have an optional or mandatory argument.
+ *
+ * The lobsubgen command initializes the ->arg_info field of each lls_option
+ * structure from the corresponding line of the .suite file.
+ *
+ * See also: ->arg_info of struct lls_option.
+ */
+enum lls_arg_info {
+       LLS_NO_ARGUMENT, /* Option does not take an argument. */
+       LLS_REQUIRED_ARGUMENT, /* An argument must be given. */
+       LLS_OPTIONAL_ARGUMENT, /* Option takes an optional argument. */
+};
+
+/*
+ * In addition to the argument type, there is a set of flags associated with
+ * each option. All but the LLS_IGNORED flag correspond directly to the
+ * possible values of the flag directive in the suite file.
+ */
+enum lls_option_flag {
+       /* Store each given argument (rather than only the last). */
+       LLS_MULTIPLE = 1,
+       /* It's an error if the option is not given. */
+       LLS_REQUIRED = 2,
+       /* Whether a default value was specified in the suite file. */
+       LLS_HAS_DEFAULT = 4,
+       /* For help text not related to any particular option. */
+       LLS_IGNORED = 8,
+};
+
+/*
+ * Ignored if arg_info is LLS_NO_ARGUMENT. A value of zero means no argument.
+ * We don't need an identifier like LLS_NONE though, because we can test the
+ * arg_info field to tell whether an argument can be supplied.
+ *
+ * See also: union lls_val.
+ */
+enum lls_arg_type {
+       /* Option takes a string argument. */
+       LLS_STRING = 1,
+       /* Signed 32 bit integer. */
+       LLS_INT32,
+       /* Unsigned 32 bit integer. */
+       LLS_UINT32,
+       /* Signed 64 bit integer. */
+       LLS_INT64,
+       /* Unsigned 64 bit integer. */
+       LLS_UINT64,
+};
+
+/*
+ * Stores the argument to an option.
+ *
+ * The default value of an option, the set of possible values for an option and
+ * the actually given value are all stored in an instance of this union.
+ *
+ * To determine the relevant alternative of the union, both the ->arg_type and
+ * the ->values field of the corresponding struct lls_option are
+ * important. For the values array itself, ->arg_type always determines the
+ * alternative of each element in the array. For other objects the following
+ * rule applies. If ->values is not given, ->arg_type determines the
+ * alternative for both ->default_val and lls_opt_result->value. On the other
+ * hand, if ->values is given, the ->uint32_val alternative is the index into
+ * the ->values array that indicates which of the possible values is the
+ * default value (for ->default_val) or was given in the arguments to
+ * lls_parse() (lls_opt_result).
+ */
+union lls_val {
+       char *string_val; /* LLS_STRING */
+       int32_t int32_val; /* LLS_INT32 */
+       uint32_t uint32_val; /* LLS_UINT32 */
+       int64_t int64_val; /* LLS_INT64 */
+       uint64_t uint64_val; /* LLS_UINT64 */
+};
+
+/*
+ * Describes one option of one command of a suite.
+ *
+ * For each option of every command in the .suite file, lopsubgen
+ * generates one structure of this type.
+ */
+struct lls_option {
+       /* The long name (short-only options are not supported). */
+       const char * const name;
+       /* Optional one-character variant. */
+       const char short_opt;
+       /* Mandatory one-line summary. */
+       const char * const summary;
+       /* Whether the option takes an argument, and if it is mandatory. */
+       const enum lls_arg_info arg_info;
+       /* Which alternative of the lls_val union is chosen. */
+       const enum lls_arg_type arg_type;
+       /* Description of the type of values (used for help output). */
+       const char * const typestr;
+       /* See enum lls_option_flag. */
+       const unsigned flags;
+       /* If not given, integer values default to 0, strings to NULL. */
+       const union lls_val default_val;
+       /* Optional multi-line help text. */
+       const char * const help;
+       /* If this is not NULL, only the given values are allowed. */
+       const union lls_val * const values;
+};
+
+/* Describes a command of a suite. */
+struct lls_command {
+       /* Only identifiers are allowed as command names. */
+       const char * const name;
+       /* One line of text, shown in all variants of help text. */
+       const char * const purpose;
+       /* Multi line text in long help and man page. */
+       const char * const description;
+       /* How non-option arguments should be called in the synopsis. */
+       const char * const non_opts_name;
+       /* Optional, will be computed if not given. */
+       const char * const synopsis;
+       /* Array of options for this command. */
+       const struct lls_option * const options;
+       /* Closing remarks after the option list in long help and man page. */
+       const char * const closing;
+       /* Initialized to com_NAME_user_data with NAME being the command name. */
+       const void * const user_data;
+       /* Contains the array size of ->options. */
+       const int num_options;
+};
+
+struct lls_suite {
+       const char * const name;
+       const char * const caption;
+       const struct lls_command * const commands;
+       const int num_subcommands;
+};
+
+struct lls_opt_result {
+       unsigned given;
+       /* for multiple options, one value for each time the option was given */
+       union lls_val *value;
+};
+
+struct lls_parse_result {
+       char **inputs; /* non-options, non-args */
+       unsigned num_inputs;
+       struct lls_opt_result *opt_result; /* one per option */
+};
+
+/*
+ * Most library functions return a negative error code on failure. The
+ * LLS_ERRORS macro expands to a list of all possible errors, optionally
+ * including the text of each error code. The LLS_ERROR macro (without the
+ * trailing S) controls how each error is expanded.
+ */
+#define LLS_ERRORS \
+       LLS_ERROR(SUCCESS, "success") \
+       LLS_ERROR(NOMEM, "allocation failure") \
+       LLS_ERROR(BAD_OPTION, "option not recognized") \
+       LLS_ERROR(AMBIG_OPTION, "option is ambiguous") \
+       LLS_ERROR(OPT_MANDATORY, "mandatory option not given") \
+       LLS_ERROR(ARG_GIVEN, "argument given to non-arg option") \
+       LLS_ERROR(NO_ARG_GIVEN, "argument required but not given") \
+       LLS_ERROR(TRAILING_BACKSLASH, "unexpected trailing backslash") \
+       LLS_ERROR(UNMATCHED_QUOTE, "unmatched quote character") \
+       LLS_ERROR(TRAILING_GARBAGE, "garbage at end of line or argument") \
+       LLS_ERROR(YY_SCAN, "error setting up lex input buffer") \
+       LLS_ERROR(YY_LEX, "yylex() failed") \
+       LLS_ERROR(BAD_SUBCMD, "invalid subcommand") \
+       LLS_ERROR(AMBIG_SUBCMD, "ambiguous subcommand") \
+       LLS_ERROR(BAD_ARG_COUNT, "invalid number of arguments") \
+       LLS_ERROR(OVERFLOW, "value too large") \
+       LLS_ERROR(NO_DIGITS, "no digits found in string") \
+       LLS_ERROR(ENUM, "invalid value for enum option") \
+
+/*
+ * Given an identifier and a string literal, forget the string, and prefix the
+ * identifier with E_LLS_ to produce an identifier for an error code like
+ * E_LLS_OVERFLOW. Finally, append a comma. While this definition of LLS_ERROR
+ * is active, LLS_ERRORS expands to a comma-separated list of error codes. See
+ * enum lls_errors below.
+ */
+#define LLS_ERROR(_n, _s) E_LLS_ ## _n,
+
+/*
+ * Declare the enumeration of all error codes as identifiers like like
+ * E_LLS_OVERFLOW. Also, the total number of error codes is declared as
+ * NUM_LLS_ERRORS.
+ */
+enum lls_errors {LLS_ERRORS NUM_LLS_ERRORS};
+#undef LLS_ERROR
diff --git a/lopsub-suite.5.m4 b/lopsub-suite.5.m4
new file mode 100644 (file)
index 0000000..d453c9c
--- /dev/null
@@ -0,0 +1,446 @@
+.TH lopsub-suite 5 "DATE()" GIT_VERSION()
+.SH NAME
+lopsub-suite \- lopsub suite syntax
+.SH DESCRIPTION
+A
+.B lopsub suite
+describes the options to a command line utility with zero or more
+related subcommands. The
+.BR lopsubgen (1)
+utility translates a lopsub suite into either C source code or a manual
+page. The generated C code can be compiled and linked against the
+lopsub library to produce command line parsers for the main command
+and all subcommands defined in the suite.
+
+This document explains the format of a lopsub suite. The overall
+structure is as follows:
+
+.EX
+       [suite mysuite]
+       suite directives
+
+       [supercommand sup_cmd]
+               command directives for sup_cmd
+               [option opt1]
+               ... (further options)
+
+       [subcommand sub1]
+               command directives for sub1
+               [option opt_1_to_sub1]
+                       option directives for opt_1
+               ... (further options)
+
+       ... (further subcommands)
+
+       [section see also]
+       ... (optional extra section for man page)
+
+       ... (further extra sections)
+.EE
+
+A suite begins with a
+.B [suite]
+line which declares the name of the suite. The
+.B suite directives,
+all subsequent lines up to the first
+.B [supercommand]
+or
+.B [subcommand]
+line, state further properties of the suite which are unrelated to
+any particular command, for example the version number. All available
+suite directives are summarized below.
+
+The
+.B [supercommand]
+and
+.B [subcommand]
+lines indicate the beginning of a command of the suite. The part between this
+line and the first
+.B [option]
+line contains the
+.B command directives,
+for example the purpose and the description of the named command.
+See the section on command directives below.
+
+Supercommands and subcommands share the same set of possible command
+directives. They differ mainly in the way the documentation is
+formated. There can only be one supercommand but arbitrary many
+subcommands. For example, the supercommand could be the name of
+the application, and the subcommands could be "load", "save" "info"
+and "quit". The subcommand would be passed as the first non-option
+argument to the supercommand, followed by options specific to that
+subcommand.
+
+Of course it is possible to define no subcommands at all. Conversely,
+one can define only subcommands but no supercommand. This makes
+sense if the commands are run by some other means, for example in an
+interactive session from a command prompt.
+
+Within the command section, an
+.B [option]
+line starts an option to the current command. It is followed by
+.B option directives,
+which specify, for example, whether or not the option takes
+an argument. All supported option directives are listed in the
+corresponding section below.
+
+Further material for man output can be included between the
+.B [section]
+and
+.B [/section]
+markers.
+This text will not be included in the generated .c and .h files and
+is thus also not part of the short and long help available at run
+time. The text is not interpreted except that leading whitespace is
+stripped from each line. Arbitrary roff source can be included here.
+
+Empty lines and lines starting with a hash character (#) are
+ignored.
+.SH SUITE DIRECTIVES
+Most directives of this section are only relevant for man page output (with
+.B aux_info_default
+being the exception), they are ignored for C output.
+.TP
+.B caption
+The optional text for an unnumbered section heading at the beginning of the
+manual page.
+.TP
+.B title
+Sets the title of the man page. Defaults to the name of the
+supercommand. If this is not given and the suite has no supercommand,
+the .TH macro to set the title of the man page is omitted.
+.TP
+.B mansect
+Sets the man page section. Defaults to 1 (user commands). Both title
+and section are positioned at the left and right in the header line.
+.TP
+.B date
+This text is positioned in the middle of the footer line of the man page. It is
+common to set this to the date of the last nontrivial change that was made to
+the man page. Defaults to the current date.
+.TP
+.B version-string
+Positioned at the left in the footer line. Defaults to the empty string. The
+value of this directive is ignored if a version string is explicitly requested
+by passing
+.B --version-string
+to
+.BR losubgen .
+.TP
+.B manual_title
+Centered in the header line. Defaults to "User commands".
+.TP
+.B introduction
+.TQ
+.B conclusion
+The text enclosed between
+.B [introduction]
+and
+.B [/introduction]
+is shown between the supercommand (if any) and the subcommand list.
+Concluding remarks after the subcommand list may be added in the same
+way with
+.B [conclusion]
+and
+.BR [/conclusion] .
+Both texts will become part of the manual page, but are not not part
+of the short or long help. Like for the
+.B section
+directive, arbitrary roff source may be included here.
+.TP
+.B aux_info_prefix
+This text is shown at the bottom of each command before the value of the
+aux_info directive. If no
+.B aux_info
+is specified for a command, the prefix is omitted as well. If
+.B aux_info_prefix
+is not given, the empty string is assumed.
+.TP
+.B aux_info_default
+This text is only used for header output.  The argument for the generated macro
+call is set to this value for all commands for which no
+.B aux_info
+directive is given. If no
+.B aux_info_default
+directive is given, the value 0 is used as the argument for the macro.
+.SH COMMAND DIRECTIVES
+.TP
+.B purpose
+A single line containing the purpose of the command. This text is printed in
+the short and long help, and is also included in the man page.
+.TP
+.B description
+.TQ
+.B closing
+Arbitrary plain text enclosed between
+.B [description]
+and
+.BR [/description] .
+The text may be split over multiple lines and paragraphs. The
+description of the supercommand (if any) becomes the description of
+the manual page, which is shown after the command summary and before
+the list of options. The descriptions of all commands are included
+in the manual page and in the long help text but not in the short help.
+
+Closing remarks for the command can be added in a similar way by enclosing
+plain text between
+.B [closing]
+and
+.BR [/closing] .
+This text will be positioned after the option list.
+.TP
+.B synopsis
+Custom synopsis of the command. If this is not given, the synopsis text will be
+auto-generated from the options and the value of the
+.B non-opts-name
+directive.
+.TP
+.B non-opts-name
+Name of the command line arguments which are not related to any option,
+for example file names. This text will be included in the automatically
+generated synopsis text.
+
+If this is not given, the command is assumed to take no arguments other than
+the specified options and their arguments. For such commands the attempt to
+parse an argv[] vector fails if it contains further non-option arguments.
+.TP
+.B aux_info
+This directive is special because its value is not included in the generated .c
+file but in the header file. More precisely, the preprocessor macro
+.B \%LSG_SUITENAME_AUX_INFOS
+will be defined which expands to a series of macro calls to
+.B \%LSG_SUITENAME_AUX_INFO(val_n),
+one for each command of the suite, where
+.B val_n
+is the (unquoted) value of the
+.B aux_info
+directive of the nth command. Commands for which no
+.B aux_info
+directive was specified receive a value of zero. The
+.B \%LSG_SUITENAME_AUX_INFO
+macro is supposed to be defined by the application.
+Hence it is up to the application to make the expansion of
+.B \%LSG_SUITENAME_AUX_INFOS
+a valid C construct.
+
+The value, if specified, is also copied to the man page at the end of the
+section for the command.
+
+.SH OPTION DIRECTIVES
+.TP
+.B short_opt
+The optional single-letter equivalent for the option. If this is
+specified, the option may be given either in the GNU-style long
+option format with two leading dashes or in the short form with a
+single dash. Otherwise only the long form will be accepted. As usual,
+multiple short option flags may be combined.
+.TP
+.B summary
+A single line which summarizes the option. This text is included in
+both the short and the long help and in the man page. Defaults to
+the empty string.
+.TP
+.B typestr
+A description for the type of the values for the option. The given text
+is printed in the synopsis of the command, which is part of the short
+and the long help and the man page. It defaults to the string "val".
+
+This directive is ignored for flag options (options without an argument).
+.TP
+.B arg_info
+This directive determines whether the option takes an argument. The
+possible values are
+.B no_arg, required_arg
+and
+.B optional_arg
+which indicate, respectively, that the option takes no argument at all,
+an argument which is mandatory, or an argument which may be omitted. The
+default value is
+.B no_arg.
+Hence an option works as a flag if the
+.B arg_info
+directive is not given.
+
+Note that arguments to options which take an optional argument must
+be given as --foo=bar rather than --foo bar because the latter form
+would be ambiguous.
+.TP
+.B arg_type
+For flag options this directive should be set to
+.B none,
+or not set at all. For options which take an argument, the value of the directive
+determines the type of the argument.
+Possible values are
+.B string
+for options which take a string argument, and
+.B int32,
+.B uint32.
+.B int64,
+.B uint64,
+for options which take a numeric argument.
+.TP
+.B flag
+Lopsub maintains for each option a bitmask which contains the value
+of each possible flag for this option. Flags may be accumulated by
+defining multiple flag directives in the suite. Note there is no
+equal sign between the
+.B flag
+directive and its value.
+
+The following flags are defined.
+.RS
+.B
+.IP multiple
+This flag instructs the lopsub library to keep track of all given
+arguments to an option, not just one as for ordinary options. This
+is only relevant for options which take an (optional or required)
+argument.
+.B
+.IP required
+Instruct the parser to fail if this option is not given in the
+argument vector. If an option may be given at the command line or
+in the config file, this flag should be avoided because the command
+line argv vector will not be parsed successfully if the option is
+only given in the config file. The recommended way to deal with
+this situation is to parse command line and config file separately,
+then merge the two parse results and check in the application if the
+option is given in the merged parse result.
+.B
+.IP ignored
+This flag indicates that the current option is in fact not a real option.
+In particular,
+.B name
+and
+.B short_opt
+are both ignored. The purpose of this flag is to add additional
+information for the help output and the man page.
+.RE
+.TP
+.B values
+Create an enumerable option.
+
+Enumerable options take one out of a fixed set of possible values
+which are predefined in the suite. Such options are always of type
+string. It is an error if a different argument type was specified or
+if the option was defined to not take an argument.
+
+The syntax for the array of values is
+
+.EX
+       values = {ID_0 = "string_0", ..., ID_N = "string_N"}
+.EE
+
+For each option for which the
+.B values
+directive was specified, the lopsubgen command generates a C
+enumeration which contains the given identifiers. This allows to
+refer to each possible value through a numeric constant.
+.TP
+.B default_val
+This directive makes only sense for options which take an argument. For
+such options it defines the value the option parser provides
+automatically if the option is not given in the argument vector.
+
+If no
+.B default_val
+is specified in the suite, and the option is not given in the argument
+vector either, the implied value depends on the argument type. For
+numeric options, a value of zero is assumed. For options which take
+a string argument, a NULL pointer is returned. For enum options,
+the first possible value (index zero) is taken as the default.
+.TP
+.B help
+The detailed, multi-line help text for the current option. Included
+in the man page and the long help.
+.SH EXAMPLE
+A minimal suite and the corresponding "application".
+
+The suite file
+.IR hello.suite :
+
+.EX
+       [suite hello]
+       [supercommand hello]
+               purpose = hello world
+               [option world]
+                       summary = add "world"
+.EE
+
+The "application"
+.IR hello.c :
+
+.EX
+       #include <stdio.h> /* printf(3) */
+       #include <stdlib.h> /* exit(3) */
+       #include <lopsub.h>
+       #include "hello.lsg.h"
+
+       int main(int argc, char **argv)
+       {
+               struct lls_parse_result *lpr;
+               const struct lls_opt_result *result;
+               const struct lls_command *cmd = lls_cmd(0, hello_suite);
+               int ret;
+
+               ret = lls_parse(argc, argv, cmd, &lpr, NULL);
+               if (ret < 0) {
+                       fprintf(stderr, "%s\\n", lls_strerror(-ret));
+                       exit(1);
+               }
+               printf("hello");
+               result = lls_opt_result(LSG_HELLO_HELLO_OPT_WORLD, lpr);
+               if (lls_opt_given(result))
+                       printf(" world");
+               printf("\\n");
+               exit(0);
+       }
+.EE
+
+Generate
+.I hello.lsg.c
+and
+.IR hello.lsg.h :
+
+.EX
+       $ lopsubgen --gen-c --gen-h < hello.suite
+.EE
+
+Compile
+.I hello.c
+and
+.I hello.lsg.c
+to produce the
+.I hello
+executable:
+
+.EX
+       $ cc hello.c hello.lsg.c -o hello -llopsub
+.EE
+
+Run it:
+
+.EX
+       $ ./hello
+       hello
+       $ ./hello --world
+       hello world
+       $ ./hello -w
+       option not recognized
+       $ ./hello 42
+       invalid number of arguments
+.EE
+
+Generate and examine the manual page:
+
+.EX
+       $ lopsubgen --gen-man < hello.suite
+       $ man -l ./hello.lsg.man
+.EE
+
+.SH SEE ALSO
+.BR lopsubgen (1),
+.BR lopsub (7),
+.BR lopsubex (1),
+.BR gengetopt (1),
+.BR getopt (1),
+.BR getopt (3)
diff --git a/lopsub.7.m4 b/lopsub.7.m4
new file mode 100644 (file)
index 0000000..ea5c123
--- /dev/null
@@ -0,0 +1,183 @@
+.\" groff -m man -Thtml <file>
+.\" See groff_www(7), groff(7) and groff_man(7)
+
+.\" save current font, switch to monospace font, output, switch back
+.de MONO
+.nr mE \\n(.f
+.ft CW
+\\$*
+.ft \\n(mE
+..
+
+.\" make text on the same line appear alternately in monospace and roman
+.de MONO_ROMAN
+.  if \\n[.$] \{\
+.    ds an-result \&\f[CW]\\$1\f[R]\"
+.    shift
+.    while (\\n[.$] >= 2) \{\
+.      as an-result \/\\$1\f[CW]\,\\$2\f[R]\"
+.      shift 2
+.    \}
+.    if \\n[.$] .as an-result \/\\$1\"
+\\*[an-result]
+.    ft R
+.  \}
+..
+
+. if !'\*(.T'html' \
+. do ftr CW I
+. \}
+
+.TH lopsub 7 "DATE()" GIT_VERSION()
+.SH About
+
+.B Lopsub
+is an open source library written in
+.B C
+which aims to ease the task of creating, documenting and parsing
+the options of Unix command line utilities. It is suitable for
+simple commands as well as complex command line utilities with many
+subcommands where each subcommand has its own set of options. Options
+and documentation are kept together in a single file which can be
+translated to
+.B C
+code (to be included in the application), or to a manual page.
+The library supports single-character short options and GNU-style
+long options. The public API is well documented and stable.
+
+To make use of the library, the programmer provides a so-called
+.I suite file
+which describes the options of the application in an intuitive
+syntax. At build time the suite file is translated into
+.B C
+code by the
+.MONO lopsubgen
+utility which also ships with the
+.B lopsub
+package. The generated code defines an instance of an opaque
+.B C
+structure and exposes a reference to this structure which can be passed to
+the
+.I option parser
+at run time, together with the usual argument vector. The
+option parser is part of the
+.B lopsub
+.IR library ,
+so applications need to link with
+.MONO_ROMAN -llopsub .
+In addition to the option parser, the library offers many more
+features. For example, there is a function for merging two different
+.I parse results
+to generate an effective configuration. This is useful for applications
+which can be configured through command line options and via a
+config file.
+
+The suite file can also be processed into roff format to create
+a manual page. Conversion into html can easily be performed with
+tools like
+.MONO grohtml
+(part of
+.BR "GNU roff" )
+or
+.MONO_ROMAN man2html .
+
+.B Lopsub
+does not rely on the system's
+.MONO getopt()
+or
+.MONO getopt_long()
+functions of the C library and is thus portable across different
+flavors of Unix. It is known to work on
+.BR Linux ,
+.B NetBSD
+and
+.BR FreeBSD .
+
+.SH License
+The
+.B lopsub library
+is licensed under the
+.B LGPLv3
+while the
+.MONO lopsubgen
+utility is licensed under the
+.BR GPLv3 .
+The examples and all code generated by the utilities, however, is
+licensed with a simple all-permissive license. You are free to do
+anything you like with the generated code, including incorporating
+it into or linking it with proprietary software.
+
+.SH Installation
+Grab your copy with
+.IP
+.EX
+git clone git://git.tuebingen.mpg.de/lopsub.git
+.EE
+.PP
+Then build the package with
+.IP
+.EX
+make
+.EE
+.PP
+The suite parser and the config file parser of the
+.B lopsub
+library are both generated by running
+.MONO_ROMAN flex ,
+a tool for generating programs that perform pattern-matching on text. Hence the
+.B flex
+package must be installed for the build to succeed. Next, run
+.IP
+.EX
+sudo make install
+.EE
+.PP
+This will install the package in
+.MONO_ROMAN /usr/local .
+If you prefer to install as an unprivileged user in
+.MONO_ROMAN /somewhere/else ,
+run
+.IP
+.EX
+make PREFIX=/somewhere/else install
+.EE
+.PP
+instead. In this case, you need to specify
+.MONO -I/somewhere/else/include
+to compile the source files of your application and
+.MONO -L/somewhere/else/lib
+for linking. Alternatively, don't run
+.MONO make install
+at all and specify the path to the top level directory of the
+repository for both
+.MONO -I
+and
+.MONO_ROMAN -L .
+
+.SH Quick Start
+Compile and run the minimal example included at the end of
+.UR ./lopsub-suite.5.html
+lopsub-suite(5)
+.UE .
+Examine
+.MONO lopsubex.c
+and
+.MONO lopsubex.suite
+in the source tree and run the
+.MONO lopsubex
+command.
+
+.SH API documentation
+See
+.UR ./lopsub-api.html
+.UE .
+Alternatively, examine
+.MONO lopsub.h
+or
+.MONO_ROMAN lopsub.h.m4 .
+
+.SH
+Reporting Bugs
+.MT maan@tuebingen.mpg.de
+Andre Noll
+.ME
diff --git a/lopsub.c b/lopsub.c
new file mode 100644 (file)
index 0000000..2ba2621
--- /dev/null
+++ b/lopsub.c
@@ -0,0 +1,1610 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+
+#include "lopsub-internal.h"
+#include "lopsub.h"
+
+/* For detecting version mismatches, see lopsub-internal.h. */
+const unsigned LLS_ABI_VERSION_VAR = 0;
+
+#define FOR_EACH_OPTION(_i, _opts) \
+       for (_i = 0; (_opts) && (_opts)[(_i)].name; (_i)++)
+
+#define FOR_EACH_OPTION_IN_COMMAND(_opt, _cmd) \
+       for ( \
+               (_opt) = (_cmd)->options; \
+               (_opt) && (_opt) < (_cmd)->options + (_cmd)->num_options; \
+               (opt)++ \
+       )
+
+/* The result of parsing one option and its arguments. */
+struct lls_arg {
+       int idx; /* index into either argv[] or the lls_option array. */
+       const char *arg; /* NULL if option has no argument. */
+};
+
+/*
+ * This structure, and the exchange_args(), decode_option() and parse_option()
+ * functions below are inspired by the glibc implementation of getopt.c,
+ * Copyright (C) 1987-2015 Free Software Foundation, Inc.
+ */
+struct lls_data {
+       const struct lls_option *opts;
+       int argc;
+       char *const *argv;
+
+       int optind; /* index into argv[] which we are parsing. */
+       const char *next_char;
+       /*
+        * These describe the part of argv[] that contains non-options that
+        * have been skipped. first_nonopt is the index in argv[] of the first
+        * of them, last_nonopt is the index after the last of them. Initially
+        * both indices are zero.
+        */
+       int first_nonopt;
+       int last_nonopt;
+};
+
+const struct lls_command *lls_cmd(unsigned cmd_num,
+               const struct lls_suite *suite)
+{
+       if (cmd_num > suite->num_subcommands)
+               return NULL;
+       return suite->commands + cmd_num;
+}
+
+const char *lls_command_name(const struct lls_command *cmd)
+{
+       return cmd->name;
+}
+
+const void *lls_user_data(const struct lls_command *cmd)
+{
+       return cmd->user_data;
+}
+
+const struct lls_option *lls_opt(unsigned opt_num,
+               const struct lls_command *cmd)
+{
+       return cmd->options + opt_num;
+}
+
+const struct lls_opt_result *lls_opt_result(unsigned opt_num,
+               const struct lls_parse_result *lpr)
+{
+       return lpr->opt_result + opt_num;
+}
+
+unsigned lls_opt_given(const struct lls_opt_result *r)
+{
+       return r->given;
+}
+
+const char *lls_enum_string_val(unsigned idx, const struct lls_option *opt)
+{
+       return opt->values[idx].string_val;
+}
+
+const char *lls_string_val(unsigned idx, const struct lls_opt_result *r)
+{
+       return r->value[idx].string_val;
+}
+
+int32_t lls_int32_val(unsigned idx, const struct lls_opt_result *r)
+{
+       return r->value[idx].int32_val;
+}
+
+uint32_t lls_uint32_val(unsigned idx, const struct lls_opt_result *r)
+{
+       return r->value[idx].uint32_val;
+}
+
+int64_t lls_int64_val(unsigned idx, const struct lls_opt_result *r)
+{
+       return r->value[idx].int64_val;
+}
+
+uint64_t lls_uint64_val(unsigned idx, const struct lls_opt_result *r)
+{
+       return r->value[idx].uint64_val;
+}
+
+unsigned lls_num_inputs(const struct lls_parse_result *lpr)
+{
+       return lpr->num_inputs;
+}
+
+const char *lls_purpose(const struct lls_command *cmd)
+{
+       return cmd->purpose;
+}
+
+const char *lls_input(unsigned input_num, const struct lls_parse_result *lpr)
+{
+       return lpr->inputs[input_num];
+}
+
+const char *lls_strerror(int lss_errno)
+{
+       #define LLS_ERROR(_n, _s) _s,
+       static const char * const error_string[] = {LLS_ERRORS NULL};
+       #undef LLS_ERROR
+       return error_string[lss_errno];
+}
+
+static int xrealloc(void *p, size_t size)
+{
+       void **pp = p, *newp = realloc(*pp, size);
+
+       if (!newp)
+               return -E_LLS_NOMEM;
+       *pp = newp;
+       return 0;
+}
+
+/* Print a formated message to a dynamically allocated string. */
+__attribute__ ((format (printf, 2, 0)))
+static int xvasprintf(char **result, const char *fmt, va_list ap)
+{
+       int ret;
+       size_t size = 150;
+       va_list aq;
+
+       if (!result)
+               return 0;
+       if (*result)
+               free(*result);
+       *result = malloc(size + 1);
+       if (!*result)
+               return -E_LLS_NOMEM;
+       va_copy(aq, ap);
+       ret = vsnprintf(*result, size, fmt, aq);
+       va_end(aq);
+       assert(ret >= 0);
+       if (ret < size) /* OK */
+               return ret;
+       size = ret + 1;
+       ret = xrealloc(result, size);
+       if (ret < 0) {
+               free(*result);
+               *result = NULL;
+               return ret;
+       }
+       va_copy(aq, ap);
+       ret = vsnprintf(*result, size, fmt, aq);
+       va_end(aq);
+       assert(ret >= 0 && ret < size);
+       return ret;
+}
+
+/* Print to a dynamically allocated string, variable number of arguments. */
+__attribute__ ((format (printf, 2, 3)))
+static int xasprintf(char **result, const char *fmt, ...)
+{
+       va_list ap;
+       unsigned ret;
+
+       va_start(ap, fmt);
+       ret = xvasprintf(result, fmt, ap);
+       va_end(ap);
+       return ret;
+}
+
+static inline unsigned num_vals_in_parse_result(const struct lls_command *cmd,
+       int opt_num, const struct lls_parse_result *lpr)
+{
+       const struct lls_option *opt = cmd->options + opt_num;
+       struct lls_opt_result *lor = lpr->opt_result + opt_num;
+
+       if (opt->arg_info == LLS_NO_ARGUMENT)
+               return 0;
+       if (lor->given == 0)
+               return 1; /* for the default value */
+       if (!(opt->flags & LLS_MULTIPLE))
+               return 1;
+       return lor->given;
+}
+
+union atoi_result {
+       int32_t int32;
+       uint32_t uint32;
+       int64_t int64;
+       uint64_t uint64;
+};
+
+enum atoi_mode {ATOI_INT32, ATOI_UINT32, ATOI_INT64, ATOI_UINT64};
+
+/*
+ * Convert a string to a 32 or 64 bit signed or unsigned integer value.
+ *
+ * For conversions to unsigned integers, negative values are considered valid
+ * input and are silently converted.
+ */
+static int lls_atoi(const char *str, enum atoi_mode mode, union atoi_result *value)
+{
+       char *endptr;
+       union atoi_result result;
+
+       memset(value, 0, sizeof(*value));
+       errno = 0; /* To distinguish success/failure after call */
+       /*
+        * We pass zero as the base to strtoll(3) and strtoull(3) to let the
+        * function recognize an optional base prefix like "0x".
+        */
+       if (mode == ATOI_UINT64) {
+               unsigned long long tmp = strtoull(str, &endptr, 0);
+               if (errno == ERANGE && tmp == ULLONG_MAX)
+                       return -E_LLS_OVERFLOW;
+               result.uint64 = tmp;
+       } else { /* parse as signed 64 bit and check range */
+               long long tmp = strtoll(str, &endptr, 0);
+               if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN))
+                       return -E_LLS_OVERFLOW;
+               switch (mode) {
+               case ATOI_INT64: /* no additional range check necessary */
+                       result.int64 = tmp;
+                       break;
+               case ATOI_INT32:
+                       if (tmp < INT_MIN || tmp > INT_MAX)
+                               return -E_LLS_OVERFLOW;
+                       result.int32 = tmp;
+                       break;
+               case ATOI_UINT32:
+                       if (tmp > UINT_MAX)
+                               return -E_LLS_OVERFLOW;
+                       result.uint32 = tmp;
+                       break;
+               default:
+                       assert(0);
+               }
+       }
+       /*
+        * If there were no digits at all, strtol() and friends store the
+        * original value of str in *endptr.
+        */
+       if (endptr == str)
+               return -E_LLS_NO_DIGITS;
+       /*
+        * The implementation may also set errno (and return 0) in case no
+        * conversion was performed.
+        */
+       if (errno != 0)
+               return -E_LLS_NO_DIGITS;
+       if (*endptr != '\0') /* Further characters after number */
+               return -E_LLS_TRAILING_GARBAGE;
+       *value = result;
+       return 1;
+}
+
+static int atoi32(const char *str, int32_t *result)
+{
+       union atoi_result ar;
+       int ret = lls_atoi(str, ATOI_INT32, &ar);
+       *result = ar.int32;
+       return ret;
+}
+
+static int atou32(const char *str, uint32_t *result)
+{
+       union atoi_result ar;
+       int ret = lls_atoi(str, ATOI_UINT32, &ar);
+       *result = ar.uint32;
+       return ret;
+}
+
+static int atoi64(const char *str, int64_t *result)
+{
+       union atoi_result ar;
+       int ret = lls_atoi(str, ATOI_INT64, &ar);
+       *result = ar.int64;
+       return ret;
+}
+
+static int atou64(const char *str, uint64_t *result)
+{
+       union atoi_result ar;
+       int ret = lls_atoi(str, ATOI_UINT64, &ar);
+       *result = ar.uint64;
+       return ret;
+}
+
+static void free_opt_result(int opt_num, struct lls_parse_result *lpr,
+               const struct lls_command *cmd)
+{
+       const struct lls_option *opt = cmd->options + opt_num;
+
+       if (opt->arg_type == LLS_STRING && !opt->values) {
+               unsigned num_vals = num_vals_in_parse_result(cmd, opt_num, lpr);
+               int j;
+               for (j = 0; j < num_vals; j++)
+                       if (lpr->opt_result[opt_num].value)
+                               free(lpr->opt_result[opt_num].value[j].string_val);
+       }
+       if (opt->arg_info != LLS_NO_ARGUMENT)
+               free(lpr->opt_result[opt_num].value);
+}
+
+void lls_free_parse_result(struct lls_parse_result *lpr,
+               const struct lls_command *cmd)
+{
+       int i;
+
+       if (!lpr)
+               return;
+       if (lpr->inputs)
+               for (i = 0; i < lpr->num_inputs; i++)
+                       free(lpr->inputs[i]);
+       free(lpr->inputs);
+       if (lpr->opt_result)
+               FOR_EACH_OPTION(i, cmd->options)
+                       free_opt_result(i, lpr, cmd);
+       free(lpr->opt_result);
+       free(lpr);
+}
+
+static struct lls_data *init_lls_data(const struct lls_option *opts,
+               int argc, char *const *argv)
+{
+       struct lls_data *d = malloc(sizeof(*d));
+
+       if (!d)
+               return NULL;
+       d->optind = 0;
+       /* start with an empty non-option list */
+       d->first_nonopt = d->last_nonopt = d->optind;
+       d->opts = opts;
+       d->argc = argc;
+       d->argv = argv;
+       d->next_char = NULL;
+       return d;
+}
+
+/*
+ * Exchange two adjacent subsets of argv[].
+ *
+ * One subset is given by indices {first_nonopt, ..., last_nonopt - 1}. It
+ * contains all the non-options that have been skipped so far. The other subset
+ * corresponds to indices {last_nonopt, ... optind - 1} which contains all the
+ * options processed since those non-options were skipped.
+ *
+ * Before the function returns, ->first_nonopt and ->last_nonopt are updated to
+ * describe the new set of non-options in argv[].
+ */
+static void exchange_args(struct lls_data *d)
+{
+       int bottom = d->first_nonopt;
+       int middle = d->last_nonopt;
+       int top = d->optind;
+       char **argv = (char **)d->argv;
+
+       /*
+        * Exchange the shorter segment with the far end of the longer segment.
+        * That puts the shorter segment into the right place. It leaves the
+        * longer segment in the right place overall, but it consists of two
+        * parts that need to be swapped next.
+       */
+       while (top > middle && middle > bottom) {
+               if (top - middle > middle - bottom) {
+                       /* Bottom segment is the short one.  */
+                       int i, len = middle - bottom;
+
+                       /* Swap it with the top part of the top segment. */
+                       for (i = 0; i < len; i++) {
+                               char *tmp = argv[bottom + i];
+                               argv[bottom + i] = argv[top - (middle - bottom) + i];
+                               argv[top - (middle - bottom) + i] = tmp;
+                       }
+                       /* Exclude the moved bottom segment from further swapping. */
+                       top -= len;
+               } else {
+                       /* Top segment is the short one.  */
+                       int i, len = top - middle;
+                       /* Swap it with the bottom part of the bottom segment. */
+                       for (i = 0; i < len; i++) {
+                               char *tmp = argv[bottom + i];
+                               argv[bottom + i] = argv[middle + i];
+                               argv[middle + i] = tmp;
+                       }
+                       /* Exclude the moved top segment from further swapping. */
+                       bottom += len;
+               }
+       }
+       /* Update records for the slots the non-options now occupy. */
+       d->first_nonopt += d->optind - d->last_nonopt;
+       d->last_nonopt = d->optind;
+}
+
+/* whether arg points to an option argument */
+static inline bool is_option(const char *arg)
+{
+       return arg[0] == '-' && arg[1] != '\0';
+}
+
+static void check_errctx(char **errctx, int ret)
+{
+       if (!errctx)
+               return;
+       if (ret >= 0)
+               assert(!*errctx); /* memory leak/uninitialized pointer */
+       else if (ret != -E_LLS_NOMEM)
+               assert(*errctx); /* we must provide an error message */
+}
+
+/*
+ * Decode the current option. On success, set result->idx to the index in the
+ * ->options array which was decoded successfully. On failure, result->idx is
+ * the index in argv[] which could not be parsed.
+ */
+static int decode_option(struct lls_data *d, struct lls_arg *result,
+               char **errctx)
+{
+       const char *word = d->argv[d->optind], *cur, *end;
+       size_t len;
+       int i;
+       const struct lls_option *match = NULL;
+       bool ambig = false, shortopt;
+
+       assert(word[0] != '\0');
+       shortopt = word[1] != '-';
+       result->idx = d->optind;
+       result->arg = word;
+
+       if (d->next_char)
+               cur = d->next_char;
+       else
+               cur = word + 1 + !shortopt; /* skip dash(es) */
+       for (end = cur; *end && *end != '='; end++)
+               ; /* nothing */
+       len = end - cur;
+
+       /* test all options for exact or abbreviated matches */
+       FOR_EACH_OPTION(i, d->opts) {
+               const struct lls_option *opt = d->opts + i;
+               if (opt->flags & LLS_IGNORED)
+                       continue;
+               if (shortopt) {
+                       if (*cur != opt->short_opt)
+                               continue;
+                       match = opt;
+                       d->next_char = cur + 1;
+                       if (d->next_char[0] == '\0' || d->next_char[0] == '=')
+                               d->next_char = NULL;
+                       break;
+               }
+               if (strncmp(opt->name, cur, len) != 0)
+                       continue;
+               if (strlen(opt->name) == len) { /* exact match */
+                       match = opt;
+                       break;
+               }
+               if (match) { /* second non-exact match */
+                       ambig = true;
+                       break;
+               }
+               /* first non-exact match */
+               match = opt;
+       }
+       if (!match) { /* option not found */
+               xasprintf(errctx, "error token: %s", cur);
+               return -E_LLS_BAD_OPTION;
+       }
+       if (ambig) {
+               xasprintf(errctx, "%s", word);
+               return -E_LLS_AMBIG_OPTION;
+       }
+       if (d->next_char) {
+               if (match->arg_info == LLS_REQUIRED_ARGUMENT) {
+                       xasprintf(errctx, "--%s", match->name);
+                       return -E_LLS_NO_ARG_GIVEN;
+               }
+               result->arg = NULL;
+               goto success;
+       }
+       d->optind++;
+       if (*end == '=') {
+               if (match->arg_info == LLS_NO_ARGUMENT) {
+                       xasprintf(errctx, "--%s", match->name);
+                       return -E_LLS_ARG_GIVEN;
+               }
+               result->arg = end + 1;
+       } else if (match->arg_info == LLS_REQUIRED_ARGUMENT) {
+               if (d->optind >= d->argc) {
+                       xasprintf(errctx, "--%s", match->name);
+                       return -E_LLS_NO_ARG_GIVEN;
+               }
+               result->arg = d->argv[d->optind++];
+       } else
+               result->arg = NULL;
+success:
+       result->idx = match - d->opts;
+       return 1;
+}
+
+/*
+ * Parse one option, including its argument (if any).
+ *
+ * We permute the contents of ARGV as we scan, so that eventually all the
+ * non-options are at the end. This allows options to be given in any order.
+ *
+ * Returns zero on end-of-argv, negative on errors, one if an option was parsed
+ * successfully. The structure pointed to by result is initialized as follows:
+ *
+ * end-of-args case: ->idx is the index of first non-option in argv[], ->arg is
+ * argv[result->idx].
+ *
+ * error case: ->idx is the index of the first problematic option in argv. ->arg is
+ * argv[result->idx] as in the end-of-args case.
+ *
+ * success case: ->idx is the index into the option array which corresponds to
+ * the option that was parsed successfully, ->arg its argument, or NULL if no
+ * argument was given.
+ *
+ * After this function returned non-positive, it must not be called again.
+ */
+static int parse_option(struct lls_data *d, struct lls_arg *result, char **errctx)
+{
+       assert(d->last_nonopt <= d->optind);
+       assert(d->first_nonopt <= d->optind);
+
+       if (d->next_char)
+               return decode_option(d, result, errctx);
+       /*
+        * If we have just processed some options following some non-options,
+        * exchange them so that the options come first.
+        */
+       if (d->first_nonopt != d->last_nonopt && d->last_nonopt != d->optind)
+               exchange_args(d);
+       else if (d->last_nonopt != d->optind)
+               d->first_nonopt = d->optind;
+       /*
+        * Skip any additional non-options and extend the range of non-options
+        * previously skipped.
+        */
+       while (d->optind < d->argc && !is_option(d->argv[d->optind]))
+               d->optind++;
+       d->last_nonopt = d->optind;
+       /*
+        * The special argument `--' forces an end of option-scanning. We skip
+        * it like a null option, then exchange it with previous non-options as
+        * if it were an option. Then we skip everything else like a non-option.
+        */
+       if (d->optind != d->argc && !strcmp(d->argv[d->optind], "--")) {
+               d->optind++;
+               if (d->first_nonopt != d->last_nonopt && d->last_nonopt != d->optind)
+                       exchange_args(d);
+               else if (d->first_nonopt == d->last_nonopt)
+                       d->first_nonopt = d->optind;
+               d->last_nonopt = d->argc;
+               d->optind = d->argc;
+       }
+       /*
+        * If we have done all the argv elements, stop the scan and back over
+        * any non-options that we skipped and permuted.
+        */
+       if (d->optind == d->argc) {
+               /*
+                * Set the index to point at the non-options that we
+                * previously skipped.
+                */
+               result->idx = d->first_nonopt;
+               result->arg = d->argv[result->idx];
+               return 0;
+       }
+       assert(is_option(d->argv[d->optind]));
+       return decode_option(d, result, errctx);
+}
+
+static int check_enum_arg(const char *arg, const struct lls_option *opt,
+               char **errctx)
+{
+       int i;
+       char *val;
+
+       for (i = 0; (val = opt->values[i].string_val); i++)
+               if (!strcmp(arg, val))
+                       return i;
+       xasprintf(errctx, "arg: %s, option: %s", arg, opt->name);
+       return -E_LLS_ENUM;
+}
+
+/*
+ * Increase the "given" count and store argument if the option takes one.
+ * Allocates or reallocates the ->value array of struct lls_opt_result in lpr.
+ */
+static int lls_parse_arg(struct lls_arg *la, const struct lls_option *opts,
+               struct lls_parse_result *lpr, char **errctx)
+{
+       const struct lls_option *opt = opts + la->idx;
+       struct lls_opt_result *lor = lpr->opt_result + la->idx;
+       bool multiple;
+       int idx, ret;
+
+       if (!la->arg)
+               goto success;
+       if (opt->arg_info == LLS_NO_ARGUMENT) {
+               xasprintf(errctx, "arg: %s, option: %s", la->arg, opt->name);
+               return -E_LLS_ARG_GIVEN;
+       }
+       multiple = opt->flags & LLS_MULTIPLE;
+       idx = multiple? lor->given : 0;
+       if (lor->given == 0 || multiple) {
+               ret = xrealloc(&lor->value,
+                       (lor->given + 1) * sizeof(*lor->value));
+               if (ret < 0) {
+                       xasprintf(errctx, "option value array for --%s",
+                               opt->name);
+                       return ret;
+               }
+       }
+       switch (opt->arg_type) {
+       case LLS_STRING:
+               if (lor->given > 0 && !multiple)
+                       free(lor->value[idx].string_val);
+               if (opt->values) {
+                       ret = check_enum_arg(la->arg, opt, errctx);
+                       if (ret < 0)
+                               return ret;
+                       lor->value[idx].uint32_val = ret;
+               } else {
+                       lor->value[idx].string_val = strdup(la->arg);
+                       if (!lor->value[idx].string_val) {
+                               xasprintf(errctx, "string value for %s",
+                                       opt->name);
+                               return -E_LLS_NOMEM;
+                       }
+               }
+               break;
+       case LLS_INT32:
+               ret = atoi32(la->arg, &lor->value[idx].int32_val);
+               if (ret < 0)
+                       goto atoi_error;
+               break;
+       case LLS_UINT32:
+               ret = atou32(la->arg, &lor->value[idx].uint32_val);
+               if (ret < 0)
+                       goto atoi_error;
+               break;
+       case LLS_INT64:
+               ret = atoi64(la->arg, &lor->value[idx].int64_val);
+               if (ret < 0)
+                       goto atoi_error;
+               break;
+       case LLS_UINT64:
+               ret = atou64(la->arg, &lor->value[idx].uint64_val);
+               if (ret < 0)
+                       goto atoi_error;
+               break;
+       default:
+               assert(false);
+       }
+success:
+       lor->given++;
+       return 1;
+atoi_error:
+       assert(ret < 0);
+       xasprintf(errctx, "conversion error for argument \"%s\" to option --%s",
+               la->arg, opt->name);
+       return ret;
+}
+
+static int copy_val(union lls_val *dst, const union lls_val *src,
+               const struct lls_option *opt, char **errctx)
+{
+       if (opt->arg_type != LLS_STRING || opt->values) {
+               *dst = *src;
+               return 0;
+       }
+       if (!src->string_val) {
+               dst->string_val = NULL;
+               return 0;
+       }
+       dst->string_val = strdup(src->string_val);
+       if (!dst->string_val) {
+               xasprintf(errctx, "copy value for --%s", opt->name);
+               return -E_LLS_NOMEM;
+       }
+       return 1;
+}
+
+int lls_check_arg_count(const struct lls_parse_result *lpr,
+               int min_argc, int max_argc, char **errctx)
+{
+       if (errctx)
+               *errctx = NULL;
+       if (lpr->num_inputs < min_argc) {
+               xasprintf(errctx, "at least %u non-option args required, "
+                       "%u given", min_argc, lpr->num_inputs);
+               return -E_LLS_BAD_ARG_COUNT;
+       }
+       if (lpr->num_inputs > max_argc) {
+               if (max_argc == 0)
+                       xasprintf(errctx, "no non-option args allowed, "
+                               "%u given", lpr->num_inputs);
+               else
+                       xasprintf(errctx, "at most %u non-option args allowed, "
+                               "%u given", max_argc, lpr->num_inputs);
+               return -E_LLS_BAD_ARG_COUNT;
+       }
+       return 1;
+}
+
+/*
+ * Unlike getopt(3) this implementation can not resume the scan where it left
+ * off.
+ */
+int lls_parse(int argc, char **argv, const struct lls_command *cmd,
+               struct lls_parse_result **lprp, char **errctx)
+{
+       const struct lls_option *opts = cmd->options;
+       struct lls_data *d = NULL;
+       int i, ret;
+       struct lls_arg la;
+       struct lls_parse_result *lpr;
+
+       if (errctx)
+               *errctx = NULL;
+       lpr = calloc(1, sizeof(*lpr));
+       if (!lpr) {
+               xasprintf(errctx, "log parse result");
+               ret = -E_LLS_NOMEM;
+               goto out;
+       }
+       d = init_lls_data(opts, argc, argv);
+       if (!d) {
+               xasprintf(errctx, "init_lls_data()");
+               ret = -E_LLS_NOMEM;
+               goto out;
+       }
+       if (cmd->num_options == 0) {
+               la.idx = 0;
+               lpr->opt_result = NULL;
+       } else {
+               lpr->opt_result = calloc(cmd->num_options,
+                       sizeof(*lpr->opt_result));
+               if (!lpr->opt_result) {
+                       xasprintf(errctx, "option result array for %s",
+                               cmd->name);
+                       ret = -E_LLS_NOMEM;
+                       goto out;
+               }
+               for (;;) {
+                       ret = parse_option(d, &la, errctx);
+                       if (ret < 0)
+                               goto out;
+                       if (ret == 0)
+                               break;
+                       ret = lls_parse_arg(&la, opts, lpr, errctx);
+                       if (ret < 0)
+                               goto out;
+               }
+       }
+       lpr->num_inputs = argc - la.idx - 1;
+       if (!cmd->non_opts_name) {
+               ret = lls_check_arg_count(lpr, 0, 0, errctx);
+               if (ret < 0) {
+                       /* needed for lls_free_parse_result() */
+                       lpr->inputs = NULL;
+                       goto out;
+               }
+       }
+       /* We always make a copy of the elements of argv[] */
+       lpr->inputs = malloc((lpr->num_inputs + 1) * sizeof(char *));
+       if (!lpr->inputs) {
+               xasprintf(errctx, "inputs array for %s", cmd->name);
+               ret = -E_LLS_NOMEM;
+               goto out;
+       }
+       for (i = 0; i < lpr->num_inputs; i++) {
+               char *arg = argv[i + la.idx + 1];
+               lpr->inputs[i] = strdup(arg);
+               if (lpr->inputs[i])
+                       continue;
+               xasprintf(errctx, "option #%d (%s) of %s", i, arg, cmd->name);
+               ret = -E_LLS_NOMEM;
+               goto out;
+       }
+       lpr->inputs[lpr->num_inputs] = NULL;
+       /* initialize default values */
+       FOR_EACH_OPTION(i, opts) {
+               const struct lls_option *opt = opts + i;
+               struct lls_opt_result *lor = lpr->opt_result + i;
+               bool required = opt->flags & LLS_REQUIRED;
+               bool has_arg = opt->arg_info != LLS_NO_ARGUMENT;
+
+               if (lor->given == 0 && required) {
+                       xasprintf(errctx, "--%s", opt->name);
+                       ret = -E_LLS_OPT_MANDATORY;
+                       goto out;
+               }
+               if (lor->value)
+                       continue;
+               if (!has_arg)
+                       continue;
+               /*
+                * allocate space for the default value, even if there is no
+                * default given in the .suite file
+                */
+               lor->value = malloc(sizeof(*lor->value));
+               if (!lor->value) {
+                       xasprintf(errctx, "value array for --%s", opt->name);
+                       ret = -E_LLS_NOMEM;
+                       goto out;
+               }
+               ret = copy_val(lor->value, &opt->default_val, opt, errctx);
+               if (ret < 0)
+                       goto out;
+       }
+       ret = 1;
+out:
+       free(d);
+       check_errctx(errctx, ret);
+       if (ret < 0) {
+               lls_free_parse_result(lpr, cmd);
+               *lprp = NULL;
+       } else
+               *lprp = lpr;
+       return ret;
+}
+
+#define MAX_OPTION_LEN 30
+#define HELP_INDENT 6
+static const char space[MAX_OPTION_LEN + 1] = "                              ";
+
+static int short_option_help(const struct lls_option *opt, char **result)
+{
+       int ret = 0;
+       char *opt_names = NULL;
+       bool overlong, has_short = opt->short_opt;
+       const char *typestr;
+
+       *result = NULL;
+       if (opt->flags & LLS_IGNORED)
+               return xasprintf(result, "%s", opt->summary);
+       if (opt->arg_info == LLS_NO_ARGUMENT)
+               typestr = "";
+       else
+               typestr = opt->typestr? opt->typestr : "val";
+
+       ret = xasprintf(&opt_names,
+               "%s%c%s"
+               " --%s"
+               "%s%s%s%s%s"
+               ,
+               has_short? "  -" : "   ",
+               has_short? opt->short_opt : ' ',
+               has_short? "," : " ",
+               opt->name,
+               opt->arg_info == LLS_OPTIONAL_ARGUMENT? "[" : "",
+               opt->arg_info == LLS_NO_ARGUMENT? "" : "=<",
+               typestr,
+               opt->arg_info == LLS_NO_ARGUMENT? "" : ">",
+               opt->arg_info == LLS_OPTIONAL_ARGUMENT? "]" : ""
+       );
+       if (ret < 0)
+               return ret;
+       overlong = ret >= MAX_OPTION_LEN;
+       ret = xasprintf(result,
+               "%s"
+               "%s"
+               "%s"
+               "%s"
+               ,
+               opt_names,
+               overlong? "\n" : "",
+               overlong? space : space + ret,
+               opt->summary? opt->summary : ""
+       );
+       free(opt_names);
+       return ret;
+}
+
+static int format_default_val(const struct lls_option *opt, char **result)
+{
+       const union lls_val *val = &opt->default_val;
+
+       *result = NULL;
+       if (opt->arg_info == LLS_NO_ARGUMENT)
+               return 0;
+       if (!(opt->flags & LLS_HAS_DEFAULT))
+               return 0;
+       switch (opt->arg_type) {
+       case LLS_STRING:
+               if (opt->values)
+                       return 0;
+               return xasprintf(result, "(string, default: %s)",
+                       val->string_val? val->string_val : "[NULL]");
+       case LLS_INT32:
+               return xasprintf(result, "(int32, default: %" PRId32 ")",
+                       val->int32_val);
+       case LLS_UINT32:
+               return xasprintf(result, "(uint32, default: %" PRIu32 ")",
+                       val->uint32_val);
+       case LLS_INT64:
+               return xasprintf(result, "(int64, default: %" PRId64 ")",
+                       val->int64_val);
+       case LLS_UINT64:
+               return xasprintf(result, "(uint64, default: %" PRIu64 ")",
+                       val->uint64_val);
+       default:
+               assert(0);
+       }
+       return 1;
+}
+
+static int format_values(const struct lls_option *opt, char **result)
+{
+       int i;
+       uint32_t dflt_idx;
+       const char *val, *pfx = "values: ";
+       size_t len, line_len;
+       const int indent_len = 6, max_len = 75, pfx_len = 8;
+       char *p;
+
+       *result = NULL;
+       if (!opt->values)
+               return 0;
+       assert(opt->arg_type == LLS_STRING);
+       dflt_idx = opt->default_val.uint32_val;
+       line_len = indent_len + pfx_len;
+       len = line_len;
+       for (i = 0; (val = opt->values[i].string_val); i++) {
+               size_t val_len = strlen(val);
+               /* comma and space, and [] around default val */
+               int extra_len = 2 * (i != 0) + 2 * (i == dflt_idx);
+               bool cr = line_len + val_len + extra_len > max_len;
+               if (cr) {
+                       line_len = indent_len + pfx_len;
+                       len += 1 + indent_len + pfx_len; /* +1 for \n */
+               }
+               len += val_len + extra_len;
+               line_len += val_len + extra_len;
+       }
+       *result = malloc(len + 1); /* +1 for terminating zero byte */
+       if (!*result)
+               return -E_LLS_NOMEM;
+       p = *result + sprintf(*result, "%.*s%s", indent_len, space, pfx);
+       line_len = p - *result;
+       for (i = 0; (val = opt->values[i].string_val); i++) {
+               size_t val_len = strlen(val);
+               int extra_len = 2 * (i != 0) + 2 * (i == dflt_idx);
+               bool cr = line_len + val_len + extra_len > max_len;
+               p += sprintf(p,
+                       "%s"
+                       "%s"
+                       "%.*s"
+                       "%s%s%s",
+                       i == 0? "" : ", ",
+                       cr? "\n" : "",
+                       cr? pfx_len + indent_len : 0, cr? space : "",
+                       i == dflt_idx? "[" : "", val, i == dflt_idx? "]" : ""
+               );
+               if (cr)
+                       line_len = indent_len + pfx_len;
+               line_len += val_len + extra_len;
+       }
+       return 1;
+}
+
+static char *create_help_buf(const struct lls_command *cmd, bool long_help)
+{
+       char *header, *option_help, *result;
+       const struct lls_option *opt;
+       int ret;
+       const char *desc = (long_help && cmd->description)?
+               cmd->description : "";
+       const char *closing = (long_help && cmd->closing)? cmd->closing : NULL;
+
+       result = NULL;
+       header = NULL;
+       ret = xasprintf(&header,
+               "%s - %s\n\n"
+               "Usage: %s %s\n"
+               "%s%s"
+       ,
+               cmd->name, cmd->purpose,
+               cmd->name, cmd->synopsis,
+               desc,
+               cmd->options? "\n" : ""
+       );
+       if (ret < 0)
+               return NULL;
+       if (!cmd->options)
+               return header;
+       option_help = NULL;
+       FOR_EACH_OPTION_IN_COMMAND(opt, cmd) {
+               char *tmp, *soh, *loh = NULL, *dflt, *values;
+               int indent = (opt->flags & LLS_IGNORED)? 0 : HELP_INDENT;
+
+               ret = short_option_help(opt, &soh);
+               if (ret < 0)
+                       goto out;
+               if (long_help && opt->help) {
+                       const char *p, *q;
+                       for (p = opt->help; (q = strchr(p, '\n')); p = q + 1) {
+                               tmp = NULL;
+                               ret = xasprintf(&tmp, "%s%.*s%.*s",
+                                       loh? loh : "\n", indent, space,
+                                       (int)(q - p + 1), p);
+                               free(loh);
+                               if (ret < 0) {
+                                       free(soh);
+                                       goto out;
+                               }
+                               loh = tmp;
+                       }
+               }
+               ret = format_default_val(opt, &dflt);
+               if (ret < 0) {
+                       free(soh);
+                       free(loh);
+                       goto out;
+               }
+               if (long_help) {
+                       ret = format_values(opt, &values);
+                       if (ret < 0) {
+                               free(dflt);
+                               free(soh);
+                               free(loh);
+                               goto out;
+                       }
+               } else
+                       values = NULL;
+               tmp = NULL;
+               ret = xasprintf(&tmp,
+                       "%s"
+                       "%s"
+                       "%s%s%s"
+                       "%s%s"
+                       "%s\n",
+                       option_help? option_help : "",
+                       soh ? soh : "",
+                       dflt? "\n" : "", dflt? space : "", dflt? dflt : "",
+                       values? "\n" : "", values? values : "",
+                       loh? loh : ""
+               );
+               free(values);
+               free(dflt);
+               free(soh);
+               free(loh);
+               if (ret < 0)
+                       goto out;
+               free(option_help);
+               option_help = tmp;
+       }
+       ret = xasprintf(&result, "%s%s%s%s", header, option_help,
+               closing? "\n" : "", closing? closing : "");
+out:
+       free(header);
+       free(option_help);
+       return ret < 0? NULL : result;
+}
+
+char *lls_long_help(const struct lls_command *cmd)
+{
+       return create_help_buf(cmd, true /* include help */);
+}
+
+char *lls_short_help(const struct lls_command *cmd)
+{
+       return create_help_buf(cmd, false /* only options */);
+}
+
+static int partial_match(const char *arg, const char *name)
+{
+       size_t arglen = strlen(arg);
+
+       if (strncmp(arg, name, arglen) != 0)
+               return 1; /* no match */
+       if (name[arglen] == '\0')
+               return 0; /* exact match */
+       return -1; /* partial match */
+}
+
+int lls_lookup_subcmd(const char *string, const struct lls_suite *suite,
+               char **errctx)
+{
+       int i, ret;
+
+       if (errctx)
+               *errctx = NULL;
+       if (!string) {
+               xasprintf(errctx, "nothing to look up");
+               return -E_LLS_BAD_SUBCMD;
+       }
+       ret = 0; /* no match so far */
+       for (i = 1; i <= suite->num_subcommands; i++) {
+               switch (partial_match(string, suite->commands[i].name)) {
+               case 1: /* no match */
+                       continue;
+               case 0: /* exact match */
+                       return i;
+               case -1: /* partial match */
+                       if (ret > 0) {
+                               ret = -E_LLS_AMBIG_SUBCMD;
+                               goto fail;
+                       }
+                       ret = i;
+               }
+       }
+       if (ret > 0) /* unique partial match */
+               return ret;
+       ret = -E_LLS_BAD_SUBCMD;
+fail:
+       xasprintf(errctx, "%s", string);
+       return ret;
+}
+
+static size_t get_opt_result_pointer(const struct lls_option *opt, int val_num,
+               struct lls_opt_result *lor, void **result)
+{
+       union lls_val *val = lor->value + val_num;
+
+       switch (opt->arg_type) {
+       case LLS_INT32:
+               *result = &val->int32_val;
+               return 4;
+       case LLS_UINT32:
+               *result = &val->uint32_val;
+               return 4;
+       case LLS_INT64:
+               *result = &val->int64_val;
+               return 8;
+       case LLS_UINT64:
+               *result = &val->uint64_val;
+               return 8;
+       default:
+               assert(0);
+       }
+}
+
+/* never fails, returns number of bytes needed/written */
+static size_t serialize_parse_result(const struct lls_parse_result *lpr,
+       const struct lls_command *cmd, char *result)
+{
+       int i, j;
+       size_t nbytes;
+
+       /* num_inputs */
+       if (result)
+               memcpy(result, &lpr->num_inputs, 4);
+       nbytes = 4;
+
+       /* inputs */
+       for (i = 0; i < lpr->num_inputs; i++) {
+               if (result)
+                       strcpy(result + nbytes, lpr->inputs[i]);
+               nbytes += strlen(lpr->inputs[i]) + 1;
+       }
+       /* options */
+       FOR_EACH_OPTION(i, cmd->options) {
+               const struct lls_option *opt = cmd->options + i;
+               struct lls_opt_result *lor = lpr->opt_result + i;
+               unsigned num_vals;
+
+               if (result)
+                       memcpy(result + nbytes, &lor->given, 4);
+               nbytes += 4;
+               if (opt->arg_info == LLS_NO_ARGUMENT)
+                       continue;
+               num_vals = num_vals_in_parse_result(cmd, i, lpr);
+               if (opt->arg_type == LLS_STRING && !opt->values) {
+                       for (j = 0; j < num_vals; j++) {
+                               if (result)
+                                       strcpy(result + nbytes,
+                                               lor->value[j].string_val);
+                               nbytes += strlen(lor->value[j].string_val) + 1;
+                       }
+               } else {
+                       for (j = 0; j < num_vals; j++) {
+                               size_t bytes;
+                               void *p;
+                               bytes = get_opt_result_pointer(opt, j, lor, &p);
+                               if (result)
+                                       memcpy(result + nbytes, p, bytes);
+                               nbytes += bytes;
+                       }
+               }
+       }
+       return nbytes;
+}
+
+int lls_serialize_parse_result(const struct lls_parse_result *lpr,
+       const struct lls_command *cmd, char **result, size_t *nbytes)
+{
+       size_t sz;
+       int ret;
+
+       if (!result || !*result) { /* need to compute needed space */
+               sz = serialize_parse_result(lpr, cmd, NULL);
+               if (!result) { /* just report needed space */
+                       ret = 0;
+                       goto out;
+               }
+               *result = malloc(sz);
+               if (!*result) {
+                       sz = 0;
+                       ret = -E_LLS_NOMEM;
+                       goto out;
+               }
+       }
+       /* serialize it */
+       sz = serialize_parse_result(lpr, cmd, *result);
+       ret = 1;
+out:
+       if (nbytes)
+               *nbytes = sz;
+       return ret;
+}
+
+int lls_deserialize_parse_result(const char *buf, const struct lls_command *cmd,
+               struct lls_parse_result **lprp)
+{
+       int i, j;
+       const char *p = buf;
+       struct lls_parse_result *lpr;
+
+       *lprp = NULL;
+       lpr = malloc(sizeof(*lpr));
+       if (!lpr)
+               return -E_LLS_NOMEM;
+       memcpy(&lpr->num_inputs, p, 4);
+       p += 4;
+       if (lpr->num_inputs > 0) {
+               lpr->inputs = malloc(lpr->num_inputs * sizeof(char *));
+               if (!lpr->inputs)
+                       goto free_lpr;
+       } else
+               lpr->inputs = NULL;
+       for (i = 0; i < lpr->num_inputs; i++) {
+               lpr->inputs[i] = strdup(p);
+               if (!lpr->inputs[i])
+                       goto free_inputs;
+               p += strlen(p) + 1;
+       }
+       lpr->opt_result = malloc(cmd->num_options * sizeof(*lpr->opt_result));
+       if (!lpr->opt_result)
+               goto free_inputs;
+       FOR_EACH_OPTION(i, cmd->options) {
+               const struct lls_option *opt = cmd->options + i;
+               struct lls_opt_result *lor = lpr->opt_result + i;
+               uint32_t num_vals;
+
+               memcpy(&lor->given, p, 4);
+               p += 4;
+               if (opt->arg_info == LLS_NO_ARGUMENT)
+                       continue;
+               num_vals = num_vals_in_parse_result(cmd, i, lpr);
+               lor->value = malloc(num_vals * sizeof(*lor->value));
+               if (!lor->value)
+                       goto free_options;
+               if (opt->arg_type == LLS_STRING && !opt->values) {
+                       for (j = 0; j < num_vals; j++) {
+                               lor->value[j].string_val = strdup(p);
+                               if (!lor->value[j].string_val) {
+                                       for (; j >= 0; j--)
+                                               free(lor->value[j].string_val);
+                                       goto free_options;
+                               }
+                               p += strlen(lor->value[j].string_val) + 1;
+                       }
+               } else {
+                       for (j = 0; j < num_vals; j++) {
+                               size_t bytes;
+                               void *q;
+                               bytes = get_opt_result_pointer(opt, j, lor, &q);
+                               memcpy(q, p, bytes);
+                               p += bytes;
+                       }
+               }
+       }
+       *lprp = lpr;
+       return 1;
+free_options:
+       for (; i >= 0; i--) {
+               const struct lls_option *opt = cmd->options + i;
+               struct lls_opt_result *lor = lpr->opt_result + i;
+               unsigned num_vals = (opt->flags & LLS_MULTIPLE)? lor->given : 1;
+               for (j = 0; j < num_vals; j++)
+                       if (opt->arg_type == LLS_STRING && !opt->values)
+                               free(lor->value[j].string_val);
+               free(lor->value);
+       }
+       free(lpr->opt_result);
+free_inputs:
+       for (; i >= 0; i--)
+               free(lpr->inputs[i]);
+       free(lpr->inputs);
+free_lpr:
+       free(lpr);
+       return -E_LLS_NOMEM;
+}
+
+static int merge_option(int opt_num, const struct lls_parse_result *primary,
+               const struct lls_parse_result *secondary,
+               const struct lls_command *cmd, struct lls_parse_result *result,
+               char **errctx)
+{
+       int l, m, ret;
+       const struct lls_option *opt = cmd->options + opt_num;
+       struct lls_opt_result *lor1, *lor2, *lor;
+
+       lor1 = primary->opt_result + opt_num;
+       lor2 = secondary->opt_result + opt_num;
+       lor = result->opt_result + opt_num;
+       lor->given = lor1->given + lor2->given;
+       if (opt->arg_info == LLS_NO_ARGUMENT)
+               return 0;
+       if (lor->given > 0 && (opt->flags & LLS_MULTIPLE)) {
+               lor->value = malloc(lor->given * sizeof(*lor->value));
+               if (!lor->value) {
+                       xasprintf(errctx, "value array for option %s", opt->name);
+                       goto fail;
+               }
+               for (l = 0; l < lor1->given; l++) {
+                       ret = copy_val(lor->value + l, lor1->value + l,
+                               opt, errctx);
+                       if (ret < 0)
+                               goto free_primary_options;
+               }
+               for (m = 0; m < lor2->given; m++) {
+                       ret = copy_val(lor->value + l + m, lor2->value + m,
+                               opt, errctx);
+                       if (ret < 0)
+                               goto free_secondary_options;
+               }
+               return 1;
+       }
+       lor->value = malloc(sizeof(*lor->value)); /* one value only */
+       if (!lor->value) {
+               xasprintf(errctx, "(single) value for option %s", opt->name);
+               goto fail;
+       }
+       if (lor1->given) {
+               ret = copy_val(lor->value, lor1->value, opt, errctx);
+               if (ret < 0)
+                       goto free_value;
+       } else if (lor2->given) {
+               ret = copy_val(lor->value, lor2->value, opt, errctx);
+               if (ret < 0)
+                       goto free_value;
+       } else {
+               ret = copy_val(lor->value, &opt->default_val, opt, errctx);
+               if (ret < 0)
+                       goto free_value;
+       }
+       return 1;
+free_secondary_options:
+       if (opt->arg_type == LLS_STRING && !opt->values)
+               for (m--; m >= 0; m--)
+                       free(lor->value[l + m].string_val);
+free_primary_options:
+       if (opt->arg_type == LLS_STRING && !opt->values)
+               for (l--; l >= 0; l--)
+                       free(lor->value[l].string_val);
+free_value:
+       free(lor->value);
+fail:
+       return -E_LLS_NOMEM;
+}
+
+int lls_merge(const struct lls_parse_result *primary,
+               const struct lls_parse_result *secondary,
+               const struct lls_command *cmd, struct lls_parse_result **lprp,
+               char **errctx)
+{
+       int i, j, k, ret;
+       unsigned num = primary->num_inputs + secondary->num_inputs;
+       struct lls_parse_result *result;
+
+       if (errctx)
+               *errctx = NULL;
+       result = malloc(sizeof(*result));
+       if (!result) {
+               ret = -E_LLS_NOMEM;
+               xasprintf(errctx, "parse result");
+               goto fail;
+       }
+       result->inputs = malloc((num + 1) * sizeof(char *));
+       if (!result->inputs) {
+               ret = -E_LLS_NOMEM;
+               xasprintf(errctx, "inputs array of size %u", num);
+               goto free_parse_result;
+       }
+       for (i = 0; i < primary->num_inputs; i++) {
+               result->inputs[i] = strdup(primary->inputs[i]);
+               if (!result->inputs[i]) {
+                       ret = -E_LLS_NOMEM;
+                       xasprintf(errctx, "primary input #%d", i);
+                       goto free_primary_inputs;
+               }
+       }
+       for (j = 0; j < secondary->num_inputs; j++) {
+               result->inputs[i + j] = strdup(secondary->inputs[j]);
+               if (!result->inputs[i + j]) {
+                       ret = -E_LLS_NOMEM;
+                       xasprintf(errctx, "secondary input #%d", i);
+                       goto free_secondary_inputs;
+               }
+       }
+       result->inputs[i + j] = NULL;
+       result->opt_result = malloc(cmd->num_options
+               * sizeof(*result->opt_result));
+       if (!result->opt_result)
+               goto free_secondary_inputs;
+       FOR_EACH_OPTION(k, cmd->options) {
+               ret = merge_option(k, primary, secondary, cmd, result, errctx);
+               if (ret < 0)
+                       goto free_opt_results;
+       }
+       result->num_inputs = num;
+       *lprp = result;
+       ret = 1;
+       goto out;
+free_opt_results:
+       for (k--; k >= 0; k--)
+               free_opt_result(k, result, cmd);
+free_secondary_inputs:
+       for (j--; j >= 0; j--)
+               free(result->inputs[i + j]);
+free_primary_inputs:
+       for (i--; i >= 0; i--)
+               free(result->inputs[i]);
+       free(result->inputs);
+free_parse_result:
+       free(result);
+fail:
+       assert(ret < 0);
+       *lprp = NULL;
+out:
+       check_errctx(errctx, ret);
+       return ret;
+}
+
+static bool is_default_val(const union lls_val *val,
+               const struct lls_option *opt)
+{
+       bool has_default = opt->flags & LLS_HAS_DEFAULT;
+       bool has_arg = opt->arg_info != LLS_NO_ARGUMENT;
+       const union lls_val *dflt;
+
+       if (!has_arg)
+               return false;
+       if (!has_default)
+               return false;
+       dflt = &opt->default_val;
+       switch (opt->arg_type) {
+       case LLS_INT32:
+               return val->int32_val == dflt->int32_val;
+       case LLS_UINT32:
+               return val->uint32_val == dflt->uint32_val;
+       case LLS_INT64:
+               return val->int64_val == dflt->int64_val;
+       case LLS_UINT64:
+               return val->uint64_val == dflt->uint64_val;
+       case LLS_STRING:
+               {
+               const char *s1, *s2;
+
+               if (opt->values)
+                       return val->uint32_val == dflt->uint32_val;
+               s1 = val->string_val;
+               s2 = dflt->string_val;
+               if (!s1 && !s2)
+                       return true;
+               if (!s1 || !s2)
+                       return false;
+               return !strcmp(s1, s2);
+               }
+       default:
+               assert(0);
+       }
+}
+
+static char *append_opt_val(const union lls_val *val,
+               const struct lls_option *opt, char *result)
+{
+       char *line = NULL, *tmp = NULL;
+
+       switch (opt->arg_type) {
+       case LLS_INT32:
+               xasprintf(&line, "%" PRId32, val->int32_val);
+               break;
+       case LLS_UINT32:
+               xasprintf(&line, "%" PRIu32, val->uint32_val);
+               break;
+       case LLS_INT64:
+               xasprintf(&line, "%" PRId64, val->int64_val);
+               break;
+       case LLS_UINT64:
+               xasprintf(&line, "%" PRIu64, val->uint64_val);
+               break;
+       case LLS_STRING:
+               {
+               const char *s, *p;
+               char *q;
+
+               if (opt->values)
+                       s = lls_enum_string_val(val->uint32_val, opt);
+               else {
+                       s = val->string_val;
+                       if (!s)
+                               return result;
+               }
+               line = malloc(2 * strlen(s) + 3);
+               if (!line) {
+                       free(result);
+                       return NULL;
+               }
+               line[0] = '"';
+               for (p = s, q = line + 1; *p; p++, q++) {
+                       if (*p == '\\' || *p == '\n' || *p == '\t' || *p == '"') {
+                               *q = '\\';
+                               q++;
+                       }
+                       *q = *p;
+               }
+               q[0] = '"';
+               q[1] = '\0';
+               break;
+               }
+       default:
+               assert(0);
+       }
+       xasprintf(&tmp, "%s%s=%s\n", result? result : "", opt->name, line);
+       free(line);
+       free(result);
+       return tmp;
+}
+
+char *lls_dump_parse_result(const struct lls_parse_result *lpr,
+               const struct lls_command *cmd, bool non_default_only)
+{
+       int i;
+       char *result = NULL;
+
+       FOR_EACH_OPTION(i, cmd->options) {
+               const struct lls_option *opt = cmd->options + i;
+               struct lls_opt_result *lor = lpr->opt_result + i;
+               bool given = lor->given;
+               int j, n;
+
+               if (!given && non_default_only)
+                       continue;
+               if (opt->arg_info == LLS_NO_ARGUMENT) {
+                       char *tmp = NULL;
+                       if (!given)
+                               continue;
+                       xasprintf(&tmp, "%s%s\n", result? result : "",
+                               opt->name);
+                       free(result);
+                       result = tmp;
+                       continue;
+               }
+               n = num_vals_in_parse_result(cmd, i, lpr);
+               for (j = 0; j < n; j++) {
+                       union lls_val *val = lor->value + j;
+                       if (non_default_only && is_default_val(val, opt))
+                               continue;
+                       result = append_opt_val(val, opt, result);
+               }
+       }
+       if (!result) { /* empty dump */
+               result = malloc(1);
+               if (result)
+                       result[0] = '\0';
+       }
+       return result;
+}
diff --git a/lopsub.h.m4 b/lopsub.h.m4
new file mode 100644 (file)
index 0000000..20d3a69
--- /dev/null
@@ -0,0 +1,806 @@
+VERBATIM_C(«
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
+ */
+
+#include <string.h>
+#include <inttypes.h>
+#include <stdbool.h>
+
+#ifndef _LOPSUB_H
+#define _LOPSUB_H
+»)
+
+STATEMENT(
+       «struct lls_suite»,
+       «Opaque structure which describes a lopsub suite.»,
+       This structure is defined in the .c file which is generated by
+       lopsubgen(1). The corresponding header file exposes a const pointer
+       to a this structure for use in the application.
+
+       Applications can not de-reference this pointer or access its content
+       directly. They must call one of the accessor functions described below.
+»)
+
+STATEMENT(
+       «struct lls_command»,
+       «Represents one command of a suite.»,
+       A command is identified by a suite and a command number. The symbolic
+       names of all commands defined in a suite are exposed in the enumeration
+       defined in the header file which is generated by lopsubgen(1),
+
+       Applications can obtain an opaque pointer to a command by calling
+       lls_cmd(), providing the command number and the suite pointer as
+       arguments.
+»)
+
+STATEMENT(
+       «struct lls_option»,
+       «Represents one option of a command.»,
+       Similar to a command, an option is identified by a command and an
+       option number. The header file created by the lopsubgen(1) utility
+       provides an enumeration for all options of each command.
+
+       The lls_opt() function returns an opaque pointer to an option, given
+       a command pointer and an option number.
+»)
+
+STATEMENT(
+       «struct lls_parse_result»,
+       «An argument vector, fully parsed according to a lopsub command.»,
+       A pointer to an opaque structure of this type is returned by
+       lls_parse() if the argument vector was found valid for the given
+       command.
+
+       Several functions (described below) take a pointer to such a
+       structure. This enables applications to obtain details about the
+       options and arguments that were given in the argument vector, for
+       example whether an option was specified and how many non-options
+       (aka inputs) were given.
+»)
+
+STATEMENT(
+       «struct lls_opt_result»,
+       «The part of a parse result related to a specific option»,
+       Given an option and a parse result, the lls_opt_result() function
+       returns an opaque pointer to a structure of this type which contains
+       information about the option in the argument vector that was used to
+       create the parse result.
+
+       A pointer to a structure of this type can be passed to the various
+       accessor functions described below. These functions return information
+       about the option in the argument vector, for example how many times
+       the option was given.
+»)
+
+DECLARE_FUNCTION(
+       «lls_strerror»,
+       «A strerror-like function for lopsub error codes.»,
+       This works just like strerror(3).
+», «
+       «int lss_errno», «positive error code returned from a lopsub library function»
+», «
+»,
+       «const char *», «points to static memory, must not be freed by the caller»
+)
+
+DECLARE_FUNCTION(
+       «lls_parse»,
+       «Parse an argument vector according to a lopsub command.»,
+       This function turns a broken-up command line into a parse result,
+       completely parsing all arguments according to the options defined
+       in the given a lopsub command. As usual, options may be given in any
+       order and the special argument "--" forces an end of option-scanning.
+
+       For each option defined in the suite, if the multiple flag is set
+       for the option, the parse result contains an array of values, with
+       one value for each time the option was given. Conversely, if the
+       multiple flag is not set, only a single value is stored in the parse
+       result. Those options may still be given multiple times, but only
+       the last given argument is stored while all previous arguments are
+       discarded.
+
+       For options which take an integer value, conversion is performed in
+       a way that recognizes an optional base prefix like "0x". The empty
+       string and strings with trailing non-digit characters result in a
+       parse error. Range violations are detected and also cause the function
+       to fail.
+», «
+       «int argc», «Usual argument counter.»,
+       «char **argv», «Usual argument vector to parse.»,
+       «const struct lls_command *cmd», «Contains names and characteristics of all allowed options.»,
+       «struct lls_parse_result **lprp», «Result pointer.»,
+       «char **errctx», «Optional error context string, only set on failure»
+», «
+       The parse_result pointer returned through lprp can be passed to several
+       accessor functions described below in order to obtain information
+       about the options and arguments in argv[].
+»,
+       «int», «Standard (negative error code on failure, non-negative on success).»,
+       On success lprp is initialized according to the options that have been
+       parsed successfully. In this case either errctx or *errctx is NULL,
+       so no cleanup needs to be performed in the caller. However, when the
+       caller is no longer interested in the parse result, it should call
+       lls_free_parse_result() to release the memory that was allocated
+       during the call to lls_parse().
+
+       On errors, *lprp is set to NULL and the function returns a negative
+       error code. This can happen for various reasons, for example if an
+       invalid option or argument was given. Another possible reason is worth
+       mentioning: when the non-opts-name directive was not specified in the
+       suite, the subcommand is assumed to take no non-option arguments. In
+       this case, lls_parse() fails if the argument vector does contain any
+       non-option arguments.
+
+       In the error case, if errctx is not NULL, *errctx points to a
+       zero-terminated string which describes the context of the error
+       condition, for example the problematic element of argv[]. The only
+       exception is when an out of memory condition occurs. In this case
+       *errctx may be NULL because the function was unable to allocate
+       the memory needed for the error context. If *errctx is not NULL,
+       the memory it points to should be freed by the caller. However,
+       lls_free_parse_result() need not be called in this case.
+»)
+
+DECLARE_FUNCTION(
+       «lls_free_parse_result»,
+       «Deallocate a parse result.»,
+       This frees the memory space allocated by lls_parse().
+», «
+       «struct lls_parse_result *lpr», «As returned by lls_parse().»,
+       «const struct lls_command *cmd»,
+               «This must match the pointer passed earlier to lls_parse().»
+», «
+       The parse result pointer must have been returned by a previous
+       call to lls_parse() or lls_serialize_parse_result(). Otherwise, or
+       if lls_free_parse_result has already been called before, undefined
+       behavior occurs. It's OK to pass a NULL pointer though. In this case
+       no action is performed.
+»,
+       «void»
+)
+
+DECLARE_FUNCTION(
+       «lls_long_help»,
+       «Return the long help text of a command.»,
+       The long help text contains the synopsis, the purpose and the help
+       text of the command, followed by the option list including descriptions
+       and help for each option.
+», «
+       «const struct lls_command *cmd», «As returned from lls_cmd().»
+», «
+»,
+       «char *», «A dynamically allocated string that must be freed by the caller.»
+)
+
+DECLARE_FUNCTION(
+       «lls_short_help»,
+       «Return the short help text of a command.»,
+       This is similar to lls_long_help() but help texts are omitted from
+       the output.
+», «
+       «const struct lls_command *cmd», «See lls_long_help().»
+», «
+»,
+       «char *», «See lls_long_help().»
+)
+
+DECLARE_FUNCTION(
+       «lls_lookup_subcmd»,
+       «Tell whether the given string is the name of a subcommand.»,
+       This tries to match the given string against the subcommands of the
+       suite. Exact matches and unique partial matches count as success.
+», «
+       «const char *string», «The name to look up.»,
+       «const struct lls_suite *suite», «Contains the command list.»,
+       «char **errctx», «Contains lookup string and the name of the suite.»
+», «
+»,
+       «int», «The command number on success, negative error code on failure.»,
+       The lookup fails if (a) the given string pointer is NULL, or (b) if
+       the string is no prefix of any subcommand of the suite, or (c) if it
+       is a proper prefix of more than one subcommand.
+
+       On success the error context pointer is set to NULL. In the error case,
+       if errctx is not NULL, *errctx is pointed to a string that must be
+       freed by the caller.
+»)
+
+DECLARE_FUNCTION(
+       «lls_cmd»,
+       «Return a pointer to a command structure.»,
+       Applications usually call this at the beginning of each function that
+       implements a lopsub command (aka command handler). The returned
+       pointer serves as an abstract reference to the command. This
+       reference is needed to call other functions of the lopsub library,
+       notably lls_parse().
+», «
+       «unsigned cmd_num», «Appropriate enum value from the header file.»,
+       «const struct lls_suite *suite», «Also declared in the header file.»
+», «
+       The suite pointer and all valid command numbers are defined in the
+       header file which is generated by lopsubgen(1). Hence this header
+       file must be included from the application to get the name of the
+       suite pointer variable and the command numbers.
+»,
+       «const struct lls_command *», «Never returns NULL.»,
+       This function always succeeds if both arguments are valid. That is,
+       the command number is a symbolic constant from the LSG_XXX_COMMANDS
+       enumeration of the header file generated by lopsubgen(1), and the suite
+       pointer equals the pointer that is declared in the same header file.
+
+       If at least one of the arguments are invalid, the behavior is
+       undefined.
+»)
+
+DECLARE_FUNCTION(
+       «lls_command_name»,
+       «Obtain the name of the command, given a command pointer.»,
+       Even in situations where the application knows the name of the command,
+       it is less error-prone to call this function rather than to duplicate
+       the command name in the application.
+», «
+       «const struct lls_command *cmd», «As obtained from lls_cmd().»
+», «
+»,
+       «const char *», «Never returns NULL.»,
+       This function succeeds unless the given command pointer is invalid
+       (was not obtained through an earlier call to lls_cmd() or is NULL),
+       in which case the behavior is undefined. The return pointer refers
+       to static storage that must not be freed by the caller.
+»)
+
+DECLARE_FUNCTION(
+       «lls_user_data»,
+       «Obtain the application-specific data pointer.»,
+       Some applications need to store further information for each subcommand,
+       for example a function pointer which refers to the implementation of
+       the subcommand. The optional user data feature allows to define one
+       application defined pointer that can be retrieved by calling this
+       function.
+
+       Of course storing one function pointer per command could simply be
+       done by defining a suitable array in the application which contains
+       one pointer per (sub)command. However, this approach has the disadvantage
+       that it effectively creates two command lists (one in the suite
+       file and one for the array) that need to be maintained and kept in
+       sync. Moreover, functions can not be declared as static if they are
+       defined in a different source file than the one that defines the array.
+
+       Therefore, lopsub offers an alternative: The .c file generated by
+       lopsubgen(1) declares one const void * pointer per command. These
+       pointers are marked with the "weak" attribute (a gcc extension, but
+       also available for clang). This instructs the compiler to store the
+       declaration as a weak symbol in the object file. Since the linker
+       does not require weak symbols to be defined, linking succeeds even
+       if the application chooses to not employ the user data feature.
+
+       To make use of the user data feature, the application needs to define
+       one pointer for each command called lsg_xxx_com_yyy_user_data, where
+       xxx is the name of the suite and yyy the name of the command. A
+       suitable preprocessor macro can make this as simple as EXPORT_CMD(foo).
+», «
+       «const struct lls_command *cmd», «As obtained from lls_cmd().»
+», «
+»,
+       «const void *», «The user data pointer defined in the application.»,
+       If the application did not define a user data pointer for the given
+       command, the function returns NULL.
+»)
+
+DECLARE_FUNCTION(
+       «lls_opt_result»,
+       «Extract information about one option from a parse result.»,
+       The returned pointer can be passed to the accessor functions described
+       below. Those functions let the applications tell how many times the
+       option was given and retrieve any argument(s) for the option.
+», «
+       «unsigned opt_num», «As declared in the header file.»,
+       «const struct lls_parse_result *lpr», «As returned from lls_parse().»
+», «
+       The header file generated by lopsubgen(1) generates for each command
+       an enumeration which declares one option number per option as a
+       symbolic constant.
+»,
+       «const struct lls_opt_result *», «Never returns NULL.»,
+       If the parse result pointer is invalid (was not returned by
+       lls_parse(), or is NULL), or the option number does not correspond to
+       the command that was used to create the parse result, the behaviour
+       is undefined. Otherwise this function succeeds.
+»)
+
+DECLARE_FUNCTION(
+       «lls_opt»,
+       «Get a reference to an option, given a command and an option number.»,
+       While an opt_result pointer described above is used to obtain
+       information in an argument vector, the pointer returned by this
+       function allows to obtain information about the option itself.
+
+       Applications rarely need to care about the option pointer. It
+       is required to get the possible values of an enumeration option
+       though. See lls_enum_string_val().
+», «
+       «unsigned opt_num», «See lls_opt_result()»,
+       «const struct lls_command *cmd», «Obtained from lls_cmd().»
+», «
+»,
+       «const struct lls_option *», «Never returns NULL.»,
+       This function always succeeds if both arguments are
+       valid. Otherwise the behavior is undefined.
+»)
+
+DECLARE_FUNCTION(
+       «lls_opt_given»,
+       «Return how many times an option was given.»,
+       This is employed as follows. Applications first call lls_parse() to
+       initialize the parse result, followed by lls_opt_result() to obtain a
+       reference to those parts of the parse result that are related to one
+       specific option. The reference can then be passed to this function
+       to find out how many times the option was given.
+», «
+       «const struct lls_opt_result *r», «As returned from lls_opt_result().»
+», «
+»,
+       «unsigned», «Zero means: Not given at all.»,
+       Even if the multiple flag was not set for the option, the returned
+       value may be greater than one because this flag only affects how many
+       arguments are stored in the parse result.
+
+       This function succeeds unless the opt_result pointer is invalid
+       (was not returned by lls_opt_result(), or is NULL), in which case
+       the behaviour is undefined.
+»)
+
+DECLARE_FUNCTION(
+       «lls_string_val»,
+       «Retrieve one argument to a string option.»,
+       This function may only be called for options which take an optional or
+       required argument of string type. Enum options (which take as their
+       argument one of a fixed, finite set of possible strings), however,
+       are treated as if the option took an argument of uint32 type. Hence
+       this function must not be called for these options.
+», «
+       «unsigned idx», «The index in the array of values.»,
+       «const struct lls_opt_result *r», «As returned from lls_opt_result.»
+», «
+       The first argument must be zero if the multiple flag is not set for
+       the option. Otherwise any number between zero and n - 1 (inclusively)
+       may be passed, where n is the number of times the option was given,
+       that is, the return value of lls_opt_given().
+
+       As as special case, if the option was not given at all (i.e., n == 0),
+       it is still OK to call this function with an index value of zero. In
+       this case, the default value for the option will be returned, or NULL
+       if no default value was specified in the suite.
+»,
+       «const char *», «The argument that corresponds to the given index.»,
+       The memory referenced by the return pointer is part of the parse
+       result and must not be freed by the caller. It will be freed when
+       lls_free_parse_result() is called.
+
+       Undefined behaviour occurs in all of the following cases: (a) the
+       index is out of range, (b) the opt_result pointer is NULL or was
+       not obtained through a previous call to lls_opt_result(), (c) the
+       opt_result pointer corresponds to an option which takes an argument
+       of different type or no argument at all. If none of these conditions
+       apply, the function is guaranteed to succeed.
+»)
+
+DECLARE_FUNCTION(
+       «lls_int32_val»,
+       «Retrieve one argument to an option that takes an int32 argument.»,
+       This is like lls_string_val(), but for options which take an optional
+       or required argument of type int32.
+», «
+       «unsigned idx», «See lls_string_val()»,
+       «const struct lls_opt_result *r», «See lls_string_val().»
+», «
+       As for lls_string_val(), a zero index value is considered a valid
+       input even if the option was not given at all. In this case. the
+       default value is returned, or zero if the option has no default value.
+»,
+       «int32_t», «The argument, converted to a 32 bit signed integer.»,
+       Since conversion of the argument to int32_t takes place earlier during
+       lls_parse(), no errors are possible unless the index parameter or the
+       the opt result pointer (or both) are invalid. See above for details.
+»)
+
+DECLARE_FUNCTION(
+       «lls_uint32_val»,
+       «Retrieve one argument to an option that takes an uint32 argument.»,
+       Identical to lls_int32_val(), except the argument type of the option
+       and the return value are different.
+
+       For enum options, this is the correct function to call in order
+       to obtain the index into the array of possible values, see
+       lls_enum_string_val() below.
+», «
+       «unsigned idx», «See lls_int32_val().»,
+       «const struct lls_opt_result *r», «See lls_int32_val().»
+», «
+»,
+       «uint32_t», «See lls_int32_val().»
+)
+
+DECLARE_FUNCTION(
+       «lls_int64_val»,
+       «Retrieve one argument to an option that takes an int64 argument.»,
+       Identical to lls_int32_val(), except that this function must be used
+       for options which take a 64 bit signed integer argument.
+», «
+       «unsigned idx», «See lls_int32_val().»,
+       «const struct lls_opt_result *r», «See lls_int32_val().»
+», «
+»,
+       «int64_t», «See lls_int32_val().»
+)
+
+DECLARE_FUNCTION(
+       «lls_uint64_val»,
+       «Retrieve one argument to an option that takes an uint64 argument.»,
+       Identical to lls_int32_val(), except that this function must be used
+       for options which take a 64 bit unsigned integer argument.
+», «
+       «unsigned idx», «See lls_int32_val().»,
+       «const struct lls_opt_result *r», «See lls_int32_val().»
+», «
+»,
+       «uint64_t», «See lls_int32_val().»
+)
+
+DECLARE_FUNCTION(
+       «lls_enum_string_val»,
+       «Get one possible argument value for an option.»,
+       This function must only be called for enum options. That is, options
+       for which the set of possible arguments was defined through the values
+       directive in the suite.
+», «
+       «unsigned idx», «Determines which of the possible values to get.»,
+       «const struct lls_option *opt», «As returned by lls_opt().»
+», «
+       The possible values of an enum option are a property of the option
+       itself and are thus independent of the command line. Therefore this
+       function expects an option pointer rather than a pointer to an opt
+       result.
+
+       The index parameter must be a value between zero and the number of
+       possible values minus one, inclusively. This number is declared as
+       the last member of the enumeration for the option, which is defined
+       of the generated header file.
+»,
+       «const char *», «Static memory, must not be freed.»,
+       Behavior is undefined if the given option is not an enum option, a
+       NULL pointer is passed, or if the index value is out of range.
+)
+
+DECLARE_FUNCTION(
+       «lls_num_inputs»,
+       «Get the number of non-option arguments.»,
+       In addition to options and their arguments, subcommands may accept
+       any number of additional arguments which are not related to any
+       particular option. For example, file names are frequently passed
+       as such non-option arguments (aka inputs).
+», «
+       «const struct lls_parse_result *lpr», «As returned from lls_parse().»
+», «
+       Passing a NULL pointer to this function results in undefined behaviour.
+»,
+       «unsigned», «Number of non-option arguments.»,
+       This function never fails. See also lls_input(), lls_check_arg_count().
+»)
+
+DECLARE_FUNCTION(
+       «lls_input»,
+       «Get a reference to one non-option argument.»,
+       If the argument vector passed to lls_parse() contained non-option
+       arguments, the value of each of them can be obtained by calling
+       this function.
+», «
+       «unsigned idx», «The index into the array of non-option arguments.»,
+       «const struct lls_parse_result *lpr», «As returned from lls_parse().»
+», «
+       The index must be between zero and n-1, inclusively, where n is the
+       number returned by lls_num_inputs(). The parse_result pointer must have
+       been obtained by an earlier call to lls_parse().
+»,
+       «const char *», «Pointer to the corresponding non-option argument.»,
+       If the conditions described above are met, the function is guaranteed
+       to succeed. It will never return a NULL pointer in this case.
+)
+
+DECLARE_FUNCTION(
+       «lls_version»,
+       «Get the version string of the lopsub library.»,
+       The version string is determined at build time from the sha1 of the
+       HEAD git commit or from the name of the top level directory for compiling
+       from a gitweb snapshot.
+», «
+       «void»,
+», «
+»,
+       «const char *», «Static storage, must not be freed by the caller.»,
+       The returned string is of the form <tag>-<d>-g<sha1>, where <tag>
+       is the name of the last tagged commit contained in the HEAD commit,
+       <d> is the number of commits between <tag> and HEAD, and <sha1> is the
+       first four hex digits of the hash of the HEAD commit. If the working
+       tree was dirty at compile time, the string "-dirty" is appended to
+       the version string.
+
+       This function always succeeds.
+)
+
+DECLARE_FUNCTION(
+       «lls_purpose»,
+       «Get the line which describes the purpose of a command.»,
+       This function is useful for applications which need to print
+       their own command summary rather than employ lls_short_help() and
+       lls_long_help(). One reason for this could be that the application
+       has additional per-command informations which should be included in
+       the command summary.
+», «
+       «const struct lls_command *cmd», «Obtained from lls_cmd().»
+», «
+»,
+       «const char *», «Static storage, must not be freed.»,
+       The returned string is the content of the corresponding directive of
+       the suite file, with leading and trailing whitespace removed.
+)
+
+DECLARE_FUNCTION(
+       «lls_convert_config»,
+       «Transform the contents of a config file into an argument vector.»,
+       This function scans the given input buffer to compute an (argc,
+       argv) pair which is suitable to be fed to lls_parse(). The function
+       is config-agnostic. That is, it does not know anything about option
+       names and their type.
+
+       Arguments are separated from the option by whitespace and an optional
+       '=' character. Arguments to string options should be enclosed in double
+       quotes and must not spawn multiple lines. Newline or tab characters
+       may be embedded into the argument string with '\n' and '\t'. To embed
+       a backslash, double it. To embed a quote, prefix it with a backslash.
+», «
+       «const char *buf», «Input buffer (content of the config file).»,
+       «size_t nbytes», «The buffer size.»,
+       «const char *subcmd», «NULL means supercommand.»,
+       «char ***result», «Argument vector is returned here.»,
+       «char **errctx», «Error context, see lls_parse().»
+», «
+       If a subcommand is specified, only the part of the input buffer which
+       is started by a [subcmd] marker is taken into account. Conversely,
+       if a NULL pointer is passed, only the beginning part until the first
+       section marker will be considered. This allows config files to contain
+       options for the supercommand and subcommands.
+»,
+       «int», «Length of the argument vector.»,
+       On success, the number of elements in the computed argument vector
+       is returned. Slot zero of the argument vector is initialized to a
+       dummy value while the remaining values correspond to the options and
+       arguments found in the input buffer. The argument vector should be
+       freed with lls_free_argv() when it is no longer needed.
+
+       On failure a negative error code is returned and *result is set to
+       NULL. Several types of failure are possible, including allocation
+       failure, errors from the lexer and various syntax errors.
+)
+
+DECLARE_FUNCTION(
+       «lls_free_argv»,
+       «Deallocate an argument vector.»,
+       lls_convert_config() dynamically allocates memory for the argument
+       and for each of its elements. This function frees this memory.
+», «
+       «char **argv», «The argument vector to free.»
+», «
+       It's OK to pass a NULL pointer, in which case the function does
+       nothing.
+»,
+       «void»
+)
+
+DECLARE_FUNCTION(
+       «lls_check_arg_count»,
+       «Check the number of non-option arguments.»,
+       This helper verifies that the number of non-option arguments lies
+       within the specified range. Although this function is kind of trivial,
+       it can help applications to provide nice and consistent error messages.
+», «
+       «const struct lls_parse_result *lpr», «As obtained from lls_parse().»,
+       «int min_argc», «Lower bound on the number of non-option arguments.»,
+       «int max_argc», «Upper bound on the number of non-option arguments.»,
+       «char **errctx», «Describes the range violation, only set on failure.»
+», «
+       For the function to succeed, the number of non-option arguments (as
+       returned by lls_num_inputs()) must be greater or equal to min_argc
+       and less or equal to max_argc.
+
+       Both min_argc and max_argc may be zero (but not negative), and min_argc
+       must be less or equal to max_argc. The value INT_MAX for max_argc
+       indicates that the number of allowed non-option arguments is unbounded.
+»,
+       «int», «Standard. The only possible error is -LLS_E_BAD_ARG_COUNT.»,
+       Examples:
+
+       min_argc = 0, max_argc = 0: no non-option argument may be given.
+
+       min_argc = 0, max_argc = INT_MAX: any number of non-option arguments OK.
+
+       min_argc = 1, max_argc = 2: either one or two non-option arguments OK.
+
+       Behaviour is undefined if min_argc or max_argc is negative, if min_argc
+       is greater than max_argc, or if lpr is invalid.
+)
+
+DECLARE_FUNCTION(
+       «lls_serialize_parse_result»,
+       «Create a buffer from a parse result.»,
+       This function is handy for passing the result from lls_parse() to a
+       different process.
+», «
+       «const struct lls_parse_result *lpr», «The parse result to serialize.»,
+       «const struct lls_command *cmd», «Must point to the command used to create the parse result.»,
+       «char **result», «The serialized parse result.»,
+       «size_t *nbytes», «The size of the serialized buffer.»
+», «
+       Depending on the initial value of the result pointer, the function
+       behaves as follows.
+
+       (a) If result is NULL, the size required to store the serialized
+       buffer is computed and returned through the nbytes argument, but no
+       serialization takes place.
+
+       (b) If result is not NULL, but *result is NULL, a suitable buffer is
+       allocated with malloc() and *result is pointed at this buffer. The
+       caller is responsible for freeing this buffer when it is no longer
+       needed.
+
+       (c) If *result is not NULL, the buffer pointed at by *result is assumed
+       be be large enough for the serialized parse result, and this buffer
+       is used to store the result.
+»,
+       «int», «Standard.»,
+       See also: lls_deserialize_parse_result().
+)
+
+DECLARE_FUNCTION(
+       «lls_deserialize_parse_result»,
+       «Initialize a parse result from a buffer.»,
+       This is the counterpart to lls_serialize_parse_result().
+», «
+       «const char *buf», «The buffer to de-serialize.»,
+       «const struct lls_command *cmd», «Must match the pointer used for serializing.»,
+       «struct lls_parse_result **lprp», «Result pointer.»
+», «
+       The input buffer should have been obtained through an earlier call
+       to lls_serialize_parse_result().
+»,
+       «int», «Standard.»,
+       On success all fields of lpr match the original values. After the
+       call, no fields of *lprp contain references to buf, so buf may safely
+       be freed.
+)
+
+DECLARE_FUNCTION(
+       «lls_merge»,
+       «Combine two parse results, creating an effective configuration.»,
+       This is useful for applications which receive options from the command
+       line and the configuration file.
+», «
+       «const struct lls_parse_result *primary», «From command line options.»,
+       «const struct lls_parse_result *secondary», «From config file.»,
+       «const struct lls_command *cmd», «Common command for both parse results.»,
+       «struct lls_parse_result **lprp», «Effective configuration is returned here.»,
+       «char **errctx», «Error context, see lls_parse().»
+», «
+       Merging works on a per-option basis as follows. If the multiple flag
+       is set for the option, the argument arrays of the primary and the
+       secondary parse result are concatenated and the concatenation is the
+       argument array for the merge result. It the multiple flag is not set,
+       the value of the primary parse result becomes the argument for the
+       merge result.
+
+       The two non-option argument arrays are concatenated in the same way
+       as the arguments to options with the multiple flag set.
+
+       All arguments are copied from the two input parse results. It is safe
+       to free them after the function returns. The merge result should be
+       freed with lls_parse_result() when it is no longer needed.
+»,
+       «int», «Standard.»,
+       The only possible error is an out of memory condition. However,
+       behaviour is undefined if the primary or secondary parse result is
+       NULL, or was not obtained from the given command.
+)
+
+DECLARE_FUNCTION(
+       «lls_dump_parse_result»,
+       «Create contents of a configuration file from a parse result.»,
+       The subcommand marker ([subcommand]) is not included in the output.
+», «
+       «const struct lls_parse_result *lpr», «As obtained from lls_parse() or lls_merge().»,
+       «const struct lls_command *cmd», «Subcommand or supercommand.»,
+       «bool non_default_only», «Only include option values that differ from the default.»
+»,«
+       If non_default_only is false, options are included in the dump even
+       if they are not given in the parse result. However, flag options are
+       excluded in this case as well as options which take an argument for
+       which no default value has been defined.
+»,
+       «char *», «Must be freed by the caller.»,
+       If no options are given, or if every option argument of the parse
+       result matches the default value of the option and non_default_only
+       is true, the function returns the empty string.
+
+       The only possible error is an out of memory condition, in which case
+       the NULL pointer is returned. Behaviour is undefined if any of the
+       pointer arguments is NULL, or if the parse result does not match the
+       given command.
+)
+VERBATIM_C(«#endif /* _LOPSUB_H */»)
diff --git a/lopsubex.c b/lopsubex.c
new file mode 100644 (file)
index 0000000..8c10684
--- /dev/null
@@ -0,0 +1,332 @@
+/*
+ * Written 2016 by Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Public domain, no copyright claims.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdbool.h>
+
+#include "lopsub.h"
+#include "lopsubex.lsg.h"
+
+typedef int (*example_handler_t)(struct lls_parse_result *);
+
+struct local_command_info {
+       example_handler_t handler;
+};
+
+#define EXPORT_CMD(_cmd) \
+       const struct local_command_info lsg_lopsubex_com_ ## _cmd ## _user_data = { \
+               .handler = com_ ## _cmd \
+       };
+
+#define CMD_PTR(_cmd) lls_cmd(LSG_LOPSUBEX_CMD_ ## _cmd, lopsubex_suite)
+#define OPT_PTR(_cmd, _opt) \
+       lls_opt(LSG_LOPSUBEX_ ## _cmd ## _OPT_ ## _opt, CMD_PTR(_cmd))
+
+#define OPT_RESULT(_cmd, _opt, _lpr) \
+       lls_opt_result(LSG_LOPSUBEX_ ## _cmd ## _OPT_ ## _opt, _lpr)
+
+static void print_available_commands(void)
+{
+       const struct lls_command *cmd;
+       int i;
+       printf("Available subcommands:\n");
+       for (i = 1; (cmd = lls_cmd(i, lopsubex_suite)); i++) {
+               const char *name = lls_command_name(cmd);
+               const char *purpose = lls_purpose(cmd);
+               printf("%-20s%s\n", name, purpose);
+       }
+}
+
+static const struct lls_command *lookup_subcmd_or_die(const char *str)
+{
+       char *errctx;
+       int ret = lls_lookup_subcmd(str, lopsubex_suite, &errctx);
+
+       if (ret < 0) {
+               if (errctx)
+                       printf("%s: ", errctx);
+               printf("%s\n", lls_strerror(-ret));
+               print_available_commands();
+               exit(EXIT_FAILURE);
+       }
+       return lls_cmd(ret, lopsubex_suite);
+}
+
+static int com_flag(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r = OPT_RESULT(FLAG, SIMPLE, lpr);
+
+       /* flag count is obtained with lls_given() */
+       printf("--simple is given %u times\n", lls_opt_given(r));
+
+       r = OPT_RESULT(FLAG, SQRT4, lpr);
+       printf("--sqrt4 (aka -2) is given %u times\n", lls_opt_given(r));
+       return 0;
+}
+EXPORT_CMD(flag)
+
+static int com_int_param(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r_r = OPT_RESULT(INT_PARAM, ROTATE, lpr);
+       printf("rotating by %d degrees\n", lls_int32_val(0, r_r));
+       return 0;
+}
+EXPORT_CMD(int_param)
+
+static int com_multiple(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r;
+       int i;
+       unsigned given;
+
+       r = OPT_RESULT(MULTIPLE, VERBOSE, lpr);
+       printf("--verbose is given %d times\n", lls_opt_given(r));
+       r = OPT_RESULT(MULTIPLE, INPUT_FILE, lpr);
+       given = lls_opt_given(r);
+       printf("--input-file is given %d times\n", lls_opt_given(r));
+       for (i = 0; i < given; i++)
+               printf("--input-file val #%d: %s\n", i, lls_string_val(i, r));
+       r = OPT_RESULT(MULTIPLE, OUTPUT_FILE, lpr);
+       printf("--output-file is given %d times\n", lls_opt_given(r));
+       printf("--output-file val: %s\n", lls_string_val(0, r));
+       return 0;
+}
+EXPORT_CMD(multiple)
+
+static int com_custom_synopsis(struct lls_parse_result *lpr)
+{
+       const struct lls_command *cmd = CMD_PTR(CUSTOM_SYNOPSIS);
+       char *long_help = lls_long_help(cmd);
+       printf("%s\n", long_help);
+       free(long_help);
+       return 0;
+}
+EXPORT_CMD(custom_synopsis)
+
+static void fruit_salad(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r_f = OPT_RESULT(SERIALIZE, FRUIT, lpr);
+       const struct lls_opt_result *r_a = OPT_RESULT(SERIALIZE, AMOUNT, lpr);
+       const struct lls_opt_result *r_s = OPT_RESULT(SERIALIZE, SUGAR, lpr);
+       const struct lls_opt_result *r_c = OPT_RESULT(SERIALIZE, CONTAINER, lpr);
+       int i, num_vals;
+
+       printf("Put %d gramms of fruits (", lls_uint32_val(0, r_a));
+       num_vals = lls_opt_given(r_f) > 0? lls_opt_given(r_f) : 1;
+       for (i = 0; i < num_vals; i++)
+               printf("%s%s", lls_string_val(i, r_f),
+                       i == num_vals - 1? "" : ", ");
+       printf(") in a %s, ", lls_string_val(0, r_c));
+       if (lls_opt_given(r_s))
+               printf("add sugar, ");
+       printf("and serve cold.\n");
+}
+
+static int com_serialize(struct lls_parse_result *lpr)
+{
+       const struct lls_command *cmd = CMD_PTR(SERIALIZE);
+       char *buf = NULL;
+       int ret;
+       size_t nbytes;
+       struct lls_parse_result *dlpr; /* deserialized */
+
+       fruit_salad(lpr);
+       ret = lls_serialize_parse_result(lpr, cmd, &buf, &nbytes);
+       if (ret < 0)
+               return ret;
+       printf("serialized parse result into %zu byte buffer\n", nbytes);
+       ret = lls_deserialize_parse_result(buf, cmd, &dlpr);
+       free(buf);
+       if (ret < 0)
+               return ret;
+       printf("successfully deserialized parse result\n");
+       fruit_salad(dlpr);
+       lls_free_parse_result(dlpr, cmd);
+       return 1;
+}
+EXPORT_CMD(serialize)
+
+static int com_non_ascii(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r_c = OPT_RESULT(NON_ASCII, CITY, lpr);
+       const char *city = lls_string_val(0, r_c);
+
+       if (strcmp(city, "Göttingen"))
+               printf("I don't know anything about %s\n", city);
+       else
+               printf("One of the most famous citicens of Göttingen was\n"
+                       "the mathematician Carl Friedrich Gauß.\n");
+       exit(EXIT_SUCCESS);
+}
+EXPORT_CMD(non_ascii)
+
+static int com_enum(struct lls_parse_result *lpr)
+{
+       const struct lls_option *o_c = OPT_PTR(ENUM, COLOR);
+       const struct lls_opt_result *r_c = OPT_RESULT(ENUM, COLOR, lpr);
+       bool c_given = lls_opt_given(r_c);
+       uint32_t num;
+       const char *color;
+
+       num = lls_uint32_val(0, r_c);
+       color = lls_enum_string_val(num, o_c);
+       printf("%s value: #%d: %s\n", c_given? "good" : "default", num, color);
+       printf("Band names containing '%s'\n", color);
+       switch (num) {
+       case COLOR_RED:
+               printf("Red Snapper\n");
+               printf("Red Lorry Yellow Lorry\n");
+               break;
+       case COLOR_GREEN:
+               printf("Green Day\n");
+               printf("Green Jelly\n");
+               break;
+       case COLOR_BLUE:
+               printf("Blue Cheer\n");
+               printf("Blue Öyster Cult\n");
+               break;
+       default:
+               printf("Nothing appropriate\n");
+       }
+       if (!c_given) {
+               printf("Available colors:\n");
+               for (num = 0; num < LSG_NUM_LOPSUBEX_ENUM_COLOR_VALUES; num++)
+                       printf("color #%d: %s\n", num,
+                               lls_enum_string_val(num, o_c));
+       }
+       return 1;
+}
+EXPORT_CMD(enum)
+
+static int com_quotes(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r_s = OPT_RESULT(QUOTES, CHARS, lpr);
+       const char *val= lls_string_val(0, r_s);
+
+       printf("Special characters: %s\n", val);
+       return 0;
+}
+EXPORT_CMD(quotes)
+
+static int com_help(struct lls_parse_result *lpr)
+{
+       const struct lls_command *cmd = CMD_PTR(HELP);
+       const struct lls_opt_result *r_l = OPT_RESULT(HELP, LONG, lpr);
+       char *txt;
+       int ret;
+
+       ret = lls_check_arg_count(lpr, 0, 1, NULL);
+       if (ret < 0)
+               return ret;
+       if (lls_num_inputs(lpr) > 0)
+               cmd = lookup_subcmd_or_die(lls_input(0, lpr));
+       if (lls_opt_given(r_l))
+               txt = lls_long_help(cmd);
+       else
+               txt = lls_short_help(cmd);
+       printf("%s", txt);
+       free(txt);
+       return 0;
+}
+EXPORT_CMD(help)
+
+static int com_default_val(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r;
+       uint32_t val1, val2;
+       const char *txt1, *txt2;
+
+       r = OPT_RESULT(DEFAULT_VAL, WIDTH, lpr);
+       val1 = lls_uint32_val(0, r);
+       r = OPT_RESULT(DEFAULT_VAL, HEIGHT, lpr);
+       val2 = lls_uint32_val(0, r);
+       printf("geometry: %" PRIu32 "x%" PRIu32 "\n", val1, val2);
+
+       r = OPT_RESULT(DEFAULT_VAL, TOWN, lpr);
+       txt1 = lls_string_val(0, r);
+       r = OPT_RESULT(DEFAULT_VAL, STREET, lpr);
+       txt2 = lls_string_val(0, r);
+       printf("address: %s, %s\n", txt2, txt1);
+
+       r = OPT_RESULT(DEFAULT_VAL, TIME, lpr);
+       val1 = lls_uint32_val(0, r);
+       txt1 = lls_enum_string_val(val1, OPT_PTR(DEFAULT_VAL, TIME));
+       r = OPT_RESULT(DEFAULT_VAL, WEEKDAY, lpr);
+       val2 = lls_uint32_val(0, r);
+       txt2 = lls_enum_string_val(val2, OPT_PTR(DEFAULT_VAL, WEEKDAY));
+       printf("when: %s %s\n", txt2, txt1);
+
+       return 0;
+}
+EXPORT_CMD(default_val)
+
+static int com_optional_arg(struct lls_parse_result *lpr)
+{
+       const struct lls_opt_result *r;
+
+       r = OPT_RESULT(OPTIONAL_ARG, WIDTH, lpr);
+
+       printf("width: %u (%u times given)\n", lls_uint32_val(0, r),
+               lls_opt_given(r));
+       r = OPT_RESULT(OPTIONAL_ARG, HEIGHT, lpr);
+       printf("height: %u (%u times given)\n", lls_uint32_val(0, r),
+               lls_opt_given(r));
+       printf("%u non-option arguments\n", lls_num_inputs(lpr));
+       return 0;
+}
+EXPORT_CMD(optional_arg)
+
+/* stringify the first argument (author information) */
+#define LOPSUBEX_AUX_INFO(_author, _perms) #_author,
+static const char * const authors[] = {LSG_LOPSUBEX_AUX_INFOS};
+#undef LOPSUBEX_AUX_INFO
+#define LOPSUBEX_AUX_INFO(_author, _perms) _perms,
+static const mode_t permissions[] = {LSG_LOPSUBEX_AUX_INFOS};
+static int com_aux_info(struct lls_parse_result *lpr)
+{
+       const struct lls_command *cmd;
+       int i;
+
+       for (i = 0; (cmd = lls_cmd(i, lopsubex_suite)); i++) {
+               const char *name = lls_command_name(cmd);
+               printf("%s: ", name);
+               printf("author: %s, permissions: %o\n", authors[i],
+                       permissions[i]);
+       }
+       return 0;
+}
+EXPORT_CMD(aux_info)
+
+int main(int argc, char **argv)
+{
+       int ret;
+       const struct lls_command *cmd;
+       struct lls_parse_result *lpr;
+       const struct local_command_info *lci;
+       char *errctx;
+
+       if (argc <= 1) {
+               printf("Usage: %s <subcommand> [options]\n", argv[0]);
+               print_available_commands();
+               exit(EXIT_FAILURE);
+       }
+       cmd = lookup_subcmd_or_die(argv[1]);
+       ret = lls_parse(argc - 1, argv + 1, cmd, &lpr, &errctx);
+       if (ret < 0) {
+               printf("%s: %s\n", errctx, lls_strerror(-ret));
+               free(errctx);
+               exit(EXIT_FAILURE);
+       }
+       lci = lls_user_data(cmd);
+       ret = lci->handler(lpr);
+       lls_free_parse_result(lpr, cmd);
+       exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS);
+}
diff --git a/lopsubex.suite b/lopsubex.suite
new file mode 100644 (file)
index 0000000..98c5649
--- /dev/null
@@ -0,0 +1,392 @@
+# Written 2016 by Andre Noll <maan@tuebingen.mpg.de>
+#
+# Public domain, no copyright claims.
+
+[suite lopsubex]
+aux_info_prefix = further information:
+aux_info_default = (unknown), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
+caption = lopsubex subcommands
+# This will be printed before the list of subcommands
+[supercommand lopsubex]
+       purpose = illustrate lopsub features
+       non-opts-name = [<subcommand>] [options]
+       [description]
+               This program illustrates the various features of the lopsub option
+               parser. Consult the lopsubex.suite file and the source in lopsubex.c
+               to see how the subcommands described in this document are implemented.
+       [/description]
+       [closing]
+               When executed with no argument, the program lists the available
+               subcommands.  Otherwise the first argument must be a subcommand from
+               the list below.
+       [/closing]
+
+[introduction]
+       Each subcommand of lopsubex illustrates a different feature of the
+       lopsub library.
+[/introduction]
+
+[subcommand flag]
+       purpose = options with no arguments
+       [description]
+               Flags are the simplest type of options as they take no argument.
+               To create a flag option, simply omit arg_type, arg_info, and typestr
+               in the suite file.
+       [/description]
+       [option simple]
+               summary = simplest option on the planet
+       [option sqrt4]
+               short_opt = 2
+               summary = Flag option with a short option equivalent.
+       [closing]
+               Lopsub counts the number of times an option was given. This can be
+               used, for example, to set the verbosity level of the application.
+
+               Decimal digits are allowed as short option equivalent for a long
+               option.
+       [/closing]
+
+[subcommand int-param]
+       purpose = options which take an integer argument
+       [description]
+               Options may be specified to take an integer argument. This subcommand
+               provides a simple example.
+       [/description]
+       [option rotate]
+               short_opt = r
+               summary = example option which takes a required integer argument
+               arg_info = required_arg
+               arg_type = int32
+               typestr = degrees
+               [help]
+                       Integers may be specified as a C99 decimal, hexadecimal or octal
+                       constant.
+
+                       The type of the argument for this option is a signed 32 bit integer,
+                       so negative values are allowed.
+               [/help]
+
+[subcommand multiple]
+       purpose = options that can be specified more than once
+       [description]
+               The multiple flag indicates that an option may be given more than
+               once. This instructs the parser to make all given arguments available
+               to the application via the parse_result structure which is returned
+               from lls_parse().
+       [/description]
+       [option verbose]
+               short_opt = v
+               summary = may be given multiple times, takes no argument
+               [help]
+                       For flag options (options which do not take an argument), the number
+                       of times the flag was given is stored in the parse result, regardless
+                       of whether the multiple flag was set in the suite file.
+
+                       To specify the option twice, one may write either -v -v or -vv.
+               [/help]
+       [option input-file]
+               short_opt = i
+               summary = multiple string values may be given
+               [help]
+                       The subcommand will print all given values.
+               [/help]
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               flag multiple
+       [option output-file]
+               short_opt = o
+               summary = only one value is stored in the parse result.
+               [help]
+                       Since this option is not declared as multiple, only one value is
+                       stored in the parse result. If the option was given more than once,
+                       the last given value wins.
+               [/help]
+               arg_info = required_arg
+               arg_type = string
+               typestr = path
+               default_val = /dev/null
+       [closing]
+               Run this command and specify each option more than once to see
+               the effect.
+       [/closing]
+
+[subcommand custom-synopsis]
+       purpose = overwrite the default usage string
+       synopsis = [file...]
+       [description]
+               If a synopsis text is specified, this text is shown
+               as the usage string in the help output. Otherwise,
+               lopsub creates a suitable string on its own.
+
+               In this example, the synopsis text is set to "[file...]".
+       [/description]
+
+[subcommand enum]
+       purpose = enumerable options
+       [description]
+               In this command, --color is an enum option, which takes the name of a
+               color as its argument. Only a pre-defined set of colors are accepted,
+               as defined in the suite file.
+
+               The last value (black) shows how to specify values with embedded
+               spaces and quotes.
+       [/description]
+       [option color]
+               summary = name of a color
+               short_opt = c
+               arg_info = required_arg
+               arg_type = string
+               typestr = color
+               values = {
+                       COLOR_RED = "red",
+                       COLOR_GREEN = "green",
+                       COLOR_BLUE = "blue",
+                       COLOR_YELLOW = "yellow",
+                       COLOR_BLACK = "black (that's not really a \"color\")"
+               }
+               default_val = green
+
+[subcommand serialize]
+       purpose = write a parse result into a buffer
+       [description]
+               The lopsub library provides a function to serialize the result
+               of a successful call to lls_parse(). This subcommand illustrates
+               this feature. First it parses the given arguments to produce a
+               parse result. Next it serializes the parse result into a buffer
+               and de-serializes this buffer into a second parse result. A text
+               which refers to the parsed values is printed twice, first with the
+               original parse result, then with the de-serialized version. The two
+               texts should be identical.
+       [/description]
+       [option fruit]
+               short_opt = f
+               arg_info = required_arg
+               arg_type = string
+               typestr = fruit
+               flag multiple
+               default_val = apples
+       [option amount]
+               short_opt = a
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = g
+               default_val = 100
+       [option sugar]
+               short_opt = s
+       [option container]
+               short_opt = c
+               arg_type = string
+               arg_info = optional_arg
+               default_val = bowl
+
+[subcommand non-ascii]
+       purpose = use of non-ASCII characters (e.g., umlauts like 'ä')
+       [description]
+               Non-ASCII characters are not allowed for command and option names. They
+               may appear, however, in the purpose text of a command (see above),
+               in the type string and the description of an option (see below),
+               and also in a help text like this: öäüß. Of course, non-ASCII
+               characters are also allowed in the argument to a string option.
+
+               The help text generated by the lopsub library functions should just
+               work on any system where UTF-8 is the default character encoding.
+
+               For generating man pages, however, UTF-8 will not do, because
+               groff interprets input character codes between 127 and 255 as the
+               corresponding characters in the latin1 (ISO-8859-1) code set. Hence,
+               it is necessary to format the generated with the command preconv -e
+               UTF-8. The man(1) command of the man-db package (default on most
+               Linux distributions) does this automatically, so man pages should
+               render correctly on those systems. On other systems it might help to
+               convert the suite file to ISO-8859-1 before feeding it to lopsubgen:
+
+                    iconv -t ISO_8859-1 app.suite | ./lopsubgen --gen-man
+
+       [/description]
+       [option city]
+               summary = name of a big city (Großstadt)
+               [help]
+                       The command prints a more or less interesting sentence about the
+                       given city. At the moment, only Göttingen is supported.
+               [/help]
+               short_opt = c
+               arg_info = required_arg
+               arg_type = string
+               typestr = Großstadt
+               default_val = Göttingen
+
+[subcommand quotes]
+       purpose = embedded quotes (") and backslashes (\).
+       [description]
+               In the C language a double quote (") denotes the beginning or the end
+               of a string literal. Embedded double quotes must be prefixed with a
+               backslash (\), and embedded backslashes need to be doubled.
+
+               Lopsubgen handles these issues internally. Hence all help texts,
+               purpose and description strings and default values may contain single
+               (') or double quotes ("), balanced or unbalanced.
+
+               Man output has similar issues. For example, if the first word of a
+               line starts with a dot or a single quote, lopsubgen prevents roff
+               from interpreting the word. Examples:
+
+               .SH This is the path to a hidden version of /bin/sh. It should not
+               be mistaken as a roff man macro for an unnumbered section heading.
+
+               'In the roff type-setting system, a single quote at the beginning
+               of a line indicates a non-breaking control character. Lopsubgen will
+               escape such lines automatically.
+
+       [/description]
+       [option chars]
+               summary = specify problematic characters (e.g., "'`$\.)
+               [help]
+                       Single (') and  double quotes (") are also allowed in the type string
+                       and the default value of an option.
+               [/help]
+               short_opt = s
+               arg_info = required_arg
+               arg_type = string
+               typestr = "set"
+               default_val = .$`"'%#?*!/\
+
+[subcommand help]
+       purpose = automatically generated help texts
+       non-opts-name = [<subcommand>]
+       [description]
+               The lopsub library provides functions which format the options and
+               the help text of a command. Short and long help texts are available.
+       [/description]
+       [option long]
+               summary = print the long help text
+               short_opt = l
+               [help]
+                       The short help omits the text of the suite file enclosed between the
+                       [help] and [/help] markers while the long version includes this text.
+               [/help]
+
+[subcommand aux_info]
+       purpose = stash additional per-command information into a suite file
+       [description]
+               The aux_info feature of lopsub allows to add additional information
+               to the subcommands of a suite without the need to maintain another
+               per-command array, which would be error-prone.
+
+               To illustrate the feature, the command section for this subcommand
+               contains an author name and a (fictitious) permission value,
+               realized as the OR of the usual file mode bits. When the subcommand
+               is executed, it prints this information for each subcommand.
+
+               Note that the man page contains the symbolic names of the permission
+               bits while the code in the aux_info command handler of lopsubex.c
+               gets the numeric value.
+       [/description]
+       aux_info = Random J. Hacker, S_IRWXU
+
+[subcommand default-val]
+       purpose = how default values are handled
+       [description]
+               Run this subcommand with no arguments to see how default values apply,
+               depending on the type of the argument and whether a default value is
+               specified in the suite file.
+       [/description]
+       [option width]
+               summary = numeric argument with default value
+               short_opt = w
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = pixels
+               default_val = 42
+       [option height]
+               summary = numeric argument without default value
+               short_opt = h
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = pixels
+       [option town]
+               short_opt = t
+               summary = string argument with default value
+               arg_info = required_arg
+               arg_type = string
+               typestr = name
+               default_val = Berlin
+       [option street]
+               short_opt = s
+               summary = string argument without default value
+               arg_info = required_arg
+               arg_type = string
+               typestr = streetname
+       [option time]
+               summary = enum option with default value
+               short_opt = T
+               arg_info = required_arg
+               arg_type = string
+               typestr = daytime
+               values = {
+                       DT_MORNING = "morning",
+                       DT_AFTERNOON = "afternoon",
+                       DT_EVENING = "evening",
+                       DT_NIGHT = "night"
+               }
+               default_val = night
+       [option weekday]
+               summary = enum option without default value
+               short_opt = w
+               arg_info = required_arg
+               arg_type = string
+               typestr = dayofweek
+               values = {
+                       DOW_MONDAY = "Monday",
+                       DOW_TUESDAY = "Tuesday",
+                       DOW_WEDNESDAY = "Wednesday",
+                       DOW_THURSDAY = "Thursday",
+                       DOW_FRIDAY = "Friday",
+                       DOW_SATURDAY = "Saturday",
+                       DOW_SUNDAY = "Sunday"
+               }
+
+[subcommand optional-arg]
+       purpose = options with optional argument
+       non-opts-name = <file>
+       [description]
+               Options which take an optional argument behave much like like options
+               with required argument. If the option is not given, or is given with
+               no argument supplied, the default value is substituted, with the
+               semantics illustrated in the default-val subcommand.
+
+               Note that the optional argument must be specified as in --opt=arg
+               rather than --opt arg because the latter form is ambiguous (arg could
+               also be a non-option argument).
+
+               Run this subcommand with -w=1, then with -w 1 to see the
+               difference. Also note the difference between -w 1 and -h 1.
+       [/description]
+       [option width]
+               summary = option with optional uint32 argument
+               short_opt = w
+               arg_info = optional_arg
+               arg_type = uint32
+               typestr = pixels
+               default_val = 42
+       [option height]
+               summary = option with required uint32 argument
+               short_opt = h
+               arg_info = required_arg
+               arg_type = uint32
+               typestr = pixels
+
+[conclusion]
+       This concludes the description of the subcommands of lopsubex. For
+       further information, consult the source code.
+[/conclusion]
+[section copyright]
+       Written by Andre Noll
+       .br
+       Public domain, no copyright claims.
+       .br
+       Report bugs to
+       .MT <maan@tuebingen.mpg.de>
+       Andre Noll
+       .ME
+[/section]
diff --git a/lopsubgen.l b/lopsubgen.l
new file mode 100644 (file)
index 0000000..f5db52b
--- /dev/null
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
+ */
+
+%option noyywrap
+%option stack
+%option never-interactive
+%option yylineno
+
+%s SC_COMMAND
+%s SC_OPTION
+%s SC_ARG_INFO
+%s SC_ARG_TYPE
+%s SC_OPTION_FLAG
+%s SC_CHAR
+%s SC_INTEGER
+%s SC_UNQUOTED_LINE
+%s SC_UNQUOTED_LINE_CHECK_DEFAULT
+%s SC_ERROR
+%s SC_VALUES_ID
+%s SC_VALUES_COMMA_OR_BRACE
+
+%x SC_INTRODUCTION
+%x SC_DESCRIPTION
+%x SC_CLOSING
+%x SC_HELP
+%x SC_CONCLUSION
+%x SC_SECTION
+%{
+       #include <ctype.h>
+       #include <stdlib.h>
+       #include <assert.h>
+       #include <stdbool.h>
+
+       #include "lsg.h"
+
+       static char **charpp;
+       static char *text_buf;
+       static size_t text_buf_len;
+       struct lsg_suite suite;
+
+       #define CURCMD (suite.commands[suite.num_subcommands])
+       #define CUROPT (CURCMD.options[CURCMD.num_options - 1])
+       #define CURSECT (suite.sections[suite.num_sections - 1])
+
+       static void *xmalloc(size_t size)
+       {
+               void *p;
+
+               assert(size > 0);
+               p = malloc(size);
+               assert(p);
+               return p;
+       }
+       void *xrealloc(void *ptr, size_t size)
+       {
+               void *p;
+               assert(size > 0);
+               p = realloc(ptr, size);
+               assert(p);
+               return p;
+       }
+
+       /*
+        * Extract ID from a string like [foo ID] or bar = ID, or a substring
+        * of a list: ID1, ID2, ...
+        */
+       static char *parse_identifier(char *p)
+       {
+               char *p2, *eq;
+
+               if (*p == '[') {
+                       while (*p && isspace(*p))
+                               p++;
+                       while (*p && !isspace(*p))
+                               p++;
+               } else {
+                       if ((eq = strchr(p + 1, '=')))
+                               p = eq + 1;
+                       else
+                               while (*p && (isspace(*p) || *p == ','))
+                                       p++;
+               }
+               assert(*p);
+               while (*p && isspace(*p))
+                       p++;
+               assert(*p);
+               p2 = p;
+               while (isalnum(*p2) || *p2 == '_' || *p2 == '-')
+                       p2++;
+               *p2 = '\0';
+               return strdup(p);
+       }
+
+       static char *parse_simple_string(void)
+       {
+               char *result, *p = yytext;
+               while (isspace(*p))
+                       p++;
+               assert(*p == '[');
+               p++;
+               while (isspace(*p))
+                       p++;
+               while (!isspace(*p)) /* skip "section" */
+                       p++;
+               while (isspace(*p))
+                       p++;
+               result = strdup(p);
+               p = strrchr(result, ']');
+               do {
+                       *p = '\0';
+                       p--;
+               } while (isspace(*p));
+               return result;
+       }
+       static void parse_id_string(void)
+       {
+               char *p, *q;
+               int val_num = CUROPT.num_values++, num_vals = val_num + 1;
+               bool backslash;
+
+               CUROPT.values = xrealloc(CUROPT.values,
+                       num_vals * sizeof(char *));
+               CUROPT.value_ids = xrealloc(CUROPT.value_ids,
+                       num_vals * sizeof(char *));
+               CUROPT.value_literals = xrealloc(CUROPT.value_literals,
+                       num_vals * sizeof(char *));
+               CUROPT.value_ids[val_num] = p = strdup(yytext);
+               while (*p && !isspace(*p) && *p != '=')
+                       p++;
+               *p = '\0';
+               p++;
+               while (*p && *p != '\"')
+                       p++;
+               CUROPT.value_literals[val_num] = p;
+               p++;
+               CUROPT.values[val_num] = q = xmalloc(strlen(p) + 1);
+               for (backslash = false; *p; p++) {
+                       if (*p == '\\') {
+                               if (!backslash) {
+                                       backslash = true;
+                                       continue;
+                               }
+                       } else if (*p == '"') {
+                               if (!backslash) {
+                                       *q = '\0';
+                                       return;
+                               }
+                       }
+                       *q++ = *p;
+                       backslash = false;
+               }
+               assert(false);
+       }
+       static char *parse_unquoted_line(void)
+       {
+               char *p = strdup(yytext);
+               size_t n = strlen(p);
+               for (; n > 0; n--) {
+                       if (isspace(p[n - 1]))
+                               continue;
+                       p[n] = '\0';
+                       break;
+               }
+               return p;
+       }
+       static void check_default_val(void)
+       {
+               int i;
+
+               if (!CUROPT.default_val)
+                       return;
+               if (!CUROPT.values)
+                       return;
+               for (i = 0; i < CUROPT.num_values; i++) {
+                       char *val = CUROPT.values[i];
+                       char buf[40];
+                       if (strcmp(val, CUROPT.default_val))
+                               continue;
+                       sprintf(buf, "%i", i);
+                       free(CUROPT.default_val);
+                       CUROPT.default_val = strdup(buf);
+                       return;
+               }
+               fprintf(stderr, "option %s: bad default value %s\n",
+                       CUROPT.name.orig, CUROPT.default_val);
+               exit(EXIT_FAILURE);
+       }
+
+%}
+EQUALS [[:space:]]*=[[:space:]]*
+IDENTIFIER [a-zA-Z]+[a-zA-Z0-9_-]*
+C99_DECIMAL_CONSTANT -?([[:digit:]]{-}[0])[[:digit:]]*
+C99_HEXADECIMAL_CONSTANT 0[xX][[:xdigit:]]+
+C99_OCTAL_CONSTANT 0[01234567]*
+INT_CONSTANT {C99_DECIMAL_CONSTANT}|{C99_HEXADECIMAL_CONSTANT}|{C99_OCTAL_CONSTANT}
+STRING_VALUE \"([^\"\\\n]|(\\[\"\\]))*\"
+SIMPLE_STRING [[:alnum:]]([[:alnum:]]|[[:space:]])*
+%%
+
+ /* skip comments and whitespace */
+^[[:space:]]*#.*\n ;
+[[:space:]]|\n+ ;
+
+<INITIAL>\[[[:space:]]*suite[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
+       free(suite.name.orig);
+       suite.name.orig = parse_identifier(yytext);
+}
+
+<INITIAL>caption{EQUALS} {
+       charpp = &suite.caption;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>title{EQUALS} {
+       charpp = &suite.title;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>mansect{EQUALS} {
+       charpp = &suite.mansect;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>date{EQUALS} {
+       charpp = &suite.date;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>version-string{EQUALS} {
+       charpp = &suite.version_string;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>manual_title{EQUALS} {
+       charpp = &suite.manual_title;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>aux_info_prefix{EQUALS} {
+       charpp = &suite.aux_info_prefix;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL>aux_info_default{EQUALS} {
+       charpp = &suite.aux_info_default;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*introduction[[:space:]]*\] {
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_INTRODUCTION);
+}
+
+<SC_INTRODUCTION>[[:space:]]*\[[[:space:]]*\/introduction[[:space:]]*\]\n {
+       suite.introduction = text_buf;
+       yy_pop_state();
+}
+
+<INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*conclusion[[:space:]]*\] {
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_CONCLUSION);
+}
+
+<SC_CONCLUSION>[[:space:]]*\[[[:space:]]*\/conclusion[[:space:]]*\]\n {
+       suite.conclusion = text_buf;
+       yy_pop_state();
+}
+
+<INITIAL>\[[[:space:]]*supercommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
+       struct lsg_command *cmd;
+
+       if (!suite.commands)
+               suite.commands = xmalloc(sizeof(*suite.commands));
+       cmd = suite.commands;
+       memset(cmd, 0, sizeof(*cmd));
+       cmd->name.orig = parse_identifier(yytext);
+       cmd->options = xmalloc(sizeof(*cmd->options));
+       BEGIN(SC_COMMAND);
+}
+
+<INITIAL,SC_COMMAND,SC_OPTION>\[[[:space:]]*subcommand[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
+       int command_num = ++suite.num_subcommands;
+       struct lsg_command *cmd;
+
+       suite.commands = realloc(suite.commands,
+               (suite.num_subcommands + 1) * sizeof(*suite.commands));
+       cmd = suite.commands + command_num;
+       memset(cmd, 0, sizeof(*cmd));
+       cmd->name.orig = parse_identifier(yytext);
+       cmd->options = xmalloc(sizeof(*cmd->options));
+       BEGIN(SC_COMMAND);
+}
+
+<SC_COMMAND>\[[[:space:]]*description[[:space:]]*\] {
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_DESCRIPTION);
+}
+
+<SC_COMMAND,SC_OPTION>\[[[:space:]]*closing[[:space:]]*\] {
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_CLOSING);
+}
+
+<SC_OPTION>\[[[:space:]]*help[[:space:]]*\] {
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_HELP);
+}
+<INITIAL,SC_OPTION,SC_COMMAND>\[[[:space:]]*section[[:space:]]+{SIMPLE_STRING}[[:space:]]*\] {
+       int sect_num = suite.num_sections++;
+       suite.sections = realloc(suite.sections,
+                suite.num_sections * sizeof(*suite.sections));
+       CURSECT.name.orig = parse_simple_string();
+       text_buf = NULL;
+       text_buf_len = 0;
+       yy_push_state(SC_SECTION);
+}
+
+<SC_DESCRIPTION>[[:space:]]*\[[[:space:]]*\/description[[:space:]]*\]\n {
+       CURCMD.description = text_buf;
+       yy_pop_state();
+}
+
+<SC_CLOSING>[[:space:]]*\[[[:space:]]*\/closing[[:space:]]*\]\n {
+       CURCMD.closing = text_buf;
+       yy_pop_state();
+}
+
+<SC_HELP>[[:space:]]*\[[[:space:]]*\/help[[:space:]]*\]\n {
+       CUROPT.help = text_buf;
+       yy_pop_state();
+}
+
+<SC_SECTION>[[:space:]]*\[[[:space:]]*\/section[[:space:]]*\]\n {
+       CURSECT.text = text_buf;
+       yy_pop_state();
+}
+
+<SC_DESCRIPTION,SC_HELP,SC_SECTION,SC_CLOSING,SC_INTRODUCTION,SC_CONCLUSION>.*\n {
+       size_t new_len = text_buf_len + yyleng;
+       size_t num_tabs = 0;
+
+       while (yytext[num_tabs] == '\t')
+               num_tabs++;
+       new_len = text_buf_len + yyleng - num_tabs;
+       text_buf = realloc(text_buf, new_len + 1);
+       memcpy(text_buf + text_buf_len, yytext + num_tabs, yyleng - num_tabs);
+       text_buf[new_len] = '\0';
+       text_buf_len = new_len;
+}
+
+<SC_COMMAND,SC_OPTION>\[[[:space:]]*option[[:space:]]+{IDENTIFIER}[[:space:]]*\] {
+       int option_num = CURCMD.num_options++;
+       struct lsg_option *opt;
+
+       CURCMD.options = realloc(CURCMD.options,
+               CURCMD.num_options * sizeof(*CURCMD.options));
+       memset(&CUROPT, 0, sizeof(CUROPT));
+       CUROPT.name.orig = parse_identifier(yytext);
+       BEGIN(SC_OPTION);
+}
+
+<SC_OPTION>arg_info{EQUALS} BEGIN(SC_ARG_INFO);
+<SC_ARG_INFO>no_arg CUROPT.arg_info = "LLS_NO_ARGUMENT"; BEGIN(SC_OPTION);
+<SC_ARG_INFO>required_arg CUROPT.arg_info = "LLS_REQUIRED_ARGUMENT"; BEGIN(SC_OPTION);
+<SC_ARG_INFO>optional_arg CUROPT.arg_info = "LLS_OPTIONAL_ARGUMENT"; BEGIN(SC_OPTION);
+
+<SC_OPTION>arg_type{EQUALS} BEGIN(SC_ARG_TYPE);
+<SC_ARG_TYPE>none CUROPT.arg_type = "LLS_NONE"; BEGIN(SC_OPTION);
+<SC_ARG_TYPE>string CUROPT.arg_type = "LLS_STRING"; BEGIN(SC_OPTION);
+<SC_ARG_TYPE>int32 CUROPT.arg_type = "LLS_INT32"; BEGIN(SC_OPTION);
+<SC_ARG_TYPE>uint32 CUROPT.arg_type = "LLS_UINT32"; BEGIN(SC_OPTION);
+<SC_ARG_TYPE>int64 CUROPT.arg_type = "LLS_INT64"; BEGIN(SC_OPTION);
+<SC_ARG_TYPE>uint64 CUROPT.arg_type = "LLS_UINT64"; BEGIN(SC_OPTION);
+
+<SC_OPTION>flag BEGIN(SC_OPTION_FLAG);
+<SC_OPTION_FLAG>multiple CUROPT.multiple = true; BEGIN(SC_OPTION);
+<SC_OPTION_FLAG>required CUROPT.required = true; BEGIN(SC_OPTION);
+<SC_OPTION_FLAG>ignored CUROPT.ignored = true; BEGIN(SC_OPTION);
+
+<SC_OPTION>default_val{EQUALS} {
+       charpp = &CUROPT.default_val;
+       if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_NONE") == 0) {
+               fprintf(stderr, "default_value for option w/o arguments!?\n");
+               exit(1);
+       } else if (strcmp(CUROPT.arg_type, "LLS_STRING") == 0)
+               yy_push_state(SC_UNQUOTED_LINE_CHECK_DEFAULT);
+       else
+               yy_push_state(SC_INTEGER);
+}
+<SC_OPTION>short_opt{EQUALS} BEGIN(SC_CHAR);
+<SC_CHAR>[a-zA-Z0-9] CUROPT.short_opt = yytext[0]; BEGIN(SC_OPTION);
+
+<SC_OPTION>values{EQUALS}\{ {
+       if (!CUROPT.arg_type || strcmp(CUROPT.arg_type, "LLS_STRING")) {
+               fprintf(stderr, "value list is only supported for string options\n");
+               exit(EXIT_FAILURE);
+       }
+       if (!CUROPT.arg_info || !strcmp(CUROPT.arg_info, "LLS_NO_ARGUMENT")) {
+               fprintf(stderr, "enum options must take an argument\n");
+               exit(EXIT_FAILURE);
+       }
+       if (CUROPT.default_val) {
+               fprintf(stderr, "value list must preceed default value\n");
+               exit(EXIT_FAILURE);
+       }
+       BEGIN(SC_VALUES_ID);
+}
+
+<SC_VALUES_ID>{IDENTIFIER}{EQUALS}{STRING_VALUE} {
+       parse_id_string();
+       BEGIN(SC_VALUES_COMMA_OR_BRACE);
+}
+
+<SC_VALUES_COMMA_OR_BRACE>[,\}] {
+       if (*yytext == ',')
+               BEGIN(SC_VALUES_ID);
+       else {
+               check_default_val();
+               BEGIN(SC_OPTION);
+       }
+}
+
+<SC_OPTION>summary{EQUALS} {
+       charpp = &CUROPT.summary;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+<SC_OPTION>typestr{EQUALS} {
+       charpp = &CUROPT.typestr;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<SC_COMMAND>purpose{EQUALS} {
+       charpp = &CURCMD.purpose;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<SC_COMMAND>non-opts-name{EQUALS} {
+       charpp = &CURCMD.non_opts_name;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<SC_COMMAND>synopsis{EQUALS} {
+       charpp = &CURCMD.synopsis;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<SC_COMMAND>aux_info{EQUALS} {
+       charpp = &CURCMD.aux_info;
+       yy_push_state(SC_UNQUOTED_LINE);
+}
+
+<SC_UNQUOTED_LINE>.*\n {
+       *charpp = parse_unquoted_line();
+       yy_pop_state();
+}
+<SC_UNQUOTED_LINE_CHECK_DEFAULT>.*\n {
+       *charpp = parse_unquoted_line();
+       check_default_val();
+       yy_pop_state();
+}
+
+<SC_INTEGER>{INT_CONSTANT} *charpp = strdup(yytext); yy_pop_state();
+
+ /* This rule runs iff none of the above patterns matched */
+. {
+       fprintf(stderr, "parse error at line %d. Unmatched: \"%s\"\n",
+               yyget_lineno(), yytext);
+       BEGIN(SC_ERROR);
+}
+<SC_ERROR>.*\n {
+       fprintf(stderr, "subsequent unparsed input: %s\n", yytext);
+       exit(EXIT_FAILURE);
+}
diff --git a/lopsubgen.suite b/lopsubgen.suite
new file mode 100644 (file)
index 0000000..d8166ab
--- /dev/null
@@ -0,0 +1,118 @@
+# Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+#
+# Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
+
+[suite lopsubgen]
+[supercommand lopsubgen]
+       [description]
+               The lopsubgen command reads a lobsub suite from stdin and converts
+               it to zero or more output formats. If no output format is specified,
+               the input is only checked for syntactic correctness.
+
+               The format of the input (the lopsub suite) is described in
+               lopsub-suite(5).
+       [/description]
+       purpose = convert a lobsup suite into C or roff format
+       [option ignored]
+               summary = General options
+               flag ignored
+       [option help]
+               short_opt = h
+               summary = print help and exit (give twice for detailed help)
+       [option version]
+               short_opt = V
+               summary = print version and exit
+       [option output-dir]
+               short_opt = o
+               summary = where to write output file(s)
+               arg_info = required_arg
+               arg_type = string
+               default_val = .
+               typestr = dir
+               [help]
+                       The default is to create all output files in the current working
+                       directory.
+               [/help]
+       [option gen-c]
+               summary = generate C output
+               arg_info = optional_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       This produces a C file containing the definition of a subsub suite
+                       structure.
+
+                       If <path> is absolute, the output is written to this path and the
+                       argument to --output-dir is ignnored. Otherwise, the output path
+                       is relative to the value of the --output-dir option.
+
+                       If the optional argument is not given, the output file name is
+                       derived from the suite name by appending the string ".lsg.c".
+               [/help]
+       [option gen-header]
+               summary = generate a .h file
+               arg_info = optional_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       The generated header file is intended to be included from the
+                       application. It defines handy C enums and preprocessor macros like
+                       the number of commands defined in the suite.
+
+                       The path for the generated header file is determined in the same way
+                       as for C output, see --gen-c.
+               [/help]
+       [option ignored]
+               summary = Options for man output
+               flag ignored
+       [option gen-man]
+               summary = generate the man page
+               arg_info = optional_arg
+               arg_type = string
+               typestr = path
+               [help]
+                       If this is not given, all subsequent options in this section are
+                       ignored.
+
+                       The path for the output file is determined in the same way as for C
+                       output, see --gen-c.
+               [/help]
+       [option version-string]
+               summary = override version string
+               arg_info = required_arg
+               arg_type = string
+               typestr = string
+               [help]
+                       If this option is given, its argument is used as the version string
+                       while the value of the version-string directive of the suite is
+                       ignored. This is useful for applications which dynamically create
+                       the version string by running a command like git-describe.
+               [/help]
+
+[section copyright]
+
+       Written by Andre Noll
+       .br
+       Copyright (C) 2016 Andre Noll
+       .br
+       License: GNU GPL version 3,
+       .UR http://www.gnu.org/licenses/gpl-3.0.html
+       .UE
+       .br
+       This is free software: you are free to change and redistribute it.
+       .br
+       There is NO WARRANTY, to the extent permitted by law.
+       .br
+       Report bugs to
+       .MT <maan@tuebingen.mpg.de>
+       Andre Noll
+       .ME
+[/section]
+
+[section see also]
+       lopsub-suite(5), lopsub(7)
+
+       Homepage:
+       .UR http://people.tuebingen.mpg.de/~maan/lopsub
+       .UE
+[/section]
diff --git a/lsg.c b/lsg.c
new file mode 100644 (file)
index 0000000..54b7816
--- /dev/null
+++ b/lsg.c
@@ -0,0 +1,812 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+
+#include "lsg.h"
+#include "lopsub-internal.h"
+#include "lopsub.h"
+
+/*
+ * To stringify the result of the *expansion* of a macro argument, we have to
+ * use two levels of macros.
+ */
+#define LLS_STRINGIFY_EXPAND(_arg) LLS_STRINGIFY(_arg)
+#define LLS_STRINGIFY(_arg) #_arg
+#define LLS_ABI_VERSION_VAR_STRING LLS_STRINGIFY_EXPAND(LLS_ABI_VERSION_VAR)
+
+int yylex(void);
+
+static void gen_license_header(FILE *out, const char *cmdline, const char *pfx,
+               const char *sfx)
+{
+       fprintf(out, "%sautogenerated by lopsubgen version %s%s\n",
+               pfx, lls_version(), sfx);
+       if (cmdline) {
+               fprintf(out, "%scommand line:", pfx);
+               fprintf(out, " %s", cmdline);
+               fprintf(out, "%s\n", sfx);
+       }
+       fprintf(out, "%sPublic domain, no copyright claims.%s\n",
+               pfx, sfx);
+}
+
+static void inplace_sanitize(char *src)
+{
+       for (; *src; src++)
+               if (*src == '-')
+                       *src = '_';
+}
+
+static void inplace_toupper(char *src)
+{
+       for (; *src; src++)
+               *src = toupper((int)*src);
+}
+
+static void lsg_init_name(struct lsg_name *name)
+{
+       assert(name->orig);
+       name->sanitized = strdup(name->orig);
+       inplace_sanitize(name->sanitized);
+       name->capitalized = strdup(name->sanitized);
+       inplace_toupper(name->capitalized);
+}
+
+static void print_tabs(int num, FILE *out)
+{
+       while (--num >= 0)
+               fprintf(out, "\t");
+}
+
+static void format_c_text(const char *text, const char *member, int indent,
+               FILE *out)
+{
+       int i;
+
+       if (!text)
+               return;
+       print_tabs(indent, out);
+       fprintf(out, ".%s = (char []) {", member);
+       for (i = 0; text[i]; i++) {
+               if ((i % 8) == 0) {
+                       fprintf(out, "\n");
+                       print_tabs(indent + 1, out);
+               }
+               fprintf(out, "0x%02x,", (unsigned char)text[i]);
+       }
+       fprintf(out, "0x00\n");
+       print_tabs(indent, out);
+       fprintf(out, "},\n");
+}
+
+static void format_flags(struct lsg_option *opt, FILE *out)
+{
+       fprintf(out, "\t\t\t\t\t.flags = 0");
+       if (opt->multiple)
+               fprintf(out, " | LLS_MULTIPLE");
+       if (opt->required)
+               fprintf(out, " | LLS_REQUIRED");
+       if (opt->ignored)
+               fprintf(out, " | LLS_IGNORED");
+       if (opt->default_val)
+               fprintf(out, " | LLS_HAS_DEFAULT");
+       fprintf(out, ",\n");
+}
+
+static int string_literal(const char *src, FILE *out)
+{
+       int len = 0;
+
+       len += fprintf(out, "\"");
+       while (src && *src) {
+               if (*src == '"' || *src == '\\')
+                       len += fprintf(out, "\\");
+               len += fprintf(out, "%c", *src);
+               src++;
+       }
+       len += fprintf(out, "\"");
+       return len;
+}
+
+static void format_man_text(FILE *out, bool bol, const char *text)
+{
+       int i;
+
+       if (!text)
+               return;
+       for (i = 0; text[i]; i++) {
+               switch (text[i]) {
+               case '\n':
+                       fprintf(out, "\n");
+                       bol = true;
+                       continue;
+               case '.':
+               case '\'':
+                       if (bol)
+                               fprintf(out, "\\&");
+                       break;
+               case '\\':
+                       fprintf(out, "\\");
+                       break;
+               case ' ':
+               case '\t':
+                       if (bol)
+                               continue;
+               }
+               bol = false;
+               fprintf(out, "%c", text[i]);
+       }
+}
+
+static int format_option_arg(const struct lsg_option *opt, FILE *out,
+               bool man_format)
+{
+       char *typestr = opt->typestr? opt->typestr : "val";
+       int arg_info, len = 0;
+
+       if (opt->ignored)
+               return 0;
+       if (!opt->arg_info)
+               arg_info = 0;
+       else if (strcmp(opt->arg_info, "LLS_NO_ARGUMENT") == 0)
+               arg_info = 0;
+       else if (strcmp(opt->arg_info, "LLS_OPTIONAL_ARGUMENT") == 0)
+               arg_info = -1;
+       else if (strcmp(opt->arg_info, "LLS_REQUIRED_ARGUMENT") == 0)
+               arg_info = 1;
+       else
+               assert(0);
+
+       if (arg_info == -1)
+               len += fprintf(out, "[");
+       if (arg_info != 0) {
+               len += fprintf(out, "=<");
+               if (man_format)
+                       len += fprintf(out, "%s", typestr);
+               else {
+                       len += fprintf(out, "\"");
+                       len += string_literal(typestr, out);
+                       len += fprintf(out, "\"");
+               }
+               fprintf(out, ">");
+       }
+       if (arg_info == -1)
+               len += fprintf(out, "]");
+       return len;
+}
+
+static void format_synopsis(const struct lsg_command *cmd, FILE *out,
+               bool man_format)
+{
+       int j, len;
+
+       if (man_format)
+               fprintf(out, "\\&");
+       if (cmd->synopsis) {
+               if (man_format)
+                       format_man_text(out, true, cmd->synopsis);
+               else {
+                       string_literal(cmd->synopsis, out);
+                       fprintf(out, ",\n");
+               }
+               return;
+       }
+       if (!man_format)
+               fprintf(out, "\"");
+       len = strlen(cmd->name.orig) + 8;
+       for (j = 0; j < cmd->num_options; j++) {
+               struct lsg_option *opt = cmd->options + j;
+               if (opt->ignored)
+                       continue;
+               if (j > 0) {
+                       if (!man_format && len > 70) {
+                               fprintf(out, "\\n\"\n\t\t\t\t\"       ");
+                               len = 8;
+                       } else
+                               len += fprintf(out, " ");
+               }
+               if (!opt->required)
+                       len += fprintf(out, "[");
+               len += fprintf(out, "--%s", opt->name.orig);
+               len += format_option_arg(opt, out, man_format);
+               if (!opt->required)
+                       len += fprintf(out, "]");
+       }
+       if (cmd->non_opts_name) {
+               len += strlen(cmd->non_opts_name);
+               if (!man_format && len > 70)
+                       fprintf(out, "\\n\"\n\t\t\t\t\"      ");
+               if (cmd->num_options > 0)
+                       fprintf(out, " [--]");
+               fprintf(out, " %s", cmd->non_opts_name);
+       }
+       if (!man_format)
+               fprintf(out, "\",");
+}
+
+static void gen_c_options(const struct lsg_command *cmd, FILE *out)
+{
+       int i, j;
+
+       if (cmd->num_options == 0)
+               return;
+       fprintf(out, "\t\t.options = (struct lls_option[]) {\n");
+       for (j = 0; j < cmd->num_options; j++) {
+               struct lsg_option *opt = cmd->options + j;
+               fprintf(out, "\t\t\t{\n");
+               fprintf(out, "\t\t\t\t.name = \"%s\",\n",
+                       opt->name.orig);
+               if (opt->short_opt)
+                       fprintf(out, "\t\t\t\t.short_opt = '%c',\n",
+                               opt->short_opt);
+               if (opt->summary) {
+                       fprintf(out, "\t\t\t\t.summary = ");
+                       string_literal(opt->summary, out);
+                       fprintf(out, ",\n");
+               }
+               if (opt->arg_info)
+                       fprintf(out, "\t\t\t\t.arg_info = %s,\n",
+                               opt->arg_info);
+               if (opt->arg_type)
+                       fprintf(out, "\t\t\t\t.arg_type = %s,\n",
+                               opt->arg_type);
+               if (opt->typestr) {
+                       fprintf(out, "\t\t\t\t.typestr = ");
+                       string_literal(opt->typestr, out);
+                       fprintf(out, ",\n");
+               }
+               fprintf(out, "\t\t\t\t.values = ");
+               if (opt->values) {
+                       fprintf(out, "(union lls_val *)(union lls_val[]) {\n");
+                       for (i = 0; i < opt->num_values; i++)
+                               fprintf(out, "\t\t\t\t\t{.string_val = %s},\n",
+                                       opt->value_literals[i]);
+                       fprintf(out, "\t\t\t\t\t{.string_val = NULL}\n");
+                       fprintf(out, "\t\t\t\t},\n");
+               } else
+                       fprintf(out, "NULL,\n");
+               format_flags(opt, out);
+               if (opt->default_val) {
+                       bool string = strcmp(opt->arg_type, "LLS_STRING") == 0;
+                       fprintf(out, "\t\t\t\t.default_val = {");
+                       if (string) {
+                               if (opt->values)
+                                       fprintf(out, ".uint32_val = ");
+                               else {
+                                       fprintf(out, ".string_val = ");
+                                       string_literal(opt->default_val, out);
+                               }
+                       } else if (strcmp(opt->arg_type, "LLS_INT32") == 0)
+                               fprintf(out, ".int32_val = ");
+                       else if (strcmp(opt->arg_type, "LLS_UINT32") == 0)
+                               fprintf(out, ".uint32_val = ");
+                       else if (strcmp(opt->arg_type, "LLS_INT64") == 0)
+                               fprintf(out, ".int64_val = ");
+                       else if (strcmp(opt->arg_type, "LLS_UINT64") == 0)
+                               fprintf(out, ".uint64_val = ");
+                       if (!string || opt->values)
+                               fprintf(out, "%s", opt->default_val);
+                       fprintf(out, "},\n");
+               }
+               format_c_text(opt->help, "help", 5, out);
+               fprintf(out, "\t\t\t},\n");
+       }
+       fprintf(out, "\t\t\t{\n\t\t\t\t\t.name = NULL\n\t\t\t}\n");
+       fprintf(out, "\t\t}\n");
+}
+
+static void format_command(const struct lsg_command *cmd, FILE *out)
+{
+       if (!cmd->name.orig) {
+               fprintf(out, "{.name = NULL}\n");
+               return;
+       }
+       fprintf(out, "{\n\t\t.name = \"%s\",\n", cmd->name.orig);
+       fprintf(out, "\t\t.purpose = ");
+       string_literal(cmd->purpose, out);
+       fprintf(out, ",\n");
+       format_c_text(cmd->description, "description", 3, out);
+       if (cmd->non_opts_name || cmd->synopsis)
+               fprintf(out, "\t\t.non_opts_name = \"%s\",\n",
+                       cmd->non_opts_name? cmd->non_opts_name : "");
+       fprintf(out, "\t\t.synopsis = ");
+       format_synopsis(cmd, out, false);
+       format_c_text(cmd->closing, "closing", 3, out);
+       fprintf(out, "\n\t\t.user_data = &lsg_%s_com_%s_user_data,\n",
+               suite.name.sanitized, cmd->name.sanitized);
+       fprintf(out, "\t\t.num_options = %d,\n", cmd->num_options);
+       gen_c_options(cmd, out);
+       fprintf(out, "\t}");
+}
+
+static void format_user_data(const struct lsg_command *cmd, FILE *out)
+{
+       if (!cmd->name.orig)
+               return;
+       fprintf(out, "extern const void *lsg_%s_com_%s_user_data "
+               "__attribute__ ((weak));\n",
+               suite.name.sanitized,
+               cmd->name.sanitized
+       );
+}
+
+static void gen_c(const char *outpath, const char *cmdline)
+{
+       int i;
+
+       FILE *out = fopen(outpath, "w");
+       if (!out) {
+               perror("fopen");
+               exit(EXIT_FAILURE);
+       }
+       gen_license_header(out, cmdline, "/* ", " */");
+       fprintf(out, "#include <stdlib.h>\n");
+       fprintf(out, "#include <inttypes.h>\n");
+       fprintf(out, "#include <lopsub-internal.h>\n");
+       fprintf(out, "#include <lopsub.h>\n");
+       fprintf(out, "__attribute__ ((unused)) "
+               "static const unsigned *abi_version = &%s;\n",
+               LLS_ABI_VERSION_VAR_STRING);
+       fprintf(out, "#if LLS_ABI_VERSION - %d\n", LLS_ABI_VERSION);
+       fprintf(out, "#error: \"ABI version mismatch: header version "
+               "differs from lopsubgen version\"\n");
+       fprintf(out, "#endif\n");
+       for (i = 0; i <= suite.num_subcommands; i++)
+               format_user_data(suite.commands + i, out);
+       fprintf(out, "static const struct lls_suite the_%s_suite = {\n",
+               suite.name.sanitized);
+       fprintf(out, "\t.name = \"%s\",\n", suite.name.orig);
+       if (suite.caption)
+               fprintf(out, "\t.caption = \"%s\",\n", suite.caption);
+       fprintf(out, "\t.num_subcommands = %d,\n", suite.num_subcommands);
+       fprintf(out, "\t.commands = (struct lls_command[]) {\n");
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               fprintf(out, "%s", i? ", " : "\t\t");
+               format_command(cmd, out);
+       }
+       fprintf(out, ", {\n\t\t\t.name = NULL\n\t\t}\n");
+       fprintf(out, "\t}\n");
+       fprintf(out, "};\n");
+       fprintf(out, "const struct lls_suite *%s_suite = &the_%s_suite;\n",
+               suite.name.sanitized, suite.name.sanitized);
+       fclose(out);
+}
+
+static inline bool has_arg(struct lsg_option *opt)
+{
+       if (!opt->arg_info)
+               return false;
+       return strcmp(opt->arg_info, "LLS_NO_ARGUMENT");
+}
+
+static void gen_header(const char *outpath, const char *cmdline)
+{
+       int i, j, k;
+       FILE *out = fopen(outpath, "w");
+       char *name;
+
+       if (!out) {
+               perror("fopen");
+               exit(EXIT_FAILURE);
+       }
+       gen_license_header(out, cmdline, "/* ", " */");
+       /* generate command enum */
+       fprintf(out, "extern const struct lls_suite *%s_suite;\n",
+               suite.name.sanitized);
+       fprintf(out, "#define LSG_%s_SUBCOMMANDS \\\n", suite.name.capitalized);
+       for (i = 1; i <= suite.num_subcommands; i++)
+               fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
+                       suite.commands[i].name.sanitized);
+       fprintf(out, "\n");
+       fprintf(out, "#define LSG_%s_COMMANDS \\\n", suite.name.capitalized);
+       name = suite.commands[0].name.sanitized;
+       fprintf(out, "\tLSG_%s_CMD(%s), \\\n", suite.name.capitalized,
+               name? name : "SUPERCOMMAND_UNAVAILABLE");
+       fprintf(out, "\tLSG_%s_SUBCOMMANDS\n", suite.name.capitalized);
+       fprintf(out, "enum lsg_%s_command {\n", suite.name.sanitized);
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               char *name = cmd->name.capitalized;
+
+               if (!name)
+                       name = "SUPERCOMMAND_UNAVAILABLE";
+               fprintf(out, "\tLSG_%s_CMD_%s,\n", suite.name.capitalized, name);
+       }
+       fprintf(out, "};\n#define LSG_NUM_%s_SUBCOMMANDS %u\n", suite.name.capitalized,
+               suite.num_subcommands);
+       fprintf(out, "#define LSG_%s_AUX_INFOS \\\n", suite.name.capitalized);
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               char *ai = cmd->aux_info;
+               if (!ai) {
+                       ai = suite.aux_info_default;
+                       if (!ai)
+                               ai = "0";
+               }
+               fprintf(out, "\t%s_AUX_INFO(%s) /* %s */ \\\n",
+                       suite.name.capitalized, ai, cmd->name.orig?
+                               cmd->name.orig : "NO_SUPERCOMMAND");
+       }
+       fprintf(out, "\n");
+       /* generate one option enum per command */
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               if (!cmd->name.orig)
+                       continue;
+               fprintf(out, "enum lsg_%s_%s_option {\n", suite.name.sanitized,
+                       cmd->name.sanitized);
+               for (j = 0; j < cmd->num_options; j++) {
+                       struct lsg_option *opt = cmd->options + j;
+                       char suffix[20] = "";
+                       if (opt->ignored)
+                               sprintf(suffix, "%d", j);
+                       fprintf(out, "\tLSG_%s_%s_OPT_%s%s,\n",
+                               suite.name.capitalized,
+                               cmd->name.capitalized,
+                               opt->name.capitalized,
+                               suffix
+                       );
+               }
+               fprintf(out, "\tLSG_NUM_%s_%s_OPTIONS\n};\n",
+                       suite.name.capitalized, cmd->name.capitalized);
+
+       }
+       /* generate enumeration for options of type enum */
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               if (!cmd->name.orig)
+                       continue;
+               for (j = 0; j < cmd->num_options; j++) {
+                       struct lsg_option *opt = cmd->options + j;
+                       if (!opt->values)
+                               continue;
+                       fprintf(out, "/* cmd %s, opt %s */\n", cmd->name.orig,
+                               opt->name.orig);
+                       fprintf(out, "enum {");
+                       for (k = 0; k < opt->num_values; k++)
+                               fprintf(out, "%s, ", opt->value_ids[k]);
+                       fprintf(out, "LSG_NUM_%s_%s_%s_VALUES};\n",
+                               suite.name.capitalized, cmd->name.capitalized,
+                               opt->name.capitalized);
+               }
+       }
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               if (!cmd->name.orig)
+                       continue;
+               fprintf(out, "#define LSG_%s_%s_SHORT_OPTS ",
+                       suite.name.capitalized, cmd->name.capitalized);
+               for (j = 0; j < cmd->num_options; j++) {
+                       struct lsg_option *opt = cmd->options + j;
+                       if (!opt->short_opt)
+                               continue;
+                       fprintf(out, "\"-%c%s\"%s", opt->short_opt,
+                               has_arg(opt)? "=" : "",
+                               j == cmd->num_options - 1? "" : ", ");
+               }
+               fprintf(out, "\n");
+
+               fprintf(out, "#define LSG_%s_%s_LONG_OPTS ",
+                       suite.name.capitalized, cmd->name.capitalized);
+               for (j = 0; j < cmd->num_options; j++) {
+                       struct lsg_option *opt = cmd->options + j;
+                       fprintf(out, "\"--%s%s\"%s", opt->name.orig,
+                               has_arg(opt)? "=" : "",
+                               j == cmd->num_options - 1? "" : ", ");
+               }
+               fprintf(out, "\n");
+               fprintf(out, "#define LSG_%s_%s_OPTS "
+                       "LSG_%s_%s_SHORT_OPTS, LSG_%s_%s_LONG_OPTS\n",
+                       suite.name.capitalized, cmd->name.capitalized,
+                       suite.name.capitalized, cmd->name.capitalized,
+                       suite.name.capitalized, cmd->name.capitalized
+               );
+       }
+       fclose(out);
+}
+
+static void check_option(struct lsg_option *opt)
+{
+       if (!opt->arg_info || !strcmp(opt->arg_info, "no_arg"))
+               return;
+       if (opt->arg_type && strcmp(opt->arg_type, "none"))
+               return;
+       fprintf(stderr, "option '%s': inconsistent arg_type/arg_info\n",
+               opt->name.orig);
+       exit(EXIT_FAILURE);
+}
+
+static void sanity_check(void)
+{
+       int i, j;
+
+       if (suite.num_subcommands == 0 && !suite.commands) {
+               fprintf(stderr, "no (sub)commands defined\n");
+               exit(EXIT_FAILURE);
+       }
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               if (!cmd->name.orig)
+                       continue;
+               for (j = 0; j < cmd->num_options; j++)
+                       check_option(cmd->options + j);
+       }
+}
+
+static void run_yylex(void)
+{
+       int i, j;
+
+       yylex();
+       if (!suite.name.orig)
+               suite.name.orig = strdup("lopsubgen");
+       sanity_check();
+       lsg_init_name(&suite.name);
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               if (!cmd->name.orig)
+                       continue;
+               lsg_init_name(&cmd->name);
+               for (j = 0; j < cmd->num_options; j++)
+                       lsg_init_name(&cmd->options[j].name);
+       }
+       for (i = 0; i < suite.num_sections; i++)
+               lsg_init_name(&suite.sections[i].name);
+}
+
+#ifdef STAGE1
+int main(int argc, char **argv)
+{
+       run_yylex();
+       gen_c("lopsubgen.lsg.c", NULL);
+       gen_header("lopsubgen.lsg.h", NULL);
+       return EXIT_SUCCESS;
+}
+
+#else /* STAGE1 */
+
+#include "lopsubgen.lsg.h"
+
+static char *get_output_path(const char *suffix, const char *arg,
+               const struct lls_parse_result *lpr)
+{
+       size_t len;
+       char *result, *output_dir;
+
+       output_dir = OPT_STRING_VAL(OUTPUT_DIR, lpr);
+
+       if (arg && arg[0] == '/') {
+               result = strdup(arg);
+               assert(result);
+               return result;
+       }
+       if (arg) { /* relative path */
+               len = strlen(output_dir) + strlen(arg) + 1;
+               result = malloc(len + 1);
+               assert(result);
+               sprintf(result, "%s/%s", output_dir, arg);
+               return result;
+       }
+       /* default: suite name plus suffix */
+       len = strlen(output_dir) + 1 /* slash */ + strlen(suite.name.orig)
+               + 1 /* dot */ + 3 /* "lsg" */ + 1 /* dot */ + strlen(suffix);
+       result = malloc(len + 1);
+       assert(result);
+       sprintf(result, "%s/%s.lsg.%s", output_dir, suite.name.orig, suffix);
+       return result;
+}
+
+static void gen_man(struct lls_parse_result *lpr, const char *cmdline)
+{
+       int i;
+       time_t t;
+       struct tm *tmp;
+       FILE *out;
+       char *outpath = get_output_path("man",
+               OPT_STRING_VAL(GEN_MAN, lpr), lpr);
+
+       out = fopen(outpath, "w");
+       free(outpath);
+       if (!out) {
+               perror("fopen");
+               exit(EXIT_FAILURE);
+       }
+       gen_license_header(out, cmdline, ".\\\" ", "");
+       if (suite.commands[0].name.orig) {
+               char date[200];
+               const char *version_string;
+
+               if (!suite.date) {
+                       t = time(NULL);
+                       tmp = localtime(&t);
+                       if (tmp == NULL) {
+                               perror("localtime");
+                               exit(EXIT_FAILURE);
+                       }
+                       if (strftime(date, sizeof(date), "%B %Y", tmp) == 0) {
+                               fprintf(stderr, "strftime returned 0\n");
+                               exit(EXIT_FAILURE);
+                       }
+               }
+               if (OPT_GIVEN(VERSION_STRING, lpr))
+                       version_string = OPT_STRING_VAL(VERSION_STRING, lpr);
+               else
+                       version_string = suite.version_string?
+                               suite.version_string : "";
+               fprintf(out, ".TH %s \"%s\" \"%s\" \"%s\" \"%s\"\n",
+                       suite.title? suite.title : suite.commands[0].name.orig,
+                       suite.mansect? suite.mansect : "1",
+                       suite.date? suite.date : date,
+                       version_string,
+                       suite.manual_title? suite.manual_title : "User commands"
+               );
+       }
+       for (i = 0; i <= suite.num_subcommands; i++) {
+               struct lsg_command *cmd = suite.commands + i;
+               int opt_num;
+               if (!cmd->name.orig)
+                       continue;
+               if (i == 0) {
+                       fprintf(out, ".SH NAME\n");
+                       fprintf(out, ".B\n%s \\- ", cmd->name.orig);
+                       format_man_text(out, false, cmd->purpose);
+               } else {
+                       if (i == 1 && suite.caption) {
+                               char *caption = strdup(suite.caption);
+                               inplace_toupper(caption);
+                               fprintf(out, ".SH %s\n.P\n", caption);
+                               free(caption);
+                       }
+                       if (i == 1 && suite.introduction)
+                               format_man_text(out, true, suite.introduction);
+                       fprintf(out, ".SS \n%s \\- ", cmd->name.orig);
+                       format_man_text(out, false, cmd->purpose);
+               }
+               fprintf(out, "\n.P\n");
+               if (i == 0)
+                       fprintf(out, ".SH SYNOPSIS\n");
+               else
+                       fprintf(out, "Usage: \n");
+               fprintf(out, ".B %s\n", cmd->name.orig);
+               format_synopsis(cmd, out, true);
+               fprintf(out, "\n.P\n");
+               if (cmd->description) {
+                       if (i == 0)
+                               fprintf(out, ".SH DESCRIPTION\n");
+                       format_man_text(out, true, cmd->description);
+               }
+               if (cmd->num_options > 0)
+                       if (i == 0)
+                               fprintf(out, ".SH OPTIONS\n");
+               for (opt_num = 0; opt_num < cmd->num_options; opt_num++) {
+                       struct lsg_option *opt = cmd->options + opt_num;
+
+                       if (opt->ignored) {
+                               fprintf(out, ".SS ");
+                               format_man_text(out, false, opt->summary);
+                               fprintf(out, "\n");
+                               if (opt->help)
+                                       format_man_text(out, true, opt->help);
+                               continue;
+
+                       }
+                       fprintf(out, ".TP\n");
+                       if (opt->short_opt != '\0')
+                               fprintf(out, "\\fB\\-%c\\fR, ", opt->short_opt);
+                       fprintf(out, "\\fB\\-\\-%s\\fR", opt->name.orig);
+                       format_option_arg(opt, out, true /* man_format */);
+                       fprintf(out, "\n");
+                       format_man_text(out, true, opt->summary);
+                       fprintf(out, "\n");
+                       if (opt->values) {
+                               unsigned n, dflt;
+                               fprintf(out, ".IP\n");
+                               fprintf(out, "values:\n");
+                               dflt = opt->default_val?
+                                       atoi(opt->default_val) : 0;
+                               for (n = 0; n < opt->num_values; n++) {
+                                       if (n == dflt)
+                                               fprintf(out, ".B ");
+                                       fprintf(out, "%s%s\n", opt->values[n],
+                                               n == opt->num_values - 1?
+                                                       "" : ",");
+                               }
+                       } else if (opt->default_val) {
+                               fprintf(out, ".IP\n");
+                               fprintf(out, "default: ");
+                               format_man_text(out, false, opt->default_val);
+                               fprintf(out, "\n.P\n");
+                       }
+                       if (opt->help) {
+                               fprintf(out, ".IP\n");
+                               format_man_text(out, true, opt->help);
+                       }
+               }
+               fprintf(out, ".PP\n");
+               if (cmd->aux_info) {
+                       char *pfx = suite.aux_info_prefix;
+                       fprintf(out, ".RS\n");
+                       if (pfx)
+                               fprintf(out, "%s ", pfx);
+                       fprintf(out, "%s\n.PP\n", cmd->aux_info);
+                       fprintf(out, ".RE\n");
+               }
+               if (cmd->closing)
+                       format_man_text(out, true, cmd->closing);
+       }
+       if (suite.conclusion)
+               format_man_text(out, true, suite.conclusion);
+       for (i = 0; i < suite.num_sections; i++) {
+               struct lsg_section *sec = suite.sections + i;
+               fprintf(out, "\n.P\n.SH \"%s\"\n", sec->name.capitalized);
+               fprintf(out, "%s\n.P\n", sec->text);
+       }
+       fclose(out);
+}
+
+int main(int argc, char **argv)
+{
+       const struct lls_command *cmd = lls_cmd(0, lopsubgen_suite);
+       char *errctx, *outpath, *cmdline;
+       struct lls_parse_result *lpr;
+       int i, ret;
+
+       /* Make a copy of the command line because lls_parse() permutes argv[] */
+       for (i = 0, ret = 0; argv[i]; i++)
+               ret += strlen(argv[i]) + 1;
+       cmdline = malloc(ret + 1);
+       assert(cmdline);
+       for (i = 0, ret = 0; argv[i]; i++)
+               ret += sprintf(cmdline + ret, "%s ", argv[i]);
+       cmdline[ret - 1] = '\0';
+
+       ret = lls_parse(argc, argv, cmd, &lpr, &errctx);
+       if (ret < 0) {
+               if (errctx)
+                       fprintf(stderr, "%s\n", errctx);
+               fprintf(stderr, "%s\n", lls_strerror(-ret));
+               return EXIT_FAILURE;
+       }
+       if (OPT_GIVEN(VERSION, lpr)) {
+               printf("lopsubgen-%s\n", lls_version());
+               return EXIT_SUCCESS;
+       }
+       if (OPT_GIVEN(HELP, lpr)) {
+               char *help;
+               if (OPT_GIVEN(HELP, lpr) > 1)
+                       help = lls_long_help(cmd);
+               else
+                       help = lls_short_help(cmd);
+               printf("%s", help);
+               free(help);
+               return EXIT_SUCCESS;
+       }
+       run_yylex();
+       if (OPT_GIVEN(GEN_C, lpr)) {
+               outpath = get_output_path("c",
+                       OPT_STRING_VAL(GEN_C, lpr), lpr);
+               gen_c(outpath, cmdline);
+               free(outpath);
+       }
+       if (OPT_GIVEN(GEN_HEADER, lpr)) {
+               outpath = get_output_path("h",
+                       OPT_STRING_VAL(GEN_HEADER, lpr), lpr);
+               gen_header(outpath, cmdline);
+               free(outpath);
+       }
+       if (OPT_GIVEN(GEN_MAN, lpr))
+               gen_man(lpr, cmdline);
+       return EXIT_SUCCESS;
+}
+#endif /* STAGE1 */
diff --git a/lsg.h b/lsg.h
new file mode 100644 (file)
index 0000000..277648b
--- /dev/null
+++ b/lsg.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the GPL v3, see http://www.gnu.org/licenses/gpl-3.0.html
+ */
+
+struct lsg_name {
+       char *orig;
+       char *sanitized;
+       char *capitalized;
+};
+
+struct lsg_option {
+       struct lsg_name name;
+       char short_opt;
+       char *summary;
+       char *arg_info;
+       char *arg_type;
+       char *typestr;
+       bool multiple, required, ignored;
+       char *default_val;
+       char *help;
+       int num_values;
+       char **values;
+       char **value_literals;
+       char **value_ids;
+};
+
+struct lsg_command {
+       struct lsg_name name;
+       char *purpose;
+       char *description;
+       char *closing;
+       char *non_opts_name;
+       char *synopsis;
+       char *aux_info;
+       struct lsg_option *options;
+       int num_options;
+};
+
+struct lsg_section {
+       struct lsg_name name;
+       char *text;
+};
+
+struct lsg_suite {
+       struct lsg_name name;
+       char *caption; /* additional section heading (.SH), default: empty */
+       char *title; /* defaults to suite name */
+       char *mansect; /* default: 1 */
+       char *date; /* default: current date */
+       char *version_string; /* default: empty */
+       char *manual_title; /* default: User commands */
+       char *aux_info_prefix;
+       char *aux_info_default;
+       char *introduction, *conclusion;
+       /* supercommand is #0, subcommands start at index 1 */
+       struct lsg_command *commands;
+       int num_subcommands;
+       struct lsg_section *sections;
+       int num_sections;
+};
+
+extern struct lsg_suite suite;
+
+#ifndef STAGE1
+#define OPT_RESULT(_name, _lpr) ((_lpr)->opt_result + LSG_LOPSUBGEN_LOPSUBGEN_OPT_ ## _name)
+#define OPT_GIVEN(_name, _lpr) (OPT_RESULT(_name, (_lpr))->given)
+#define OPT_STRING_VAL(_name, _lpr) (OPT_RESULT(_name, (_lpr))->value[0].string_val)
+#endif /* STAGE1 */
diff --git a/version-gen.sh b/version-gen.sh
new file mode 100755 (executable)
index 0000000..ed3a22e
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+version_file='version.c'
+ver='unnamed_version'
+# First try git, then gitweb, then default.
+if [ -e '.git' -o -e '../.git' ]; then
+       git_ver=$(git describe --abbrev=4 HEAD 2>/dev/null)
+       [ -z "$git_ver" ] && git_ver="$ver"
+       # update stat information in index to match working tree
+       git update-index -q --refresh
+       # if there are differences (exit code 1), the working tree is dirty
+       git diff-index --quiet HEAD || git_ver=$git_ver-dirty
+       ver=$git_ver
+elif [ "${PWD%%-*}" = 'lopsub-' ]; then
+       ver=${PWD##*/lopsub-}
+fi
+ver=${ver#v}
+
+echo "$ver"
+
+# update version file if necessary
+content="const char *lls_version(void) {return \"$ver\";};"
+[ -r "$version_file" ] && echo "$content" | cmp -s - $version_file && exit 0
+echo >&2 "new git version: $ver"
+echo "$content" > $version_file
diff --git a/web/footer.html b/web/footer.html
new file mode 100644 (file)
index 0000000..308b1d0
--- /dev/null
@@ -0,0 +1,2 @@
+</body>
+</html>
diff --git a/web/header.html b/web/header.html
new file mode 100644 (file)
index 0000000..3cf72cc
--- /dev/null
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+   "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+       <meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
+       <title>Lopsub</title>
+</head>
+
+<body>
+
+       <table width=100%>
+               <tr>
+                       <td>
+                               <h2>The long option parser for subcommands</h2>
+
+                       </td>
+                       <td align="right">
+                               <a href="./index.html">
+                                       <img
+                                               src="lopsub.png"
+                                               alt="lopsub logo"
+                                       >
+                               </a>
+                       </td>
+               </tr>
+       </table>
+
+       <hr>
+
+       <table width=100%>
+               <tr>
+                       <td align="center">
+                               <a href="./index.html">About</a>
+                       </td>
+                       <td align="center">
+                               <a href="./lopsub-suite.5.html">lopsub-suite(5)</a>
+                       </td>
+                       <td align="center">
+                               <a href="./lopsubgen.1.html">lopsubgen(1)</a>
+                       </td>
+                       <td align="center">
+                               <a href="./lopsub-api.html">API</a>
+                       </td>
+       </table>
+
+       <hr>
diff --git a/web/lopsub.png b/web/lopsub.png
new file mode 100644 (file)
index 0000000..23dfb64
Binary files /dev/null and b/web/lopsub.png differ