]> git.cameronkatri.com Git - mandoc.git/blob - man_validate.c
Decide whether to use_pager as early as possible,
[mandoc.git] / man_validate.c
1 /* $OpenBSD$ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012-2015 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 AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "mandoc_aux.h"
32 #include "mandoc.h"
33 #include "roff.h"
34 #include "man.h"
35 #include "libmandoc.h"
36 #include "roff_int.h"
37 #include "libman.h"
38
39 #define CHKARGS struct roff_man *man, struct roff_node *n
40
41 typedef void (*v_check)(CHKARGS);
42
43 static void check_par(CHKARGS);
44 static void check_part(CHKARGS);
45 static void check_root(CHKARGS);
46 static void check_text(CHKARGS);
47
48 static void post_AT(CHKARGS);
49 static void post_IP(CHKARGS);
50 static void post_vs(CHKARGS);
51 static void post_fi(CHKARGS);
52 static void post_ft(CHKARGS);
53 static void post_nf(CHKARGS);
54 static void post_OP(CHKARGS);
55 static void post_TH(CHKARGS);
56 static void post_UC(CHKARGS);
57 static void post_UR(CHKARGS);
58
59 static v_check man_valids[MAN_MAX] = {
60 post_vs, /* br */
61 post_TH, /* TH */
62 NULL, /* SH */
63 NULL, /* SS */
64 NULL, /* TP */
65 check_par, /* LP */
66 check_par, /* PP */
67 check_par, /* P */
68 post_IP, /* IP */
69 NULL, /* HP */
70 NULL, /* SM */
71 NULL, /* SB */
72 NULL, /* BI */
73 NULL, /* IB */
74 NULL, /* BR */
75 NULL, /* RB */
76 NULL, /* R */
77 NULL, /* B */
78 NULL, /* I */
79 NULL, /* IR */
80 NULL, /* RI */
81 post_vs, /* sp */
82 post_nf, /* nf */
83 post_fi, /* fi */
84 NULL, /* RE */
85 check_part, /* RS */
86 NULL, /* DT */
87 post_UC, /* UC */
88 NULL, /* PD */
89 post_AT, /* AT */
90 NULL, /* in */
91 post_ft, /* ft */
92 post_OP, /* OP */
93 post_nf, /* EX */
94 post_fi, /* EE */
95 post_UR, /* UR */
96 NULL, /* UE */
97 NULL, /* ll */
98 };
99
100
101 void
102 man_valid_post(struct roff_man *man)
103 {
104 struct roff_node *n;
105 v_check *cp;
106
107 n = man->last;
108 if (n->flags & MAN_VALID)
109 return;
110 n->flags |= MAN_VALID;
111
112 switch (n->type) {
113 case ROFFT_TEXT:
114 check_text(man, n);
115 break;
116 case ROFFT_ROOT:
117 check_root(man, n);
118 break;
119 case ROFFT_EQN:
120 /* FALLTHROUGH */
121 case ROFFT_TBL:
122 break;
123 default:
124 cp = man_valids + n->tok;
125 if (*cp)
126 (*cp)(man, n);
127 break;
128 }
129 }
130
131 static void
132 check_root(CHKARGS)
133 {
134
135 assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
136
137 if (NULL == man->first->child)
138 mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
139 n->line, n->pos, NULL);
140 else
141 man->meta.hasbody = 1;
142
143 if (NULL == man->meta.title) {
144 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
145 n->line, n->pos, NULL);
146
147 /*
148 * If a title hasn't been set, do so now (by
149 * implication, date and section also aren't set).
150 */
151
152 man->meta.title = mandoc_strdup("");
153 man->meta.msec = mandoc_strdup("");
154 man->meta.date = man->quick ? mandoc_strdup("") :
155 mandoc_normdate(man->parse, NULL, n->line, n->pos);
156 }
157 }
158
159 static void
160 check_text(CHKARGS)
161 {
162 char *cp, *p;
163
164 if (MAN_LITERAL & man->flags)
165 return;
166
167 cp = n->string;
168 for (p = cp; NULL != (p = strchr(p, '\t')); p++)
169 mandoc_msg(MANDOCERR_FI_TAB, man->parse,
170 n->line, n->pos + (p - cp), NULL);
171 }
172
173 static void
174 post_OP(CHKARGS)
175 {
176
177 if (n->nchild == 0)
178 mandoc_msg(MANDOCERR_OP_EMPTY, man->parse,
179 n->line, n->pos, "OP");
180 else if (n->nchild > 2) {
181 n = n->child->next->next;
182 mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
183 n->line, n->pos, "OP ... %s", n->string);
184 }
185 }
186
187 static void
188 post_UR(CHKARGS)
189 {
190
191 if (n->type == ROFFT_HEAD && n->child == NULL)
192 mandoc_vmsg(MANDOCERR_UR_NOHEAD, man->parse,
193 n->line, n->pos, "UR");
194 check_part(man, n);
195 }
196
197 static void
198 post_ft(CHKARGS)
199 {
200 char *cp;
201 int ok;
202
203 if (0 == n->nchild)
204 return;
205
206 ok = 0;
207 cp = n->child->string;
208 switch (*cp) {
209 case '1':
210 /* FALLTHROUGH */
211 case '2':
212 /* FALLTHROUGH */
213 case '3':
214 /* FALLTHROUGH */
215 case '4':
216 /* FALLTHROUGH */
217 case 'I':
218 /* FALLTHROUGH */
219 case 'P':
220 /* FALLTHROUGH */
221 case 'R':
222 if ('\0' == cp[1])
223 ok = 1;
224 break;
225 case 'B':
226 if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
227 ok = 1;
228 break;
229 case 'C':
230 if ('W' == cp[1] && '\0' == cp[2])
231 ok = 1;
232 break;
233 default:
234 break;
235 }
236
237 if (0 == ok) {
238 mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
239 n->line, n->pos, "ft %s", cp);
240 *cp = '\0';
241 }
242 }
243
244 static void
245 check_part(CHKARGS)
246 {
247
248 if (n->type == ROFFT_BODY && n->child == NULL)
249 mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
250 n->line, n->pos, man_macronames[n->tok]);
251 }
252
253 static void
254 check_par(CHKARGS)
255 {
256
257 switch (n->type) {
258 case ROFFT_BLOCK:
259 if (0 == n->body->nchild)
260 roff_node_delete(man, n);
261 break;
262 case ROFFT_BODY:
263 if (0 == n->nchild)
264 mandoc_vmsg(MANDOCERR_PAR_SKIP,
265 man->parse, n->line, n->pos,
266 "%s empty", man_macronames[n->tok]);
267 break;
268 case ROFFT_HEAD:
269 if (n->nchild)
270 mandoc_vmsg(MANDOCERR_ARG_SKIP,
271 man->parse, n->line, n->pos,
272 "%s %s%s", man_macronames[n->tok],
273 n->child->string,
274 n->nchild > 1 ? " ..." : "");
275 break;
276 default:
277 break;
278 }
279 }
280
281 static void
282 post_IP(CHKARGS)
283 {
284
285 switch (n->type) {
286 case ROFFT_BLOCK:
287 if (0 == n->head->nchild && 0 == n->body->nchild)
288 roff_node_delete(man, n);
289 break;
290 case ROFFT_BODY:
291 if (0 == n->parent->head->nchild && 0 == n->nchild)
292 mandoc_vmsg(MANDOCERR_PAR_SKIP,
293 man->parse, n->line, n->pos,
294 "%s empty", man_macronames[n->tok]);
295 break;
296 default:
297 break;
298 }
299 }
300
301 static void
302 post_TH(CHKARGS)
303 {
304 struct roff_node *nb;
305 const char *p;
306
307 free(man->meta.title);
308 free(man->meta.vol);
309 free(man->meta.os);
310 free(man->meta.msec);
311 free(man->meta.date);
312
313 man->meta.title = man->meta.vol = man->meta.date =
314 man->meta.msec = man->meta.os = NULL;
315
316 nb = n;
317
318 /* ->TITLE<- MSEC DATE OS VOL */
319
320 n = n->child;
321 if (n && n->string) {
322 for (p = n->string; '\0' != *p; p++) {
323 /* Only warn about this once... */
324 if (isalpha((unsigned char)*p) &&
325 ! isupper((unsigned char)*p)) {
326 mandoc_vmsg(MANDOCERR_TITLE_CASE,
327 man->parse, n->line,
328 n->pos + (p - n->string),
329 "TH %s", n->string);
330 break;
331 }
332 }
333 man->meta.title = mandoc_strdup(n->string);
334 } else {
335 man->meta.title = mandoc_strdup("");
336 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
337 nb->line, nb->pos, "TH");
338 }
339
340 /* TITLE ->MSEC<- DATE OS VOL */
341
342 if (n)
343 n = n->next;
344 if (n && n->string)
345 man->meta.msec = mandoc_strdup(n->string);
346 else {
347 man->meta.msec = mandoc_strdup("");
348 mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
349 nb->line, nb->pos, "TH %s", man->meta.title);
350 }
351
352 /* TITLE MSEC ->DATE<- OS VOL */
353
354 if (n)
355 n = n->next;
356 if (n && n->string && '\0' != n->string[0]) {
357 man->meta.date = man->quick ?
358 mandoc_strdup(n->string) :
359 mandoc_normdate(man->parse, n->string,
360 n->line, n->pos);
361 } else {
362 man->meta.date = mandoc_strdup("");
363 mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
364 n ? n->line : nb->line,
365 n ? n->pos : nb->pos, "TH");
366 }
367
368 /* TITLE MSEC DATE ->OS<- VOL */
369
370 if (n && (n = n->next))
371 man->meta.os = mandoc_strdup(n->string);
372 else if (man->defos != NULL)
373 man->meta.os = mandoc_strdup(man->defos);
374
375 /* TITLE MSEC DATE OS ->VOL<- */
376 /* If missing, use the default VOL name for MSEC. */
377
378 if (n && (n = n->next))
379 man->meta.vol = mandoc_strdup(n->string);
380 else if ('\0' != man->meta.msec[0] &&
381 (NULL != (p = mandoc_a2msec(man->meta.msec))))
382 man->meta.vol = mandoc_strdup(p);
383
384 if (n != NULL && (n = n->next) != NULL)
385 mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
386 n->line, n->pos, "TH ... %s", n->string);
387
388 /*
389 * Remove the `TH' node after we've processed it for our
390 * meta-data.
391 */
392 roff_node_delete(man, man->last);
393 }
394
395 static void
396 post_nf(CHKARGS)
397 {
398
399 if (man->flags & MAN_LITERAL)
400 mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
401 n->line, n->pos, "nf");
402
403 man->flags |= MAN_LITERAL;
404 }
405
406 static void
407 post_fi(CHKARGS)
408 {
409
410 if ( ! (MAN_LITERAL & man->flags))
411 mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
412 n->line, n->pos, "fi");
413
414 man->flags &= ~MAN_LITERAL;
415 }
416
417 static void
418 post_UC(CHKARGS)
419 {
420 static const char * const bsd_versions[] = {
421 "3rd Berkeley Distribution",
422 "4th Berkeley Distribution",
423 "4.2 Berkeley Distribution",
424 "4.3 Berkeley Distribution",
425 "4.4 Berkeley Distribution",
426 };
427
428 const char *p, *s;
429
430 n = n->child;
431
432 if (n == NULL || n->type != ROFFT_TEXT)
433 p = bsd_versions[0];
434 else {
435 s = n->string;
436 if (0 == strcmp(s, "3"))
437 p = bsd_versions[0];
438 else if (0 == strcmp(s, "4"))
439 p = bsd_versions[1];
440 else if (0 == strcmp(s, "5"))
441 p = bsd_versions[2];
442 else if (0 == strcmp(s, "6"))
443 p = bsd_versions[3];
444 else if (0 == strcmp(s, "7"))
445 p = bsd_versions[4];
446 else
447 p = bsd_versions[0];
448 }
449
450 free(man->meta.os);
451 man->meta.os = mandoc_strdup(p);
452 }
453
454 static void
455 post_AT(CHKARGS)
456 {
457 static const char * const unix_versions[] = {
458 "7th Edition",
459 "System III",
460 "System V",
461 "System V Release 2",
462 };
463
464 struct roff_node *nn;
465 const char *p, *s;
466
467 n = n->child;
468
469 if (n == NULL || n->type != ROFFT_TEXT)
470 p = unix_versions[0];
471 else {
472 s = n->string;
473 if (0 == strcmp(s, "3"))
474 p = unix_versions[0];
475 else if (0 == strcmp(s, "4"))
476 p = unix_versions[1];
477 else if (0 == strcmp(s, "5")) {
478 nn = n->next;
479 if (nn != NULL &&
480 nn->type == ROFFT_TEXT &&
481 nn->string[0] != '\0')
482 p = unix_versions[3];
483 else
484 p = unix_versions[2];
485 } else
486 p = unix_versions[0];
487 }
488
489 free(man->meta.os);
490 man->meta.os = mandoc_strdup(p);
491 }
492
493 static void
494 post_vs(CHKARGS)
495 {
496
497 if (NULL != n->prev)
498 return;
499
500 switch (n->parent->tok) {
501 case MAN_SH:
502 /* FALLTHROUGH */
503 case MAN_SS:
504 mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
505 "%s after %s", man_macronames[n->tok],
506 man_macronames[n->parent->tok]);
507 /* FALLTHROUGH */
508 case TOKEN_NONE:
509 /*
510 * Don't warn about this because it occurs in pod2man
511 * and would cause considerable (unfixable) warnage.
512 */
513 roff_node_delete(man, n);
514 break;
515 default:
516 break;
517 }
518 }