1/*	$Id: main.c,v 1.165 2011/10/06 22:29:12 kristaps Exp $ */
2/*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#ifdef HAVE_CONFIG_H
19#include "config.h"
20#endif
21
22#include <assert.h>
23#include <stdio.h>
24#include <stdint.h>
25#include <stdlib.h>
26#include <string.h>
27#include <unistd.h>
28
29#include "mandoc.h"
30#include "main.h"
31#include "mdoc.h"
32#include "man.h"
33
34#if !defined(__GNUC__) || (__GNUC__ < 2)
35# if !defined(lint)
36#  define __attribute__(x)
37# endif
38#endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
39
40typedef	void		(*out_mdoc)(void *, const struct mdoc *);
41typedef	void		(*out_man)(void *, const struct man *);
42typedef	void		(*out_free)(void *);
43
44enum	outt {
45	OUTT_ASCII = 0,	/* -Tascii */
46	OUTT_LOCALE,	/* -Tlocale */
47	OUTT_UTF8,	/* -Tutf8 */
48	OUTT_TREE,	/* -Ttree */
49	OUTT_MAN,	/* -Tman */
50	OUTT_HTML,	/* -Thtml */
51	OUTT_XHTML,	/* -Txhtml */
52	OUTT_LINT,	/* -Tlint */
53	OUTT_PS,	/* -Tps */
54	OUTT_PDF	/* -Tpdf */
55};
56
57struct	curparse {
58	struct mparse	 *mp;
59	enum mandoclevel  wlevel;	/* ignore messages below this */
60	int		  wstop;	/* stop after a file with a warning */
61	enum outt	  outtype; 	/* which output to use */
62	out_mdoc	  outmdoc;	/* mdoc output ptr */
63	out_man	  	  outman;	/* man output ptr */
64	out_free	  outfree;	/* free output ptr */
65	void		 *outdata;	/* data for output */
66	char		  outopts[BUFSIZ]; /* buf of output opts */
67};
68
69static	int		  moptions(enum mparset *, char *);
70static	void		  mmsg(enum mandocerr, enum mandoclevel,
71				const char *, int, int, const char *);
72static	void		  parse(struct curparse *, int,
73				const char *, enum mandoclevel *);
74static	int		  toptions(struct curparse *, char *);
75static	void		  usage(void) __attribute__((noreturn));
76static	void		  version(void) __attribute__((noreturn));
77static	int		  woptions(struct curparse *, char *);
78
79static	const char	 *progname;
80
81int
82main(int argc, char *argv[])
83{
84	int		 c;
85	struct curparse	 curp;
86	enum mparset	 type;
87	enum mandoclevel rc;
88
89	progname = strrchr(argv[0], '/');
90	if (progname == NULL)
91		progname = argv[0];
92	else
93		++progname;
94
95	memset(&curp, 0, sizeof(struct curparse));
96
97	type = MPARSE_AUTO;
98	curp.outtype = OUTT_ASCII;
99	curp.wlevel  = MANDOCLEVEL_FATAL;
100
101	/* LINTED */
102	while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
103		switch (c) {
104		case ('m'):
105			if ( ! moptions(&type, optarg))
106				return((int)MANDOCLEVEL_BADARG);
107			break;
108		case ('O'):
109			(void)strlcat(curp.outopts, optarg, BUFSIZ);
110			(void)strlcat(curp.outopts, ",", BUFSIZ);
111			break;
112		case ('T'):
113			if ( ! toptions(&curp, optarg))
114				return((int)MANDOCLEVEL_BADARG);
115			break;
116		case ('W'):
117			if ( ! woptions(&curp, optarg))
118				return((int)MANDOCLEVEL_BADARG);
119			break;
120		case ('V'):
121			version();
122			/* NOTREACHED */
123		default:
124			usage();
125			/* NOTREACHED */
126		}
127
128	curp.mp = mparse_alloc(type, curp.wlevel, mmsg, &curp);
129
130	/*
131	 * Conditionally start up the lookaside buffer before parsing.
132	 */
133	if (OUTT_MAN == curp.outtype)
134		mparse_keep(curp.mp);
135
136	argc -= optind;
137	argv += optind;
138
139	rc = MANDOCLEVEL_OK;
140
141	if (NULL == *argv)
142		parse(&curp, STDIN_FILENO, "<stdin>", &rc);
143
144	while (*argv) {
145		parse(&curp, -1, *argv, &rc);
146		if (MANDOCLEVEL_OK != rc && curp.wstop)
147			break;
148		++argv;
149	}
150
151	if (curp.outfree)
152		(*curp.outfree)(curp.outdata);
153	if (curp.mp)
154		mparse_free(curp.mp);
155
156	return((int)rc);
157}
158
159static void
160version(void)
161{
162
163	printf("%s %s\n", progname, VERSION);
164	exit((int)MANDOCLEVEL_OK);
165}
166
167static void
168usage(void)
169{
170
171	fprintf(stderr, "usage: %s "
172			"[-V] "
173			"[-foption] "
174			"[-mformat] "
175			"[-Ooption] "
176			"[-Toutput] "
177			"[-Wlevel] "
178			"[file...]\n",
179			progname);
180
181	exit((int)MANDOCLEVEL_BADARG);
182}
183
184static void
185parse(struct curparse *curp, int fd,
186		const char *file, enum mandoclevel *level)
187{
188	enum mandoclevel  rc;
189	struct mdoc	 *mdoc;
190	struct man	 *man;
191
192	/* Begin by parsing the file itself. */
193
194	assert(file);
195	assert(fd >= -1);
196
197	rc = mparse_readfd(curp->mp, fd, file);
198
199	/* Stop immediately if the parse has failed. */
200
201	if (MANDOCLEVEL_FATAL <= rc)
202		goto cleanup;
203
204	/*
205	 * With -Wstop and warnings or errors of at least the requested
206	 * level, do not produce output.
207	 */
208
209	if (MANDOCLEVEL_OK != rc && curp->wstop)
210		goto cleanup;
211
212	/* If unset, allocate output dev now (if applicable). */
213
214	if ( ! (curp->outman && curp->outmdoc)) {
215		switch (curp->outtype) {
216		case (OUTT_XHTML):
217			curp->outdata = xhtml_alloc(curp->outopts);
218			curp->outfree = html_free;
219			break;
220		case (OUTT_HTML):
221			curp->outdata = html_alloc(curp->outopts);
222			curp->outfree = html_free;
223			break;
224		case (OUTT_UTF8):
225			curp->outdata = utf8_alloc(curp->outopts);
226			curp->outfree = ascii_free;
227			break;
228		case (OUTT_LOCALE):
229			curp->outdata = locale_alloc(curp->outopts);
230			curp->outfree = ascii_free;
231			break;
232		case (OUTT_ASCII):
233			curp->outdata = ascii_alloc(curp->outopts);
234			curp->outfree = ascii_free;
235			break;
236		case (OUTT_PDF):
237			curp->outdata = pdf_alloc(curp->outopts);
238			curp->outfree = pspdf_free;
239			break;
240		case (OUTT_PS):
241			curp->outdata = ps_alloc(curp->outopts);
242			curp->outfree = pspdf_free;
243			break;
244		default:
245			break;
246		}
247
248		switch (curp->outtype) {
249		case (OUTT_HTML):
250			/* FALLTHROUGH */
251		case (OUTT_XHTML):
252			curp->outman = html_man;
253			curp->outmdoc = html_mdoc;
254			break;
255		case (OUTT_TREE):
256			curp->outman = tree_man;
257			curp->outmdoc = tree_mdoc;
258			break;
259		case (OUTT_MAN):
260			curp->outmdoc = man_mdoc;
261			curp->outman = man_man;
262			break;
263		case (OUTT_PDF):
264			/* FALLTHROUGH */
265		case (OUTT_ASCII):
266			/* FALLTHROUGH */
267		case (OUTT_UTF8):
268			/* FALLTHROUGH */
269		case (OUTT_LOCALE):
270			/* FALLTHROUGH */
271		case (OUTT_PS):
272			curp->outman = terminal_man;
273			curp->outmdoc = terminal_mdoc;
274			break;
275		default:
276			break;
277		}
278	}
279
280	mparse_result(curp->mp, &mdoc, &man);
281
282	/* Execute the out device, if it exists. */
283
284	if (man && curp->outman)
285		(*curp->outman)(curp->outdata, man);
286	if (mdoc && curp->outmdoc)
287		(*curp->outmdoc)(curp->outdata, mdoc);
288
289 cleanup:
290
291	mparse_reset(curp->mp);
292
293	if (*level < rc)
294		*level = rc;
295}
296
297static int
298moptions(enum mparset *tflags, char *arg)
299{
300
301	if (0 == strcmp(arg, "doc"))
302		*tflags = MPARSE_MDOC;
303	else if (0 == strcmp(arg, "andoc"))
304		*tflags = MPARSE_AUTO;
305	else if (0 == strcmp(arg, "an"))
306		*tflags = MPARSE_MAN;
307	else {
308		fprintf(stderr, "%s: Bad argument\n", arg);
309		return(0);
310	}
311
312	return(1);
313}
314
315static int
316toptions(struct curparse *curp, char *arg)
317{
318
319	if (0 == strcmp(arg, "ascii"))
320		curp->outtype = OUTT_ASCII;
321	else if (0 == strcmp(arg, "lint")) {
322		curp->outtype = OUTT_LINT;
323		curp->wlevel  = MANDOCLEVEL_WARNING;
324	} else if (0 == strcmp(arg, "tree"))
325		curp->outtype = OUTT_TREE;
326	else if (0 == strcmp(arg, "man"))
327		curp->outtype = OUTT_MAN;
328	else if (0 == strcmp(arg, "html"))
329		curp->outtype = OUTT_HTML;
330	else if (0 == strcmp(arg, "utf8"))
331		curp->outtype = OUTT_UTF8;
332	else if (0 == strcmp(arg, "locale"))
333		curp->outtype = OUTT_LOCALE;
334	else if (0 == strcmp(arg, "xhtml"))
335		curp->outtype = OUTT_XHTML;
336	else if (0 == strcmp(arg, "ps"))
337		curp->outtype = OUTT_PS;
338	else if (0 == strcmp(arg, "pdf"))
339		curp->outtype = OUTT_PDF;
340	else {
341		fprintf(stderr, "%s: Bad argument\n", arg);
342		return(0);
343	}
344
345	return(1);
346}
347
348static int
349woptions(struct curparse *curp, char *arg)
350{
351	char		*v, *o;
352	const char	*toks[6];
353
354	toks[0] = "stop";
355	toks[1] = "all";
356	toks[2] = "warning";
357	toks[3] = "error";
358	toks[4] = "fatal";
359	toks[5] = NULL;
360
361	while (*arg) {
362		o = arg;
363		switch (getsubopt(&arg, UNCONST(toks), &v)) {
364		case (0):
365			curp->wstop = 1;
366			break;
367		case (1):
368			/* FALLTHROUGH */
369		case (2):
370			curp->wlevel = MANDOCLEVEL_WARNING;
371			break;
372		case (3):
373			curp->wlevel = MANDOCLEVEL_ERROR;
374			break;
375		case (4):
376			curp->wlevel = MANDOCLEVEL_FATAL;
377			break;
378		default:
379			fprintf(stderr, "-W%s: Bad argument\n", o);
380			return(0);
381		}
382	}
383
384	return(1);
385}
386
387static void
388mmsg(enum mandocerr t, enum mandoclevel lvl,
389		const char *file, int line, int col, const char *msg)
390{
391
392	fprintf(stderr, "%s:%d:%d: %s: %s",
393			file, line, col + 1,
394			mparse_strlevel(lvl),
395			mparse_strerror(t));
396
397	if (msg)
398		fprintf(stderr, ": %s", msg);
399
400	fputc('\n', stderr);
401}
402