1 /* 2 * Copyright (c) 2014-2018, 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 " --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"); 223 } 224 225 static struct opts { 226 int o_close_instance; 227 int o_close_list; 228 int o_depth; 229 int o_help; 230 int o_not_first; 231 int o_open_instance; 232 int o_open_list; 233 int o_top_wrap; 234 int o_version; 235 int o_warn_xml; 236 int o_wrap; 237 int o_xpath; 238 } opts; 239 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 }, 265 { NULL, 0, NULL, 0 } 266 }; 267 268 int 269 main (int argc UNUSED, char **argv) 270 { 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; 276 int opt_depth = 0; 277 int opt_not_first = 0; 278 int opt_top_wrap = 0; 279 int rc; 280 281 argc = xo_parse_args(argc, argv); 282 if (argc < 0) 283 return 1; 284 285 while ((rc = getopt_long(argc, argv, "Cc:HJl:O:o:ps:TXW", 286 long_opts, NULL)) != -1) { 287 switch (rc) { 288 case 'C': 289 xo_set_flags(NULL, XOF_CONTINUATION); 290 break; 291 292 case 'c': 293 opt_closer = optarg; 294 xo_set_flags(NULL, XOF_IGNORE_CLOSE); 295 break; 296 297 case 'H': 298 xo_set_style(NULL, XO_STYLE_HTML); 299 break; 300 301 case 'J': 302 xo_set_style(NULL, XO_STYLE_JSON); 303 break; 304 305 case 'l': 306 xo_set_leading_xpath(NULL, optarg); 307 break; 308 309 case 'O': 310 opt_options = optarg; 311 break; 312 313 case 'o': 314 opt_opener = optarg; 315 break; 316 317 case 'p': 318 xo_set_flags(NULL, XOF_PRETTY); 319 break; 320 321 case 's': 322 if (xo_set_style_name(NULL, optarg) < 0) 323 xo_errx(1, "unknown style: %s", optarg); 324 break; 325 326 case 'T': 327 xo_set_style(NULL, XO_STYLE_TEXT); 328 break; 329 330 case 'X': 331 xo_set_style(NULL, XO_STYLE_XML); 332 break; 333 334 case 'W': 335 opt_warn = 1; 336 xo_set_flags(NULL, XOF_WARN); 337 break; 338 339 case ':': 340 xo_errx(1, "missing argument"); 341 break; 342 343 case 0: 344 if (opts.o_depth) { 345 opt_depth = atoi(optarg); 346 347 } else if (opts.o_help) { 348 print_help(); 349 return 1; 350 351 } else if (opts.o_not_first) { 352 opt_not_first = 1; 353 354 } else if (opts.o_xpath) { 355 xo_set_flags(NULL, XOF_XPATH); 356 357 } else if (opts.o_version) { 358 print_version(); 359 return 0; 360 361 } else if (opts.o_warn_xml) { 362 opt_warn = 1; 363 xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML); 364 365 } else if (opts.o_wrap) { 366 opt_wrapper = optarg; 367 368 } else if (opts.o_top_wrap) { 369 opt_top_wrap = 1; 370 371 } else if (opts.o_open_list) { 372 if (opt_name) 373 xo_errx(1, "only one open/close list/instance allowed: %s", 374 optarg); 375 376 opt_name = optarg; 377 new_state = XSS_OPEN_LIST; 378 379 } else if (opts.o_open_instance) { 380 if (opt_name) 381 xo_errx(1, "only one open/close list/instance allowed: %s", 382 optarg); 383 384 opt_name = optarg; 385 new_state = XSS_OPEN_INSTANCE; 386 387 } else if (opts.o_close_list) { 388 if (opt_name) 389 xo_errx(1, "only one open/close list/instance allowed: %s", 390 optarg); 391 392 opt_name = optarg; 393 new_state = XSS_CLOSE_LIST; 394 395 } else if (opts.o_close_instance) { 396 if (opt_name) 397 xo_errx(1, "only one open/close list/instance allowed: %s", 398 optarg); 399 400 opt_name = optarg; 401 new_state = XSS_CLOSE_INSTANCE; 402 403 } else { 404 print_help(); 405 return 1; 406 } 407 408 bzero(&opts, sizeof(opts)); /* Reset all the options */ 409 break; 410 411 default: 412 print_help(); 413 return 1; 414 } 415 } 416 417 argc -= optind; 418 argv += optind; 419 420 if (opt_options) { 421 rc = xo_set_options(NULL, opt_options); 422 if (rc < 0) 423 xo_errx(1, "invalid options: %s", opt_options); 424 } 425 426 xo_set_formatter(NULL, formatter, checkpoint); 427 xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE); 428 429 /* 430 * If we have some explicit state change, handle it 431 */ 432 if (new_state) { 433 if (opt_depth > 0) 434 xo_set_depth(NULL, opt_depth); 435 436 if (opt_not_first) 437 xo_set_flags(NULL, XOF_NOT_FIRST); 438 439 xo_explicit_transition(NULL, new_state, opt_name, 0); 440 xo_finish(); 441 exit(0); 442 } 443 444 fmt = *argv++; 445 if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) { 446 print_help(); 447 return 1; 448 } 449 450 if (opt_top_wrap) { 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) 453 opt_depth += 1; 454 else 455 xo_clear_flags(NULL, XOF_NO_TOP); 456 } 457 458 if (opt_closer) { 459 opt_depth += 1; 460 for (cp = opt_closer; cp && *cp; cp = np) { 461 np = strchr(cp, '/'); 462 if (np == NULL) 463 break; 464 np += 1; 465 opt_depth += 1; 466 } 467 } 468 469 if (opt_depth > 0) 470 xo_set_depth(NULL, opt_depth); 471 472 if (opt_not_first) 473 xo_set_flags(NULL, XOF_NOT_FIRST); 474 475 /* If there's an opening hierarchy, open each element as a container */ 476 if (opt_opener) { 477 for (cp = opt_opener; cp && *cp; cp = np) { 478 np = strchr(cp, '/'); 479 if (np) 480 *np = '\0'; 481 xo_open_container(cp); 482 if (np) 483 np += 1; 484 } 485 } 486 487 /* If there's an wrapper hierarchy, open each element as a container */ 488 if (opt_wrapper) { 489 for (cp = opt_wrapper; cp && *cp; cp = np) { 490 np = strchr(cp, '/'); 491 if (np) 492 *np = '\0'; 493 xo_open_container(cp); 494 if (np) 495 *np++ = '/'; /* Put it back */ 496 } 497 } 498 499 /* If there's a format string, call xo_emit to emit the contents */ 500 if (fmt && *fmt) { 501 save_argv = argv; 502 prep_arg(fmt); 503 xo_emit(fmt); /* This call does the real formatting */ 504 } 505 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); 510 if (np) 511 *np = '\0'; 512 else 513 opt_wrapper = NULL; 514 } 515 516 /* Remember to undo the depth before calling xo_finish() */ 517 opt_depth = (opt_closer && opt_top_wrap) ? -1 : 0; 518 519 /* If there's an closing hierarchy, close each element's container */ 520 while (opt_closer) { 521 np = strrchr(opt_closer, '/'); 522 xo_close_container(np ? np + 1 : opt_closer); 523 if (np) 524 *np = '\0'; 525 else 526 opt_closer = NULL; 527 } 528 529 /* If there's a closer and a wrapper, we need to clean it up */ 530 if (opt_depth) { 531 xo_set_depth(NULL, opt_depth); 532 xo_clear_flags(NULL, XOF_NO_TOP); 533 } 534 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); 538 539 xo_finish(); 540 541 return 0; 542 } 543