mandoc(1): Update to 1.9.19.
[dragonfly.git] / usr.bin / mandoc / main.c
1 /*      $Id: main.c,v 1.60 2010/03/22 20:43:00 kristaps Exp $ */
2 /*
3  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/stat.h>
18
19 #include <assert.h>
20 #include <fcntl.h>
21 #include <stdio.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "mdoc.h"
28 #include "man.h"
29 #include "main.h"
30
31 #define UNCONST(a)      ((void *)(uintptr_t)(const void *)(a))
32
33 /* FIXME: Intel's compiler?  LLVM?  pcc?  */
34
35 #if !defined(__GNUC__) || (__GNUC__ < 2)
36 # if !defined(lint)
37 #  define __attribute__(x)
38 # endif
39 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
40
41 typedef void            (*out_mdoc)(void *, const struct mdoc *);
42 typedef void            (*out_man)(void *, const struct man *);
43 typedef void            (*out_free)(void *);
44
45 struct  buf {
46         char             *buf;
47         size_t            sz;
48 };
49
50 enum    intt {
51         INTT_AUTO,
52         INTT_MDOC,
53         INTT_MAN
54 };
55
56 enum    outt {
57         OUTT_ASCII = 0,
58         OUTT_TREE,
59         OUTT_HTML,
60         OUTT_XHTML,
61         OUTT_LINT
62 };
63
64 struct  curparse {
65         const char       *file;         /* Current parse. */
66         int               fd;           /* Current parse. */
67         int               wflags;
68 #define WARN_WALL        (1 << 0)       /* All-warnings mask. */
69 #define WARN_WERR        (1 << 2)       /* Warnings->errors. */
70         int               fflags;
71 #define FL_IGN_SCOPE     (1 << 0)       /* Ignore scope errors. */
72 #define FL_NIGN_ESCAPE   (1 << 1)       /* Don't ignore bad escapes. */
73 #define FL_NIGN_MACRO    (1 << 2)       /* Don't ignore bad macros. */
74 #define FL_NIGN_CHARS    (1 << 3)       /* Don't ignore bad chars. */
75 #define FL_IGN_ERRORS    (1 << 4)       /* Ignore failed parse. */
76         enum intt         inttype;      /* Input parsers... */
77         struct man       *man;
78         struct man       *lastman;
79         struct mdoc      *mdoc;
80         struct mdoc      *lastmdoc;
81         enum outt         outtype;      /* Output devices... */
82         out_mdoc          outmdoc;
83         out_man           outman;
84         out_free          outfree;
85         void             *outdata;
86         char              outopts[BUFSIZ];
87 };
88
89 #define FL_STRICT         FL_NIGN_ESCAPE | \
90                           FL_NIGN_MACRO | \
91                           FL_NIGN_CHARS
92
93 static  int               foptions(int *, char *);
94 static  int               toptions(struct curparse *, char *);
95 static  int               moptions(enum intt *, char *);
96 static  int               woptions(int *, char *);
97 static  int               merr(void *, int, int, const char *);
98 static  int               mwarn(void *, int, int, const char *);
99 static  int               ffile(struct buf *, struct buf *,
100                                 const char *, struct curparse *);
101 static  int               fdesc(struct buf *, struct buf *,
102                                 struct curparse *);
103 static  int               pset(const char *, int, struct curparse *,
104                                 struct man **, struct mdoc **);
105 static  struct man       *man_init(struct curparse *);
106 static  struct mdoc      *mdoc_init(struct curparse *);
107 static  void              version(void) __attribute__((noreturn));
108 static  void              usage(void) __attribute__((noreturn));
109
110 static  const char       *progname;
111
112
113 int
114 main(int argc, char *argv[])
115 {
116         int              c, rc;
117         struct buf       ln, blk;
118         struct curparse  curp;
119
120         progname = strrchr(argv[0], '/');
121         if (progname == NULL)
122                 progname = argv[0];
123         else
124                 ++progname;
125
126         memset(&curp, 0, sizeof(struct curparse));
127
128         curp.inttype = INTT_AUTO;
129         curp.outtype = OUTT_ASCII;
130
131         /* LINTED */
132         while (-1 != (c = getopt(argc, argv, "f:m:O:T:VW:")))
133                 switch (c) {
134                 case ('f'):
135                         if ( ! foptions(&curp.fflags, optarg))
136                                 return(EXIT_FAILURE);
137                         break;
138                 case ('m'):
139                         if ( ! moptions(&curp.inttype, optarg))
140                                 return(EXIT_FAILURE);
141                         break;
142                 case ('O'):
143                         (void)strlcat(curp.outopts, optarg, BUFSIZ);
144                         (void)strlcat(curp.outopts, ",", BUFSIZ);
145                         break;
146                 case ('T'):
147                         if ( ! toptions(&curp, optarg))
148                                 return(EXIT_FAILURE);
149                         break;
150                 case ('W'):
151                         if ( ! woptions(&curp.wflags, optarg))
152                                 return(EXIT_FAILURE);
153                         break;
154                 case ('V'):
155                         version();
156                         /* NOTREACHED */
157                 default:
158                         usage();
159                         /* NOTREACHED */
160                 }
161
162         argc -= optind;
163         argv += optind;
164
165         memset(&ln, 0, sizeof(struct buf));
166         memset(&blk, 0, sizeof(struct buf));
167
168         rc = 1;
169
170         if (NULL == *argv) {
171                 curp.file = "<stdin>";
172                 curp.fd = STDIN_FILENO;
173
174                 c = fdesc(&blk, &ln, &curp);
175                 if ( ! (FL_IGN_ERRORS & curp.fflags))
176                         rc = 1 == c ? 1 : 0;
177                 else
178                         rc = -1 == c ? 0 : 1;
179         }
180
181         while (rc && *argv) {
182                 c = ffile(&blk, &ln, *argv, &curp);
183                 if ( ! (FL_IGN_ERRORS & curp.fflags))
184                         rc = 1 == c ? 1 : 0;
185                 else
186                         rc = -1 == c ? 0 : 1;
187
188                 argv++;
189                 if (*argv && rc) {
190                         if (curp.lastman)
191                                 man_reset(curp.lastman);
192                         if (curp.lastmdoc)
193                                 mdoc_reset(curp.lastmdoc);
194                         curp.lastman = NULL;
195                         curp.lastmdoc = NULL;
196                 }
197         }
198
199         if (blk.buf)
200                 free(blk.buf);
201         if (ln.buf)
202                 free(ln.buf);
203         if (curp.outfree)
204                 (*curp.outfree)(curp.outdata);
205         if (curp.mdoc)
206                 mdoc_free(curp.mdoc);
207         if (curp.man)
208                 man_free(curp.man);
209
210         return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
211 }
212
213
214 static void
215 version(void)
216 {
217
218         (void)printf("%s %s\n", progname, VERSION);
219         exit(EXIT_SUCCESS);
220 }
221
222
223 static void
224 usage(void)
225 {
226
227         (void)fprintf(stderr, "usage: %s [-V] [-foption...] "
228                         "[-mformat] [-Ooption] [-Toutput] "
229                         "[-Werr...]\n", progname);
230         exit(EXIT_FAILURE);
231 }
232
233
234 static struct man *
235 man_init(struct curparse *curp)
236 {
237         int              pflags;
238         struct man_cb    mancb;
239
240         mancb.man_err = merr;
241         mancb.man_warn = mwarn;
242
243         /* Defaults from mandoc.1. */
244
245         pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE | MAN_IGN_CHARS;
246
247         if (curp->fflags & FL_NIGN_MACRO)
248                 pflags &= ~MAN_IGN_MACRO;
249         if (curp->fflags & FL_NIGN_CHARS)
250                 pflags &= ~MAN_IGN_CHARS;
251         if (curp->fflags & FL_NIGN_ESCAPE)
252                 pflags &= ~MAN_IGN_ESCAPE;
253
254         return(man_alloc(curp, pflags, &mancb));
255 }
256
257
258 static struct mdoc *
259 mdoc_init(struct curparse *curp)
260 {
261         int              pflags;
262         struct mdoc_cb   mdoccb;
263
264         mdoccb.mdoc_err = merr;
265         mdoccb.mdoc_warn = mwarn;
266
267         /* Defaults from mandoc.1. */
268
269         pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE | MDOC_IGN_CHARS;
270
271         if (curp->fflags & FL_IGN_SCOPE)
272                 pflags |= MDOC_IGN_SCOPE;
273         if (curp->fflags & FL_NIGN_ESCAPE)
274                 pflags &= ~MDOC_IGN_ESCAPE;
275         if (curp->fflags & FL_NIGN_MACRO)
276                 pflags &= ~MDOC_IGN_MACRO;
277         if (curp->fflags & FL_NIGN_CHARS)
278                 pflags &= ~MDOC_IGN_CHARS;
279
280         return(mdoc_alloc(curp, pflags, &mdoccb));
281 }
282
283
284 static int
285 ffile(struct buf *blk, struct buf *ln,
286                 const char *file, struct curparse *curp)
287 {
288         int              c;
289
290         curp->file = file;
291         if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
292                 perror(curp->file);
293                 return(-1);
294         }
295
296         c = fdesc(blk, ln, curp);
297
298         if (-1 == close(curp->fd))
299                 perror(curp->file);
300
301         return(c);
302 }
303
304
305 static int
306 fdesc(struct buf *blk, struct buf *ln, struct curparse *curp)
307 {
308         size_t           sz;
309         ssize_t          ssz;
310         struct stat      st;
311         int              j, i, pos, lnn, comment;
312         struct man      *man;
313         struct mdoc     *mdoc;
314
315         sz = BUFSIZ;
316         man = NULL;
317         mdoc = NULL;
318
319         /*
320          * Two buffers: ln and buf.  buf is the input buffer optimised
321          * here for each file's block size.  ln is a line buffer.  Both
322          * growable, hence passed in by ptr-ptr.
323          */
324
325         if (-1 == fstat(curp->fd, &st))
326                 perror(curp->file);
327         else if ((size_t)st.st_blksize > sz)
328                 sz = st.st_blksize;
329
330         if (sz > blk->sz) {
331                 blk->buf = realloc(blk->buf, sz);
332                 if (NULL == blk->buf) {
333                         perror(NULL);
334                         exit(EXIT_FAILURE);
335                 }
336                 blk->sz = sz;
337         }
338
339         /* Fill buf with file blocksize. */
340
341         for (lnn = pos = comment = 0; ; ) {
342                 if (-1 == (ssz = read(curp->fd, blk->buf, sz))) {
343                         perror(curp->file);
344                         return(-1);
345                 } else if (0 == ssz)
346                         break;
347
348                 /* Parse the read block into partial or full lines. */
349
350                 for (i = 0; i < (int)ssz; i++) {
351                         if (pos >= (int)ln->sz) {
352                                 ln->sz += 256; /* Step-size. */
353                                 ln->buf = realloc(ln->buf, ln->sz);
354                                 if (NULL == ln->buf) {
355                                         perror(NULL);
356                                         return(EXIT_FAILURE);
357                                 }
358                         }
359
360                         if ('\n' != blk->buf[i]) {
361                                 if (comment)
362                                         continue;
363                                 ln->buf[pos++] = blk->buf[i];
364
365                                 /* Handle in-line `\"' comments. */
366
367                                 if (1 == pos || '\"' != ln->buf[pos - 1])
368                                         continue;
369
370                                 for (j = pos - 2; j >= 0; j--)
371                                         if ('\\' != ln->buf[j])
372                                                 break;
373
374                                 if ( ! ((pos - 2 - j) % 2))
375                                         continue;
376
377                                 comment = 1;
378                                 pos -= 2;
379                                 continue;
380                         }
381
382                         /* Handle escaped `\\n' newlines. */
383
384                         if (pos > 0 && 0 == comment &&
385                                         '\\' == ln->buf[pos - 1]) {
386                                 for (j = pos - 1; j >= 0; j--)
387                                         if ('\\' != ln->buf[j])
388                                                 break;
389                                 if ( ! ((pos - j) % 2)) {
390                                         pos--;
391                                         lnn++;
392                                         continue;
393                                 }
394                         }
395
396                         ln->buf[pos] = 0;
397                         lnn++;
398
399                         /* If unset, assign parser in pset(). */
400
401                         if ( ! (man || mdoc) && ! pset(ln->buf,
402                                                 pos, curp, &man, &mdoc))
403                                 return(-1);
404
405                         pos = comment = 0;
406
407                         /* Pass down into parsers. */
408
409                         if (man && ! man_parseln(man, lnn, ln->buf))
410                                 return(0);
411                         if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf))
412                                 return(0);
413                 }
414         }
415
416         /* NOTE a parser may not have been assigned, yet. */
417
418         if ( ! (man || mdoc)) {
419                 fprintf(stderr, "%s: Not a manual\n", curp->file);
420                 return(0);
421         }
422
423         if (mdoc && ! mdoc_endparse(mdoc))
424                 return(0);
425         if (man && ! man_endparse(man))
426                 return(0);
427
428         /* If unset, allocate output dev now (if applicable). */
429
430         if ( ! (curp->outman && curp->outmdoc)) {
431                 switch (curp->outtype) {
432                 case (OUTT_XHTML):
433                         curp->outdata = xhtml_alloc(curp->outopts);
434                         curp->outman = html_man;
435                         curp->outmdoc = html_mdoc;
436                         curp->outfree = html_free;
437                         break;
438                 case (OUTT_HTML):
439                         curp->outdata = html_alloc(curp->outopts);
440                         curp->outman = html_man;
441                         curp->outmdoc = html_mdoc;
442                         curp->outfree = html_free;
443                         break;
444                 case (OUTT_TREE):
445                         curp->outman = tree_man;
446                         curp->outmdoc = tree_mdoc;
447                         break;
448                 case (OUTT_LINT):
449                         break;
450                 default:
451                         curp->outdata = ascii_alloc();
452                         curp->outman = terminal_man;
453                         curp->outmdoc = terminal_mdoc;
454                         curp->outfree = terminal_free;
455                         break;
456                 }
457         }
458
459         /* Execute the out device, if it exists. */
460
461         if (man && curp->outman)
462                 (*curp->outman)(curp->outdata, man);
463         if (mdoc && curp->outmdoc)
464                 (*curp->outmdoc)(curp->outdata, mdoc);
465
466         return(1);
467 }
468
469
470 static int
471 pset(const char *buf, int pos, struct curparse *curp,
472                 struct man **man, struct mdoc **mdoc)
473 {
474         int              i;
475
476         /*
477          * Try to intuit which kind of manual parser should be used.  If
478          * passed in by command-line (-man, -mdoc), then use that
479          * explicitly.  If passed as -mandoc, then try to guess from the
480          * line: either skip dot-lines, use -mdoc when finding `.Dt', or
481          * default to -man, which is more lenient.
482          */
483
484         if (buf[0] == '.') {
485                 for (i = 1; buf[i]; i++)
486                         if (' ' != buf[i] && '\t' != buf[i])
487                                 break;
488                 if (0 == buf[i])
489                         return(1);
490         }
491
492         switch (curp->inttype) {
493         case (INTT_MDOC):
494                 if (NULL == curp->mdoc)
495                         curp->mdoc = mdoc_init(curp);
496                 if (NULL == (*mdoc = curp->mdoc))
497                         return(0);
498                 curp->lastmdoc = *mdoc;
499                 return(1);
500         case (INTT_MAN):
501                 if (NULL == curp->man)
502                         curp->man = man_init(curp);
503                 if (NULL == (*man = curp->man))
504                         return(0);
505                 curp->lastman = *man;
506                 return(1);
507         default:
508                 break;
509         }
510
511         if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3))  {
512                 if (NULL == curp->mdoc)
513                         curp->mdoc = mdoc_init(curp);
514                 if (NULL == (*mdoc = curp->mdoc))
515                         return(0);
516                 curp->lastmdoc = *mdoc;
517                 return(1);
518         }
519
520         if (NULL == curp->man)
521                 curp->man = man_init(curp);
522         if (NULL == (*man = curp->man))
523                 return(0);
524         curp->lastman = *man;
525         return(1);
526 }
527
528
529 static int
530 moptions(enum intt *tflags, char *arg)
531 {
532
533         if (0 == strcmp(arg, "doc"))
534                 *tflags = INTT_MDOC;
535         else if (0 == strcmp(arg, "andoc"))
536                 *tflags = INTT_AUTO;
537         else if (0 == strcmp(arg, "an"))
538                 *tflags = INTT_MAN;
539         else {
540                 fprintf(stderr, "%s: Bad argument\n", arg);
541                 return(0);
542         }
543
544         return(1);
545 }
546
547
548 static int
549 toptions(struct curparse *curp, char *arg)
550 {
551
552         if (0 == strcmp(arg, "ascii"))
553                 curp->outtype = OUTT_ASCII;
554         else if (0 == strcmp(arg, "lint")) {
555                 curp->outtype = OUTT_LINT;
556                 curp->wflags |= WARN_WALL;
557                 curp->fflags |= FL_STRICT;
558         }
559         else if (0 == strcmp(arg, "tree"))
560                 curp->outtype = OUTT_TREE;
561         else if (0 == strcmp(arg, "html"))
562                 curp->outtype = OUTT_HTML;
563         else if (0 == strcmp(arg, "xhtml"))
564                 curp->outtype = OUTT_XHTML;
565         else {
566                 fprintf(stderr, "%s: Bad argument\n", arg);
567                 return(0);
568         }
569
570         return(1);
571 }
572
573
574 static int
575 foptions(int *fflags, char *arg)
576 {
577         char            *v, *o;
578         const char      *toks[8];
579
580         toks[0] = "ign-scope";
581         toks[1] = "no-ign-escape";
582         toks[2] = "no-ign-macro";
583         toks[3] = "no-ign-chars";
584         toks[4] = "ign-errors";
585         toks[5] = "strict";
586         toks[6] = "ign-escape";
587         toks[7] = NULL;
588
589         while (*arg) {
590                 o = arg;
591                 switch (getsubopt(&arg, UNCONST(toks), &v)) {
592                 case (0):
593                         *fflags |= FL_IGN_SCOPE;
594                         break;
595                 case (1):
596                         *fflags |= FL_NIGN_ESCAPE;
597                         break;
598                 case (2):
599                         *fflags |= FL_NIGN_MACRO;
600                         break;
601                 case (3):
602                         *fflags |= FL_NIGN_CHARS;
603                         break;
604                 case (4):
605                         *fflags |= FL_IGN_ERRORS;
606                         break;
607                 case (5):
608                         *fflags |= FL_STRICT;
609                         break;
610                 case (6):
611                         *fflags &= ~FL_NIGN_ESCAPE;
612                         break;
613                 default:
614                         fprintf(stderr, "%s: Bad argument\n", o);
615                         return(0);
616                 }
617         }
618
619         return(1);
620 }
621
622
623 static int
624 woptions(int *wflags, char *arg)
625 {
626         char            *v, *o;
627         const char      *toks[3];
628
629         toks[0] = "all";
630         toks[1] = "error";
631         toks[2] = NULL;
632
633         while (*arg) {
634                 o = arg;
635                 switch (getsubopt(&arg, UNCONST(toks), &v)) {
636                 case (0):
637                         *wflags |= WARN_WALL;
638                         break;
639                 case (1):
640                         *wflags |= WARN_WERR;
641                         break;
642                 default:
643                         fprintf(stderr, "%s: Bad argument\n", o);
644                         return(0);
645                 }
646         }
647
648         return(1);
649 }
650
651
652 /* ARGSUSED */
653 static int
654 merr(void *arg, int line, int col, const char *msg)
655 {
656         struct curparse *curp;
657
658         curp = (struct curparse *)arg;
659
660         (void)fprintf(stderr, "%s:%d:%d: error: %s\n",
661                         curp->file, line, col + 1, msg);
662
663         return(0);
664 }
665
666
667 static int
668 mwarn(void *arg, int line, int col, const char *msg)
669 {
670         struct curparse *curp;
671
672         curp = (struct curparse *)arg;
673
674         if ( ! (curp->wflags & WARN_WALL))
675                 return(1);
676
677         (void)fprintf(stderr, "%s:%d:%d: warning: %s\n",
678                         curp->file, line, col + 1, msg);
679
680         if ( ! (curp->wflags & WARN_WERR))
681                 return(1);
682
683         return(0);
684 }