]> git.cameronkatri.com Git - mandoc.git/blob - mmain.c
952393e059820e53b8e5a25d39a3d454e5752eb9
[mandoc.git] / mmain.c
1 /* $Id: mmain.c,v 1.7 2009/03/08 18:02:36 kristaps Exp $ */
2 /*
3 * Copyright (c) 2008 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
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 #include <sys/param.h>
21
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <err.h>
25 #include <getopt.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30
31 #include "mmain.h"
32
33 #define MD_LINE_SZ (256) /* Max input line size. */
34
35 struct mmain {
36 int warn; /* Warning flags. */
37 #define MD_WARN_SYNTAX (1 << 0) /* Show syntax warnings. */
38 #define MD_WARN_COMPAT (1 << 1) /* Show compat warnings. */
39 #define MD_WARN_ALL (0x03) /* Show all warnings. */
40 #define MD_WARN_ERR (1 << 2) /* Make warnings->errors. */
41 int dbg; /* Debug level. */
42 struct mdoc *mdoc; /* Active parser. */
43 char *buf; /* Input buffer. */
44 u_long bufsz; /* Input buffer size. */
45 char *in; /* Input file name. */
46 int fdin; /* Input file desc. */
47 int pflags; /* Parse flags. */
48 };
49
50 extern char *__progname;
51
52 static int optswarn(struct mmain *, char *);
53 static int optsopt(struct mmain *, char *);
54 static int parse(struct mmain *);
55 static void msg_msg(void *, int, int, const char *);
56 static int msg_err(void *, int, int, const char *);
57 static int msg_warn(void *, int, int,
58 enum mdoc_warn, const char *);
59
60 #ifdef __linux__
61 extern int getsubopt(char **, char *const *, char **);
62 extern size_t strlcpy(char *, const char *, size_t);
63 extern size_t strlcat(char *, const char *, size_t);
64 #endif
65
66
67 /*
68 * Print our and our caller's usage message.
69 */
70 void
71 mmain_usage(const char *help)
72 {
73
74 warnx("usage: %s %s%s[-v] [-foption...] [-Wwarn...] [infile]", __progname,
75 help ? help : "", help ? " " : "");
76 }
77
78
79 /*
80 * Allocate the convenience library and initialise some values.
81 */
82 struct mmain *
83 mmain_alloc(void)
84 {
85 struct mmain *p;
86
87 if (NULL == (p = calloc(1, sizeof(struct mmain))))
88 err(1, "malloc");
89
90 p->in = "-";
91 p->fdin = STDIN_FILENO;
92
93 return(p);
94 }
95
96
97 /*
98 * Parse command-line options. Accepts a small (<28 char) opstring "u"
99 * parameter (e.g. "ho:") or NULL, a corresponding "help" string (e.g.
100 * "[-h] [-o output]" or NULL, a callback function for parsed arguments
101 * and an opaque pointer argument for that function.
102 */
103 int
104 mmain_getopt(struct mmain *p, int argc, char *argv[],
105 const char *help, const char *u, void *arg,
106 int (*getopt_cb)(void *, int, const char *))
107 {
108 int c;
109 char opts[32]; /* XXX */
110 size_t sz;
111
112 extern int optind;
113
114 sz = strlcpy(opts, "VvW:f:", 32);
115 assert(sz < 32);
116
117 if (u) {
118 sz = strlcat(opts, u, 32);
119 assert(sz < 32);
120 }
121
122 optind = 1;
123
124 /* LINTED */
125 while (-1 != (c = getopt(argc, argv, opts)))
126 switch (c) {
127 case ('f'):
128 if ( ! optsopt(p, optarg))
129 return(-1);
130 break;
131 case ('v'):
132 p->dbg++;
133 break;
134 case ('V'):
135 (void)printf("%s %s\n", __progname, VERSION);
136 return(0);
137 case ('W'):
138 if ( ! optswarn(p, optarg))
139 return(-1);
140 break;
141 case ('?'):
142 mmain_usage(help);
143 return(-1);
144 default:
145 assert(getopt_cb);
146 if ((*getopt_cb)(arg, c, optarg))
147 break;
148 return(-1);
149 }
150
151 argv += optind;
152 if ((argc -= optind) > 0)
153 p->in = *argv++;
154
155 return(1);
156 }
157
158
159 dead_pre void
160 mmain_exit(struct mmain *p, int code)
161 {
162
163 if (p->mdoc)
164 mdoc_free(p->mdoc);
165 free(p);
166 exit(code);
167 }
168
169
170 struct mdoc *
171 mmain_mdoc(struct mmain *p)
172 {
173 struct stat st;
174 int c;
175 struct mdoc_cb cb;
176
177 cb.mdoc_err = msg_err;
178 cb.mdoc_warn = msg_warn;
179 cb.mdoc_msg = msg_msg;
180
181 if (0 != strcmp(p->in, "-"))
182 if (-1 == (p->fdin = open(p->in, O_RDONLY, 0))) {
183 warn("%s", p->in);
184 return(0);
185 }
186
187 /* Allocate a buffer to be BUFSIZ/block size. */
188
189 if (-1 == fstat(p->fdin, &st)) {
190 warn("%s", p->in);
191 p->bufsz = BUFSIZ;
192 } else
193 p->bufsz = MAX(st.st_blksize, BUFSIZ);
194
195 p->buf = malloc(p->bufsz);
196 if (NULL == p->buf)
197 err(1, "malloc");
198
199 /* Allocate the parser. */
200
201 p->mdoc = mdoc_alloc(p, p->pflags, &cb);
202
203 /* Parse the input file. */
204
205 c = parse(p);
206 free(p->buf);
207
208 if (STDIN_FILENO != p->fdin)
209 if (-1 == close(p->fdin))
210 warn("%s", p->in);
211
212 return(c ? p->mdoc : NULL);
213 }
214
215
216 static int
217 optsopt(struct mmain *p, char *arg)
218 {
219 char *v;
220 char *toks[] = { "ign-scope", "ign-escape", NULL };
221
222 while (*arg)
223 switch (getsubopt(&arg, toks, &v)) {
224 case (0):
225 p->pflags |= MDOC_IGN_SCOPE;
226 break;
227 case (1):
228 p->pflags |= MDOC_IGN_ESCAPE;
229 break;
230 default:
231 /* FIXME: report? */
232 return(0);
233 }
234
235 return(1);
236 }
237
238
239 static int
240 optswarn(struct mmain *p, char *arg)
241 {
242 char *v;
243 char *toks[] = { "all", "compat",
244 "syntax", "error", NULL };
245
246 while (*arg)
247 switch (getsubopt(&arg, toks, &v)) {
248 case (0):
249 p->warn |= MD_WARN_ALL;
250 break;
251 case (1):
252 p->warn |= MD_WARN_COMPAT;
253 break;
254 case (2):
255 p->warn |= MD_WARN_SYNTAX;
256 break;
257 case (3):
258 p->warn |= MD_WARN_ERR;
259 break;
260 default:
261 /* FIXME: report? */
262 return(0);
263 }
264
265 return(1);
266 }
267
268
269 static int
270 parse(struct mmain *p)
271 {
272 ssize_t sz, i;
273 size_t pos;
274 char line[MD_LINE_SZ];
275 int lnn;
276
277 /*
278 * This is a little more complicated than fgets. TODO: have
279 * some benchmarks that show it's faster (note that I want to
280 * check many, many manuals simultaneously, so speed is
281 * important). Fill a buffer (sized to the block size) with a
282 * single read, then parse \n-terminated lines into a line
283 * buffer, which is passed to the parser. Hard-code the line
284 * buffer to a particular size -- a reasonable assumption.
285 */
286
287 for (lnn = 1, pos = 0; ; ) {
288 if (-1 == (sz = read(p->fdin, p->buf, p->bufsz))) {
289 warn("%s", p->in);
290 return(0);
291 } else if (0 == sz)
292 break;
293
294 for (i = 0; i < sz; i++) {
295 if ('\n' != p->buf[i]) {
296 if (pos < sizeof(line)) {
297 line[(int)pos++] = p->buf[(int)i];
298 continue;
299 }
300 warnx("%s: line %d too long", p->in, lnn);
301 return(0);
302 }
303
304 line[(int)pos] = 0;
305 if ( ! mdoc_parseln(p->mdoc, lnn, line))
306 return(0);
307
308 lnn++;
309 pos = 0;
310 }
311 }
312
313 return(mdoc_endparse(p->mdoc));
314 }
315
316
317 static int
318 msg_err(void *arg, int line, int col, const char *msg)
319 {
320 struct mmain *p;
321
322 p = (struct mmain *)arg;
323
324 warnx("%s:%d: error: %s (column %d)",
325 p->in, line, msg, col);
326 return(0);
327 }
328
329
330 static void
331 msg_msg(void *arg, int line, int col, const char *msg)
332 {
333 struct mmain *p;
334
335 p = (struct mmain *)arg;
336
337 if (0 == p->dbg)
338 return;
339
340 warnx("%s:%d: debug: %s (column %d)",
341 p->in, line, msg, col);
342 }
343
344
345 static int
346 msg_warn(void *arg, int line, int col,
347 enum mdoc_warn type, const char *msg)
348 {
349 struct mmain *p;
350
351 p = (struct mmain *)arg;
352
353 switch (type) {
354 case (WARN_COMPAT):
355 if (p->warn & MD_WARN_COMPAT)
356 break;
357 return(1);
358 case (WARN_SYNTAX):
359 if (p->warn & MD_WARN_SYNTAX)
360 break;
361 return(1);
362 }
363
364 warnx("%s:%d: warning: %s (column %d)",
365 p->in, line, msg, col);
366
367 if ( ! (p->warn & MD_WARN_ERR))
368 return(1);
369
370 warnx("%s: considering warnings as errors", __progname);
371 return(0);
372 }