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 *
next_arg(void)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
prep_arg(char * fmt)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
checkpoint(xo_handle_t * xop UNUSED,va_list vap UNUSED,int restore)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
formatter(xo_handle_t * xop,char * buf,xo_ssize_t bufsiz,const char * fmt,va_list vap UNUSED)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
print_version(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
print_help(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
main(int argc UNUSED,char ** argv)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