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
26static int opt_warn;		/* Enable warnings */
27
28static char **save_argv;
29static char **checkpoint_argv;
30
31static char *
32next_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
43static void
44prep_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
80static void
81checkpoint (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 */
94static xo_ssize_t
95formatter (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
183static void
184print_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
192static void
193print_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
226static 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
241static 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
270int
271main (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