chiark / gitweb /
Makefile.am: Also distribute the `README' file.
[runlisp] / query-runlisp-config.c
1 /* -*-c-*-
2  *
3  * Explore and debug `runlisp' configration
4  *
5  * (c) 2020 Mark Wooding
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Runlisp, a tool for invoking Common Lisp scripts.
11  *
12  * Runlisp is free software: you can redistribute it and/or modify it
13  * under the terms of the GNU General Public License as published by the
14  * Free Software Foundation; either version 3 of the License, or (at your
15  * option) any later version.
16  *
17  * Runlisp is distributed in the hope that it will be useful, but WITHOUT
18  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
20  * for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with Runlisp.  If not, see <https://www.gnu.org/licenses/>.
24  */
25
26 /*----- Header files ------------------------------------------------------*/
27
28 #include "config.h"
29
30 #include <assert.h>
31 #include <ctype.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "common.h"
37 #include "lib.h"
38 #include "mdwopt.h"
39
40 /*----- Static data -------------------------------------------------------*/
41
42 /* Query operations. */
43 enum {
44   OP_LISTSEC,                           /* list all sections */
45   OP_LISTVAR,                           /* list variables in a section */
46   OP_RAW,                               /* print a variable's value */
47   OP_SUBST,                             /* print variable's expansion */
48   OP_SPLIT,                             /* print word-split variable */
49   OP_LIMIT
50 };
51
52 /* A node in the list of queued-up operations. */
53 struct op {
54   struct op *next;                      /* link to next op in the list */
55   unsigned code;                        /* operation code (`OP_...') */
56   const char *arg;                      /* argument (from command-line) */
57 };
58
59 static struct op *oplist;               /* list of queued-up operations */
60
61 static unsigned flags = 0;              /* flags for the application */
62 #define AF_BOGUS 0x0001u                /*   invalid command-line syntax */
63 #define AF_SETCONF 0x0002u              /*   explicit configuration */
64
65 /*----- Main code ---------------------------------------------------------*/
66
67 /* Append a new node to the list of delayed operations, with CODE and ARG.
68  *
69  * The address of the final link (initially, the list head) is in
70  * *TAIL_INOUT: make this point to the new node, and then update it to point
71  * to the link in the new node.
72  */
73 static void add_op(struct op ***tail_inout, unsigned code, const char *arg)
74 {
75   struct op *op = xmalloc(sizeof(*op));
76   op->code = code; op->arg = arg; **tail_inout = op; *tail_inout = &op->next;
77 }
78
79 /* Given an ARG of the form `[SECT:]VAR', set *SECT_OUT and *VAR_OUT to the
80  * requested home section and variable.  Leave these null if they can't be
81  * found.
82  */
83 static void find_var(const char *arg,
84                      struct config_section **sect_out,
85                      struct config_var **var_out)
86 {
87   struct config_section *sect;
88   const char *p;
89
90   p = strchr(arg, ':');
91   if (!p)
92     { sect = toplevel; p = arg; }
93   else
94     { sect = config_find_section_n(&config, 0, arg, p - arg); p++; }
95   *sect_out = sect;
96   if (!sect) *var_out = 0;
97   else *var_out = config_find_var(&config, sect, CF_INHERIT, p);
98 }
99
100 /* Help and related functions. */
101 static void version(FILE *fp)
102   { fprintf(fp, "%s, runlisp version %s\n", progname, PACKAGE_VERSION); }
103
104 static void usage(FILE *fp)
105 {
106   fprintf(fp, "\
107 usage: %s [-Lqv] [-c CONF] [-o [SECT:]VAR=VAL]\n\
108         [-l SECT] [-p [SECT:]VAR] [-w [SECT:]VAR] [-x [SECT:]VAR]\n",
109           progname);
110 }
111
112 static void help(FILE *fp)
113 {
114   version(fp); fputc('\n', fp); usage(fp);
115   fputs("\n\
116 Help options:\n\
117   -h, --help                    Show this help text and exit successfully.\n\
118   -V, --version                 Show version number and exit successfully.\n\
119 \n\
120 Diagnostics:\n\
121   -q, --quiet                   Don't print warning messages.\n\
122   -v, --verbose                 Print informational messages (repeatable).\n\
123 \n\
124 Configuration:\n\
125   -c, --config-file=CONF        Read configuration from CONF (repeatable).\n\
126   -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\
127 \n\
128 Output:\n\
129   -L, --list-sections           List all known section names in order.\n\
130   -l, --list-variables=SECTION  List all defined variables in SECTION.\n\
131   -p, --print-variable=[SECT:]VAR Print the raw (unexpanded) value of VAR.\n\
132   -w, --split-variable=[SECT:]VAR Expand and word-split VAR and print.\n\
133   -x, --expand-variable=[SECT:]VAR Expand VAR and print the result.\n", fp);
134 }
135
136 /* Main program. */
137 int main(int argc, char *argv[])
138 {
139   struct config_section_iter si;
140   struct config_section *sect;
141   struct config_var_iter vi;
142   struct config_var *var;
143   struct op *op, **tail = &oplist;
144   struct dstr d = DSTR_INIT;
145   struct argv av = ARGV_INIT;
146   int i;
147
148   /* Command-line options. */
149   static const struct option opts[] = {
150     { "help",                   0,              0,      'h' },
151     { "version",                0,              0,      'V' },
152     { "list-sections",          0,              0,      'L' },
153     { "config-file",            OPTF_ARGREQ,    0,      'c' },
154     { "list-variables",         OPTF_ARGREQ,    0,      'l' },
155     { "set-option",             OPTF_ARGREQ,    0,      'o' },
156     { "print-variable",         OPTF_ARGREQ,    0,      'p' },
157     { "quiet",                  0,              0,      'q' },
158     { "verbose",                0,              0,      'v' },
159     { "split-variable",         OPTF_ARGREQ,    0,      'w' },
160     { "expand-variable",        OPTF_ARGREQ,    0,      'x' },
161     { 0,                        0,              0,      0 }
162   };
163
164   /* Initial setup. */
165   set_progname(argv[0]);
166   init_config();
167
168   /* Parse the options.
169    *
170    * We must delay the query operations until the configuration is loaded,
171    * but we won't know whether to load the default configuration until we're
172    * sure that that there are no `-c' options.  So just stash the queries in
173    * a list until later.
174    */
175   optprog = (/*unconst*/ char *)progname;
176   for (;;) {
177     i = mdwopt(argc - 1, argv + 1, "hVLc:l:o:p:qvw:x:", opts, 0, 0,
178                OPTF_NOPROGNAME);
179     if (i < 0) break;
180     switch (i) {
181       case 'h': help(stdout); exit(0);
182       case 'V': version(stdout); exit(0);
183       case 'L': add_op(&tail, OP_LISTSEC, 0); break;
184       case 'c': read_config_path(optarg, 0); flags |= AF_SETCONF; break;
185       case 'l': add_op(&tail, OP_LISTVAR, optarg); break;
186       case 'o': if (set_config_var(optarg)) flags |= AF_BOGUS; break;
187       case 'p': add_op(&tail, OP_RAW, optarg); break;
188       case 'q': if (verbose) verbose--; break;
189       case 'v': verbose++; break;
190       case 'w': add_op(&tail, OP_SPLIT, optarg); break;
191       case 'x': add_op(&tail, OP_SUBST, optarg); break;
192       default: flags |= AF_BOGUS; break;
193     }
194   }
195
196   /* Check that everything worked. */
197   optind++;
198   if (optind < argc) flags |= AF_BOGUS;
199   if (flags&AF_BOGUS) { usage(stderr); exit(127); }
200   *tail = 0; if (!oplist) lose("nothing to do");
201
202   /* Load default configuration if no explicit files were requested. */
203   if (!(flags&AF_SETCONF)) load_default_config();
204
205   /* Work through the operations we stashed earlier. */
206   for (op = oplist; op; op = op->next)
207     switch (op->code) {
208
209       case OP_LISTSEC:
210         printf("sections:\n");
211         for (config_start_section_iter(&config, &si);
212              (sect = config_next_section(&si)); )
213           printf("\t%s\n", CONFIG_SECTION_NAME(sect));
214         break;
215
216       case OP_LISTVAR:
217         sect = config_find_section(&config, 0, op->arg);
218         if (!sect)
219           printf("section `%s' not found\n", op->arg);
220         else {
221           printf("section `%s' variables:\n", CONFIG_SECTION_NAME(sect));
222           for (config_start_var_iter(&config, sect, &vi);
223                (var = config_next_var(&vi)); )
224             printf("\t%s\n", CONFIG_VAR_NAME(var));
225         }
226         break;
227
228       case OP_RAW:
229         find_var(op->arg, &sect, &var);
230         if (!var) printf("%s not found\n", op->arg);
231         else printf("%s = %s\n", op->arg, var->val);
232         break;
233
234       case OP_SUBST:
235         find_var(op->arg, &sect, &var);
236         if (!var)
237           printf("%s not found\n", op->arg);
238         else {
239           dstr_reset(&d); config_subst_var(&config, sect, var, &d);
240           printf("%s = %s\n", op->arg, d.p);
241         }
242         break;
243
244       case OP_SPLIT:
245         find_var(op->arg, &sect, &var);
246         if (!var)
247           printf("%s not found\n", op->arg);
248         else {
249           argv_reset(&av); config_subst_split_var(&config, sect, var, &av);
250           dstr_reset(&d); argv_string(&d, &av);
251           printf("%s = %s\n", op->arg, d.p);
252         }
253         break;
254
255       default:
256         assert(0);
257     }
258
259   /* All done. */
260   return (0);
261 }
262
263 /*----- That's all, folks -------------------------------------------------*/