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