summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpgen <p.gen.progs@gmail.com>2019-09-15 23:40:37 +0200
committerpgen <p.gen.progs@gmail.com>2019-10-01 00:32:56 +0200
commit24037bdfe6aaf3ba6fa3453869466b6fd7b2579d (patch)
tree5fc9a1e3eb2d9998d64cfc7b6f0d63e2042ed695
parenta2adfc0f3c304aec4a573478838ea769c3f590a8 (diff)
Use ctxopt.[ch] for options management
The old getopt, derived from egetopt, was too restrictive, so I developed ctxopt to better manage smenu options.
-rw-r--r--Makefile.am2
-rw-r--r--Makefile.in6
-rw-r--r--ctxopt.c3525
-rw-r--r--ctxopt.h71
-rw-r--r--getopt.c211
-rw-r--r--getopt.h35
-rw-r--r--smenu.c2434
-rw-r--r--tests/initial_selection/t0001.good2
-rw-r--r--tests/initial_selection/t0001.tst2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0001.good2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0001.tst2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0002.good2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0002.tst2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0003.good2
-rw-r--r--tests/rows_cols_in-ex-clusions/t0003.tst2
-rw-r--r--tests/special_levels/t0003.good12
-rw-r--r--tests/special_levels/t0003.tst2
-rw-r--r--tests/tagging/t0013.good2
-rw-r--r--tests/tagging/t0013.tst2
-rw-r--r--usage.c207
-rw-r--r--usage.h4
21 files changed, 5022 insertions, 1507 deletions
diff --git a/Makefile.am b/Makefile.am
index 9518f43..0dfba2a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
bin_PROGRAMS = smenu
smenu_SOURCES = smenu.c smenu.h list.c list.h xmalloc.c xmalloc.h \
index.c index.h utf8.c utf8.h fgetc.c fgetc.h \
- utils.c utils.h getopt.c getopt.h usage.c usage.h
+ utils.c utils.h usage.c usage.h ctxopt.h ctxopt.c
dist_man_MANS = smenu.1
EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \
COPYRIGHT LICENSE.rst README.rst examples build-aux tests \
diff --git a/Makefile.in b/Makefile.in
index a45cb86..645e44d 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -104,7 +104,7 @@ am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(man1dir)"
PROGRAMS = $(bin_PROGRAMS)
am_smenu_OBJECTS = smenu.$(OBJEXT) list.$(OBJEXT) xmalloc.$(OBJEXT) \
index.$(OBJEXT) utf8.$(OBJEXT) fgetc.$(OBJEXT) utils.$(OBJEXT) \
- getopt.$(OBJEXT) usage.$(OBJEXT)
+ usage.$(OBJEXT) ctxopt.$(OBJEXT)
smenu_OBJECTS = $(am_smenu_OBJECTS)
smenu_LDADD = $(LDADD)
AM_V_P = $(am__v_P_@AM_V@)
@@ -309,7 +309,7 @@ top_builddir = @top_builddir@
top_srcdir = @top_srcdir@
smenu_SOURCES = smenu.c smenu.h list.c list.h xmalloc.c xmalloc.h \
index.c index.h utf8.c utf8.h fgetc.c fgetc.h \
- utils.c utils.h getopt.c getopt.h usage.c usage.h
+ utils.c utils.h usage.c usage.h ctxopt.h ctxopt.c
dist_man_MANS = smenu.1
EXTRA_DIST = smenu.spec.in smenu.spec ChangeLog build.sh version \
@@ -425,8 +425,8 @@ mostlyclean-compile:
distclean-compile:
-rm -f *.tab.c
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ctxopt.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fgetc.Po@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/index.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/smenu.Po@am__quote@
diff --git a/ctxopt.c b/ctxopt.c
new file mode 100644
index 0000000..3ff3fae
--- /dev/null
+++ b/ctxopt.c
@@ -0,0 +1,3525 @@
+//#include "config.h"
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <string.h>
+#include "ctxopt.h"
+
+errors ctxopterrno = CTXOPTNOERR;
+
+/* *********************** */
+/* Static global variables */
+/* *********************** */
+static void * contexts_bst;
+static void * options_bst;
+
+/* Prototypes */
+
+/* ****************** */
+/* Messages interface */
+/* ****************** */
+
+static void (**err_functions)(char *, char *, char *, char *, char *);
+
+static void
+fatal(errors e, char * opt_par, char * opt_params, char * opt_name,
+ char * ctx_par, char * ctx_name, const char * format, ...);
+
+static void
+error(const char * format, ...);
+
+static int user_rc; /* Used by various callback functions */
+static int user_value; /* Used by various callback functions */
+static char * user_string; /* Used by various callback functions */
+static void * user_object; /* Used by various callback functions */
+
+/* *************************** */
+/* Memory management interface */
+/* *************************** */
+
+static void *
+xmalloc(size_t size);
+
+static void *
+xcalloc(size_t num, size_t size);
+
+static void *
+xrealloc(void * ptr, size_t size);
+
+static char *
+xstrdup(const char * p);
+
+static char *
+xstrndup(const char * str, size_t len);
+
+/* ************* */
+/* BST interface */
+/* ************* */
+typedef struct bst_s bst_t;
+
+typedef enum
+{
+ preorder,
+ postorder,
+ endorder,
+ leaf
+} walk_order_e;
+
+static void *
+bst_delete(const void * vkey, void ** vrootp,
+ int (*compar)(const void *, const void *));
+
+static void
+bst_destroy_recurse(bst_t * root, void (*free_action)(void *));
+
+static void
+bst_destroy(void * vrootp, void (*freefct)(void *));
+
+static void *
+bst_find(const void * vkey, void * const * vrootp,
+ int (*compar)(const void *, const void *));
+
+static void *
+bst_search(const void * vkey, void ** vrootp,
+ int (*compar)(const void *, const void *));
+
+static void
+bst_walk_recurse(const bst_t * root,
+ void (*action)(const void *, walk_order_e, int), int level);
+
+static void
+bst_walk(const void * vroot, void (*action)(const void *, walk_order_e, int));
+
+/* ********************* */
+/* Linked list Interface */
+/* ********************* */
+
+typedef struct ll_node_s ll_node_t;
+typedef struct ll_s ll_t;
+
+static void
+ll_append(ll_t * const list, void * const data);
+
+static void
+ll_prepend(ll_t * const list, void * const data);
+
+static void
+ll_insert_after(ll_t * const list, ll_node_t * node, void * const data);
+
+static void
+ll_insert_before(ll_t * const list, ll_node_t * node, void * const data);
+
+static int
+ll_delete(ll_t * const list, ll_node_t * node);
+
+static ll_node_t *
+ll_find(ll_t * const, void * const, int (*)(const void *, const void *));
+
+static void
+ll_init(ll_t * list);
+
+static ll_node_t *
+ll_new_node(void);
+
+static ll_t *
+ll_new(void);
+
+static int
+ll_strarray(ll_t * list, ll_node_t * start_node, int * count, char *** array);
+
+/* ****************** */
+/* various interfaces */
+/* ****************** */
+
+static void
+ltrim(char * str, const char * trim_str);
+
+static void
+rtrim(char * str, const char * trim_str, size_t min);
+
+static int
+strchrcount(char * str, char c);
+
+int
+strpref(char * s1, char * s2);
+
+static char *
+xstrtok_r(char * str, const char * delim, char ** end);
+
+/* ************* */
+/* cop interface */
+/* ************* */
+
+typedef struct opt_s opt_t;
+typedef struct par_s par_t;
+typedef struct ctx_s ctx_t;
+typedef struct constraint_s constraint_t;
+typedef struct ctx_inst_s ctx_inst_t;
+typedef struct opt_inst_s opt_inst_t;
+typedef struct seen_opt_s seen_opt_t;
+
+static char *
+strtoken(char * s, char * token, size_t tok_len, char * pattern, int * pos);
+
+static int
+ctx_compare(const void * c1, const void * c2);
+
+static int
+opt_compare(const void * o1, const void * o2);
+
+static int
+par_compare(const void * a1, const void * a2);
+
+static int
+seen_opt_compare(const void * so1, const void * so2);
+
+static int
+str_compare(const void * s1, const void * s2);
+
+static ctx_t *
+locate_ctx(char * name);
+
+static opt_t *
+locate_opt(char * name);
+
+static par_t *
+locate_par(char * name, ctx_t * ctx);
+
+static void
+bst_seen_opt_cb(const void * node, walk_order_e kind, int level);
+
+static void
+bst_seen_opt_seen_cb(const void * node, walk_order_e kind, int level);
+
+static void
+bst_print_ctx_cb(const void * node, walk_order_e kind, int level);
+
+static void
+match_prefix_cb(const void * node, walk_order_e kind, int level);
+
+static int
+has_unseen_mandatory_opt(ctx_inst_t * ctx_inst);
+
+static int
+opt_parse(char * s, opt_t ** opt);
+
+static int
+init_opts(char * spec, ctx_t * ctx);
+
+static int
+ctxopt_build_cmdline_list(int nb_words, char ** words);
+
+static int
+opt_set_parms(char * opt_name, char * par_str);
+
+static ctx_inst_t *
+new_ctx_inst(ctx_t * ctx, ctx_inst_t * prev_ctx_inst);
+
+static void
+evaluate_ctx_inst(ctx_inst_t * ctx_inst);
+
+/* ====================== */
+/* Message implementation */
+/* ====================== */
+static void
+fatal(errors e, char * opt_par, char * opt_params, char * opt_name,
+ char * ctx_par, char * ctx_name, const char * format, ...)
+{
+ va_list args;
+
+ ctxopterrno = e;
+
+ if (e == CTXOPTINTERNAL)
+ fprintf(stderr, "ctxopt internal: ");
+
+ if (err_functions[e] != NULL)
+ err_functions[e](opt_par, opt_params, opt_name, ctx_par, ctx_name);
+ else
+ {
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ }
+
+ if (opt_par != NULL)
+ ctxopt_disp_usage(continue_after);
+
+ va_end(args);
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+error(const char * format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, "\n");
+ ctxopt_disp_usage(exit_after);
+}
+
+/* ******************************** */
+/* Memory management implementation */
+/* ******************************** */
+
+/* ================= */
+/* Customized malloc */
+/* ================= */
+static void *
+xmalloc(size_t size)
+{
+ void * allocated;
+ size_t real_size;
+
+ real_size = (size > 0) ? size : 1;
+ allocated = malloc(real_size);
+ if (allocated == NULL)
+ fatal(CTXOPTINTERNAL, NULL, NULL, NULL, NULL, NULL,
+ "Insufficient memory (attempt to malloc %lu bytes)\n",
+ (unsigned long int)size);
+
+ return allocated;
+}
+
+/* ================= */
+/* Customized calloc */
+/* ================= */
+static void *
+xcalloc(size_t n, size_t size)
+{
+ void * allocated;
+
+ n = (n > 0) ? n : 1;
+ size = (size > 0) ? size : 1;
+ allocated = calloc(n, size);
+ if (allocated == NULL)
+ fatal(CTXOPTINTERNAL, NULL, NULL, NULL, NULL, NULL,
+ "Insufficient memory (attempt to calloc %lu bytes)\n",
+ (unsigned long int)size);
+
+ return allocated;
+}
+
+/* ================== */
+/* Customized realloc */
+/* ================== */
+static void *
+xrealloc(void * p, size_t size)
+{
+ void * allocated;
+
+ allocated = realloc(p, size);
+ if (allocated == NULL && size > 0)
+ fatal(CTXOPTINTERNAL, NULL, NULL, NULL, NULL, NULL,
+ "Insufficient memory (attempt to xrealloc %lu bytes)\n",
+ (unsigned long int)size);
+
+ return allocated;
+}
+
+/* =================================== */
+/* strdup implementation using xmalloc */
+/* =================================== */
+static char *
+xstrdup(const char * p)
+{
+ char * allocated;
+
+ allocated = xmalloc(strlen(p) + 1);
+ strcpy(allocated, p);
+
+ return allocated;
+}
+
+/* ================================================== */
+/* strndup implementation using xmalloc */
+/* This version guarantees that there is a final '\0' */
+/* ================================================== */
+static char *
+xstrndup(const char * str, size_t len)
+{
+ char * p;
+
+ p = memchr(str, '\0', len);
+
+ if (p)
+ len = p - str;
+
+ p = xmalloc(len + 1);
+ memcpy(p, str, len);
+ p[len] = '\0';
+
+ return p;
+}
+
+/* ************************** */
+/* Linked list implementation */
+/* ************************** */
+
+/* Linked list node structure */
+/* """""""""""""""""""""""""" */
+struct ll_node_s
+{
+ void * data;
+ struct ll_node_s * next;
+ struct ll_node_s * prev;
+};
+
+/* Linked List structure */
+/* """"""""""""""""""""" */
+struct ll_s
+{
+ ll_node_t * head;
+ ll_node_t * tail;
+ long len;
+};
+
+/* ======================== */
+/* Create a new linked list */
+/* ======================== */
+static ll_t *
+ll_new(void)
+{
+ ll_t * ret = xmalloc(sizeof(ll_t));
+ ll_init(ret);
+
+ return ret;
+}
+
+/* ======================== */
+/* Initialize a linked list */
+/* ======================== */
+static void
+ll_init(ll_t * list)
+{
+ list->head = NULL;
+ list->tail = NULL;
+ list->len = 0;
+}
+
+/* ==================================================== */
+/* Allocate the space for a new node in the linked list */
+/* ==================================================== */
+static ll_node_t *
+ll_new_node(void)
+{
+ ll_node_t * ret = xmalloc(sizeof(ll_node_t));
+
+ return ret;
+}
+
+/* ==================================================================== */
+/* Append a new node filled with its data at the end of the linked list */
+/* The user is responsible for the memory management of the data */
+/* ==================================================================== */
+static void
+ll_append(ll_t * const list, void * const data)
+{
+ ll_node_t * node;
+
+ node = ll_new_node(); /* ll_new_node cannot return NULL because it *
+ | uses xmalloc which does not return if there *
+ | is an allocation error. */
+
+ node->data = data;
+ node->next = NULL;
+
+ node->prev = list->tail;
+ if (list->tail)
+ list->tail->next = node;
+ else
+ list->head = node;
+
+ list->tail = node;
+
+ ++list->len;
+}
+
+/* =================================================================== */
+/* Put a new node filled with its data at the beginning of the linked */
+/* list. The user is responsible for the memory management of the data */
+/* =================================================================== */
+static void
+ll_prepend(ll_t * const list, void * const data)
+{
+ ll_node_t * node;
+
+ node = ll_new_node(); /* ll_new_node cannot return NULL because it *
+ | uses xmalloc which does not return if there *
+ | is an allocation error. */
+
+ node->data = data;
+ node->prev = NULL;
+
+ node->next = list->head;
+ if (list->head)
+ list->head->prev = node;
+ else
+ list->tail = node;
+
+ list->head = node;
+
+ ++list->len;
+}
+
+/* ======================================================= */
+/* Insert a new node before the specified node in the list */
+/* ======================================================= */
+static void
+ll_insert_before(ll_t * const list, ll_node_t * node, void * const data)
+{
+ ll_node_t * new_node;
+
+ if (node->prev == NULL)
+ ll_prepend(list, data);
+ else
+ {
+ new_node = ll_new_node(); /* ll_new_node cannot return NULL because it *
+ | uses xmalloc which does not return if there *
+ | is an allocation error. */
+
+ new_node->data = data;
+ new_node->next = node;
+ new_node->prev = node->prev;
+ node->prev->next = new_node;
+ node->prev = new_node;
+
+ ++list->len;
+ }
+}
+
+/* ====================================================== */
+/* Insert a new node after the specified node in the list */
+/* ====================================================== */
+void
+ll_insert_after(ll_t * const list, ll_node_t * node, void * const data)
+{
+ ll_node_t * new_node;
+
+ if (node->next == NULL)
+ ll_append(list, data);
+ else
+ {
+ new_node = ll_new_node(); /* ll_new_node cannot return NULL because it *
+ | uses xmalloc which does not return if there *
+ | is an allocation error. */
+
+ new_node->data = data;
+ new_node->prev = node;
+ new_node->next = node->next;
+ node->next->prev = new_node;
+ node->next = new_node;
+
+ ++list->len;
+ }
+}
+
+/* ================================================================ */
+/* Remove a node from a linked list */
+/* The memory taken by the deleted node must be freed by the caller */
+/* ================================================================ */
+static int
+ll_delete(ll_t * const list, ll_node_t * node)
+{
+ if (list->head == list->tail)
+ {
+ if (list->head != NULL)
+ list->head = list->tail = NULL;
+ else
+ return 0;
+ }
+ else if (node->prev == NULL)
+ {
+ list->head = node->next;
+ list->head->prev = NULL;
+ }
+ else if (node->next == NULL)
+ {
+ list->tail = node->prev;
+ list->tail->next = NULL;
+ }
+ else
+ {
+ node->next->prev = node->prev;
+ node->prev->next = node->next;
+ }
+
+ --list->len;
+
+ return 1;
+}
+
+/* =========================================================================*/
+/* Find a node in the list containing data. Return the node pointer or NULL */
+/* if not found. */
+/* A comparison function must be provided to compare a and b (strcmp like). */
+/* =========================================================================*/
+static ll_node_t *
+ll_find(ll_t * const list, void * const data,
+ int (*cmpfunc)(const void * a, const void * b))
+{
+ ll_node_t * node;
+
+ if (NULL == (node = list->head))
+ return NULL;
+
+ do
+ {
+ if (0 == cmpfunc(node->data, data))
+ return node;
+ } while (NULL != (node = node->next));
+
+ return NULL;
+}
+
+/* ==================================================================== */
+/* Allocates and fills an array of strings from a list */
+/* WARNINGS: */
+/* 1) The list node must contain strings (char *) */
+/* 2) The strings in the resulting array MUST NOT be freed as the are */
+/* NOT copied from the strings of the list. */
+/* */
+/* IN list : The list from which the array is generated */
+/* IN start_node : The node of the list which will be the first node to */
+/* consider to create the array */
+/* OUT: count : The number of elements of the resulting array. */
+/* OUT: array : The resulting array. */
+/* RC : : The number of elements of the resulting array. */
+/* ==================================================================== */
+static int
+ll_strarray(ll_t * list, ll_node_t * start_node, int * count, char *** array)
+{
+ int n = 0;
+ ll_node_t * node;
+
+ *count = 0;
+
+ node = start_node;
+
+ if (list == NULL || node == NULL)
+ return 0;
+
+ *array = xmalloc((list->len + 1) * sizeof(char **));
+ while (node != NULL)
+ {
+ (*array)[n++] = (char *)(node->data);
+ (*count)++;
+
+ node = node->next;
+ }
+
+ (*array)[*count] = NULL;
+ *array = xrealloc(*array, ((*count) + 1) * sizeof(char **));
+
+ return *count;
+}
+
+/* ******************************************************************* */
+/* BST (search.h compatible) implementation */
+/* */
+/* Tree search generalized from Knuth (6.2.2) Algorithm T just like */
+/* the AT&T man page says. */
+/* */
+/* Written by reading the System V Interface Definition, not the code. */
+/* */
+/* Totally public domain. */
+/* ******************************************************************* */
+
+struct bst_s
+{
+ void * key;
+ struct bst_s * llink;
+ struct bst_s * rlink;
+};
+
+/* ========================== */
+/* delete node with given key */
+/* ========================== */
+static void *
+bst_delete(const void * vkey, void ** vrootp,
+ int (*compar)(const void *, const void *))
+{
+ bst_t ** rootp = (bst_t **)vrootp;
+ bst_t * p, *q, *r;
+ int cmp;
+
+ if (rootp == NULL || (p = *rootp) == NULL)
+ return NULL;
+
+ while ((cmp = (*compar)(vkey, (*rootp)->key)) != 0)
+ {
+ p = *rootp;
+ rootp = (cmp < 0) ? &(*rootp)->llink /* follow llink branch */
+ : &(*rootp)->rlink; /* follow rlink branch */
+ if (*rootp == NULL)
+ return NULL; /* key not found */
+ }
+ r = (*rootp)->rlink; /* D1: */
+ if ((q = (*rootp)->llink) == NULL) /* Left NULL? */
+ q = r;
+ else if (r != NULL)
+ { /* Right link is NULL? */
+ if (r->llink == NULL)
+ { /* D2: Find successor */
+ r->llink = q;
+ q = r;
+ }
+ else
+ { /* D3: Find NULL link */
+ for (q = r->llink; q->llink != NULL; q = r->llink)
+ r = q;
+ r->llink = q->rlink;
+ q->llink = (*rootp)->llink;
+ q->rlink = (*rootp)->rlink;
+ }
+ }
+ if (p != *rootp)
+ free(*rootp); /* D4: Free node */
+ *rootp = q; /* link parent to new node */
+ return p;
+}
+
+/* ============== */
+/* destroy a tree */
+/* ============== */
+static void
+bst_destroy_recurse(bst_t * root, void (*free_action)(void *))
+{
+ if (root->llink != NULL)
+ bst_destroy_recurse(root->llink, free_action);
+ if (root->rlink != NULL)
+ bst_destroy_recurse(root->rlink, free_action);
+
+ (*free_action)((void *)root->key);
+ free(root);
+}
+
+static void
+bst_destroy(void * vrootp, void (*freefct)(void *))
+{
+ bst_t * root = (bst_t *)vrootp;
+
+ if (root != NULL)
+ bst_destroy_recurse(root, freefct);
+}
+
+/* ======================== */
+/* find a node, or return 0 */
+/* ======================== */
+static void *
+bst_find(const void * vkey, void * const * vrootp,
+ int (*compar)(const void *, const void *))
+{
+ bst_t * const * rootp = (bst_t * const *)vrootp;
+
+ if (rootp == NULL)
+ return NULL;
+
+ while (*rootp != NULL)
+ { /* T1: */
+ int r;
+
+ if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */
+ return *rootp; /* key found */
+ rootp = (r < 0) ? &(*rootp)->llink /* T3: follow left branch */
+ : &(*rootp)->rlink; /* T4: follow right branch */
+ }
+ return NULL;
+}
+
+/* ===================================== */
+/* find or insert datum into search tree */
+/* ===================================== */
+static void *
+bst_search(const void * vkey, void ** vrootp,
+ int (*compar)(const void *, const void *))
+{
+ bst_t * q;
+ bst_t ** rootp = (bst_t **)vrootp;
+
+ if (rootp == NULL)
+ return NULL;
+
+ while (*rootp != NULL)
+ { /* Knuth's T1: */
+ int r;
+
+ if ((r = (*compar)(vkey, (*rootp)->key)) == 0) /* T2: */
+ return *rootp; /* we found it! */
+
+ rootp = (r < 0) ? &(*rootp)->llink /* T3: follow left branch */
+ : &(*rootp)->rlink; /* T4: follow right branch */
+ }
+
+ q = xmalloc(sizeof(bst_t)); /* T5: key not found */
+ if (q != 0)
+ { /* make new node */
+ *rootp = q; /* link new node to old */
+ q->key = (void *)vkey; /* initialize new node */
+ q->llink = q->rlink = NULL;
+ }
+ return q;
+}
+
+/* ======================== */
+/* Walk the nodes of a tree */
+/* ======================== */
+static void
+bst_walk_recurse(const bst_t * root,
+ void (*action)(const void *, walk_order_e, int), int level)
+{
+ if (root->llink == NULL && root->rlink == NULL)
+ (*action)(root, leaf, level);
+ else
+ {
+ (*action)(root, preorder, level);
+ if (root->llink != NULL)
+ bst_walk_recurse(root->llink, action, level + 1);
+ (*action)(root, postorder, level);
+ if (root->rlink != NULL)
+ bst_walk_recurse(root->rlink, action, level + 1);
+ (*action)(root, endorder, level);
+ }
+}
+
+static void
+bst_walk(const void * vroot, void (*action)(const void *, walk_order_e, int))
+{
+ if (vroot != NULL && action != NULL)
+ bst_walk_recurse(vroot, action, 0);
+}
+
+/* *********************** */
+/* various implementations */
+/* *********************** */
+
+/* ======================= */
+/* Trim leading characters */
+/* ======================= */
+static void
+ltrim(char * str, const char * trim_str)
+{
+ size_t len = strlen(str);
+ size_t begin = strspn(str, trim_str);
+ size_t i;
+
+ if (begin > 0)
+ for (i = begin; i <= len; ++i)
+ str[i - begin] = str[i];
+}
+
+/* ================================================= */
+/* Trim trailing characters */
+/* The resulting string will have at least min bytes */
+/* even if trailing spaces remain. */
+/* ================================================= */
+static void
+rtrim(char * str, const char * trim_str, size_t min)
+{
+ size_t len = strlen(str);
+ while (len > min && strchr(trim_str, str[len - 1]))
+ str[--len] = '\0';
+}
+
+/* ================================================== */
+/* Count the number of occurrences of the character c */
+/* in the string str. */
+/* The str pointer is assumed to be not NULL */
+/* ================================================== */
+static int
+strchrcount(char * str, char c)
+{
+ int count = 0;
+
+ while (*str)
+ if (*str++ == c)
+ count++;
+
+ return count;
+}
+
+/* =============================================== */
+/* Is the string str2 a prefix of the string str1? */
+/* =============================================== */
+int
+strpref(char * str1, char * str2)
+{
+ while (*str1 != '\0' && *str1 == *str2)
+ {
+ str1++;
+ str2++;
+ }
+
+ return *str2 == '\0';
+}
+
+/*===================================================================== */
+/* Allocate memory and safely concatenate strings. Stolen from a public */
+/* domain implementation which can be found here: */
+/* http://openwall.info/wiki/people/solar/software/\ */
+/* public-domain-source-code */
+/* ==================================================================== */
+static char *
+concat(const char * s1, ...)
+{
+ va_list args;
+ const char * s;
+ char * p, *result;
+ size_t l, m, n;
+
+ m = n = strlen(s1);
+ va_start(args, s1);
+ while ((s = va_arg(args, char *)))
+ {
+ l = strlen(s);
+ if ((m += l) < l)
+ break;
+ }
+ va_end(args);
+ if (s || m >= INT_MAX)
+ return NULL;
+
+ result = (char *)xmalloc(m + 1);
+
+ memcpy(p = result, s1, n);
+ p += n;
+ va_start(args, s1);
+ while ((s = va_arg(args, char *)))
+ {
+ l = strlen(s);
+ if ((n += l) < l || n > m)
+ break;
+ memcpy(p, s, l);
+ p += l;
+ }
+ va_end(args);
+ if (s || m != n || p != result + n)
+ {
+ free(result);
+ return NULL;
+ }
+
+ *p = 0;
+ return result;
+}
+
+/* ====================================================================== */
+/* public domain strtok_r() by Charlie Gordon */
+/* from comp.lang.c 9/14/2007 */
+/* http://groups.google.com/group/comp.lang.c/msg/2ab1ecbb86646684 */
+/* */
+/* (Declaration that it's public domain): */
+/* http://groups.google.com/group/comp.lang.c/msg/7c7b39328fefab9c */
+/* */
+/* Also, fixed by Fletcher T. Penney --- added the "return NULL" when */
+/* *end == NULL */
+/* ====================================================================== */
+static char *
+xstrtok_r(char * str, const char * delim, char ** end)
+{
+ char * ret;
+
+ if (str == NULL)
+ str = *end;
+
+ if (str == NULL)
+ return NULL;
+
+ str += strspn(str, delim);
+
+ if (*str == '\0')
+ return NULL;
+
+ ret = str;
+
+ str += strcspn(str, delim);
+
+ if (*str)
+ *str++ = '\0';
+
+ *end = str;
+
+ return ret;
+}
+
+/* =========================================================== */
+/* Fills an array of strings from the words composing a string */
+/* */
+/* str: initial string which will be altered */
+/* args: array of pointers to the start of the words in str */
+/* max: maximum number of words used before giving up */
+/* return: the number of words (<=max) */
+/* =========================================================== */
+static int
+str2argv(char * str, char ** args, int max)
+{
+ int nb_args = 0;
+
+ while (*str)
+ {
+ if (nb_args >= max)
+ return nb_args;
+
+ while (*str == ' ' || *str == '\t')
+ *(str++) = '\0';
+
+ if (!*str)
+ return nb_args;
+
+ args[nb_args] = str;
+ nb_args++;
+
+ while (*str && (*str != ' ') && (*str != '\t'))
+ str++;
+ }