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