1273562Smarcel/*
2273562Smarcel * Copyright (c) 2014, Juniper Networks, Inc.
3273562Smarcel * All rights reserved.
4273562Smarcel * This SOFTWARE is licensed under the LICENSE provided in the
5273562Smarcel * ../Copyright file. By downloading, installing, copying, or otherwise
6273562Smarcel * using the SOFTWARE, you agree to be bound by the terms of that
7273562Smarcel * LICENSE.
8273562Smarcel * Phil Shafer, July 2014
9273562Smarcel */
10273562Smarcel
11273562Smarcel#include <stdio.h>
12273562Smarcel#include <stdlib.h>
13273562Smarcel#include <stdarg.h>
14273562Smarcel#include <string.h>
15273562Smarcel
16287111Smarcel#include "xo_config.h"
17273562Smarcel#include "xo.h"
18273562Smarcel
19273562Smarcel#include <getopt.h>		/* Include after xo.h for testing */
20273562Smarcel
21273562Smarcel#ifndef UNUSED
22273562Smarcel#define UNUSED __attribute__ ((__unused__))
23273562Smarcel#endif /* UNUSED */
24273562Smarcel
25273562Smarcelstatic int opt_warn;		/* Enable warnings */
26273562Smarcel
27273562Smarcelstatic char **save_argv;
28273562Smarcelstatic char **checkpoint_argv;
29273562Smarcel
30273562Smarcelstatic char *
31273562Smarcelnext_arg (void)
32273562Smarcel{
33273562Smarcel    char *cp = *save_argv;
34273562Smarcel
35273562Smarcel    if (cp == NULL)
36273562Smarcel	xo_errx(1, "missing argument");
37273562Smarcel
38273562Smarcel    save_argv += 1;
39273562Smarcel    return cp;
40273562Smarcel}
41273562Smarcel
42273562Smarcelstatic void
43273562Smarcelprep_arg (char *fmt)
44273562Smarcel{
45273562Smarcel    char *cp, *fp;
46273562Smarcel
47273562Smarcel    for (cp = fp = fmt; *cp; cp++, fp++) {
48273562Smarcel	if (*cp != '\\') {
49273562Smarcel	    if (cp != fp)
50273562Smarcel		*fp = *cp;
51273562Smarcel	    continue;
52273562Smarcel	}
53273562Smarcel
54273562Smarcel	switch (*++cp) {
55273562Smarcel	case 'n':
56273562Smarcel	    *fp = '\n';
57273562Smarcel	    break;
58273562Smarcel
59273562Smarcel	case 'r':
60273562Smarcel	    *fp = '\r';
61273562Smarcel	    break;
62273562Smarcel
63273562Smarcel	case 'b':
64273562Smarcel	    *fp = '\b';
65273562Smarcel	    break;
66273562Smarcel
67273562Smarcel	case 'e':
68273562Smarcel	    *fp = '\e';
69273562Smarcel	    break;
70273562Smarcel
71273562Smarcel	default:
72273562Smarcel	    *fp = *cp;
73273562Smarcel	}
74273562Smarcel    }
75273562Smarcel
76273562Smarcel    *fp = '\0';
77273562Smarcel}
78273562Smarcel
79273562Smarcelstatic void
80273562Smarcelcheckpoint (xo_handle_t *xop UNUSED, va_list vap UNUSED, int restore)
81273562Smarcel{
82273562Smarcel    if (restore)
83273562Smarcel	save_argv = checkpoint_argv;
84273562Smarcel    else
85273562Smarcel	checkpoint_argv = save_argv;
86273562Smarcel}
87273562Smarcel
88273562Smarcel/*
89273562Smarcel * Our custom formatter is responsible for combining format string pieces
90273562Smarcel * with our command line arguments to build strings.  This involves faking
91273562Smarcel * some printf-style logic.
92273562Smarcel */
93322172Sphilstatic xo_ssize_t
94322172Sphilformatter (xo_handle_t *xop, char *buf, xo_ssize_t bufsiz,
95273562Smarcel	   const char *fmt, va_list vap UNUSED)
96273562Smarcel{
97287111Smarcel    int lflag UNUSED = 0;	/* Parse long flag, though currently ignored */
98287111Smarcel    int hflag = 0, jflag = 0, tflag = 0,
99273562Smarcel	zflag = 0, qflag = 0, star1 = 0, star2 = 0;
100273562Smarcel    int rc = 0;
101273562Smarcel    int w1 = 0, w2 = 0;
102273562Smarcel    const char *cp;
103273562Smarcel
104273562Smarcel    for (cp = fmt + 1; *cp; cp++) {
105273562Smarcel	if (*cp == 'l')
106273562Smarcel	    lflag += 1;
107273562Smarcel	else if (*cp == 'h')
108273562Smarcel	    hflag += 1;
109273562Smarcel	else if (*cp == 'j')
110273562Smarcel	    jflag += 1;
111273562Smarcel	else if (*cp == 't')
112273562Smarcel	    tflag += 1;
113273562Smarcel	else if (*cp == 'z')
114273562Smarcel	    zflag += 1;
115273562Smarcel	else if (*cp == 'q')
116273562Smarcel	    qflag += 1;
117273562Smarcel	else if (*cp == '*') {
118273562Smarcel	    if (star1 == 0)
119273562Smarcel		star1 = 1;
120273562Smarcel	    else
121273562Smarcel		star2 = 1;
122273562Smarcel	} else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
123273562Smarcel	    break;
124273562Smarcel	else if (*cp == 'n' || *cp == 'v') {
125273562Smarcel	    if (opt_warn)
126273562Smarcel		xo_error_h(xop, "unsupported format: '%s'", fmt);
127273562Smarcel	    return -1;
128273562Smarcel	}
129273562Smarcel    }
130273562Smarcel
131273562Smarcel    char fc = *cp;
132273562Smarcel
133273562Smarcel    /* Handle "%*.*s" */
134273562Smarcel    if (star1)
135273562Smarcel	w1 = strtol(next_arg(), NULL, 0);
136273562Smarcel    if (star2 > 1)
137273562Smarcel	w2 = strtol(next_arg(), NULL, 0);
138273562Smarcel
139273562Smarcel    if (fc == 'D' || fc == 'O' || fc == 'U')
140273562Smarcel	lflag = 1;
141273562Smarcel
142273562Smarcel    if (strchr("diD", fc) != NULL) {
143273562Smarcel	long long value = strtoll(next_arg(), NULL, 0);
144273562Smarcel	if (star1 && star2)
145273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
146273562Smarcel	else if (star1)
147273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, value);
148273562Smarcel	else
149273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, value);
150273562Smarcel
151273562Smarcel    } else if (strchr("ouxXOUp", fc) != NULL) {
152273562Smarcel	unsigned long long value = strtoull(next_arg(), NULL, 0);
153273562Smarcel	if (star1 && star2)
154273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
155273562Smarcel	else if (star1)
156273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, value);
157273562Smarcel	else
158273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, value);
159273562Smarcel
160273562Smarcel    } else if (strchr("eEfFgGaA", fc) != NULL) {
161273562Smarcel	double value = strtold(next_arg(), NULL);
162273562Smarcel	if (star1 && star2)
163273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
164273562Smarcel	else if (star1)
165273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, value);
166273562Smarcel	else
167273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, value);
168273562Smarcel
169273562Smarcel    } else if (fc == 'C' || fc == 'c' || fc == 'S' || fc == 's') {
170273562Smarcel	char *value = next_arg();
171273562Smarcel	if (star1 && star2)
172273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
173273562Smarcel	else if (star1)
174273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, w1, value);
175273562Smarcel	else
176273562Smarcel	    rc = snprintf(buf, bufsiz, fmt, value);
177273562Smarcel    }
178273562Smarcel
179273562Smarcel    return rc;
180273562Smarcel}
181273562Smarcel
182273562Smarcelstatic void
183273562Smarcelprint_version (void)
184273562Smarcel{
185273562Smarcel    fprintf(stderr, "libxo version %s%s\n",
186273562Smarcel	    xo_version, xo_version_extra);
187273562Smarcel    fprintf(stderr, "xo version %s%s\n",
188273562Smarcel	    LIBXO_VERSION, LIBXO_VERSION_EXTRA);
189273562Smarcel}
190273562Smarcel
191273562Smarcelstatic void
192273562Smarcelprint_help (void)
193273562Smarcel{
194273562Smarcel    fprintf(stderr,
195273562Smarcel"Usage: xo [options] format [fields]\n"
196273562Smarcel"    --close <path>        Close tags for the given path\n"
197273562Smarcel"    --depth <num>         Set the depth for pretty printing\n"
198273562Smarcel"    --help                Display this help text\n"
199273562Smarcel"    --html OR -H          Generate HTML output\n"
200273562Smarcel"    --json OR -J          Generate JSON output\n"
201273562Smarcel"    --leading-xpath <path> OR -l <path> "
202273562Smarcel	    "Add a prefix to generated XPaths (HTML)\n"
203273562Smarcel"    --open <path>         Open tags for the given path\n"
204287111Smarcel"    --option <opts> -or -O <opts>  Give formatting options\n"
205273562Smarcel"    --pretty OR -p        Make 'pretty' output (add indent, newlines)\n"
206273562Smarcel"    --style <style> OR -s <style>  "
207273562Smarcel	    "Generate given style (xml, json, text, html)\n"
208273562Smarcel"    --text OR -T          Generate text output (the default style)\n"
209273562Smarcel"    --version             Display version information\n"
210273562Smarcel"    --warn OR -W          Display warnings in text on stderr\n"
211273562Smarcel"    --warn-xml            Display warnings in xml on stdout\n"
212273562Smarcel"    --wrap <path>         Wrap output in a set of containers\n"
213273562Smarcel"    --xml OR -X           Generate XML output\n"
214273562Smarcel"    --xpath               Add XPath data to HTML output\n");
215273562Smarcel}
216273562Smarcel
217273562Smarcelstatic struct opts {
218273562Smarcel    int o_depth;
219273562Smarcel    int o_help;
220273562Smarcel    int o_not_first;
221273562Smarcel    int o_xpath;
222273562Smarcel    int o_version;
223273562Smarcel    int o_warn_xml;
224273562Smarcel    int o_wrap;
225273562Smarcel} opts;
226273562Smarcel
227273562Smarcelstatic struct option long_opts[] = {
228273562Smarcel    { "close", required_argument, NULL, 'c' },
229273562Smarcel    { "depth", required_argument, &opts.o_depth, 1 },
230273562Smarcel    { "help", no_argument, &opts.o_help, 1 },
231273562Smarcel    { "html", no_argument, NULL, 'H' },
232273562Smarcel    { "json", no_argument, NULL, 'J' },
233273562Smarcel    { "leading-xpath", required_argument, NULL, 'l' },
234273562Smarcel    { "not-first", no_argument, &opts.o_not_first, 1 },
235273562Smarcel    { "open", required_argument, NULL, 'o' },
236273562Smarcel    { "option", required_argument, NULL, 'O' },
237273562Smarcel    { "pretty", no_argument, NULL, 'p' },
238273562Smarcel    { "style", required_argument, NULL, 's' },
239273562Smarcel    { "text", no_argument, NULL, 'T' },
240273562Smarcel    { "xml", no_argument, NULL, 'X' },
241273562Smarcel    { "xpath", no_argument, &opts.o_xpath, 1 },
242273562Smarcel    { "version", no_argument, &opts.o_version, 1 },
243273562Smarcel    { "warn", no_argument, NULL, 'W' },
244273562Smarcel    { "warn-xml", no_argument, &opts.o_warn_xml, 1 },
245273562Smarcel    { "wrap", required_argument, &opts.o_wrap, 1 },
246273562Smarcel    { NULL, 0, NULL, 0 }
247273562Smarcel};
248273562Smarcel
249273562Smarcelint
250273562Smarcelmain (int argc UNUSED, char **argv)
251273562Smarcel{
252273562Smarcel    char *fmt = NULL, *cp, *np;
253273562Smarcel    char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL;
254273562Smarcel    char *opt_options = NULL;
255273562Smarcel    int opt_depth = 0;
256273562Smarcel    int opt_not_first = 0;
257273562Smarcel    int rc;
258273562Smarcel
259273562Smarcel    argc = xo_parse_args(argc, argv);
260273562Smarcel    if (argc < 0)
261273562Smarcel	return 1;
262273562Smarcel
263287111Smarcel    while ((rc = getopt_long(argc, argv, "c:HJl:O:o:ps:TXW",
264273562Smarcel				long_opts, NULL)) != -1) {
265273562Smarcel	switch (rc) {
266273562Smarcel	case 'c':
267273562Smarcel	    opt_closer = optarg;
268273562Smarcel	    xo_set_flags(NULL, XOF_IGNORE_CLOSE);
269273562Smarcel	    break;
270273562Smarcel
271273562Smarcel	case 'H':
272273562Smarcel	    xo_set_style(NULL, XO_STYLE_HTML);
273273562Smarcel	    break;
274273562Smarcel
275273562Smarcel	case 'J':
276273562Smarcel	    xo_set_style(NULL, XO_STYLE_JSON);
277273562Smarcel	    break;
278273562Smarcel
279273562Smarcel	case 'l':
280273562Smarcel	    xo_set_leading_xpath(NULL, optarg);
281273562Smarcel	    break;
282273562Smarcel
283273562Smarcel	case 'O':
284273562Smarcel	    opt_options = optarg;
285273562Smarcel	    break;
286273562Smarcel
287273562Smarcel	case 'o':
288273562Smarcel	    opt_opener = optarg;
289273562Smarcel	    break;
290273562Smarcel
291273562Smarcel	case 'p':
292273562Smarcel	    xo_set_flags(NULL, XOF_PRETTY);
293273562Smarcel	    break;
294273562Smarcel
295273562Smarcel	case 's':
296273562Smarcel	    if (xo_set_style_name(NULL, optarg) < 0)
297273562Smarcel		xo_errx(1, "unknown style: %s", optarg);
298273562Smarcel	    break;
299273562Smarcel
300273562Smarcel	case 'T':
301273562Smarcel	    xo_set_style(NULL, XO_STYLE_TEXT);
302273562Smarcel	    break;
303273562Smarcel
304273562Smarcel	case 'X':
305273562Smarcel	    xo_set_style(NULL, XO_STYLE_XML);
306273562Smarcel	    break;
307273562Smarcel
308273562Smarcel	case 'W':
309273562Smarcel	    opt_warn = 1;
310273562Smarcel	    xo_set_flags(NULL, XOF_WARN);
311273562Smarcel	    break;
312273562Smarcel
313273562Smarcel	case ':':
314273562Smarcel	    xo_errx(1, "missing argument");
315273562Smarcel	    break;
316273562Smarcel
317273562Smarcel	case 0:
318273562Smarcel	    if (opts.o_depth) {
319273562Smarcel		opt_depth = atoi(optarg);
320273562Smarcel
321273562Smarcel	    } else if (opts.o_help) {
322273562Smarcel		print_help();
323273562Smarcel		return 1;
324273562Smarcel
325273562Smarcel	    } else if (opts.o_not_first) {
326273562Smarcel		opt_not_first = 1;
327273562Smarcel
328273562Smarcel	    } else if (opts.o_xpath) {
329273562Smarcel		xo_set_flags(NULL, XOF_XPATH);
330273562Smarcel
331273562Smarcel	    } else if (opts.o_version) {
332273562Smarcel		print_version();
333273562Smarcel		return 0;
334273562Smarcel
335273562Smarcel	    } else if (opts.o_warn_xml) {
336273562Smarcel		opt_warn = 1;
337273562Smarcel		xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML);
338273562Smarcel
339273562Smarcel	    } else if (opts.o_wrap) {
340273562Smarcel		opt_wrapper = optarg;
341273562Smarcel
342273562Smarcel	    } else {
343273562Smarcel		print_help();
344273562Smarcel		return 1;
345273562Smarcel	    }
346273562Smarcel
347273562Smarcel	    bzero(&opts, sizeof(opts)); /* Reset all the options */
348273562Smarcel	    break;
349273562Smarcel
350273562Smarcel	default:
351273562Smarcel	    print_help();
352273562Smarcel	    return 1;
353273562Smarcel	}
354273562Smarcel    }
355273562Smarcel
356273562Smarcel    argc -= optind;
357273562Smarcel    argv += optind;
358273562Smarcel
359273562Smarcel    if (opt_options) {
360273562Smarcel	rc = xo_set_options(NULL, opt_options);
361273562Smarcel	if (rc < 0)
362273562Smarcel	    xo_errx(1, "invalid options: %s", opt_options);
363273562Smarcel    }
364273562Smarcel
365273562Smarcel    xo_set_formatter(NULL, formatter, checkpoint);
366277353Smarcel    xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE);
367273562Smarcel
368273562Smarcel    fmt = *argv++;
369273562Smarcel    if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) {
370273562Smarcel	print_help();
371273562Smarcel	return 1;
372273562Smarcel    }
373273562Smarcel
374273562Smarcel    if (opt_not_first)
375273562Smarcel	xo_set_flags(NULL, XOF_NOT_FIRST);
376273562Smarcel
377273562Smarcel    if (opt_closer) {
378273562Smarcel	opt_depth += 1;
379273562Smarcel	for (cp = opt_closer; cp && *cp; cp = np) {
380273562Smarcel	    np = strchr(cp, '/');
381273562Smarcel	    if (np == NULL)
382273562Smarcel		break;
383273562Smarcel	    np += 1;
384273562Smarcel	    opt_depth += 1;
385273562Smarcel	}
386273562Smarcel    }
387273562Smarcel
388273562Smarcel    if (opt_depth > 0)
389273562Smarcel	xo_set_depth(NULL, opt_depth);
390273562Smarcel
391273562Smarcel    if (opt_opener) {
392273562Smarcel	for (cp = opt_opener; cp && *cp; cp = np) {
393273562Smarcel	    np = strchr(cp, '/');
394273562Smarcel	    if (np)
395273562Smarcel		*np = '\0';
396273562Smarcel	    xo_open_container(cp);
397273562Smarcel	    if (np)
398273562Smarcel		*np++ = '/';
399273562Smarcel	}
400273562Smarcel    }
401273562Smarcel
402273562Smarcel    if (opt_wrapper) {
403273562Smarcel	for (cp = opt_wrapper; cp && *cp; cp = np) {
404273562Smarcel	    np = strchr(cp, '/');
405273562Smarcel	    if (np)
406273562Smarcel		*np = '\0';
407273562Smarcel	    xo_open_container(cp);
408273562Smarcel	    if (np)
409273562Smarcel		*np++ = '/';
410273562Smarcel	}
411273562Smarcel    }
412273562Smarcel
413273562Smarcel    if (fmt && *fmt) {
414273562Smarcel	save_argv = argv;
415273562Smarcel	prep_arg(fmt);
416273562Smarcel	xo_emit(fmt);
417273562Smarcel    }
418273562Smarcel
419273562Smarcel    while (opt_wrapper) {
420273562Smarcel	np = strrchr(opt_wrapper, '/');
421273562Smarcel	xo_close_container(np ? np + 1 : opt_wrapper);
422273562Smarcel	if (np)
423273562Smarcel	    *np = '\0';
424273562Smarcel	else
425273562Smarcel	    opt_wrapper = NULL;
426273562Smarcel    }
427273562Smarcel
428273562Smarcel    while (opt_closer) {
429273562Smarcel	np = strrchr(opt_closer, '/');
430273562Smarcel	xo_close_container(np ? np + 1 : opt_closer);
431273562Smarcel	if (np)
432273562Smarcel	    *np = '\0';
433273562Smarcel	else
434273562Smarcel	    opt_closer = NULL;
435273562Smarcel    }
436273562Smarcel
437273562Smarcel    xo_finish();
438273562Smarcel
439273562Smarcel    return 0;
440273562Smarcel}
441