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