xref: /openbsd-src/usr.bin/mandoc/main.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$Id: main.c,v 1.94 2014/06/21 22:23:44 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 
20 #include <assert.h>
21 #include <stdio.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include "mandoc.h"
28 #include "mandoc_aux.h"
29 #include "main.h"
30 #include "mdoc.h"
31 #include "man.h"
32 
33 typedef	void		(*out_mdoc)(void *, const struct mdoc *);
34 typedef	void		(*out_man)(void *, const struct man *);
35 typedef	void		(*out_free)(void *);
36 
37 enum	outt {
38 	OUTT_ASCII = 0,	/* -Tascii */
39 	OUTT_LOCALE,	/* -Tlocale */
40 	OUTT_UTF8,	/* -Tutf8 */
41 	OUTT_TREE,	/* -Ttree */
42 	OUTT_MAN,	/* -Tman */
43 	OUTT_HTML,	/* -Thtml */
44 	OUTT_XHTML,	/* -Txhtml */
45 	OUTT_LINT,	/* -Tlint */
46 	OUTT_PS,	/* -Tps */
47 	OUTT_PDF	/* -Tpdf */
48 };
49 
50 struct	curparse {
51 	struct mparse	 *mp;
52 	enum mandoclevel  wlevel;	/* ignore messages below this */
53 	int		  wstop;	/* stop after a file with a warning */
54 	enum outt	  outtype;	/* which output to use */
55 	out_mdoc	  outmdoc;	/* mdoc output ptr */
56 	out_man		  outman;	/* man output ptr */
57 	out_free	  outfree;	/* free output ptr */
58 	void		 *outdata;	/* data for output */
59 	char		  outopts[BUFSIZ]; /* buf of output opts */
60 };
61 
62 int			  apropos(int, char**);
63 int			  mandocdb(int, char**);
64 
65 static	int		  moptions(int *, char *);
66 static	void		  mmsg(enum mandocerr, enum mandoclevel,
67 				const char *, int, int, const char *);
68 static	void		  parse(struct curparse *, int,
69 				const char *, enum mandoclevel *);
70 static	int		  toptions(struct curparse *, char *);
71 static	void		  usage(void) __attribute__((noreturn));
72 static	void		  version(void) __attribute__((noreturn));
73 static	int		  woptions(struct curparse *, char *);
74 
75 static	const char	 *progname;
76 
77 
78 int
79 main(int argc, char *argv[])
80 {
81 	int		 c;
82 	struct curparse	 curp;
83 	int		 options;
84 	enum mandoclevel rc;
85 	char		*defos;
86 
87 	progname = strrchr(argv[0], '/');
88 	if (progname == NULL)
89 		progname = argv[0];
90 	else
91 		++progname;
92 
93 	if (0 == strncmp(progname, "apropos", 7) ||
94 	    0 == strncmp(progname, "whatis", 6))
95 		return(apropos(argc, argv));
96 	if (0 == strncmp(progname, "mandocdb", 8) ||
97 	    0 == strncmp(progname, "makewhatis", 10))
98 		return(mandocdb(argc, argv));
99 
100 	memset(&curp, 0, sizeof(struct curparse));
101 
102 	options = MPARSE_SO;
103 	curp.outtype = OUTT_ASCII;
104 	curp.wlevel  = MANDOCLEVEL_FATAL;
105 	defos = NULL;
106 
107 	while (-1 != (c = getopt(argc, argv, "I:m:O:T:VW:")))
108 		switch (c) {
109 		case 'I':
110 			if (strncmp(optarg, "os=", 3)) {
111 				fprintf(stderr,
112 				    "%s: -I%s: Bad argument\n",
113 				    progname, optarg);
114 				return((int)MANDOCLEVEL_BADARG);
115 			}
116 			if (defos) {
117 				fprintf(stderr,
118 				    "%s: -I%s: Duplicate argument\n",
119 				    progname, optarg);
120 				return((int)MANDOCLEVEL_BADARG);
121 			}
122 			defos = mandoc_strdup(optarg + 3);
123 			break;
124 		case 'm':
125 			if ( ! moptions(&options, optarg))
126 				return((int)MANDOCLEVEL_BADARG);
127 			break;
128 		case 'O':
129 			(void)strlcat(curp.outopts, optarg, BUFSIZ);
130 			(void)strlcat(curp.outopts, ",", BUFSIZ);
131 			break;
132 		case 'T':
133 			if ( ! toptions(&curp, optarg))
134 				return((int)MANDOCLEVEL_BADARG);
135 			break;
136 		case 'W':
137 			if ( ! woptions(&curp, optarg))
138 				return((int)MANDOCLEVEL_BADARG);
139 			break;
140 		case 'V':
141 			version();
142 			/* NOTREACHED */
143 		default:
144 			usage();
145 			/* NOTREACHED */
146 		}
147 
148 	curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
149 
150 	/*
151 	 * Conditionally start up the lookaside buffer before parsing.
152 	 */
153 	if (OUTT_MAN == curp.outtype)
154 		mparse_keep(curp.mp);
155 
156 	argc -= optind;
157 	argv += optind;
158 
159 	rc = MANDOCLEVEL_OK;
160 
161 	if (NULL == *argv)
162 		parse(&curp, STDIN_FILENO, "<stdin>", &rc);
163 
164 	while (*argv) {
165 		parse(&curp, -1, *argv, &rc);
166 		if (MANDOCLEVEL_OK != rc && curp.wstop)
167 			break;
168 		++argv;
169 	}
170 
171 	if (curp.outfree)
172 		(*curp.outfree)(curp.outdata);
173 	if (curp.mp)
174 		mparse_free(curp.mp);
175 	free(defos);
176 
177 	return((int)rc);
178 }
179 
180 static void
181 version(void)
182 {
183 
184 	printf("%s %s\n", progname, VERSION);
185 	exit((int)MANDOCLEVEL_OK);
186 }
187 
188 static void
189 usage(void)
190 {
191 
192 	fprintf(stderr, "usage: %s "
193 			"[-V] "
194 			"[-Ios=name] "
195 			"[-mformat] "
196 			"[-Ooption] "
197 			"[-Toutput] "
198 			"[-Wlevel]\n"
199 			"\t      [file ...]\n",
200 			progname);
201 
202 	exit((int)MANDOCLEVEL_BADARG);
203 }
204 
205 static void
206 parse(struct curparse *curp, int fd, const char *file,
207 	enum mandoclevel *level)
208 {
209 	enum mandoclevel  rc;
210 	struct mdoc	 *mdoc;
211 	struct man	 *man;
212 
213 	/* Begin by parsing the file itself. */
214 
215 	assert(file);
216 	assert(fd >= -1);
217 
218 	rc = mparse_readfd(curp->mp, fd, file);
219 
220 	/* Stop immediately if the parse has failed. */
221 
222 	if (MANDOCLEVEL_FATAL <= rc)
223 		goto cleanup;
224 
225 	/*
226 	 * With -Wstop and warnings or errors of at least the requested
227 	 * level, do not produce output.
228 	 */
229 
230 	if (MANDOCLEVEL_OK != rc && curp->wstop)
231 		goto cleanup;
232 
233 	/* If unset, allocate output dev now (if applicable). */
234 
235 	if ( ! (curp->outman && curp->outmdoc)) {
236 		switch (curp->outtype) {
237 		case OUTT_XHTML:
238 			curp->outdata = xhtml_alloc(curp->outopts);
239 			curp->outfree = html_free;
240 			break;
241 		case OUTT_HTML:
242 			curp->outdata = html_alloc(curp->outopts);
243 			curp->outfree = html_free;
244 			break;
245 		case OUTT_UTF8:
246 			curp->outdata = utf8_alloc(curp->outopts);
247 			curp->outfree = ascii_free;
248 			break;
249 		case OUTT_LOCALE:
250 			curp->outdata = locale_alloc(curp->outopts);
251 			curp->outfree = ascii_free;
252 			break;
253 		case OUTT_ASCII:
254 			curp->outdata = ascii_alloc(curp->outopts);
255 			curp->outfree = ascii_free;
256 			break;
257 		case OUTT_PDF:
258 			curp->outdata = pdf_alloc(curp->outopts);
259 			curp->outfree = pspdf_free;
260 			break;
261 		case OUTT_PS:
262 			curp->outdata = ps_alloc(curp->outopts);
263 			curp->outfree = pspdf_free;
264 			break;
265 		default:
266 			break;
267 		}
268 
269 		switch (curp->outtype) {
270 		case OUTT_HTML:
271 			/* FALLTHROUGH */
272 		case OUTT_XHTML:
273 			curp->outman = html_man;
274 			curp->outmdoc = html_mdoc;
275 			break;
276 		case OUTT_TREE:
277 			curp->outman = tree_man;
278 			curp->outmdoc = tree_mdoc;
279 			break;
280 		case OUTT_MAN:
281 			curp->outmdoc = man_mdoc;
282 			curp->outman = man_man;
283 			break;
284 		case OUTT_PDF:
285 			/* FALLTHROUGH */
286 		case OUTT_ASCII:
287 			/* FALLTHROUGH */
288 		case OUTT_UTF8:
289 			/* FALLTHROUGH */
290 		case OUTT_LOCALE:
291 			/* FALLTHROUGH */
292 		case OUTT_PS:
293 			curp->outman = terminal_man;
294 			curp->outmdoc = terminal_mdoc;
295 			break;
296 		default:
297 			break;
298 		}
299 	}
300 
301 	mparse_result(curp->mp, &mdoc, &man, NULL);
302 
303 	/* Execute the out device, if it exists. */
304 
305 	if (man && curp->outman)
306 		(*curp->outman)(curp->outdata, man);
307 	if (mdoc && curp->outmdoc)
308 		(*curp->outmdoc)(curp->outdata, mdoc);
309 
310  cleanup:
311 
312 	mparse_reset(curp->mp);
313 
314 	if (*level < rc)
315 		*level = rc;
316 }
317 
318 static int
319 moptions(int *options, char *arg)
320 {
321 
322 	if (0 == strcmp(arg, "doc"))
323 		*options |= MPARSE_MDOC;
324 	else if (0 == strcmp(arg, "andoc"))
325 		/* nothing to do */;
326 	else if (0 == strcmp(arg, "an"))
327 		*options |= MPARSE_MAN;
328 	else {
329 		fprintf(stderr, "%s: -m%s: Bad argument\n",
330 		    progname, arg);
331 		return(0);
332 	}
333 
334 	return(1);
335 }
336 
337 static int
338 toptions(struct curparse *curp, char *arg)
339 {
340 
341 	if (0 == strcmp(arg, "ascii"))
342 		curp->outtype = OUTT_ASCII;
343 	else if (0 == strcmp(arg, "lint")) {
344 		curp->outtype = OUTT_LINT;
345 		curp->wlevel  = MANDOCLEVEL_WARNING;
346 	} else if (0 == strcmp(arg, "tree"))
347 		curp->outtype = OUTT_TREE;
348 	else if (0 == strcmp(arg, "man"))
349 		curp->outtype = OUTT_MAN;
350 	else if (0 == strcmp(arg, "html"))
351 		curp->outtype = OUTT_HTML;
352 	else if (0 == strcmp(arg, "utf8"))
353 		curp->outtype = OUTT_UTF8;
354 	else if (0 == strcmp(arg, "locale"))
355 		curp->outtype = OUTT_LOCALE;
356 	else if (0 == strcmp(arg, "xhtml"))
357 		curp->outtype = OUTT_XHTML;
358 	else if (0 == strcmp(arg, "ps"))
359 		curp->outtype = OUTT_PS;
360 	else if (0 == strcmp(arg, "pdf"))
361 		curp->outtype = OUTT_PDF;
362 	else {
363 		fprintf(stderr, "%s: -T%s: Bad argument\n",
364 		    progname, arg);
365 		return(0);
366 	}
367 
368 	return(1);
369 }
370 
371 static int
372 woptions(struct curparse *curp, char *arg)
373 {
374 	char		*v, *o;
375 	const char	*toks[6];
376 
377 	toks[0] = "stop";
378 	toks[1] = "all";
379 	toks[2] = "warning";
380 	toks[3] = "error";
381 	toks[4] = "fatal";
382 	toks[5] = NULL;
383 
384 	while (*arg) {
385 		o = arg;
386 		switch (getsubopt(&arg, UNCONST(toks), &v)) {
387 		case 0:
388 			curp->wstop = 1;
389 			break;
390 		case 1:
391 			/* FALLTHROUGH */
392 		case 2:
393 			curp->wlevel = MANDOCLEVEL_WARNING;
394 			break;
395 		case 3:
396 			curp->wlevel = MANDOCLEVEL_ERROR;
397 			break;
398 		case 4:
399 			curp->wlevel = MANDOCLEVEL_FATAL;
400 			break;
401 		default:
402 			fprintf(stderr, "%s: -W%s: Bad argument\n",
403 			    progname, o);
404 			return(0);
405 		}
406 	}
407 
408 	return(1);
409 }
410 
411 static void
412 mmsg(enum mandocerr t, enum mandoclevel lvl,
413 		const char *file, int line, int col, const char *msg)
414 {
415 	const char	*mparse_msg;
416 
417 	fprintf(stderr, "%s: %s:", progname, file);
418 
419 	if (line)
420 		fprintf(stderr, "%d:%d:", line, col + 1);
421 
422 	fprintf(stderr, " %s", mparse_strlevel(lvl));
423 
424 	if (NULL != (mparse_msg = mparse_strerror(t)))
425 		fprintf(stderr, ": %s", mparse_msg);
426 
427 	if (msg)
428 		fprintf(stderr, ": %s", msg);
429 
430 	fputc('\n', stderr);
431 }
432