]> git.cameronkatri.com Git - mandoc.git/blob - main.c
3601b7af4bdae72c8129a9bccffcfedc1ebbc84d
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.12 2009/03/23 15:41:09 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
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
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19 #include <sys/stat.h>
20
21 #include <assert.h>
22 #include <err.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "mdoc.h"
30 #include "man.h"
31
32 #ifdef __linux__
33 extern int getsubopt(char **, char * const *, char **);
34 # ifndef __dead
35 # define __dead __attribute__((__noreturn__))
36 # endif
37 #elif defined(__FreeBSD__)
38 # ifndef __dead
39 # define __dead __dead2
40 # endif
41 #endif
42
43 struct buf {
44 char *buf;
45 size_t sz;
46 };
47
48 struct curparse {
49 const char *file;
50 int wflags;
51 #define WARN_WALL 0x03 /* All-warnings mask. */
52 #define WARN_WCOMPAT (1 << 0) /* Compatibility warnings. */
53 #define WARN_WSYNTAX (1 << 1) /* Syntax warnings. */
54 #define WARN_WERR (1 << 2) /* Warnings->errors. */
55 };
56
57 enum intt {
58 INTT_MDOC = 0,
59 INTT_MAN
60 };
61
62 enum outt {
63 OUTT_ASCII = 0,
64 OUTT_LATIN1,
65 OUTT_UTF8,
66 OUTT_TREE,
67 OUTT_LINT
68 };
69
70 typedef int (*out_run)(void *, const struct man *,
71 const struct mdoc *);
72 typedef void (*out_free)(void *);
73
74 extern char *__progname;
75
76 extern void *ascii_alloc(void);
77 extern void *latin1_alloc(void);
78 extern void *utf8_alloc(void);
79 extern int terminal_run(void *, const struct man *,
80 const struct mdoc *);
81 extern int tree_run(void *, const struct man *,
82 const struct mdoc *);
83 extern void terminal_free(void *);
84
85 static int foptions(int *, char *);
86 static int toptions(enum outt *, char *);
87 static int moptions(enum intt *, char *);
88 static int woptions(int *, char *);
89 static int merr(void *, int, int, const char *);
90 static int mwarn(void *, int, int,
91 enum mdoc_warn, const char *);
92 static int file(struct buf *, struct buf *,
93 const char *,
94 struct man *, struct mdoc *);
95 static int fdesc(struct buf *, struct buf *,
96 const char *, int,
97 struct man *, struct mdoc *);
98
99 __dead static void version(void);
100 __dead static void usage(void);
101
102
103 int
104 main(int argc, char *argv[])
105 {
106 int c, rc, fflags;
107 struct mdoc_cb cb;
108 struct man *man;
109 struct mdoc *mdoc;
110 void *outdata;
111 enum outt outtype;
112 enum intt inttype;
113 struct buf ln, blk;
114 out_run outrun;
115 out_free outfree;
116 struct curparse curp;
117
118 fflags = 0;
119 outtype = OUTT_ASCII;
120 inttype = INTT_MDOC;
121
122 bzero(&curp, sizeof(struct curparse));
123
124 /* LINTED */
125 while (-1 != (c = getopt(argc, argv, "f:m:VW:T:")))
126 switch (c) {
127 case ('f'):
128 if ( ! foptions(&fflags, optarg))
129 return(0);
130 break;
131 case ('m'):
132 if ( ! moptions(&inttype, optarg))
133 return(0);
134 break;
135 case ('T'):
136 if ( ! toptions(&outtype, optarg))
137 return(0);
138 break;
139 case ('W'):
140 if ( ! woptions(&curp.wflags, optarg))
141 return(0);
142 break;
143 case ('V'):
144 version();
145 /* NOTREACHED */
146 default:
147 usage();
148 /* NOTREACHED */
149 }
150
151 argc -= optind;
152 argv += optind;
153
154 /*
155 * Allocate the appropriate front-end. Note that utf8, ascii
156 * and latin1 all resolve to the terminal front-end with
157 * different encodings (see terminal.c). Not all frontends have
158 * cleanup or alloc routines.
159 */
160
161 switch (outtype) {
162 case (OUTT_LATIN1):
163 outdata = latin1_alloc();
164 outrun = terminal_run;
165 outfree = terminal_free;
166 break;
167 case (OUTT_UTF8):
168 outdata = utf8_alloc();
169 outrun = terminal_run;
170 outfree = terminal_free;
171 break;
172 case (OUTT_TREE):
173 outdata = NULL;
174 outrun = tree_run;
175 outfree = NULL;
176 break;
177 case (OUTT_LINT):
178 outdata = NULL;
179 outrun = NULL;
180 outfree = NULL;
181 break;
182 default:
183 outdata = ascii_alloc();
184 outrun = terminal_run;
185 outfree = terminal_free;
186 break;
187 }
188
189 /*
190 * All callbacks route into here, where we print them onto the
191 * screen. XXX - for now, no path for debugging messages.
192 */
193
194 cb.mdoc_msg = NULL;
195 cb.mdoc_err = merr;
196 cb.mdoc_warn = mwarn;
197
198 bzero(&ln, sizeof(struct buf));
199 bzero(&blk, sizeof(struct buf));
200
201 man = NULL;
202 mdoc = NULL;
203
204 switch (inttype) {
205 case (INTT_MAN):
206 man = man_alloc();
207 break;
208 default:
209 mdoc = mdoc_alloc(&curp, fflags, &cb);
210 break;
211 }
212
213 /*
214 * Loop around available files.
215 */
216
217 if (NULL == *argv) {
218 curp.file = "<stdin>";
219 rc = 0;
220 c = fdesc(&blk, &ln, "stdin",
221 STDIN_FILENO, man, mdoc);
222
223 if (c && NULL == outrun)
224 rc = 1;
225 else if (c && outrun && (*outrun)(outdata, man, mdoc))
226 rc = 1;
227 } else {
228 while (*argv) {
229 curp.file = *argv;
230 c = file(&blk, &ln, *argv, man, mdoc);
231 if ( ! c)
232 break;
233 if (outrun && ! (*outrun)(outdata, man, mdoc))
234 break;
235 if (man)
236 man_reset(man);
237 if (mdoc)
238 mdoc_reset(mdoc);
239
240 argv++;
241 }
242 rc = NULL == *argv;
243 }
244
245 if (blk.buf)
246 free(blk.buf);
247 if (ln.buf)
248 free(ln.buf);
249 if (outfree)
250 (*outfree)(outdata);
251 if (mdoc)
252 mdoc_free(mdoc);
253 if (man)
254 man_free(man);
255
256 return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
257 }
258
259
260 __dead static void
261 version(void)
262 {
263
264 (void)printf("%s %s\n", __progname, VERSION);
265 exit(0);
266 /* NOTREACHED */
267 }
268
269
270 __dead static void
271 usage(void)
272 {
273
274 (void)fprintf(stderr, "usage: %s [-V] [-foption...] "
275 "[-mformat] [-Toutput] [-Werr...]\n",
276 __progname);
277 exit(1);
278 /* NOTREACHED */
279 }
280
281
282 static int
283 file(struct buf *blk, struct buf *ln, const char *file,
284 struct man *man, struct mdoc *mdoc)
285 {
286 int fd, c;
287
288 if (-1 == (fd = open(file, O_RDONLY, 0))) {
289 warn("%s", file);
290 return(0);
291 }
292
293 c = fdesc(blk, ln, file, fd, man, mdoc);
294
295 if (-1 == close(fd))
296 warn("%s", file);
297
298 return(c);
299 }
300
301
302 static int
303 fdesc(struct buf *blk, struct buf *ln,
304 const char *f, int fd,
305 struct man *man, struct mdoc *mdoc)
306 {
307 size_t sz;
308 ssize_t ssz;
309 struct stat st;
310 int j, i, pos, lnn;
311
312 assert( ! (man && mdoc));
313
314 /*
315 * Two buffers: ln and buf. buf is the input buffer, optimised
316 * for each file's block size. ln is a line buffer. Both
317 * growable, hence passed in by ptr-ptr.
318 */
319
320 sz = BUFSIZ;
321
322 if (-1 == fstat(fd, &st))
323 warnx("%s", f);
324 else if ((size_t)st.st_blksize > sz)
325 sz = st.st_blksize;
326
327 if (sz > blk->sz) {
328 blk->buf = realloc(blk->buf, sz);
329 if (NULL == blk->buf)
330 err(1, "realloc");
331 blk->sz = sz;
332 }
333
334 /*
335 * Fill buf with file blocksize and parse newlines into ln.
336 */
337
338 for (lnn = 1, pos = 0; ; ) {
339 if (-1 == (ssz = read(fd, blk->buf, sz))) {
340 warn("%s", f);
341 return(0);
342 } else if (0 == ssz)
343 break;
344
345 for (i = 0; i < (int)ssz; i++) {
346 if (pos >= (int)ln->sz) {
347 ln->sz += 256; /* Step-size. */
348 ln->buf = realloc(ln->buf, ln->sz);
349 if (NULL == ln->buf)
350 err(1, "realloc");
351 }
352
353 if ('\n' != blk->buf[i]) {
354 ln->buf[pos++] = blk->buf[i];
355 continue;
356 }
357
358 /* Check for CPP-escaped newline. */
359
360 if (pos > 0 && '\\' == ln->buf[pos - 1]) {
361 for (j = pos - 1; j >= 0; j--)
362 if ('\\' != ln->buf[j])
363 break;
364
365 if ( ! ((pos - j) % 2)) {
366 pos--;
367 lnn++;
368 continue;
369 }
370 }
371
372 ln->buf[pos] = 0;
373 if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf))
374 return(0);
375 if (man && ! man_parseln(man, lnn, ln->buf))
376 return(0);
377 lnn++;
378 pos = 0;
379 }
380 }
381
382 if (mdoc)
383 return(mdoc_endparse(mdoc));
384
385 return(man_endparse(man));
386 }
387
388
389 static int
390 moptions(enum intt *tflags, char *arg)
391 {
392
393 if (0 == strcmp(arg, "mdoc"))
394 *tflags = INTT_MDOC;
395 else if (0 == strcmp(arg, "man"))
396 *tflags = INTT_MAN;
397 else {
398 warnx("bad argument: -m%s", arg);
399 return(0);
400 }
401
402 return(1);
403 }
404
405
406 static int
407 toptions(enum outt *tflags, char *arg)
408 {
409
410 if (0 == strcmp(arg, "ascii"))
411 *tflags = OUTT_ASCII;
412 else if (0 == strcmp(arg, "latin1"))
413 *tflags = OUTT_LATIN1;
414 else if (0 == strcmp(arg, "utf8"))
415 *tflags = OUTT_UTF8;
416 else if (0 == strcmp(arg, "lint"))
417 *tflags = OUTT_LINT;
418 else if (0 == strcmp(arg, "tree"))
419 *tflags = OUTT_TREE;
420 else {
421 warnx("bad argument: -T%s", arg);
422 return(0);
423 }
424
425 return(1);
426 }
427
428
429 /*
430 * Parse out the options for [-fopt...] setting compiler options. These
431 * can be comma-delimited or called again.
432 */
433 static int
434 foptions(int *fflags, char *arg)
435 {
436 char *v;
437 char *toks[4];
438
439 toks[0] = "ign-scope";
440 toks[1] = "ign-escape";
441 toks[2] = "ign-macro";
442 toks[3] = NULL;
443
444 while (*arg)
445 switch (getsubopt(&arg, toks, &v)) {
446 case (0):
447 *fflags |= MDOC_IGN_SCOPE;
448 break;
449 case (1):
450 *fflags |= MDOC_IGN_ESCAPE;
451 break;
452 case (2):
453 *fflags |= MDOC_IGN_MACRO;
454 break;
455 default:
456 warnx("bad argument: -f%s", arg);
457 return(0);
458 }
459
460 return(1);
461 }
462
463
464 /*
465 * Parse out the options for [-Werr...], which sets warning modes.
466 * These can be comma-delimited or called again.
467 */
468 static int
469 woptions(int *wflags, char *arg)
470 {
471 char *v;
472 char *toks[5];
473
474 toks[0] = "all";
475 toks[1] = "compat";
476 toks[2] = "syntax";
477 toks[3] = "error";
478 toks[4] = NULL;
479
480 while (*arg)
481 switch (getsubopt(&arg, toks, &v)) {
482 case (0):
483 *wflags |= WARN_WALL;
484 break;
485 case (1):
486 *wflags |= WARN_WCOMPAT;
487 break;
488 case (2):
489 *wflags |= WARN_WSYNTAX;
490 break;
491 case (3):
492 *wflags |= WARN_WERR;
493 break;
494 default:
495 warnx("bad argument: -W%s", arg);
496 return(0);
497 }
498
499 return(1);
500 }
501
502
503 /* ARGSUSED */
504 static int
505 merr(void *arg, int line, int col, const char *msg)
506 {
507 struct curparse *curp;
508
509 curp = (struct curparse *)arg;
510
511 warnx("%s:%d: error: %s (column %d)",
512 curp->file, line, msg, col);
513 return(0);
514 }
515
516
517 static int
518 mwarn(void *arg, int line, int col,
519 enum mdoc_warn type, const char *msg)
520 {
521 struct curparse *curp;
522 char *wtype;
523
524 curp = (struct curparse *)arg;
525 wtype = NULL;
526
527 switch (type) {
528 case (WARN_COMPAT):
529 wtype = "compat";
530 if (curp->wflags & WARN_WCOMPAT)
531 break;
532 return(1);
533 case (WARN_SYNTAX):
534 wtype = "syntax";
535 if (curp->wflags & WARN_WSYNTAX)
536 break;
537 return(1);
538 }
539
540 assert(wtype);
541 warnx("%s:%d: %s warning: %s (column %d)",
542 curp->file, line, wtype, msg, col);
543
544 if ( ! (curp->wflags & WARN_WERR))
545 return(1);
546
547 warnx("%s: considering warnings as errors",
548 __progname);
549 return(0);
550 }
551
552