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 * 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 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 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 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 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 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 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