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