chiark / gitweb /
@@@ more wip
[runlisp] / query-runlisp-config.c
diff --git a/query-runlisp-config.c b/query-runlisp-config.c
new file mode 100644 (file)
index 0000000..bbfb150
--- /dev/null
@@ -0,0 +1,263 @@
+/* -*-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, &sect, &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, &sect, &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, &sect, &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 -------------------------------------------------*/