Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/libxo/xo/xo.c
39478 views
1
/*
2
* Copyright (c) 2014-2019, Juniper Networks, Inc.
3
* All rights reserved.
4
* This SOFTWARE is licensed under the LICENSE provided in the
5
* ../Copyright file. By downloading, installing, copying, or otherwise
6
* using the SOFTWARE, you agree to be bound by the terms of that
7
* LICENSE.
8
* Phil Shafer, July 2014
9
*/
10
11
#include <stdio.h>
12
#include <stdlib.h>
13
#include <stdarg.h>
14
#include <string.h>
15
16
#include "xo_config.h"
17
#include "xo.h"
18
#include "xo_explicit.h"
19
20
#include <getopt.h> /* Include after xo.h for testing */
21
22
#ifndef UNUSED
23
#define UNUSED __attribute__ ((__unused__))
24
#endif /* UNUSED */
25
26
static int opt_warn; /* Enable warnings */
27
28
static char **save_argv;
29
static char **checkpoint_argv;
30
31
static char *
32
next_arg (void)
33
{
34
char *cp = *save_argv;
35
36
if (cp == NULL)
37
xo_errx(1, "missing argument");
38
39
save_argv += 1;
40
return cp;
41
}
42
43
static void
44
prep_arg (char *fmt)
45
{
46
char *cp, *fp;
47
48
for (cp = fp = fmt; *cp; cp++, fp++) {
49
if (*cp != '\\') {
50
if (cp != fp)
51
*fp = *cp;
52
continue;
53
}
54
55
switch (*++cp) {
56
case 'n':
57
*fp = '\n';
58
break;
59
60
case 'r':
61
*fp = '\r';
62
break;
63
64
case 'b':
65
*fp = '\b';
66
break;
67
68
case 'e':
69
*fp = '\e';
70
break;
71
72
default:
73
*fp = *cp;
74
}
75
}
76
77
*fp = '\0';
78
}
79
80
static void
81
checkpoint (xo_handle_t *xop UNUSED, va_list vap UNUSED, int restore)
82
{
83
if (restore)
84
save_argv = checkpoint_argv;
85
else
86
checkpoint_argv = save_argv;
87
}
88
89
/*
90
* Our custom formatter is responsible for combining format string pieces
91
* with our command line arguments to build strings. This involves faking
92
* some printf-style logic.
93
*/
94
static xo_ssize_t
95
formatter (xo_handle_t *xop, char *buf, xo_ssize_t bufsiz,
96
const char *fmt, va_list vap UNUSED)
97
{
98
int lflag UNUSED = 0; /* Parse long flag, though currently ignored */
99
int hflag = 0, jflag = 0, tflag = 0,
100
zflag = 0, qflag = 0, star1 = 0, star2 = 0;
101
int rc = 0;
102
int w1 = 0, w2 = 0;
103
const char *cp;
104
105
for (cp = fmt + 1; *cp; cp++) {
106
if (*cp == 'l')
107
lflag += 1;
108
else if (*cp == 'h')
109
hflag += 1;
110
else if (*cp == 'j')
111
jflag += 1;
112
else if (*cp == 't')
113
tflag += 1;
114
else if (*cp == 'z')
115
zflag += 1;
116
else if (*cp == 'q')
117
qflag += 1;
118
else if (*cp == '*') {
119
if (star1 == 0)
120
star1 = 1;
121
else
122
star2 = 1;
123
} else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
124
break;
125
else if (*cp == 'n' || *cp == 'v') {
126
if (opt_warn)
127
xo_error_h(xop, "unsupported format: '%s'", fmt);
128
return -1;
129
}
130
}
131
132
char fc = *cp;
133
134
/* Handle "%*.*s" */
135
if (star1)
136
w1 = strtol(next_arg(), NULL, 0);
137
if (star2 > 1)
138
w2 = strtol(next_arg(), NULL, 0);
139
140
if (fc == 'D' || fc == 'O' || fc == 'U')
141
lflag = 1;
142
143
if (strchr("diD", fc) != NULL) {
144
long long value = strtoll(next_arg(), NULL, 0);
145
if (star1 && star2)
146
rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
147
else if (star1)
148
rc = snprintf(buf, bufsiz, fmt, w1, value);
149
else
150
rc = snprintf(buf, bufsiz, fmt, value);
151
152
} else if (strchr("ouxXOUp", fc) != NULL) {
153
unsigned long long value = strtoull(next_arg(), NULL, 0);
154
if (star1 && star2)
155
rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
156
else if (star1)
157
rc = snprintf(buf, bufsiz, fmt, w1, value);
158
else
159
rc = snprintf(buf, bufsiz, fmt, value);
160
161
} else if (strchr("eEfFgGaA", fc) != NULL) {
162
double value = strtold(next_arg(), NULL);
163
if (star1 && star2)
164
rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
165
else if (star1)
166
rc = snprintf(buf, bufsiz, fmt, w1, value);
167
else
168
rc = snprintf(buf, bufsiz, fmt, value);
169
170
} else if (fc == 'C' || fc == 'c' || fc == 'S' || fc == 's') {
171
char *value = next_arg();
172
if (star1 && star2)
173
rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
174
else if (star1)
175
rc = snprintf(buf, bufsiz, fmt, w1, value);
176
else
177
rc = snprintf(buf, bufsiz, fmt, value);
178
}
179
180
return rc;
181
}
182
183
static void
184
print_version (void)
185
{
186
fprintf(stderr, "libxo version %s%s\n",
187
xo_version, xo_version_extra);
188
fprintf(stderr, "xo version %s%s\n",
189
LIBXO_VERSION, LIBXO_VERSION_EXTRA);
190
}
191
192
static void
193
print_help (void)
194
{
195
fprintf(stderr,
196
"Usage: xo [options] format [fields]\n"
197
" --close <path> Close tags for the given path\n"
198
" --close-instance <name> Close an open instance name\n"
199
" --close-list <name> Close an open list name\n"
200
" --continuation OR -C Output belongs on same line as previous output\n"
201
" --depth <num> Set the depth for pretty printing\n"
202
" --help Display this help text\n"
203
" --html OR -H Generate HTML output\n"
204
" --instance OR -I <name> Wrap in an instance of the given name\n"
205
" --json OR -J Generate JSON output\n"
206
" --leading-xpath <path> OR -l <path> "
207
"Add a prefix to generated XPaths (HTML)\n"
208
" --not-first Indicate this object is not the first (JSON)\n"
209
" --open <path> Open tags for the given path\n"
210
" --open-instance <name> Open an instance given by name\n"
211
" --open-list <name> Open a list given by name\n"
212
" --option <opts> -or -O <opts> Give formatting options\n"
213
" --pretty OR -p Make 'pretty' output (add indent, newlines)\n"
214
" --style <style> OR -s <style> "
215
"Generate given style (xml, json, text, html)\n"
216
" --text OR -T Generate text output (the default style)\n"
217
" --top-wrap Generate a top-level object wrapper (JSON)\n"
218
" --version Display version information\n"
219
" --warn OR -W Display warnings in text on stderr\n"
220
" --warn-xml Display warnings in xml on stdout\n"
221
" --wrap <path> Wrap output in a set of containers\n"
222
" --xml OR -X Generate XML output\n"
223
" --xpath Add XPath data to HTML output\n");
224
}
225
226
static struct opts {
227
int o_close_instance;
228
int o_close_list;
229
int o_depth;
230
int o_help;
231
int o_not_first;
232
int o_open_instance;
233
int o_open_list;
234
int o_top_wrap;
235
int o_version;
236
int o_warn_xml;
237
int o_wrap;
238
int o_xpath;
239
} opts;
240
241
static struct option long_opts[] = {
242
{ "close", required_argument, NULL, 'c' },
243
{ "close-instance", required_argument, &opts.o_close_instance, 1 },
244
{ "close-list", required_argument, &opts.o_close_list, 1 },
245
{ "continuation", no_argument, NULL, 'C' },
246
{ "depth", required_argument, &opts.o_depth, 1 },
247
{ "help", no_argument, &opts.o_help, 1 },
248
{ "html", no_argument, NULL, 'H' },
249
{ "instance", required_argument, NULL, 'I' },
250
{ "json", no_argument, NULL, 'J' },
251
{ "leading-xpath", required_argument, NULL, 'l' },
252
{ "not-first", no_argument, &opts.o_not_first, 1 },
253
{ "open", required_argument, NULL, 'o' },
254
{ "open-instance", required_argument, &opts.o_open_instance, 1 },
255
{ "open-list", required_argument, &opts.o_open_list, 1 },
256
{ "option", required_argument, NULL, 'O' },
257
{ "pretty", no_argument, NULL, 'p' },
258
{ "style", required_argument, NULL, 's' },
259
{ "text", no_argument, NULL, 'T' },
260
{ "top-wrap", no_argument, &opts.o_top_wrap, 1 },
261
{ "xml", no_argument, NULL, 'X' },
262
{ "xpath", no_argument, &opts.o_xpath, 1 },
263
{ "version", no_argument, &opts.o_version, 1 },
264
{ "warn", no_argument, NULL, 'W' },
265
{ "warn-xml", no_argument, &opts.o_warn_xml, 1 },
266
{ "wrap", required_argument, &opts.o_wrap, 1 },
267
{ NULL, 0, NULL, 0 }
268
};
269
270
int
271
main (int argc UNUSED, char **argv)
272
{
273
char *fmt = NULL, *cp, *np;
274
char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL;
275
char *opt_options = NULL;
276
char *opt_instance = NULL;
277
char *opt_name = NULL;
278
xo_state_t new_state = 0;
279
int opt_depth = 0;
280
int opt_not_first = 0;
281
int opt_top_wrap = 0;
282
int rc;
283
284
argc = xo_parse_args(argc, argv);
285
if (argc < 0)
286
return 1;
287
288
while ((rc = getopt_long(argc, argv, "Cc:HJl:O:o:ps:TXW",
289
long_opts, NULL)) != -1) {
290
switch (rc) {
291
case 'C':
292
xo_set_flags(NULL, XOF_CONTINUATION);
293
break;
294
295
case 'c':
296
opt_closer = optarg;
297
xo_set_flags(NULL, XOF_IGNORE_CLOSE);
298
break;
299
300
case 'H':
301
xo_set_style(NULL, XO_STYLE_HTML);
302
break;
303
304
case 'I':
305
opt_instance = optarg;
306
break;
307
308
case 'J':
309
xo_set_style(NULL, XO_STYLE_JSON);
310
break;
311
312
case 'l':
313
xo_set_leading_xpath(NULL, optarg);
314
break;
315
316
case 'O':
317
opt_options = optarg;
318
break;
319
320
case 'o':
321
opt_opener = optarg;
322
break;
323
324
case 'p':
325
xo_set_flags(NULL, XOF_PRETTY);
326
break;
327
328
case 's':
329
if (xo_set_style_name(NULL, optarg) < 0)
330
xo_errx(1, "unknown style: %s", optarg);
331
break;
332
333
case 'T':
334
xo_set_style(NULL, XO_STYLE_TEXT);
335
break;
336
337
case 'X':
338
xo_set_style(NULL, XO_STYLE_XML);
339
break;
340
341
case 'W':
342
opt_warn = 1;
343
xo_set_flags(NULL, XOF_WARN);
344
break;
345
346
case ':':
347
xo_errx(1, "missing argument");
348
break;
349
350
case 0:
351
if (opts.o_depth) {
352
opt_depth = atoi(optarg);
353
354
} else if (opts.o_help) {
355
print_help();
356
return 1;
357
358
} else if (opts.o_not_first) {
359
opt_not_first = 1;
360
361
} else if (opts.o_xpath) {
362
xo_set_flags(NULL, XOF_XPATH);
363
364
} else if (opts.o_version) {
365
print_version();
366
return 0;
367
368
} else if (opts.o_warn_xml) {
369
opt_warn = 1;
370
xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML);
371
372
} else if (opts.o_wrap) {
373
opt_wrapper = optarg;
374
375
} else if (opts.o_top_wrap) {
376
opt_top_wrap = 1;
377
378
} else if (opts.o_open_list) {
379
if (opt_name)
380
xo_errx(1, "only one open/close list/instance allowed: %s",
381
optarg);
382
383
opt_name = optarg;
384
new_state = XSS_OPEN_LIST;
385
386
} else if (opts.o_open_instance) {
387
if (opt_name)
388
xo_errx(1, "only one open/close list/instance allowed: %s",
389
optarg);
390
391
opt_name = optarg;
392
new_state = XSS_OPEN_INSTANCE;
393
394
} else if (opts.o_close_list) {
395
if (opt_name)
396
xo_errx(1, "only one open/close list/instance allowed: %s",
397
optarg);
398
399
opt_name = optarg;
400
new_state = XSS_CLOSE_LIST;
401
402
} else if (opts.o_close_instance) {
403
if (opt_name)
404
xo_errx(1, "only one open/close list/instance allowed: %s",
405
optarg);
406
407
opt_name = optarg;
408
new_state = XSS_CLOSE_INSTANCE;
409
410
} else {
411
print_help();
412
return 1;
413
}
414
415
bzero(&opts, sizeof(opts)); /* Reset all the options */
416
break;
417
418
default:
419
print_help();
420
return 1;
421
}
422
}
423
424
argc -= optind;
425
argv += optind;
426
427
if (opt_options) {
428
rc = xo_set_options(NULL, opt_options);
429
if (rc < 0)
430
xo_errx(1, "invalid options: %s", opt_options);
431
}
432
433
xo_set_formatter(NULL, formatter, checkpoint);
434
xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE);
435
436
/*
437
* If we have some explicit state change, handle it
438
*/
439
if (new_state) {
440
if (opt_depth > 0)
441
xo_set_depth(NULL, opt_depth);
442
443
if (opt_not_first)
444
xo_set_flags(NULL, XOF_NOT_FIRST);
445
446
xo_explicit_transition(NULL, new_state, opt_name, 0);
447
xo_finish();
448
exit(0);
449
}
450
451
fmt = *argv++;
452
if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) {
453
print_help();
454
return 1;
455
}
456
457
if (opt_top_wrap) {
458
/* If we have a closing path, we'll be one extra level deeper */
459
if (opt_closer && xo_get_style(NULL) == XO_STYLE_JSON)
460
opt_depth += 1;
461
else
462
xo_clear_flags(NULL, XOF_NO_TOP);
463
}
464
465
if (opt_closer) {
466
opt_depth += 1;
467
for (cp = opt_closer; cp && *cp; cp = np) {
468
np = strchr(cp, '/');
469
if (np == NULL)
470
break;
471
np += 1;
472
opt_depth += 1;
473
}
474
}
475
476
if (opt_depth > 0)
477
xo_set_depth(NULL, opt_depth);
478
479
if (opt_not_first)
480
xo_set_flags(NULL, XOF_NOT_FIRST);
481
482
/* If there's an opening hierarchy, open each element as a container */
483
if (opt_opener) {
484
for (cp = opt_opener; cp && *cp; cp = np) {
485
np = strchr(cp, '/');
486
if (np)
487
*np = '\0';
488
xo_open_container(cp);
489
if (np)
490
np += 1;
491
}
492
}
493
494
/* If there's an wrapper hierarchy, open each element as a container */
495
if (opt_wrapper) {
496
for (cp = opt_wrapper; cp && *cp; cp = np) {
497
np = strchr(cp, '/');
498
if (np)
499
*np = '\0';
500
xo_open_container(cp);
501
if (np)
502
*np++ = '/'; /* Put it back */
503
}
504
}
505
506
if (opt_instance)
507
xo_open_instance(opt_instance);
508
509
/* If there's a format string, call xo_emit to emit the contents */
510
if (fmt && *fmt) {
511
save_argv = argv;
512
prep_arg(fmt);
513
xo_emit(fmt); /* This call does the real formatting */
514
}
515
516
if (opt_instance)
517
xo_close_instance(opt_instance);
518
519
/* If there's an wrapper hierarchy, close each element's container */
520
while (opt_wrapper) {
521
np = strrchr(opt_wrapper, '/');
522
xo_close_container(np ? np + 1 : opt_wrapper);
523
if (np)
524
*np = '\0';
525
else
526
opt_wrapper = NULL;
527
}
528
529
/* Remember to undo the depth before calling xo_finish() */
530
opt_depth = (opt_closer && opt_top_wrap) ? -1 : 0;
531
532
/* If there's an closing hierarchy, close each element's container */
533
while (opt_closer) {
534
np = strrchr(opt_closer, '/');
535
xo_close_container(np ? np + 1 : opt_closer);
536
if (np)
537
*np = '\0';
538
else
539
opt_closer = NULL;
540
}
541
542
/* If there's a closer and a wrapper, we need to clean it up */
543
if (opt_depth) {
544
xo_set_depth(NULL, opt_depth);
545
xo_clear_flags(NULL, XOF_NO_TOP);
546
}
547
548
/* If we're wrapping the entire content, skip the closer */
549
if (opt_top_wrap && opt_opener)
550
xo_set_flags(NULL, XOF_NO_TOP);
551
552
xo_finish();
553
554
return 0;
555
}
556
557