]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/libxo/xo/xo.c
Remove spurious newline
[FreeBSD/FreeBSD.git] / contrib / libxo / xo / xo.c
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 "    --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 }