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