runlisp.c: Undefine local option-parsing macros at the end of the block.
[runlisp] / query-runlisp-config.c
CommitLineData
8996f767
MW
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. */
43enum {
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. */
53struct 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
59static struct op *oplist; /* list of queued-up operations */
60
61static unsigned flags = 0; /* flags for the application */
62#define AF_BOGUS 0x0001u /* invalid command-line syntax */
63#define AF_SETCONF 0x0002u /* explicit configuration */
b27b366b 64#define AF_PLAIN 0x0004u /* don't print labels */
8996f767
MW
65
66/*----- Main code ---------------------------------------------------------*/
67
68/* Append a new node to the list of delayed operations, with CODE and ARG.
69 *
70 * The address of the final link (initially, the list head) is in
71 * *TAIL_INOUT: make this point to the new node, and then update it to point
72 * to the link in the new node.
73 */
74static void add_op(struct op ***tail_inout, unsigned code, const char *arg)
75{
76 struct op *op = xmalloc(sizeof(*op));
77 op->code = code; op->arg = arg; **tail_inout = op; *tail_inout = &op->next;
78}
79
80/* Given an ARG of the form `[SECT:]VAR', set *SECT_OUT and *VAR_OUT to the
81 * requested home section and variable. Leave these null if they can't be
82 * found.
83 */
84static void find_var(const char *arg,
85 struct config_section **sect_out,
86 struct config_var **var_out)
87{
88 struct config_section *sect;
89 const char *p;
90
91 p = strchr(arg, ':');
92 if (!p)
93 { sect = toplevel; p = arg; }
94 else
95 { sect = config_find_section_n(&config, 0, arg, p - arg); p++; }
96 *sect_out = sect;
97 if (!sect) *var_out = 0;
10427eb2 98 else *var_out = config_find_var(&config, sect, CF_INHERIT, p);
8996f767
MW
99}
100
101/* Help and related functions. */
102static void version(FILE *fp)
103 { fprintf(fp, "%s, runlisp version %s\n", progname, PACKAGE_VERSION); }
104
105static void usage(FILE *fp)
106{
107 fprintf(fp, "\
b27b366b 108usage: %s [-LMqv] [+M] [-c CONF] [-o [SECT:]VAR=VAL]\n\
8996f767
MW
109 [-l SECT] [-p [SECT:]VAR] [-w [SECT:]VAR] [-x [SECT:]VAR]\n",
110 progname);
111}
112
113static void help(FILE *fp)
114{
115 version(fp); fputc('\n', fp); usage(fp);
116 fputs("\n\
117Help options:\n\
118 -h, --help Show this help text and exit successfully.\n\
119 -V, --version Show version number and exit successfully.\n\
120\n\
121Diagnostics:\n\
122 -q, --quiet Don't print warning messages.\n\
123 -v, --verbose Print informational messages (repeatable).\n\
124\n\
125Configuration:\n\
126 -c, --config-file=CONF Read configuration from CONF (repeatable).\n\
127 -o, --set-option=[SECT:]VAR=VAL Set configuration variable (repeatable).\n\
128\n\
129Output:\n\
130 -L, --list-sections List all known section names in order.\n\
b27b366b 131 -M, --machine-readable Don't print headers or labels.\n\
8996f767
MW
132 -l, --list-variables=SECTION List all defined variables in SECTION.\n\
133 -p, --print-variable=[SECT:]VAR Print the raw (unexpanded) value of VAR.\n\
134 -w, --split-variable=[SECT:]VAR Expand and word-split VAR and print.\n\
135 -x, --expand-variable=[SECT:]VAR Expand VAR and print the result.\n", fp);
136}
137
138/* Main program. */
139int main(int argc, char *argv[])
140{
141 struct config_section_iter si;
142 struct config_section *sect;
143 struct config_var_iter vi;
144 struct config_var *var;
145 struct op *op, **tail = &oplist;
146 struct dstr d = DSTR_INIT;
147 struct argv av = ARGV_INIT;
148 int i;
149
150 /* Command-line options. */
151 static const struct option opts[] = {
152 { "help", 0, 0, 'h' },
153 { "version", 0, 0, 'V' },
154 { "list-sections", 0, 0, 'L' },
b27b366b 155 { "machine-readable", OPTF_NEGATE, 0, 'M' },
8996f767
MW
156 { "config-file", OPTF_ARGREQ, 0, 'c' },
157 { "list-variables", OPTF_ARGREQ, 0, 'l' },
158 { "set-option", OPTF_ARGREQ, 0, 'o' },
159 { "print-variable", OPTF_ARGREQ, 0, 'p' },
160 { "quiet", 0, 0, 'q' },
161 { "verbose", 0, 0, 'v' },
162 { "split-variable", OPTF_ARGREQ, 0, 'w' },
163 { "expand-variable", OPTF_ARGREQ, 0, 'x' },
164 { 0, 0, 0, 0 }
165 };
166
167 /* Initial setup. */
168 set_progname(argv[0]);
169 init_config();
170
171 /* Parse the options.
172 *
173 * We must delay the query operations until the configuration is loaded,
174 * but we won't know whether to load the default configuration until we're
175 * sure that that there are no `-c' options. So just stash the queries in
176 * a list until later.
177 */
178 optprog = (/*unconst*/ char *)progname;
b27b366b
MW
179
180#define FLAGOPT(ch, f) \
181 case ch: \
182 flags |= f; \
183 break; \
184 case ch | OPTF_NEGATED: \
185 flags &= ~f; \
186 break
187
8996f767 188 for (;;) {
b27b366b 189 i = mdwopt(argc - 1, argv + 1, "hVLM+c:l:o:p:qvw:x:", opts, 0, 0,
8996f767
MW
190 OPTF_NOPROGNAME);
191 if (i < 0) break;
192 switch (i) {
193 case 'h': help(stdout); exit(0);
194 case 'V': version(stdout); exit(0);
195 case 'L': add_op(&tail, OP_LISTSEC, 0); break;
b27b366b 196 FLAGOPT('M', AF_PLAIN);
8996f767
MW
197 case 'c': read_config_path(optarg, 0); flags |= AF_SETCONF; break;
198 case 'l': add_op(&tail, OP_LISTVAR, optarg); break;
199 case 'o': if (set_config_var(optarg)) flags |= AF_BOGUS; break;
200 case 'p': add_op(&tail, OP_RAW, optarg); break;
201 case 'q': if (verbose) verbose--; break;
202 case 'v': verbose++; break;
203 case 'w': add_op(&tail, OP_SPLIT, optarg); break;
204 case 'x': add_op(&tail, OP_SUBST, optarg); break;
205 default: flags |= AF_BOGUS; break;
206 }
207 }
208
b27b366b
MW
209#undef FLAGOPT
210
8996f767
MW
211 /* Check that everything worked. */
212 optind++;
213 if (optind < argc) flags |= AF_BOGUS;
214 if (flags&AF_BOGUS) { usage(stderr); exit(127); }
215 *tail = 0; if (!oplist) lose("nothing to do");
216
217 /* Load default configuration if no explicit files were requested. */
218 if (!(flags&AF_SETCONF)) load_default_config();
219
220 /* Work through the operations we stashed earlier. */
221 for (op = oplist; op; op = op->next)
222 switch (op->code) {
223
224 case OP_LISTSEC:
b27b366b 225 if (!(flags&AF_PLAIN)) printf("sections:\n");
8996f767
MW
226 for (config_start_section_iter(&config, &si);
227 (sect = config_next_section(&si)); )
b27b366b
MW
228 printf("%s%s\n",
229 flags&AF_PLAIN ? "" : "\t",
230 CONFIG_SECTION_NAME(sect));
8996f767
MW
231 break;
232
233 case OP_LISTVAR:
234 sect = config_find_section(&config, 0, op->arg);
b27b366b
MW
235 if (sect) {
236 if (!(flags&AF_PLAIN))
237 printf("section `%s' variables:\n", CONFIG_SECTION_NAME(sect));
8996f767
MW
238 for (config_start_var_iter(&config, sect, &vi);
239 (var = config_next_var(&vi)); )
b27b366b
MW
240 printf("%s%s\n",
241 flags&AF_PLAIN ? "" : "\t",
242 CONFIG_VAR_NAME(var));
243 } else {
244 if (flags&AF_PLAIN) lose("section `%s' not found", op->arg);
245 else printf("section `%s' not found\n", op->arg);
8996f767
MW
246 }
247 break;
248
249 case OP_RAW:
250 find_var(op->arg, &sect, &var);
b27b366b
MW
251 if (flags&AF_PLAIN) {
252 if (var) puts(var->val);
253 else lose("variable `%s' not found", op->arg);
254 } else {
255 if (var) printf("%s = %s\n", op->arg, var->val);
256 else printf("%s not found\n", op->arg);
257 }
8996f767
MW
258 break;
259
260 case OP_SUBST:
261 find_var(op->arg, &sect, &var);
b27b366b 262 if (var) {
8996f767 263 dstr_reset(&d); config_subst_var(&config, sect, var, &d);
b27b366b
MW
264 if (flags&AF_PLAIN) puts(d.p);
265 else printf("%s = %s\n", op->arg, d.p);
266 } else {
267 if (flags&AF_PLAIN) lose("variable %s not found", op->arg);
268 else printf("%s not found\n", op->arg);
8996f767
MW
269 }
270 break;
271
272 case OP_SPLIT:
273 find_var(op->arg, &sect, &var);
b27b366b 274 if (var) {
8996f767
MW
275 argv_reset(&av); config_subst_split_var(&config, sect, var, &av);
276 dstr_reset(&d); argv_string(&d, &av);
b27b366b
MW
277 if (flags&AF_PLAIN) puts(d.p);
278 else printf("%s = %s\n", op->arg, d.p);
279 } else {
280 if (flags&AF_PLAIN) lose("variable %s not found", op->arg);
281 else printf("%s not found\n", op->arg);
8996f767
MW
282 }
283 break;
284
285 default:
286 assert(0);
287 }
288
289 /* All done. */
290 return (0);
291}
292
293/*----- That's all, folks -------------------------------------------------*/