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