1 /* 2 * Copyright (c) 2014, 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 "xoconfig.h" 17 #include "xo.h" 18 #include "xoversion.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 int 95 formatter (xo_handle_t *xop, char *buf, int bufsiz, 96 const char *fmt, va_list vap UNUSED) 97 { 98 int lflag = 0, hflag = 0, jflag = 0, tflag = 0, 99 zflag = 0, qflag = 0, star1 = 0, star2 = 0; 100 int rc = 0; 101 int w1 = 0, w2 = 0; 102 const char *cp; 103 104 for (cp = fmt + 1; *cp; cp++) { 105 if (*cp == 'l') 106 lflag += 1; 107 else if (*cp == 'h') 108 hflag += 1; 109 else if (*cp == 'j') 110 jflag += 1; 111 else if (*cp == 't') 112 tflag += 1; 113 else if (*cp == 'z') 114 zflag += 1; 115 else if (*cp == 'q') 116 qflag += 1; 117 else if (*cp == '*') { 118 if (star1 == 0) 119 star1 = 1; 120 else 121 star2 = 1; 122 } else if (strchr("diouxXDOUeEfFgGaAcCsSp", *cp) != NULL) 123 break; 124 else if (*cp == 'n' || *cp == 'v') { 125 if (opt_warn) 126 xo_error_h(xop, "unsupported format: '%s'", fmt); 127 return -1; 128 } 129 } 130 131 char fc = *cp; 132 133 /* Handle "%*.*s" */ 134 if (star1) 135 w1 = strtol(next_arg(), NULL, 0); 136 if (star2 > 1) 137 w2 = strtol(next_arg(), NULL, 0); 138 139 if (fc == 'D' || fc == 'O' || fc == 'U') 140 lflag = 1; 141 142 if (strchr("diD", fc) != NULL) { 143 long long value = strtoll(next_arg(), NULL, 0); 144 if (star1 && star2) 145 rc = snprintf(buf, bufsiz, fmt, w1, w2, value); 146 else if (star1) 147 rc = snprintf(buf, bufsiz, fmt, w1, value); 148 else 149 rc = snprintf(buf, bufsiz, fmt, value); 150 151 } else if (strchr("ouxXOUp", fc) != NULL) { 152 unsigned long long value = strtoull(next_arg(), NULL, 0); 153 if (star1 && star2) 154 rc = snprintf(buf, bufsiz, fmt, w1, w2, value); 155 else if (star1) 156 rc = snprintf(buf, bufsiz, fmt, w1, value); 157 else 158 rc = snprintf(buf, bufsiz, fmt, value); 159 160 } else if (strchr("eEfFgGaA", fc) != NULL) { 161 double value = strtold(next_arg(), NULL); 162 if (star1 && star2) 163 rc = snprintf(buf, bufsiz, fmt, w1, w2, value); 164 else if (star1) 165 rc = snprintf(buf, bufsiz, fmt, w1, value); 166 else 167 rc = snprintf(buf, bufsiz, fmt, value); 168 169 } else if (fc == 'C' || fc == 'c' || fc == 'S' || fc == 's') { 170 char *value = next_arg(); 171 if (star1 && star2) 172 rc = snprintf(buf, bufsiz, fmt, w1, w2, value); 173 else if (star1) 174 rc = snprintf(buf, bufsiz, fmt, w1, value); 175 else 176 rc = snprintf(buf, bufsiz, fmt, value); 177 } 178 179 return rc; 180 } 181 182 static void 183 print_version (void) 184 { 185 fprintf(stderr, "libxo version %s%s\n", 186 xo_version, xo_version_extra); 187 fprintf(stderr, "xo version %s%s\n", 188 LIBXO_VERSION, LIBXO_VERSION_EXTRA); 189 } 190 191 static void 192 print_help (void) 193 { 194 fprintf(stderr, 195 "Usage: xo [options] format [fields]\n" 196 " --close <path> Close tags for the given path\n" 197 " --depth <num> Set the depth for pretty printing\n" 198 " --help Display this help text\n" 199 " --html OR -H Generate HTML output\n" 200 " --json OR -J Generate JSON output\n" 201 " --leading-xpath <path> OR -l <path> " 202 "Add a prefix to generated XPaths (HTML)\n" 203 " --open <path> Open tags for the given path\n" 204 " --pretty OR -p Make 'pretty' output (add indent, newlines)\n" 205 " --style <style> OR -s <style> " 206 "Generate given style (xml, json, text, html)\n" 207 " --text OR -T Generate text output (the default style)\n" 208 " --version Display version information\n" 209 " --warn OR -W Display warnings in text on stderr\n" 210 " --warn-xml Display warnings in xml on stdout\n" 211 " --wrap <path> Wrap output in a set of containers\n" 212 " --xml OR -X Generate XML output\n" 213 " --xpath Add XPath data to HTML output\n"); 214 } 215 216 static struct opts { 217 int o_depth; 218 int o_help; 219 int o_not_first; 220 int o_xpath; 221 int o_version; 222 int o_warn_xml; 223 int o_wrap; 224 } opts; 225 226 static struct option long_opts[] = { 227 { "close", required_argument, NULL, 'c' }, 228 { "depth", required_argument, &opts.o_depth, 1 }, 229 { "help", no_argument, &opts.o_help, 1 }, 230 { "html", no_argument, NULL, 'H' }, 231 { "json", no_argument, NULL, 'J' }, 232 { "leading-xpath", required_argument, NULL, 'l' }, 233 { "not-first", no_argument, &opts.o_not_first, 1 }, 234 { "open", required_argument, NULL, 'o' }, 235 { "option", required_argument, NULL, 'O' }, 236 { "pretty", no_argument, NULL, 'p' }, 237 { "style", required_argument, NULL, 's' }, 238 { "text", no_argument, NULL, 'T' }, 239 { "xml", no_argument, NULL, 'X' }, 240 { "xpath", no_argument, &opts.o_xpath, 1 }, 241 { "version", no_argument, &opts.o_version, 1 }, 242 { "warn", no_argument, NULL, 'W' }, 243 { "warn-xml", no_argument, &opts.o_warn_xml, 1 }, 244 { "wrap", required_argument, &opts.o_wrap, 1 }, 245 { NULL, 0, NULL, 0 } 246 }; 247 248 int 249 main (int argc UNUSED, char **argv) 250 { 251 char *fmt = NULL, *cp, *np; 252 char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL; 253 char *opt_options = NULL; 254 int opt_depth = 0; 255 int opt_not_first = 0; 256 int rc; 257 258 argc = xo_parse_args(argc, argv); 259 if (argc < 0) 260 return 1; 261 262 while ((rc = getopt_long(argc, argv, "c:HJl:ps:TXW", 263 long_opts, NULL)) != -1) { 264 switch (rc) { 265 case 'c': 266 opt_closer = optarg; 267 xo_set_flags(NULL, XOF_IGNORE_CLOSE); 268 break; 269 270 case 'H': 271 xo_set_style(NULL, XO_STYLE_HTML); 272 break; 273 274 case 'J': 275 xo_set_style(NULL, XO_STYLE_JSON); 276 break; 277 278 case 'l': 279 xo_set_leading_xpath(NULL, optarg); 280 break; 281 282 case 'O': 283 opt_options = optarg; 284 break; 285 286 case 'o': 287 opt_opener = optarg; 288 break; 289 290 case 'p': 291 xo_set_flags(NULL, XOF_PRETTY); 292 break; 293 294 case 's': 295 if (xo_set_style_name(NULL, optarg) < 0) 296 xo_errx(1, "unknown style: %s", optarg); 297 break; 298 299 case 'T': 300 xo_set_style(NULL, XO_STYLE_TEXT); 301 break; 302 303 case 'X': 304 xo_set_style(NULL, XO_STYLE_XML); 305 break; 306 307 case 'W': 308 opt_warn = 1; 309 xo_set_flags(NULL, XOF_WARN); 310 break; 311 312 case ':': 313 xo_errx(1, "missing argument"); 314 break; 315 316 case 0: 317 if (opts.o_depth) { 318 opt_depth = atoi(optarg); 319 320 } else if (opts.o_help) { 321 print_help(); 322 return 1; 323 324 } else if (opts.o_not_first) { 325 opt_not_first = 1; 326 327 } else if (opts.o_xpath) { 328 xo_set_flags(NULL, XOF_XPATH); 329 330 } else if (opts.o_version) { 331 print_version(); 332 return 0; 333 334 } else if (opts.o_warn_xml) { 335 opt_warn = 1; 336 xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML); 337 338 } else if (opts.o_wrap) { 339 opt_wrapper = optarg; 340 341 } else { 342 print_help(); 343 return 1; 344 } 345 346 bzero(&opts, sizeof(opts)); /* Reset all the options */ 347 break; 348 349 default: 350 print_help(); 351 return 1; 352 } 353 } 354 355 argc -= optind; 356 argv += optind; 357 358 if (opt_options) { 359 rc = xo_set_options(NULL, opt_options); 360 if (rc < 0) 361 xo_errx(1, "invalid options: %s", opt_options); 362 } 363 364 xo_set_formatter(NULL, formatter, checkpoint); 365 xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE); 366 367 fmt = *argv++; 368 if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) { 369 print_help(); 370 return 1; 371 } 372 373 if (opt_not_first) 374 xo_set_flags(NULL, XOF_NOT_FIRST); 375 376 if (opt_closer) { 377 opt_depth += 1; 378 for (cp = opt_closer; cp && *cp; cp = np) { 379 np = strchr(cp, '/'); 380 if (np == NULL) 381 break; 382 np += 1; 383 opt_depth += 1; 384 } 385 } 386 387 if (opt_depth > 0) 388 xo_set_depth(NULL, opt_depth); 389 390 if (opt_opener) { 391 for (cp = opt_opener; cp && *cp; cp = np) { 392 np = strchr(cp, '/'); 393 if (np) 394 *np = '\0'; 395 xo_open_container(cp); 396 if (np) 397 *np++ = '/'; 398 } 399 } 400 401 if (opt_wrapper) { 402 for (cp = opt_wrapper; cp && *cp; cp = np) { 403 np = strchr(cp, '/'); 404 if (np) 405 *np = '\0'; 406 xo_open_container(cp); 407 if (np) 408 *np++ = '/'; 409 } 410 } 411 412 if (fmt && *fmt) { 413 save_argv = argv; 414 prep_arg(fmt); 415 xo_emit(fmt); 416 } 417 418 while (opt_wrapper) { 419 np = strrchr(opt_wrapper, '/'); 420 xo_close_container(np ? np + 1 : opt_wrapper); 421 if (np) 422 *np = '\0'; 423 else 424 opt_wrapper = NULL; 425 } 426 427 while (opt_closer) { 428 np = strrchr(opt_closer, '/'); 429 xo_close_container(np ? np + 1 : opt_closer); 430 if (np) 431 *np = '\0'; 432 else 433 opt_closer = NULL; 434 } 435 436 xo_finish(); 437 438 return 0; 439 } 440