]> git.cameronkatri.com Git - mandoc.git/blob - main.c
Implement classic man(1) output mode showing only one manual even
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.182 2014/08/21 00:32:15 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011, 2012, 2014 Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19 #include "config.h"
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29
30 #include "mandoc.h"
31 #include "mandoc_aux.h"
32 #include "main.h"
33 #include "mdoc.h"
34 #include "man.h"
35 #include "manpath.h"
36 #include "mansearch.h"
37
38 #if !defined(__GNUC__) || (__GNUC__ < 2)
39 # if !defined(lint)
40 # define __attribute__(x)
41 # endif
42 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
43
44 enum outmode {
45 OUTMODE_DEF = 0,
46 OUTMODE_FLN,
47 OUTMODE_LST,
48 OUTMODE_ALL,
49 OUTMODE_INT,
50 OUTMODE_ONE
51 };
52
53 typedef void (*out_mdoc)(void *, const struct mdoc *);
54 typedef void (*out_man)(void *, const struct man *);
55 typedef void (*out_free)(void *);
56
57 enum outt {
58 OUTT_ASCII = 0, /* -Tascii */
59 OUTT_LOCALE, /* -Tlocale */
60 OUTT_UTF8, /* -Tutf8 */
61 OUTT_TREE, /* -Ttree */
62 OUTT_MAN, /* -Tman */
63 OUTT_HTML, /* -Thtml */
64 OUTT_XHTML, /* -Txhtml */
65 OUTT_LINT, /* -Tlint */
66 OUTT_PS, /* -Tps */
67 OUTT_PDF /* -Tpdf */
68 };
69
70 struct curparse {
71 struct mparse *mp;
72 enum mandoclevel wlevel; /* ignore messages below this */
73 int wstop; /* stop after a file with a warning */
74 enum outt outtype; /* which output to use */
75 out_mdoc outmdoc; /* mdoc output ptr */
76 out_man outman; /* man output ptr */
77 out_free outfree; /* free output ptr */
78 void *outdata; /* data for output */
79 char outopts[BUFSIZ]; /* buf of output opts */
80 };
81
82 static int moptions(int *, char *);
83 static void mmsg(enum mandocerr, enum mandoclevel,
84 const char *, int, int, const char *);
85 static void parse(struct curparse *, int,
86 const char *, enum mandoclevel *);
87 static int toptions(struct curparse *, char *);
88 static void usage(enum argmode) __attribute__((noreturn));
89 static void version(void) __attribute__((noreturn));
90 static int woptions(struct curparse *, char *);
91
92 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
93 static const char *progname;
94
95
96 int
97 main(int argc, char *argv[])
98 {
99 struct curparse curp;
100 struct mansearch search;
101 struct manpaths paths;
102 char *conf_file, *defpaths, *auxpaths;
103 char *defos;
104 #if HAVE_SQLITE3
105 struct manpage *res;
106 char **auxargv;
107 size_t isec, i, sz;
108 int prio, best_prio;
109 char sec;
110 #endif
111 enum mandoclevel rc;
112 enum outmode outmode;
113 int show_usage;
114 int options;
115 int c;
116
117 progname = strrchr(argv[0], '/');
118 if (progname == NULL)
119 progname = argv[0];
120 else
121 ++progname;
122
123 /* Search options. */
124
125 memset(&paths, 0, sizeof(struct manpaths));
126 conf_file = defpaths = auxpaths = NULL;
127
128 memset(&search, 0, sizeof(struct mansearch));
129 search.outkey = "Nd";
130
131 if (strcmp(progname, "man") == 0)
132 search.argmode = ARG_NAME;
133 else if (strncmp(progname, "apropos", 7) == 0)
134 search.argmode = ARG_EXPR;
135 else if (strncmp(progname, "whatis", 6) == 0)
136 search.argmode = ARG_WORD;
137 else
138 search.argmode = ARG_FILE;
139
140 /* Parser and formatter options. */
141
142 memset(&curp, 0, sizeof(struct curparse));
143 curp.outtype = OUTT_ASCII;
144 curp.wlevel = MANDOCLEVEL_FATAL;
145 options = MPARSE_SO;
146 defos = NULL;
147
148 show_usage = 0;
149 outmode = OUTMODE_DEF;
150 while (-1 != (c = getopt(argc, argv, "aC:fI:ikM:m:O:S:s:T:VW:w"))) {
151 switch (c) {
152 case 'a':
153 outmode = OUTMODE_ALL;
154 break;
155 case 'C':
156 conf_file = optarg;
157 break;
158 case 'f':
159 search.argmode = ARG_WORD;
160 break;
161 case 'I':
162 if (strncmp(optarg, "os=", 3)) {
163 fprintf(stderr,
164 "%s: -I%s: Bad argument\n",
165 progname, optarg);
166 return((int)MANDOCLEVEL_BADARG);
167 }
168 if (defos) {
169 fprintf(stderr,
170 "%s: -I%s: Duplicate argument\n",
171 progname, optarg);
172 return((int)MANDOCLEVEL_BADARG);
173 }
174 defos = mandoc_strdup(optarg + 3);
175 break;
176 case 'i':
177 outmode = OUTMODE_INT;
178 break;
179 case 'k':
180 search.argmode = ARG_EXPR;
181 break;
182 case 'M':
183 defpaths = optarg;
184 break;
185 case 'm':
186 auxpaths = optarg;
187 break;
188 case 'O':
189 search.outkey = optarg;
190 (void)strlcat(curp.outopts, optarg, BUFSIZ);
191 (void)strlcat(curp.outopts, ",", BUFSIZ);
192 break;
193 case 'S':
194 search.arch = optarg;
195 break;
196 case 's':
197 search.sec = optarg;
198 break;
199 case 'T':
200 if ( ! toptions(&curp, optarg))
201 return((int)MANDOCLEVEL_BADARG);
202 break;
203 case 'W':
204 if ( ! woptions(&curp, optarg))
205 return((int)MANDOCLEVEL_BADARG);
206 break;
207 case 'w':
208 outmode = OUTMODE_FLN;
209 break;
210 case 'V':
211 version();
212 /* NOTREACHED */
213 default:
214 show_usage = 1;
215 break;
216 }
217 }
218
219 if (show_usage)
220 usage(search.argmode);
221
222 if (outmode == OUTMODE_DEF) {
223 switch (search.argmode) {
224 case ARG_FILE:
225 outmode = OUTMODE_ALL;
226 break;
227 case ARG_NAME:
228 outmode = OUTMODE_ONE;
229 break;
230 default:
231 outmode = OUTMODE_LST;
232 break;
233 }
234 }
235
236 argc -= optind;
237 argv += optind;
238 #if HAVE_SQLITE3
239 auxargv = NULL;
240 #endif
241
242 rc = MANDOCLEVEL_OK;
243
244 /* man(1), whatis(1), apropos(1) */
245
246 if (search.argmode != ARG_FILE) {
247 #if HAVE_SQLITE3
248 if (argc == 0)
249 usage(search.argmode);
250
251 /* Access the mandoc database. */
252
253 manpath_parse(&paths, conf_file, defpaths, auxpaths);
254 mansearch_setup(1);
255 if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
256 usage(search.argmode);
257 manpath_free(&paths);
258
259 /*
260 * For standard man(1) and -a output mode,
261 * prepare for copying filename pointers
262 * into the program parameter array.
263 */
264
265 if (outmode == OUTMODE_ONE) {
266 argc = 1;
267 argv[0] = res[0].file;
268 argv[1] = NULL;
269 best_prio = 10;
270 } else if (outmode == OUTMODE_ALL) {
271 argc = (int)sz;
272 argv = auxargv = mandoc_reallocarray(
273 NULL, sz + 1, sizeof(char *));
274 argv[argc] = NULL;
275 }
276
277 /* Iterate all matching manuals. */
278
279 for (i = 0; i < sz; i++) {
280 if (outmode == OUTMODE_FLN)
281 puts(res[i].file);
282 else if (outmode == OUTMODE_LST)
283 printf("%s - %s\n", res[i].names,
284 res[i].output == NULL ? "" :
285 res[i].output);
286 else if (outmode == OUTMODE_ALL)
287 argv[i] = res[i].file;
288 else {
289 /* Search for the best section. */
290 isec = strcspn(res[i].file, "123456789");
291 sec = res[i].file[isec];
292 if ('\0' == sec)
293 continue;
294 prio = sec_prios[sec - '1'];
295 if (prio >= best_prio)
296 continue;
297 best_prio = prio;
298 argv[0] = res[i].file;
299 }
300 }
301
302 /*
303 * For man(1), -a and -i output mode, fall through
304 * to the main mandoc(1) code iterating files
305 * and running the parsers on each of them.
306 */
307
308 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
309 goto out;
310 #else
311 fputs("mandoc: database support not compiled in\n",
312 stderr);
313 return((int)MANDOCLEVEL_BADARG);
314 #endif
315 }
316
317 /* mandoc(1) */
318
319 if ( ! moptions(&options, auxpaths))
320 return((int)MANDOCLEVEL_BADARG);
321
322 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
323
324 /*
325 * Conditionally start up the lookaside buffer before parsing.
326 */
327 if (OUTT_MAN == curp.outtype)
328 mparse_keep(curp.mp);
329
330 if (NULL == *argv)
331 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
332
333 while (*argv) {
334 parse(&curp, -1, *argv, &rc);
335 if (MANDOCLEVEL_OK != rc && curp.wstop)
336 break;
337 ++argv;
338 }
339
340 if (curp.outfree)
341 (*curp.outfree)(curp.outdata);
342 if (curp.mp)
343 mparse_free(curp.mp);
344
345 #if HAVE_SQLITE3
346 out:
347 if (search.argmode != ARG_FILE) {
348 mansearch_free(res, sz);
349 mansearch_setup(0);
350 free(auxargv);
351 }
352 #endif
353
354 free(defos);
355
356 return((int)rc);
357 }
358
359 static void
360 version(void)
361 {
362
363 printf("mandoc %s\n", VERSION);
364 exit((int)MANDOCLEVEL_OK);
365 }
366
367 static void
368 usage(enum argmode argmode)
369 {
370
371 switch (argmode) {
372 case ARG_FILE:
373 fputs("usage: mandoc [-V] [-Ios=name] [-mformat]"
374 " [-Ooption] [-Toutput] [-Wlevel]\n"
375 "\t [file ...]\n", stderr);
376 break;
377 case ARG_NAME:
378 fputs("usage: man [-acfhkVw] [-C file] "
379 "[-M path] [-m path] [-S arch] [-s section]\n"
380 "\t [section] name ...\n", stderr);
381 break;
382 case ARG_WORD:
383 fputs("usage: whatis [-V] [-C file] [-M path] [-m path] "
384 "[-S arch] [-s section] name ...\n", stderr);
385 break;
386 case ARG_EXPR:
387 fputs("usage: apropos [-V] [-C file] [-M path] [-m path] "
388 "[-O outkey] [-S arch]\n"
389 "\t [-s section] expression ...\n", stderr);
390 break;
391 }
392 exit((int)MANDOCLEVEL_BADARG);
393 }
394
395 static void
396 parse(struct curparse *curp, int fd, const char *file,
397 enum mandoclevel *level)
398 {
399 enum mandoclevel rc;
400 struct mdoc *mdoc;
401 struct man *man;
402
403 /* Begin by parsing the file itself. */
404
405 assert(file);
406 assert(fd >= -1);
407
408 rc = mparse_readfd(curp->mp, fd, file);
409
410 /* Stop immediately if the parse has failed. */
411
412 if (MANDOCLEVEL_FATAL <= rc)
413 goto cleanup;
414
415 /*
416 * With -Wstop and warnings or errors of at least the requested
417 * level, do not produce output.
418 */
419
420 if (MANDOCLEVEL_OK != rc && curp->wstop)
421 goto cleanup;
422
423 /* If unset, allocate output dev now (if applicable). */
424
425 if ( ! (curp->outman && curp->outmdoc)) {
426 switch (curp->outtype) {
427 case OUTT_XHTML:
428 curp->outdata = xhtml_alloc(curp->outopts);
429 curp->outfree = html_free;
430 break;
431 case OUTT_HTML:
432 curp->outdata = html_alloc(curp->outopts);
433 curp->outfree = html_free;
434 break;
435 case OUTT_UTF8:
436 curp->outdata = utf8_alloc(curp->outopts);
437 curp->outfree = ascii_free;
438 break;
439 case OUTT_LOCALE:
440 curp->outdata = locale_alloc(curp->outopts);
441 curp->outfree = ascii_free;
442 break;
443 case OUTT_ASCII:
444 curp->outdata = ascii_alloc(curp->outopts);
445 curp->outfree = ascii_free;
446 break;
447 case OUTT_PDF:
448 curp->outdata = pdf_alloc(curp->outopts);
449 curp->outfree = pspdf_free;
450 break;
451 case OUTT_PS:
452 curp->outdata = ps_alloc(curp->outopts);
453 curp->outfree = pspdf_free;
454 break;
455 default:
456 break;
457 }
458
459 switch (curp->outtype) {
460 case OUTT_HTML:
461 /* FALLTHROUGH */
462 case OUTT_XHTML:
463 curp->outman = html_man;
464 curp->outmdoc = html_mdoc;
465 break;
466 case OUTT_TREE:
467 curp->outman = tree_man;
468 curp->outmdoc = tree_mdoc;
469 break;
470 case OUTT_MAN:
471 curp->outmdoc = man_mdoc;
472 curp->outman = man_man;
473 break;
474 case OUTT_PDF:
475 /* FALLTHROUGH */
476 case OUTT_ASCII:
477 /* FALLTHROUGH */
478 case OUTT_UTF8:
479 /* FALLTHROUGH */
480 case OUTT_LOCALE:
481 /* FALLTHROUGH */
482 case OUTT_PS:
483 curp->outman = terminal_man;
484 curp->outmdoc = terminal_mdoc;
485 break;
486 default:
487 break;
488 }
489 }
490
491 mparse_result(curp->mp, &mdoc, &man, NULL);
492
493 /* Execute the out device, if it exists. */
494
495 if (man && curp->outman)
496 (*curp->outman)(curp->outdata, man);
497 if (mdoc && curp->outmdoc)
498 (*curp->outmdoc)(curp->outdata, mdoc);
499
500 cleanup:
501
502 mparse_reset(curp->mp);
503
504 if (*level < rc)
505 *level = rc;
506 }
507
508 static int
509 moptions(int *options, char *arg)
510 {
511
512 if (arg == NULL)
513 /* nothing to do */;
514 else if (0 == strcmp(arg, "doc"))
515 *options |= MPARSE_MDOC;
516 else if (0 == strcmp(arg, "andoc"))
517 /* nothing to do */;
518 else if (0 == strcmp(arg, "an"))
519 *options |= MPARSE_MAN;
520 else {
521 fprintf(stderr, "%s: -m%s: Bad argument\n",
522 progname, arg);
523 return(0);
524 }
525
526 return(1);
527 }
528
529 static int
530 toptions(struct curparse *curp, char *arg)
531 {
532
533 if (0 == strcmp(arg, "ascii"))
534 curp->outtype = OUTT_ASCII;
535 else if (0 == strcmp(arg, "lint")) {
536 curp->outtype = OUTT_LINT;
537 curp->wlevel = MANDOCLEVEL_WARNING;
538 } else if (0 == strcmp(arg, "tree"))
539 curp->outtype = OUTT_TREE;
540 else if (0 == strcmp(arg, "man"))
541 curp->outtype = OUTT_MAN;
542 else if (0 == strcmp(arg, "html"))
543 curp->outtype = OUTT_HTML;
544 else if (0 == strcmp(arg, "utf8"))
545 curp->outtype = OUTT_UTF8;
546 else if (0 == strcmp(arg, "locale"))
547 curp->outtype = OUTT_LOCALE;
548 else if (0 == strcmp(arg, "xhtml"))
549 curp->outtype = OUTT_XHTML;
550 else if (0 == strcmp(arg, "ps"))
551 curp->outtype = OUTT_PS;
552 else if (0 == strcmp(arg, "pdf"))
553 curp->outtype = OUTT_PDF;
554 else {
555 fprintf(stderr, "%s: -T%s: Bad argument\n",
556 progname, arg);
557 return(0);
558 }
559
560 return(1);
561 }
562
563 static int
564 woptions(struct curparse *curp, char *arg)
565 {
566 char *v, *o;
567 const char *toks[6];
568
569 toks[0] = "stop";
570 toks[1] = "all";
571 toks[2] = "warning";
572 toks[3] = "error";
573 toks[4] = "fatal";
574 toks[5] = NULL;
575
576 while (*arg) {
577 o = arg;
578 switch (getsubopt(&arg, UNCONST(toks), &v)) {
579 case 0:
580 curp->wstop = 1;
581 break;
582 case 1:
583 /* FALLTHROUGH */
584 case 2:
585 curp->wlevel = MANDOCLEVEL_WARNING;
586 break;
587 case 3:
588 curp->wlevel = MANDOCLEVEL_ERROR;
589 break;
590 case 4:
591 curp->wlevel = MANDOCLEVEL_FATAL;
592 break;
593 default:
594 fprintf(stderr, "%s: -W%s: Bad argument\n",
595 progname, o);
596 return(0);
597 }
598 }
599
600 return(1);
601 }
602
603 static void
604 mmsg(enum mandocerr t, enum mandoclevel lvl,
605 const char *file, int line, int col, const char *msg)
606 {
607 const char *mparse_msg;
608
609 fprintf(stderr, "%s: %s:", progname, file);
610
611 if (line)
612 fprintf(stderr, "%d:%d:", line, col + 1);
613
614 fprintf(stderr, " %s", mparse_strlevel(lvl));
615
616 if (NULL != (mparse_msg = mparse_strerror(t)))
617 fprintf(stderr, ": %s", mparse_msg);
618
619 if (msg)
620 fprintf(stderr, ": %s", msg);
621
622 fputc('\n', stderr);
623 }