1/*
2 * Copyright (c) 2014, 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
19#include <getopt.h>		/* Include after xo.h for testing */
20
21#ifndef UNUSED
22#define UNUSED __attribute__ ((__unused__))
23#endif /* UNUSED */
24
25static int opt_warn;		/* Enable warnings */
26
27static char **save_argv;
28static char **checkpoint_argv;
29
30static char *
31next_arg (void)
32{
33    char *cp = *save_argv;
34
35    if (cp == NULL)
36	xo_errx(1, "missing argument");
37
38    save_argv += 1;
39    return cp;
40}
41
42static void
43prep_arg (char *fmt)
44{
45    char *cp, *fp;
46
47    for (cp = fp = fmt; *cp; cp++, fp++) {
48	if (*cp != '\\') {
49	    if (cp != fp)
50		*fp = *cp;
51	    continue;
52	}
53
54	switch (*++cp) {
55	case 'n':
56	    *fp = '\n';
57	    break;
58
59	case 'r':
60	    *fp = '\r';
61	    break;
62
63	case 'b':
64	    *fp = '\b';
65	    break;
66
67	case 'e':
68	    *fp = '\e';
69	    break;
70
71	default:
72	    *fp = *cp;
73	}
74    }
75
76    *fp = '\0';
77}
78
79static void
80checkpoint (xo_handle_t *xop UNUSED, va_list vap UNUSED, int restore)
81{
82    if (restore)
83	save_argv = checkpoint_argv;
84    else
85	checkpoint_argv = save_argv;
86}
87
88/*
89 * Our custom formatter is responsible for combining format string pieces
90 * with our command line arguments to build strings.  This involves faking
91 * some printf-style logic.
92 */
93static xo_ssize_t
94formatter (xo_handle_t *xop, char *buf, xo_ssize_t bufsiz,
95	   const char *fmt, va_list vap UNUSED)
96{
97    int lflag UNUSED = 0;	/* Parse long flag, though currently ignored */
98    int hflag = 0, jflag = 0, tflag = 0,
99	zflag = 0, qflag = 0, star1 = 0, star2 = 0;
100    int rc = 0;
101    int w1 = 0, w2 = 0;
102    const char *cp;
103
104    for (cp = fmt + 1; *cp; cp++) {
105	if (*cp == 'l')
106	    lflag += 1;
107	else if (*cp == 'h')
108	    hflag += 1;
109	else if (*cp == 'j')
110	    jflag += 1;
111	else if (*cp == 't')
112	    tflag += 1;
113	else if (*cp == 'z')
114	    zflag += 1;
115	else if (*cp == 'q')
116	    qflag += 1;
117	else if (*cp == '*') {
118	    if (star1 == 0)
119		star1 = 1;
120	    else
121		star2 = 1;
122	} else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
123	    break;
124	else if (*cp == 'n' || *cp == 'v') {
125	    if (opt_warn)
126		xo_error_h(xop, "unsupported format: '%s'", fmt);
127	    return -1;
128	}
129    }
130
131    char fc = *cp;
132
133    /* Handle "%*.*s" */
134    if (star1)
135	w1 = strtol(next_arg(), NULL, 0);
136    if (star2 > 1)
137	w2 = strtol(next_arg(), NULL, 0);
138
139    if (fc == 'D' || fc == 'O' || fc == 'U')
140	lflag = 1;
141
142    if (strchr("diD", fc) != NULL) {
143	long long value = strtoll(next_arg(), NULL, 0);
144	if (star1 && star2)
145	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
146	else if (star1)
147	    rc = snprintf(buf, bufsiz, fmt, w1, value);
148	else
149	    rc = snprintf(buf, bufsiz, fmt, value);
150
151    } else if (strchr("ouxXOUp", fc) != NULL) {
152	unsigned long long value = strtoull(next_arg(), NULL, 0);
153	if (star1 && star2)
154	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
155	else if (star1)
156	    rc = snprintf(buf, bufsiz, fmt, w1, value);
157	else
158	    rc = snprintf(buf, bufsiz, fmt, value);
159
160    } else if (strchr("eEfFgGaA", fc) != NULL) {
161	double value = strtold(next_arg(), NULL);
162	if (star1 && star2)
163	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
164	else if (star1)
165	    rc = snprintf(buf, bufsiz, fmt, w1, value);
166	else
167	    rc = snprintf(buf, bufsiz, fmt, value);
168
169    } else if (fc == 'C' || fc == 'c' || fc == 'S' || fc == 's') {
170	char *value = next_arg();
171	if (star1 && star2)
172	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
173	else if (star1)
174	    rc = snprintf(buf, bufsiz, fmt, w1, value);
175	else
176	    rc = snprintf(buf, bufsiz, fmt, value);
177    }
178
179    return rc;
180}
181
182static void
183print_version (void)
184{
185    fprintf(stderr, "libxo version %s%s\n",
186	    xo_version, xo_version_extra);
187    fprintf(stderr, "xo version %s%s\n",
188	    LIBXO_VERSION, LIBXO_VERSION_EXTRA);
189}
190
191static void
192print_help (void)
193{
194    fprintf(stderr,
195"Usage: xo [options] format [fields]\n"
196"    --close <path>        Close tags for the given path\n"
197"    --depth <num>         Set the depth for pretty printing\n"
198"    --help                Display this help text\n"
199"    --html OR -H          Generate HTML output\n"
200"    --json OR -J          Generate JSON output\n"
201"    --leading-xpath <path> OR -l <path> "
202	    "Add a prefix to generated XPaths (HTML)\n"
203"    --open <path>         Open tags for the given path\n"
204"    --option <opts> -or -O <opts>  Give formatting options\n"
205"    --pretty OR -p        Make 'pretty' output (add indent, newlines)\n"
206"    --style <style> OR -s <style>  "
207	    "Generate given style (xml, json, text, html)\n"
208"    --text OR -T          Generate text output (the default style)\n"
209"    --version             Display version information\n"
210"    --warn OR -W          Display warnings in text on stderr\n"
211"    --warn-xml            Display warnings in xml on stdout\n"
212"    --wrap <path>         Wrap output in a set of containers\n"
213"    --xml OR -X           Generate XML output\n"
214"    --xpath               Add XPath data to HTML output\n");
215}
216
217static struct opts {
218    int o_depth;
219    int o_help;
220    int o_not_first;
221    int o_xpath;
222    int o_version;
223    int o_warn_xml;
224    int o_wrap;
225} opts;
226
227static struct option long_opts[] = {
228    { "close", required_argument, NULL, 'c' },
229    { "depth", required_argument, &opts.o_depth, 1 },
230    { "help", no_argument, &opts.o_help, 1 },
231    { "html", no_argument, NULL, 'H' },
232    { "json", no_argument, NULL, 'J' },
233    { "leading-xpath", required_argument, NULL, 'l' },
234    { "not-first", no_argument, &opts.o_not_first, 1 },
235    { "open", required_argument, NULL, 'o' },
236    { "option", required_argument, NULL, 'O' },
237    { "pretty", no_argument, NULL, 'p' },
238    { "style", required_argument, NULL, 's' },
239    { "text", no_argument, NULL, 'T' },
240    { "xml", no_argument, NULL, 'X' },
241    { "xpath", no_argument, &opts.o_xpath, 1 },
242    { "version", no_argument, &opts.o_version, 1 },
243    { "warn", no_argument, NULL, 'W' },
244    { "warn-xml", no_argument, &opts.o_warn_xml, 1 },
245    { "wrap", required_argument, &opts.o_wrap, 1 },
246    { NULL, 0, NULL, 0 }
247};
248
249int
250main (int argc UNUSED, char **argv)
251{
252    char *fmt = NULL, *cp, *np;
253    char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL;
254    char *opt_options = NULL;
255    int opt_depth = 0;
256    int opt_not_first = 0;
257    int rc;
258
259    argc = xo_parse_args(argc, argv);
260    if (argc < 0)
261	return 1;
262
263    while ((rc = getopt_long(argc, argv, "c:HJl:O:o:ps:TXW",
264				long_opts, NULL)) != -1) {
265	switch (rc) {
266	case 'c':
267	    opt_closer = optarg;
268	    xo_set_flags(NULL, XOF_IGNORE_CLOSE);
269	    break;
270
271	case 'H':
272	    xo_set_style(NULL, XO_STYLE_HTML);
273	    break;
274
275	case 'J':
276	    xo_set_style(NULL, XO_STYLE_JSON);
277	    break;
278
279	case 'l':
280	    xo_set_leading_xpath(NULL, optarg);
281	    break;
282
283	case 'O':
284	    opt_options = optarg;
285	    break;
286
287	case 'o':
288	    opt_opener = optarg;
289	    break;
290
291	case 'p':
292	    xo_set_flags(NULL, XOF_PRETTY);
293	    break;
294
295	case 's':
296	    if (xo_set_style_name(NULL, optarg) < 0)
297		xo_errx(1, "unknown style: %s", optarg);
298	    break;
299
300	case 'T':
301	    xo_set_style(NULL, XO_STYLE_TEXT);
302	    break;
303
304	case 'X':
305	    xo_set_style(NULL, XO_STYLE_XML);
306	    break;
307
308	case 'W':
309	    opt_warn = 1;
310	    xo_set_flags(NULL, XOF_WARN);
311	    break;
312
313	case ':':
314	    xo_errx(1, "missing argument");
315	    break;
316
317	case 0:
318	    if (opts.o_depth) {
319		opt_depth = atoi(optarg);
320
321	    } else if (opts.o_help) {
322		print_help();
323		return 1;
324
325	    } else if (opts.o_not_first) {
326		opt_not_first = 1;
327
328	    } else if (opts.o_xpath) {
329		xo_set_flags(NULL, XOF_XPATH);
330
331	    } else if (opts.o_version) {
332		print_version();
333		return 0;
334
335	    } else if (opts.o_warn_xml) {
336		opt_warn = 1;
337		xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML);
338
339	    } else if (opts.o_wrap) {
340		opt_wrapper = optarg;
341
342	    } else {
343		print_help();
344		return 1;
345	    }
346
347	    bzero(&opts, sizeof(opts)); /* Reset all the options */
348	    break;
349
350	default:
351	    print_help();
352	    return 1;
353	}
354    }
355
356    argc -= optind;
357    argv += optind;
358
359    if (opt_options) {
360	rc = xo_set_options(NULL, opt_options);
361	if (rc < 0)
362	    xo_errx(1, "invalid options: %s", opt_options);
363    }
364
365    xo_set_formatter(NULL, formatter, checkpoint);
366    xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE);
367
368    fmt = *argv++;
369    if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) {
370	print_help();
371	return 1;
372    }
373
374    if (opt_not_first)
375	xo_set_flags(NULL, XOF_NOT_FIRST);
376
377    if (opt_closer) {
378	opt_depth += 1;
379	for (cp = opt_closer; cp && *cp; cp = np) {
380	    np = strchr(cp, '/');
381	    if (np == NULL)
382		break;
383	    np += 1;
384	    opt_depth += 1;
385	}
386    }
387
388    if (opt_depth > 0)
389	xo_set_depth(NULL, opt_depth);
390
391    if (opt_opener) {
392	for (cp = opt_opener; cp && *cp; cp = np) {
393	    np = strchr(cp, '/');
394	    if (np)
395		*np = '\0';
396	    xo_open_container(cp);
397	    if (np)
398		*np++ = '/';
399	}
400    }
401
402    if (opt_wrapper) {
403	for (cp = opt_wrapper; cp && *cp; cp = np) {
404	    np = strchr(cp, '/');
405	    if (np)
406		*np = '\0';
407	    xo_open_container(cp);
408	    if (np)
409		*np++ = '/';
410	}
411    }
412
413    if (fmt && *fmt) {
414	save_argv = argv;
415	prep_arg(fmt);
416	xo_emit(fmt);
417    }
418
419    while (opt_wrapper) {
420	np = strrchr(opt_wrapper, '/');
421	xo_close_container(np ? np + 1 : opt_wrapper);
422	if (np)
423	    *np = '\0';
424	else
425	    opt_wrapper = NULL;
426    }
427
428    while (opt_closer) {
429	np = strrchr(opt_closer, '/');
430	xo_close_container(np ? np + 1 : opt_closer);
431	if (np)
432	    *np = '\0';
433	else
434	    opt_closer = NULL;
435    }
436
437    xo_finish();
438
439    return 0;
440}
441