| 1 | /* $Id: main.c,v 1.177 2014/06/21 22:24:01 schwarze Exp $ */ |
| 2 | /* |
| 3 | * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
| 4 | * Copyright (c) 2010, 2011, 2012, 2014 Ingo Schwarze <schwarze@openbsd.org> |
| 5 | * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org> |
| 6 | * |
| 7 | * Permission to use, copy, modify, and distribute this software for any |
| 8 | * purpose with or without fee is hereby granted, provided that the above |
| 9 | * copyright notice and this permission notice appear in all copies. |
| 10 | * |
| 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 18 | */ |
| 19 | #ifdef HAVE_CONFIG_H |
| 20 | #include "config.h" |
| 21 | #endif |
| 22 | |
| 23 | #include <assert.h> |
| 24 | #include <stdio.h> |
| 25 | #include <stdint.h> |
| 26 | #include <stdlib.h> |
| 27 | #include <string.h> |
| 28 | #include <unistd.h> |
| 29 | |
| 30 | #include "mandoc.h" |
| 31 | #include "mandoc_aux.h" |
| 32 | #include "main.h" |
| 33 | #include "mdoc.h" |
| 34 | #include "man.h" |
| 35 | |
| 36 | #if !defined(__GNUC__) || (__GNUC__ < 2) |
| 37 | # if !defined(lint) |
| 38 | # define __attribute__(x) |
| 39 | # endif |
| 40 | #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */ |
| 41 | |
| 42 | typedef void (*out_mdoc)(void *, const struct mdoc *); |
| 43 | typedef void (*out_man)(void *, const struct man *); |
| 44 | typedef void (*out_free)(void *); |
| 45 | |
| 46 | enum outt { |
| 47 | OUTT_ASCII = 0, /* -Tascii */ |
| 48 | OUTT_LOCALE, /* -Tlocale */ |
| 49 | OUTT_UTF8, /* -Tutf8 */ |
| 50 | OUTT_TREE, /* -Ttree */ |
| 51 | OUTT_MAN, /* -Tman */ |
| 52 | OUTT_HTML, /* -Thtml */ |
| 53 | OUTT_XHTML, /* -Txhtml */ |
| 54 | OUTT_LINT, /* -Tlint */ |
| 55 | OUTT_PS, /* -Tps */ |
| 56 | OUTT_PDF /* -Tpdf */ |
| 57 | }; |
| 58 | |
| 59 | struct curparse { |
| 60 | struct mparse *mp; |
| 61 | enum mandoclevel wlevel; /* ignore messages below this */ |
| 62 | int wstop; /* stop after a file with a warning */ |
| 63 | enum outt outtype; /* which output to use */ |
| 64 | out_mdoc outmdoc; /* mdoc output ptr */ |
| 65 | out_man outman; /* man output ptr */ |
| 66 | out_free outfree; /* free output ptr */ |
| 67 | void *outdata; /* data for output */ |
| 68 | char outopts[BUFSIZ]; /* buf of output opts */ |
| 69 | }; |
| 70 | |
| 71 | static int moptions(int *, char *); |
| 72 | static void mmsg(enum mandocerr, enum mandoclevel, |
| 73 | const char *, int, int, const char *); |
| 74 | static void parse(struct curparse *, int, |
| 75 | const char *, enum mandoclevel *); |
| 76 | static int toptions(struct curparse *, char *); |
| 77 | static void usage(void) __attribute__((noreturn)); |
| 78 | static void version(void) __attribute__((noreturn)); |
| 79 | static int woptions(struct curparse *, char *); |
| 80 | |
| 81 | static const char *progname; |
| 82 | |
| 83 | |
| 84 | int |
| 85 | main(int argc, char *argv[]) |
| 86 | { |
| 87 | int c; |
| 88 | struct curparse curp; |
| 89 | int options; |
| 90 | enum mandoclevel rc; |
| 91 | char *defos; |
| 92 | |
| 93 | progname = strrchr(argv[0], '/'); |
| 94 | if (progname == NULL) |
| 95 | progname = argv[0]; |
| 96 | else |
| 97 | ++progname; |
| 98 | |
| 99 | memset(&curp, 0, sizeof(struct curparse)); |
| 100 | |
| 101 | options = MPARSE_SO; |
| 102 | curp.outtype = OUTT_ASCII; |
| 103 | curp.wlevel = MANDOCLEVEL_FATAL; |
| 104 | defos = NULL; |
| 105 | |
| 106 | while (-1 != (c = getopt(argc, argv, "I:m:O:T:VW:"))) |
| 107 | switch (c) { |
| 108 | case 'I': |
| 109 | if (strncmp(optarg, "os=", 3)) { |
| 110 | fprintf(stderr, |
| 111 | "%s: -I%s: Bad argument\n", |
| 112 | progname, optarg); |
| 113 | return((int)MANDOCLEVEL_BADARG); |
| 114 | } |
| 115 | if (defos) { |
| 116 | fprintf(stderr, |
| 117 | "%s: -I%s: Duplicate argument\n", |
| 118 | progname, optarg); |
| 119 | return((int)MANDOCLEVEL_BADARG); |
| 120 | } |
| 121 | defos = mandoc_strdup(optarg + 3); |
| 122 | break; |
| 123 | case 'm': |
| 124 | if ( ! moptions(&options, optarg)) |
| 125 | return((int)MANDOCLEVEL_BADARG); |
| 126 | break; |
| 127 | case 'O': |
| 128 | (void)strlcat(curp.outopts, optarg, BUFSIZ); |
| 129 | (void)strlcat(curp.outopts, ",", BUFSIZ); |
| 130 | break; |
| 131 | case 'T': |
| 132 | if ( ! toptions(&curp, optarg)) |
| 133 | return((int)MANDOCLEVEL_BADARG); |
| 134 | break; |
| 135 | case 'W': |
| 136 | if ( ! woptions(&curp, optarg)) |
| 137 | return((int)MANDOCLEVEL_BADARG); |
| 138 | break; |
| 139 | case 'V': |
| 140 | version(); |
| 141 | /* NOTREACHED */ |
| 142 | default: |
| 143 | usage(); |
| 144 | /* NOTREACHED */ |
| 145 | } |
| 146 | |
| 147 | curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos); |
| 148 | |
| 149 | /* |
| 150 | * Conditionally start up the lookaside buffer before parsing. |
| 151 | */ |
| 152 | if (OUTT_MAN == curp.outtype) |
| 153 | mparse_keep(curp.mp); |
| 154 | |
| 155 | argc -= optind; |
| 156 | argv += optind; |
| 157 | |
| 158 | rc = MANDOCLEVEL_OK; |
| 159 | |
| 160 | if (NULL == *argv) |
| 161 | parse(&curp, STDIN_FILENO, "<stdin>", &rc); |
| 162 | |
| 163 | while (*argv) { |
| 164 | parse(&curp, -1, *argv, &rc); |
| 165 | if (MANDOCLEVEL_OK != rc && curp.wstop) |
| 166 | break; |
| 167 | ++argv; |
| 168 | } |
| 169 | |
| 170 | if (curp.outfree) |
| 171 | (*curp.outfree)(curp.outdata); |
| 172 | if (curp.mp) |
| 173 | mparse_free(curp.mp); |
| 174 | free(defos); |
| 175 | |
| 176 | return((int)rc); |
| 177 | } |
| 178 | |
| 179 | static void |
| 180 | version(void) |
| 181 | { |
| 182 | |
| 183 | printf("%s %s\n", progname, VERSION); |
| 184 | exit((int)MANDOCLEVEL_OK); |
| 185 | } |
| 186 | |
| 187 | static void |
| 188 | usage(void) |
| 189 | { |
| 190 | |
| 191 | fprintf(stderr, "usage: %s " |
| 192 | "[-V] " |
| 193 | "[-Ios=name] " |
| 194 | "[-mformat] " |
| 195 | "[-Ooption] " |
| 196 | "[-Toutput] " |
| 197 | "[-Wlevel]\n" |
| 198 | "\t [file ...]\n", |
| 199 | progname); |
| 200 | |
| 201 | exit((int)MANDOCLEVEL_BADARG); |
| 202 | } |
| 203 | |
| 204 | static void |
| 205 | parse(struct curparse *curp, int fd, const char *file, |
| 206 | enum mandoclevel *level) |
| 207 | { |
| 208 | enum mandoclevel rc; |
| 209 | struct mdoc *mdoc; |
| 210 | struct man *man; |
| 211 | |
| 212 | /* Begin by parsing the file itself. */ |
| 213 | |
| 214 | assert(file); |
| 215 | assert(fd >= -1); |
| 216 | |
| 217 | rc = mparse_readfd(curp->mp, fd, file); |
| 218 | |
| 219 | /* Stop immediately if the parse has failed. */ |
| 220 | |
| 221 | if (MANDOCLEVEL_FATAL <= rc) |
| 222 | goto cleanup; |
| 223 | |
| 224 | /* |
| 225 | * With -Wstop and warnings or errors of at least the requested |
| 226 | * level, do not produce output. |
| 227 | */ |
| 228 | |
| 229 | if (MANDOCLEVEL_OK != rc && curp->wstop) |
| 230 | goto cleanup; |
| 231 | |
| 232 | /* If unset, allocate output dev now (if applicable). */ |
| 233 | |
| 234 | if ( ! (curp->outman && curp->outmdoc)) { |
| 235 | switch (curp->outtype) { |
| 236 | case OUTT_XHTML: |
| 237 | curp->outdata = xhtml_alloc(curp->outopts); |
| 238 | curp->outfree = html_free; |
| 239 | break; |
| 240 | case OUTT_HTML: |
| 241 | curp->outdata = html_alloc(curp->outopts); |
| 242 | curp->outfree = html_free; |
| 243 | break; |
| 244 | case OUTT_UTF8: |
| 245 | curp->outdata = utf8_alloc(curp->outopts); |
| 246 | curp->outfree = ascii_free; |
| 247 | break; |
| 248 | case OUTT_LOCALE: |
| 249 | curp->outdata = locale_alloc(curp->outopts); |
| 250 | curp->outfree = ascii_free; |
| 251 | break; |
| 252 | case OUTT_ASCII: |
| 253 | curp->outdata = ascii_alloc(curp->outopts); |
| 254 | curp->outfree = ascii_free; |
| 255 | break; |
| 256 | case OUTT_PDF: |
| 257 | curp->outdata = pdf_alloc(curp->outopts); |
| 258 | curp->outfree = pspdf_free; |
| 259 | break; |
| 260 | case OUTT_PS: |
| 261 | curp->outdata = ps_alloc(curp->outopts); |
| 262 | curp->outfree = pspdf_free; |
| 263 | break; |
| 264 | default: |
| 265 | break; |
| 266 | } |
| 267 | |
| 268 | switch (curp->outtype) { |
| 269 | case OUTT_HTML: |
| 270 | /* FALLTHROUGH */ |
| 271 | case OUTT_XHTML: |
| 272 | curp->outman = html_man; |
| 273 | curp->outmdoc = html_mdoc; |
| 274 | break; |
| 275 | case OUTT_TREE: |
| 276 | curp->outman = tree_man; |
| 277 | curp->outmdoc = tree_mdoc; |
| 278 | break; |
| 279 | case OUTT_MAN: |
| 280 | curp->outmdoc = man_mdoc; |
| 281 | curp->outman = man_man; |
| 282 | break; |
| 283 | case OUTT_PDF: |
| 284 | /* FALLTHROUGH */ |
| 285 | case OUTT_ASCII: |
| 286 | /* FALLTHROUGH */ |
| 287 | case OUTT_UTF8: |
| 288 | /* FALLTHROUGH */ |
| 289 | case OUTT_LOCALE: |
| 290 | /* FALLTHROUGH */ |
| 291 | case OUTT_PS: |
| 292 | curp->outman = terminal_man; |
| 293 | curp->outmdoc = terminal_mdoc; |
| 294 | break; |
| 295 | default: |
| 296 | break; |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | mparse_result(curp->mp, &mdoc, &man, NULL); |
| 301 | |
| 302 | /* Execute the out device, if it exists. */ |
| 303 | |
| 304 | if (man && curp->outman) |
| 305 | (*curp->outman)(curp->outdata, man); |
| 306 | if (mdoc && curp->outmdoc) |
| 307 | (*curp->outmdoc)(curp->outdata, mdoc); |
| 308 | |
| 309 | cleanup: |
| 310 | |
| 311 | mparse_reset(curp->mp); |
| 312 | |
| 313 | if (*level < rc) |
| 314 | *level = rc; |
| 315 | } |
| 316 | |
| 317 | static int |
| 318 | moptions(int *options, char *arg) |
| 319 | { |
| 320 | |
| 321 | if (0 == strcmp(arg, "doc")) |
| 322 | *options |= MPARSE_MDOC; |
| 323 | else if (0 == strcmp(arg, "andoc")) |
| 324 | /* nothing to do */; |
| 325 | else if (0 == strcmp(arg, "an")) |
| 326 | *options |= MPARSE_MAN; |
| 327 | else { |
| 328 | fprintf(stderr, "%s: -m%s: Bad argument\n", |
| 329 | progname, arg); |
| 330 | return(0); |
| 331 | } |
| 332 | |
| 333 | return(1); |
| 334 | } |
| 335 | |
| 336 | static int |
| 337 | toptions(struct curparse *curp, char *arg) |
| 338 | { |
| 339 | |
| 340 | if (0 == strcmp(arg, "ascii")) |
| 341 | curp->outtype = OUTT_ASCII; |
| 342 | else if (0 == strcmp(arg, "lint")) { |
| 343 | curp->outtype = OUTT_LINT; |
| 344 | curp->wlevel = MANDOCLEVEL_WARNING; |
| 345 | } else if (0 == strcmp(arg, "tree")) |
| 346 | curp->outtype = OUTT_TREE; |
| 347 | else if (0 == strcmp(arg, "man")) |
| 348 | curp->outtype = OUTT_MAN; |
| 349 | else if (0 == strcmp(arg, "html")) |
| 350 | curp->outtype = OUTT_HTML; |
| 351 | else if (0 == strcmp(arg, "utf8")) |
| 352 | curp->outtype = OUTT_UTF8; |
| 353 | else if (0 == strcmp(arg, "locale")) |
| 354 | curp->outtype = OUTT_LOCALE; |
| 355 | else if (0 == strcmp(arg, "xhtml")) |
| 356 | curp->outtype = OUTT_XHTML; |
| 357 | else if (0 == strcmp(arg, "ps")) |
| 358 | curp->outtype = OUTT_PS; |
| 359 | else if (0 == strcmp(arg, "pdf")) |
| 360 | curp->outtype = OUTT_PDF; |
| 361 | else { |
| 362 | fprintf(stderr, "%s: -T%s: Bad argument\n", |
| 363 | progname, arg); |
| 364 | return(0); |
| 365 | } |
| 366 | |
| 367 | return(1); |
| 368 | } |
| 369 | |
| 370 | static int |
| 371 | woptions(struct curparse *curp, char *arg) |
| 372 | { |
| 373 | char *v, *o; |
| 374 | const char *toks[6]; |
| 375 | |
| 376 | toks[0] = "stop"; |
| 377 | toks[1] = "all"; |
| 378 | toks[2] = "warning"; |
| 379 | toks[3] = "error"; |
| 380 | toks[4] = "fatal"; |
| 381 | toks[5] = NULL; |
| 382 | |
| 383 | while (*arg) { |
| 384 | o = arg; |
| 385 | switch (getsubopt(&arg, UNCONST(toks), &v)) { |
| 386 | case 0: |
| 387 | curp->wstop = 1; |
| 388 | break; |
| 389 | case 1: |
| 390 | /* FALLTHROUGH */ |
| 391 | case 2: |
| 392 | curp->wlevel = MANDOCLEVEL_WARNING; |
| 393 | break; |
| 394 | case 3: |
| 395 | curp->wlevel = MANDOCLEVEL_ERROR; |
| 396 | break; |
| 397 | case 4: |
| 398 | curp->wlevel = MANDOCLEVEL_FATAL; |
| 399 | break; |
| 400 | default: |
| 401 | fprintf(stderr, "%s: -W%s: Bad argument\n", |
| 402 | progname, o); |
| 403 | return(0); |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | return(1); |
| 408 | } |
| 409 | |
| 410 | static void |
| 411 | mmsg(enum mandocerr t, enum mandoclevel lvl, |
| 412 | const char *file, int line, int col, const char *msg) |
| 413 | { |
| 414 | const char *mparse_msg; |
| 415 | |
| 416 | fprintf(stderr, "%s: %s:", progname, file); |
| 417 | |
| 418 | if (line) |
| 419 | fprintf(stderr, "%d:%d:", line, col + 1); |
| 420 | |
| 421 | fprintf(stderr, " %s", mparse_strlevel(lvl)); |
| 422 | |
| 423 | if (NULL != (mparse_msg = mparse_strerror(t))) |
| 424 | fprintf(stderr, ": %s", mparse_msg); |
| 425 | |
| 426 | if (msg) |
| 427 | fprintf(stderr, ": %s", msg); |
| 428 | |
| 429 | fputc('\n', stderr); |
| 430 | } |