2 * Copyright (c) 2014-2019, Juniper Networks, Inc.
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
8 * Phil Shafer, July 2014
16 #include "xo_config.h"
18 #include "xo_explicit.h"
20 #include <getopt.h> /* Include after xo.h for testing */
23 #define UNUSED __attribute__ ((__unused__))
26 static int opt_warn; /* Enable warnings */
28 static char **save_argv;
29 static char **checkpoint_argv;
34 char *cp = *save_argv;
37 xo_errx(1, "missing argument");
48 for (cp = fp = fmt; *cp; cp++, fp++) {
81 checkpoint (xo_handle_t *xop UNUSED, va_list vap UNUSED, int restore)
84 save_argv = checkpoint_argv;
86 checkpoint_argv = save_argv;
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.
95 formatter (xo_handle_t *xop, char *buf, xo_ssize_t bufsiz,
96 const char *fmt, va_list vap UNUSED)
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;
105 for (cp = fmt + 1; *cp; cp++) {
118 else if (*cp == '*') {
123 } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL)
125 else if (*cp == 'n' || *cp == 'v') {
127 xo_error_h(xop, "unsupported format: '%s'", fmt);
136 w1 = strtol(next_arg(), NULL, 0);
138 w2 = strtol(next_arg(), NULL, 0);
140 if (fc == 'D' || fc == 'O' || fc == 'U')
143 if (strchr("diD", fc) != NULL) {
144 long long value = strtoll(next_arg(), NULL, 0);
146 rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
148 rc = snprintf(buf, bufsiz, fmt, w1, value);
150 rc = snprintf(buf, bufsiz, fmt, value);
152 } else if (strchr("ouxXOUp", fc) != NULL) {
153 unsigned long long value = strtoull(next_arg(), NULL, 0);
155 rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
157 rc = snprintf(buf, bufsiz, fmt, w1, value);
159 rc = snprintf(buf, bufsiz, fmt, value);
161 } else if (strchr("eEfFgGaA", fc) != NULL) {
162 double value = strtold(next_arg(), NULL);
164 rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
166 rc = snprintf(buf, bufsiz, fmt, w1, value);
168 rc = snprintf(buf, bufsiz, fmt, value);
170 } else if (fc == 'C' || fc == 'c' || fc == 'S' || fc == 's') {
171 char *value = next_arg();
173 rc = snprintf(buf, bufsiz, fmt, w1, w2, value);
175 rc = snprintf(buf, bufsiz, fmt, w1, value);
177 rc = snprintf(buf, bufsiz, fmt, value);
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);
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 " --json OR -J Generate JSON output\n"
205 " --leading-xpath <path> OR -l <path> "
206 "Add a prefix to generated XPaths (HTML)\n"
207 " --not-first Indicate this object is not the first (JSON)\n"
208 " --open <path> Open tags for the given path\n"
209 " --open-instance <name> Open an instance given by name\n"
210 " --open-list <name> Open a list given by name\n"
211 " --option <opts> -or -O <opts> Give formatting options\n"
212 " --pretty OR -p Make 'pretty' output (add indent, newlines)\n"
213 " --style <style> OR -s <style> "
214 "Generate given style (xml, json, text, html)\n"
215 " --text OR -T Generate text output (the default style)\n"
216 " --top-wrap Generate a top-level object wrapper (JSON)\n"
217 " --version Display version information\n"
218 " --warn OR -W Display warnings in text on stderr\n"
219 " --warn-xml Display warnings in xml on stdout\n"
220 " --wrap <path> Wrap output in a set of containers\n"
221 " --xml OR -X Generate XML output\n"
222 " --xpath Add XPath data to HTML output\n");
226 int o_close_instance;
240 static struct option long_opts[] = {
241 { "close", required_argument, NULL, 'c' },
242 { "close-instance", required_argument, &opts.o_close_instance, 1 },
243 { "close-list", required_argument, &opts.o_close_list, 1 },
244 { "continuation", no_argument, NULL, 'C' },
245 { "depth", required_argument, &opts.o_depth, 1 },
246 { "help", no_argument, &opts.o_help, 1 },
247 { "html", no_argument, NULL, 'H' },
248 { "json", no_argument, NULL, 'J' },
249 { "leading-xpath", required_argument, NULL, 'l' },
250 { "not-first", no_argument, &opts.o_not_first, 1 },
251 { "open", required_argument, NULL, 'o' },
252 { "open-instance", required_argument, &opts.o_open_instance, 1 },
253 { "open-list", required_argument, &opts.o_open_list, 1 },
254 { "option", required_argument, NULL, 'O' },
255 { "pretty", no_argument, NULL, 'p' },
256 { "style", required_argument, NULL, 's' },
257 { "text", no_argument, NULL, 'T' },
258 { "top-wrap", no_argument, &opts.o_top_wrap, 1 },
259 { "xml", no_argument, NULL, 'X' },
260 { "xpath", no_argument, &opts.o_xpath, 1 },
261 { "version", no_argument, &opts.o_version, 1 },
262 { "warn", no_argument, NULL, 'W' },
263 { "warn-xml", no_argument, &opts.o_warn_xml, 1 },
264 { "wrap", required_argument, &opts.o_wrap, 1 },
269 main (int argc UNUSED, char **argv)
271 char *fmt = NULL, *cp, *np;
272 char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL;
273 char *opt_options = NULL;
274 char *opt_name = NULL;
275 xo_state_t new_state = 0;
277 int opt_not_first = 0;
278 int opt_top_wrap = 0;
281 argc = xo_parse_args(argc, argv);
285 while ((rc = getopt_long(argc, argv, "Cc:HJl:O:o:ps:TXW",
286 long_opts, NULL)) != -1) {
289 xo_set_flags(NULL, XOF_CONTINUATION);
294 xo_set_flags(NULL, XOF_IGNORE_CLOSE);
298 xo_set_style(NULL, XO_STYLE_HTML);
302 xo_set_style(NULL, XO_STYLE_JSON);
306 xo_set_leading_xpath(NULL, optarg);
310 opt_options = optarg;
318 xo_set_flags(NULL, XOF_PRETTY);
322 if (xo_set_style_name(NULL, optarg) < 0)
323 xo_errx(1, "unknown style: %s", optarg);
327 xo_set_style(NULL, XO_STYLE_TEXT);
331 xo_set_style(NULL, XO_STYLE_XML);
336 xo_set_flags(NULL, XOF_WARN);
340 xo_errx(1, "missing argument");
345 opt_depth = atoi(optarg);
347 } else if (opts.o_help) {
351 } else if (opts.o_not_first) {
354 } else if (opts.o_xpath) {
355 xo_set_flags(NULL, XOF_XPATH);
357 } else if (opts.o_version) {
361 } else if (opts.o_warn_xml) {
363 xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML);
365 } else if (opts.o_wrap) {
366 opt_wrapper = optarg;
368 } else if (opts.o_top_wrap) {
371 } else if (opts.o_open_list) {
373 xo_errx(1, "only one open/close list/instance allowed: %s",
377 new_state = XSS_OPEN_LIST;
379 } else if (opts.o_open_instance) {
381 xo_errx(1, "only one open/close list/instance allowed: %s",
385 new_state = XSS_OPEN_INSTANCE;
387 } else if (opts.o_close_list) {
389 xo_errx(1, "only one open/close list/instance allowed: %s",
393 new_state = XSS_CLOSE_LIST;
395 } else if (opts.o_close_instance) {
397 xo_errx(1, "only one open/close list/instance allowed: %s",
401 new_state = XSS_CLOSE_INSTANCE;
408 bzero(&opts, sizeof(opts)); /* Reset all the options */
421 rc = xo_set_options(NULL, opt_options);
423 xo_errx(1, "invalid options: %s", opt_options);
426 xo_set_formatter(NULL, formatter, checkpoint);
427 xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE);
430 * If we have some explicit state change, handle it
434 xo_set_depth(NULL, opt_depth);
437 xo_set_flags(NULL, XOF_NOT_FIRST);
439 xo_explicit_transition(NULL, new_state, opt_name, 0);
445 if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) {
451 /* If we have a closing path, we'll be one extra level deeper */
452 if (opt_closer && xo_get_style(NULL) == XO_STYLE_JSON)
455 xo_clear_flags(NULL, XOF_NO_TOP);
460 for (cp = opt_closer; cp && *cp; cp = np) {
461 np = strchr(cp, '/');
470 xo_set_depth(NULL, opt_depth);
473 xo_set_flags(NULL, XOF_NOT_FIRST);
475 /* If there's an opening hierarchy, open each element as a container */
477 for (cp = opt_opener; cp && *cp; cp = np) {
478 np = strchr(cp, '/');
481 xo_open_container(cp);
487 /* If there's an wrapper hierarchy, open each element as a container */
489 for (cp = opt_wrapper; cp && *cp; cp = np) {
490 np = strchr(cp, '/');
493 xo_open_container(cp);
495 *np++ = '/'; /* Put it back */
499 /* If there's a format string, call xo_emit to emit the contents */
503 xo_emit(fmt); /* This call does the real formatting */
506 /* If there's an wrapper hierarchy, close each element's container */
507 while (opt_wrapper) {
508 np = strrchr(opt_wrapper, '/');
509 xo_close_container(np ? np + 1 : opt_wrapper);
516 /* Remember to undo the depth before calling xo_finish() */
517 opt_depth = (opt_closer && opt_top_wrap) ? -1 : 0;
519 /* If there's an closing hierarchy, close each element's container */
521 np = strrchr(opt_closer, '/');
522 xo_close_container(np ? np + 1 : opt_closer);
529 /* If there's a closer and a wrapper, we need to clean it up */
531 xo_set_depth(NULL, opt_depth);
532 xo_clear_flags(NULL, XOF_NO_TOP);
535 /* If we're wrapping the entire content, skip the closer */
536 if (opt_top_wrap && opt_opener)
537 xo_set_flags(NULL, XOF_NO_TOP);