--- /dev/null
+/* -*-c-*-
+ *
+ * Explore and debug `runlisp' configration
+ *
+ * (c) 2020 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
+ *
+ * Runlisp is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Runlisp is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Runlisp. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+#include "lib.h"
+#include "mdwopt.h"
+
+/*----- Static data -------------------------------------------------------*/
+
+/* Query operations. */
+enum {
+ OP_LISTSEC, /* list all sections */
+ OP_LISTVAR, /* list variables in a section */
+ OP_RAW, /* print a variable's value */
+ OP_SUBST, /* print variable's expansion */
+ OP_SPLIT, /* print word-split variable */
+ OP_LIMIT
+};
+
+/* A node in the list of queued-up operations. */
+struct op {
+ struct op *next; /* link to next op in the list */
+ unsigned code; /* operation code (`OP_...') */
+ const char *arg; /* argument (from command-line) */
+};
+
+static struct op *oplist; /* list of queued-up operations */
+
+static unsigned flags = 0; /* flags for the application */
+#define AF_BOGUS 0x0001u /* invalid command-line syntax */
+#define AF_SETCONF 0x0002u /* explicit configuration */
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* Append a new node to the list of delayed operations, with CODE and ARG.
+ *
+ * The address of the final link (initially, the list head) is in
+ * *TAIL_INOUT: make this point to the new node, and then update it to point
+ * to the link in the new node.
+ */
+static void add_op(struct op ***tail_inout, unsigned code, const char *arg)
+{
+ struct op *op = xmalloc(sizeof(*op));
+ op->code = code; op->arg = arg; **tail_inout = op; *tail_inout = &op->next;
+}
+
+/* Given an ARG of the form `[SECT:]VAR', set *SECT_OUT and *VAR_OUT to the
+ * requested home section and variable. Leave these null if they can't be
+ * found.
+ */
+static void find_var(const char *arg,
+ struct config_section **sect_out,
+ struct config_var **var_out)
+{
+ struct config_section *sect;
+ const char *p;
+
+ p = strchr(arg, ':');
+ if (!p)
+ { sect = toplevel; p = arg; }
+ else
+ { sect = config_find_section_n(&config, 0, arg, p - arg); p++; }
+ *sect_out = sect;
+ if (!sect) *var_out = 0;
+ else *var_out = config_find_var(&config, sect, 0, p);
+}
+
+/* Help and related functions. */
+static void version(FILE *fp)
+ { fprintf(fp, "%s, runlisp version %s\n", progname, PACKAGE_VERSION); }
+
+static void usage(FILE *fp)
+{
+ fprintf(fp, "\
+usage: %s [-Lqv] [-c CONF] [-o [SECT:]VAR=VAL]\n\
+ [-l SECT] [-p [SECT:]VAR] [-w [SECT:]VAR] [-x [SECT:]VAR]\n",
+ progname);
+}
+
+static void help(FILE *fp)
+{
+ version(fp); fputc('\n', fp); usage(fp);
+ fputs("\n\
+Help options:\n\
+ -h, --help Show this help text and exit successfully.\n\
+ -V, --version Show version number and exit successfully.\n\
+\n\
+Diagnostics:\n\
+ -q, --quiet Don't print warning messages.\n\
+ -v, --verbose Print informational messages (repeatable).\n\
+\n\
+Configuration:\n\
+ -c, --config-file=CONF Read configuration from CONF (repeatable).\n\
+ -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\
+\n\
+Output:\n\
+ -L, --list-sections List all known section names in order.\n\
+ -l, --list-variables=SECTION List all defined variables in SECTION.\n\
+ -p, --print-variable=[SECT:]VAR Print the raw (unexpanded) value of VAR.\n\
+ -w, --split-variable=[SECT:]VAR Expand and word-split VAR and print.\n\
+ -x, --expand-variable=[SECT:]VAR Expand VAR and print the result.\n", fp);
+}
+
+/* Main program. */
+int main(int argc, char *argv[])
+{
+ struct config_section_iter si;
+ struct config_section *sect;
+ struct config_var_iter vi;
+ struct config_var *var;
+ struct op *op, **tail = &oplist;
+ struct dstr d = DSTR_INIT;
+ struct argv av = ARGV_INIT;
+ int i;
+
+ /* Command-line options. */
+ static const struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'V' },
+ { "list-sections", 0, 0, 'L' },
+ { "config-file", OPTF_ARGREQ, 0, 'c' },
+ { "list-variables", OPTF_ARGREQ, 0, 'l' },
+ { "set-option", OPTF_ARGREQ, 0, 'o' },
+ { "print-variable", OPTF_ARGREQ, 0, 'p' },
+ { "quiet", 0, 0, 'q' },
+ { "verbose", 0, 0, 'v' },
+ { "split-variable", OPTF_ARGREQ, 0, 'w' },
+ { "expand-variable", OPTF_ARGREQ, 0, 'x' },
+ { 0, 0, 0, 0 }
+ };
+
+ /* Initial setup. */
+ set_progname(argv[0]);
+ init_config();
+
+ /* Parse the options.
+ *
+ * We must delay the query operations until the configuration is loaded,
+ * but we won't know whether to load the default configuration until we're
+ * sure that that there are no `-c' options. So just stash the queries in
+ * a list until later.
+ */
+ optprog = (/*unconst*/ char *)progname;
+ for (;;) {
+ i = mdwopt(argc - 1, argv + 1, "hVLc:l:o:p:qvw:x:", opts, 0, 0,
+ OPTF_NOPROGNAME);
+ if (i < 0) break;
+ switch (i) {
+ case 'h': help(stdout); exit(0);
+ case 'V': version(stdout); exit(0);
+ case 'L': add_op(&tail, OP_LISTSEC, 0); break;
+ case 'c': read_config_path(optarg, 0); flags |= AF_SETCONF; break;
+ case 'l': add_op(&tail, OP_LISTVAR, optarg); break;
+ case 'o': if (set_config_var(optarg)) flags |= AF_BOGUS; break;
+ case 'p': add_op(&tail, OP_RAW, optarg); break;
+ case 'q': if (verbose) verbose--; break;
+ case 'v': verbose++; break;
+ case 'w': add_op(&tail, OP_SPLIT, optarg); break;
+ case 'x': add_op(&tail, OP_SUBST, optarg); break;
+ default: flags |= AF_BOGUS; break;
+ }
+ }
+
+ /* Check that everything worked. */
+ optind++;
+ if (optind < argc) flags |= AF_BOGUS;
+ if (flags&AF_BOGUS) { usage(stderr); exit(127); }
+ *tail = 0; if (!oplist) lose("nothing to do");
+
+ /* Load default configuration if no explicit files were requested. */
+ if (!(flags&AF_SETCONF)) load_default_config();
+
+ /* Work through the operations we stashed earlier. */
+ for (op = oplist; op; op = op->next)
+ switch (op->code) {
+
+ case OP_LISTSEC:
+ printf("sections:\n");
+ for (config_start_section_iter(&config, &si);
+ (sect = config_next_section(&si)); )
+ printf("\t%s\n", CONFIG_SECTION_NAME(sect));
+ break;
+
+ case OP_LISTVAR:
+ sect = config_find_section(&config, 0, op->arg);
+ if (!sect)
+ printf("section `%s' not found\n", op->arg);
+ else {
+ printf("section `%s' variables:\n", CONFIG_SECTION_NAME(sect));
+ for (config_start_var_iter(&config, sect, &vi);
+ (var = config_next_var(&vi)); )
+ printf("\t%s\n", CONFIG_VAR_NAME(var));
+ }
+ break;
+
+ case OP_RAW:
+ find_var(op->arg, §, &var);
+ if (!var) printf("%s not found\n", op->arg);
+ else printf("%s = %s\n", op->arg, var->val);
+ break;
+
+ case OP_SUBST:
+ find_var(op->arg, §, &var);
+ if (!var)
+ printf("%s not found\n", op->arg);
+ else {
+ dstr_reset(&d); config_subst_var(&config, sect, var, &d);
+ printf("%s = %s\n", op->arg, d.p);
+ }
+ break;
+
+ case OP_SPLIT:
+ find_var(op->arg, §, &var);
+ if (!var)
+ printf("%s not found\n", op->arg);
+ else {
+ argv_reset(&av); config_subst_split_var(&config, sect, var, &av);
+ dstr_reset(&d); argv_string(&d, &av);
+ printf("%s = %s\n", op->arg, d.p);
+ }
+ break;
+
+ default:
+ assert(0);
+ }
+
+ /* All done. */
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/