xref: /freebsd/contrib/libxo/xo/xo.c (revision 39ee7a7a6bdd1557b1c3532abf60d139798ac88b)
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 int
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
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