--- /dev/null
+**/*.o
+*.lsg.*
+*.[1-9]
+config_file.c
+lopsubgen.c
+version.c
+lopsub.h
+liblopsub.a
+lopsubex
+lopsubgen
+lopsubgen-stage1
+web/*.html
--- /dev/null
+# 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
+
--- /dev/null
+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.
--- /dev/null
+/*
+ * 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
--- /dev/null
+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», «<», «<»)»)
+ define(«FIXUP_GT», «patsubst(«$1», «>», «>»)»)
+ define(«FIXUP_AMPERSAND», «patsubst(«$1», «&», «&»)»)
+ 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.
+»
+)
+»)
--- /dev/null
+/*
+ * 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
--- /dev/null
+.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)
--- /dev/null
+.\" 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
--- /dev/null
+/*
+ * 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;
+}
--- /dev/null
+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 */»)
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+# 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]
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+# 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]
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+/*
+ * 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 */
--- /dev/null
+#!/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
--- /dev/null
+</body>
+</html>
--- /dev/null
+<!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>