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 "xo_config.h" 17 #include "xo.h" 18 19 #include <getopt.h> /* Include after xo.h for testing */ 20 21 #ifndef UNUSED 22 #define UNUSED __attribute__ ((__unused__)) 23 #endif /* UNUSED */ 24 25 static int opt_warn; /* Enable warnings */ 26 27 static char **save_argv; 28 static char **checkpoint_argv; 29 30 static char * 31 next_arg (void) 32 { 33 char *cp = *save_argv; 34 35 if (cp == NULL) 36 xo_errx(1, "missing argument"); 37 38 save_argv += 1; 39 return cp; 40 } 41 42 static void 43 prep_arg (char *fmt) 44 { 45 char *cp, *fp; 46 47 for (cp = fp = fmt; *cp; cp++, fp++) { 48 if (*cp != '\\') { 49 if (cp != fp) 50 *fp = *cp; 51 continue; 52 } 53 54 switch (*++cp) { 55 case 'n': 56 *fp = '\n'; 57 break; 58 59 case 'r': 60 *fp = '\r'; 61 break; 62 63 case 'b': 64 *fp = '\b'; 65 break; 66 67 case 'e': 68 *fp = '\e'; 69 break; 70 71 default: 72 *fp = *cp; 73 } 74 } 75 76 *fp = '\0'; 77 } 78 79 static void 80 checkpoint (xo_handle_t *xop UNUSED, va_list vap UNUSED, int restore) 81 { 82 if (restore) 83 save_argv = checkpoint_argv; 84 else 85 checkpoint_argv = save_argv; 86 } 87 88 /* 89 * Our custom formatter is responsible for combining format string pieces 90 * with our command line arguments to build strings. This involves faking 91 * some printf-style logic. 92 */ 93 static xo_ssize_t 94 formatter (xo_handle_t *xop, char *buf, xo_ssize_t bufsiz, 95 const char *fmt, va_list vap UNUSED) 96 { 97 int lflag UNUSED = 0; /* Parse long flag, though currently ignored */ 98 int 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 " --option <opts> -or -O <opts> Give formatting options\n" 205 " --pretty OR -p Make 'pretty' output (add indent, newlines)\n" 206 " --style <style> OR -s <style> " 207 "Generate given style (xml, json, text, html)\n" 208 " --text OR -T Generate text output (the default style)\n" 209 " --version Display version information\n" 210 " --warn OR -W Display warnings in text on stderr\n" 211 " --warn-xml Display warnings in xml on stdout\n" 212 " --wrap <path> Wrap output in a set of containers\n" 213 " --xml OR -X Generate XML output\n" 214 " --xpath Add XPath data to HTML output\n"); 215 } 216 217 static struct opts { 218 int o_depth; 219 int o_help; 220 int o_not_first; 221 int o_xpath; 222 int o_version; 223 int o_warn_xml; 224 int o_wrap; 225 } opts; 226 227 static struct option long_opts[] = { 228 { "close", required_argument, NULL, 'c' }, 229 { "depth", required_argument, &opts.o_depth, 1 }, 230 { "help", no_argument, &opts.o_help, 1 }, 231 { "html", no_argument, NULL, 'H' }, 232 { "json", no_argument, NULL, 'J' }, 233 { "leading-xpath", required_argument, NULL, 'l' }, 234 { "not-first", no_argument, &opts.o_not_first, 1 }, 235 { "open", required_argument, NULL, 'o' }, 236 { "option", required_argument, NULL, 'O' }, 237 { "pretty", no_argument, NULL, 'p' }, 238 { "style", required_argument, NULL, 's' }, 239 { "text", no_argument, NULL, 'T' }, 240 { "xml", no_argument, NULL, 'X' }, 241 { "xpath", no_argument, &opts.o_xpath, 1 }, 242 { "version", no_argument, &opts.o_version, 1 }, 243 { "warn", no_argument, NULL, 'W' }, 244 { "warn-xml", no_argument, &opts.o_warn_xml, 1 }, 245 { "wrap", required_argument, &opts.o_wrap, 1 }, 246 { NULL, 0, NULL, 0 } 247 }; 248 249 int 250 main (int argc UNUSED, char **argv) 251 { 252 char *fmt = NULL, *cp, *np; 253 char *opt_opener = NULL, *opt_closer = NULL, *opt_wrapper = NULL; 254 char *opt_options = NULL; 255 int opt_depth = 0; 256 int opt_not_first = 0; 257 int rc; 258 259 argc = xo_parse_args(argc, argv); 260 if (argc < 0) 261 return 1; 262 263 while ((rc = getopt_long(argc, argv, "c:HJl:O:o:ps:TXW", 264 long_opts, NULL)) != -1) { 265 switch (rc) { 266 case 'c': 267 opt_closer = optarg; 268 xo_set_flags(NULL, XOF_IGNORE_CLOSE); 269 break; 270 271 case 'H': 272 xo_set_style(NULL, XO_STYLE_HTML); 273 break; 274 275 case 'J': 276 xo_set_style(NULL, XO_STYLE_JSON); 277 break; 278 279 case 'l': 280 xo_set_leading_xpath(NULL, optarg); 281 break; 282 283 case 'O': 284 opt_options = optarg; 285 break; 286 287 case 'o': 288 opt_opener = optarg; 289 break; 290 291 case 'p': 292 xo_set_flags(NULL, XOF_PRETTY); 293 break; 294 295 case 's': 296 if (xo_set_style_name(NULL, optarg) < 0) 297 xo_errx(1, "unknown style: %s", optarg); 298 break; 299 300 case 'T': 301 xo_set_style(NULL, XO_STYLE_TEXT); 302 break; 303 304 case 'X': 305 xo_set_style(NULL, XO_STYLE_XML); 306 break; 307 308 case 'W': 309 opt_warn = 1; 310 xo_set_flags(NULL, XOF_WARN); 311 break; 312 313 case ':': 314 xo_errx(1, "missing argument"); 315 break; 316 317 case 0: 318 if (opts.o_depth) { 319 opt_depth = atoi(optarg); 320 321 } else if (opts.o_help) { 322 print_help(); 323 return 1; 324 325 } else if (opts.o_not_first) { 326 opt_not_first = 1; 327 328 } else if (opts.o_xpath) { 329 xo_set_flags(NULL, XOF_XPATH); 330 331 } else if (opts.o_version) { 332 print_version(); 333 return 0; 334 335 } else if (opts.o_warn_xml) { 336 opt_warn = 1; 337 xo_set_flags(NULL, XOF_WARN | XOF_WARN_XML); 338 339 } else if (opts.o_wrap) { 340 opt_wrapper = optarg; 341 342 } else { 343 print_help(); 344 return 1; 345 } 346 347 bzero(&opts, sizeof(opts)); /* Reset all the options */ 348 break; 349 350 default: 351 print_help(); 352 return 1; 353 } 354 } 355 356 argc -= optind; 357 argv += optind; 358 359 if (opt_options) { 360 rc = xo_set_options(NULL, opt_options); 361 if (rc < 0) 362 xo_errx(1, "invalid options: %s", opt_options); 363 } 364 365 xo_set_formatter(NULL, formatter, checkpoint); 366 xo_set_flags(NULL, XOF_NO_VA_ARG | XOF_NO_TOP | XOF_NO_CLOSE); 367 368 fmt = *argv++; 369 if (opt_opener == NULL && opt_closer == NULL && fmt == NULL) { 370 print_help(); 371 return 1; 372 } 373 374 if (opt_not_first) 375 xo_set_flags(NULL, XOF_NOT_FIRST); 376 377 if (opt_closer) { 378 opt_depth += 1; 379 for (cp = opt_closer; cp && *cp; cp = np) { 380 np = strchr(cp, '/'); 381 if (np == NULL) 382 break; 383 np += 1; 384 opt_depth += 1; 385 } 386 } 387 388 if (opt_depth > 0) 389 xo_set_depth(NULL, opt_depth); 390 391 if (opt_opener) { 392 for (cp = opt_opener; cp && *cp; cp = np) { 393 np = strchr(cp, '/'); 394 if (np) 395 *np = '\0'; 396 xo_open_container(cp); 397 if (np) 398 *np++ = '/'; 399 } 400 } 401 402 if (opt_wrapper) { 403 for (cp = opt_wrapper; cp && *cp; cp = np) { 404 np = strchr(cp, '/'); 405 if (np) 406 *np = '\0'; 407 xo_open_container(cp); 408 if (np) 409 *np++ = '/'; 410 } 411 } 412 413 if (fmt && *fmt) { 414 save_argv = argv; 415 prep_arg(fmt); 416 xo_emit(fmt); 417 } 418 419 while (opt_wrapper) { 420 np = strrchr(opt_wrapper, '/'); 421 xo_close_container(np ? np + 1 : opt_wrapper); 422 if (np) 423 *np = '\0'; 424 else 425 opt_wrapper = NULL; 426 } 427 428 while (opt_closer) { 429 np = strrchr(opt_closer, '/'); 430 xo_close_container(np ? np + 1 : opt_closer); 431 if (np) 432 *np = '\0'; 433 else 434 opt_closer = NULL; 435 } 436 437 xo_finish(); 438 439 return 0; 440 } 441