Import mdocml-1.11.3
[dragonfly.git] / contrib / mdocml / makewhatis.c
1 /*      $Id: makewhatis.c,v 1.2 2011/05/15 02:47:17 kristaps Exp $ */
2 /*
3  * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
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 above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20
21 #include <sys/param.h>
22
23 #include <assert.h>
24 #ifdef __linux__
25 # include <db_185.h>
26 #else
27 # include <db.h>
28 #endif
29 #include <fcntl.h>
30 #include <getopt.h>
31 #include <stdio.h>
32 #include <stdint.h>
33 #include <stdlib.h>
34 #include <string.h>
35
36 #include "man.h"
37 #include "mdoc.h"
38 #include "mandoc.h"
39
40 #define MANDOC_DB        "mandoc.db"
41 #define MANDOC_IDX       "mandoc.index"
42 #define MANDOC_BUFSZ      BUFSIZ
43 #define MANDOC_FLAGS      O_CREAT|O_TRUNC|O_RDWR
44
45 enum    type {
46         MANDOC_NONE = 0,
47         MANDOC_NAME,
48         MANDOC_FUNCTION,
49         MANDOC_UTILITY,
50         MANDOC_INCLUDES,
51         MANDOC_VARIABLE,
52         MANDOC_STANDARD,
53         MANDOC_AUTHOR,
54         MANDOC_CONFIG
55 };
56
57 #define MAN_ARGS          DB *db, \
58                           const char *dbn, \
59                           DBT *key, size_t *ksz, \
60                           DBT *val, \
61                           DBT *rval, size_t *rsz, \
62                           const struct man_node *n
63 #define MDOC_ARGS         DB *db, \
64                           const char *dbn, \
65                           DBT *key, size_t *ksz, \
66                           DBT *val, \
67                           DBT *rval, size_t *rsz, \
68                           const struct mdoc_node *n
69
70 static  void              dbt_append(DBT *, size_t *, const char *);
71 static  void              dbt_appendb(DBT *, size_t *, 
72                                 const void *, size_t);
73 static  void              dbt_init(DBT *, size_t *);
74 static  void              dbt_put(DB *, const char *, DBT *, DBT *);
75 static  void              usage(void);
76 static  void              pman(DB *, const char *, DBT *, size_t *, 
77                                 DBT *, DBT *, size_t *, struct man *);
78 static  int               pman_node(MAN_ARGS);
79 static  void              pmdoc(DB *, const char *, DBT *, size_t *, 
80                                 DBT *, DBT *, size_t *, struct mdoc *);
81 static  void              pmdoc_node(MDOC_ARGS);
82 static  void              pmdoc_An(MDOC_ARGS);
83 static  void              pmdoc_Cd(MDOC_ARGS);
84 static  void              pmdoc_Fd(MDOC_ARGS);
85 static  void              pmdoc_In(MDOC_ARGS);
86 static  void              pmdoc_Fn(MDOC_ARGS);
87 static  void              pmdoc_Fo(MDOC_ARGS);
88 static  void              pmdoc_Nd(MDOC_ARGS);
89 static  void              pmdoc_Nm(MDOC_ARGS);
90 static  void              pmdoc_St(MDOC_ARGS);
91 static  void              pmdoc_Vt(MDOC_ARGS);
92
93 typedef void            (*pmdoc_nf)(MDOC_ARGS);
94
95 static  const char       *progname;
96
97 static  const pmdoc_nf    mdocs[MDOC_MAX] = {
98         NULL, /* Ap */
99         NULL, /* Dd */
100         NULL, /* Dt */
101         NULL, /* Os */
102         NULL, /* Sh */ 
103         NULL, /* Ss */ 
104         NULL, /* Pp */ 
105         NULL, /* D1 */
106         NULL, /* Dl */
107         NULL, /* Bd */
108         NULL, /* Ed */
109         NULL, /* Bl */ 
110         NULL, /* El */
111         NULL, /* It */
112         NULL, /* Ad */ 
113         pmdoc_An, /* An */ 
114         NULL, /* Ar */
115         pmdoc_Cd, /* Cd */ 
116         NULL, /* Cm */
117         NULL, /* Dv */ 
118         NULL, /* Er */ 
119         NULL, /* Ev */ 
120         NULL, /* Ex */ 
121         NULL, /* Fa */ 
122         pmdoc_Fd, /* Fd */
123         NULL, /* Fl */
124         pmdoc_Fn, /* Fn */ 
125         NULL, /* Ft */ 
126         NULL, /* Ic */ 
127         pmdoc_In, /* In */ 
128         NULL, /* Li */
129         pmdoc_Nd, /* Nd */
130         pmdoc_Nm, /* Nm */
131         NULL, /* Op */
132         NULL, /* Ot */
133         NULL, /* Pa */
134         NULL, /* Rv */
135         pmdoc_St, /* St */ 
136         pmdoc_Vt, /* Va */
137         pmdoc_Vt, /* Vt */ 
138         NULL, /* Xr */ 
139         NULL, /* %A */
140         NULL, /* %B */
141         NULL, /* %D */
142         NULL, /* %I */
143         NULL, /* %J */
144         NULL, /* %N */
145         NULL, /* %O */
146         NULL, /* %P */
147         NULL, /* %R */
148         NULL, /* %T */
149         NULL, /* %V */
150         NULL, /* Ac */
151         NULL, /* Ao */
152         NULL, /* Aq */
153         NULL, /* At */ 
154         NULL, /* Bc */
155         NULL, /* Bf */
156         NULL, /* Bo */
157         NULL, /* Bq */
158         NULL, /* Bsx */
159         NULL, /* Bx */
160         NULL, /* Db */
161         NULL, /* Dc */
162         NULL, /* Do */
163         NULL, /* Dq */
164         NULL, /* Ec */
165         NULL, /* Ef */ 
166         NULL, /* Em */ 
167         NULL, /* Eo */
168         NULL, /* Fx */
169         NULL, /* Ms */ 
170         NULL, /* No */
171         NULL, /* Ns */
172         NULL, /* Nx */
173         NULL, /* Ox */
174         NULL, /* Pc */
175         NULL, /* Pf */
176         NULL, /* Po */
177         NULL, /* Pq */
178         NULL, /* Qc */
179         NULL, /* Ql */
180         NULL, /* Qo */
181         NULL, /* Qq */
182         NULL, /* Re */
183         NULL, /* Rs */
184         NULL, /* Sc */
185         NULL, /* So */
186         NULL, /* Sq */
187         NULL, /* Sm */ 
188         NULL, /* Sx */
189         NULL, /* Sy */
190         NULL, /* Tn */
191         NULL, /* Ux */
192         NULL, /* Xc */
193         NULL, /* Xo */
194         pmdoc_Fo, /* Fo */ 
195         NULL, /* Fc */ 
196         NULL, /* Oo */
197         NULL, /* Oc */
198         NULL, /* Bk */
199         NULL, /* Ek */
200         NULL, /* Bt */
201         NULL, /* Hf */
202         NULL, /* Fr */
203         NULL, /* Ud */
204         NULL, /* Lb */
205         NULL, /* Lp */ 
206         NULL, /* Lk */ 
207         NULL, /* Mt */ 
208         NULL, /* Brq */ 
209         NULL, /* Bro */ 
210         NULL, /* Brc */ 
211         NULL, /* %C */
212         NULL, /* Es */
213         NULL, /* En */
214         NULL, /* Dx */
215         NULL, /* %Q */
216         NULL, /* br */
217         NULL, /* sp */
218         NULL, /* %U */
219         NULL, /* Ta */
220 };
221
222 int
223 main(int argc, char *argv[])
224 {
225         struct mparse   *mp; /* parse sequence */
226         struct mdoc     *mdoc; /* resulting mdoc */
227         struct man      *man; /* resulting man */
228         char            *fn; /* current file being parsed */
229         const char      *msec, /* manual section */
230                         *mtitle, /* manual title */
231                         *arch, /* manual architecture */
232                         *dir; /* result dir (default: cwd) */
233         char             ibuf[MAXPATHLEN], /* index fname */
234                          ibbuf[MAXPATHLEN], /* index backup fname */
235                          fbuf[MAXPATHLEN],  /* btree fname */
236                          fbbuf[MAXPATHLEN]; /* btree backup fname */
237         int              ch;
238         DB              *idx, /* index database */
239                         *db; /* keyword database */
240         DBT              rkey, rval, /* recno entries */
241                          key, val; /* persistent keyword entries */
242         size_t           sv,
243                          ksz, rsz; /* entry buffer size */
244         char             vbuf[8]; /* stringified record number */
245         BTREEINFO        info; /* btree configuration */
246         recno_t          rec; /* current record number */
247         extern int       optind;
248         extern char     *optarg;
249
250         progname = strrchr(argv[0], '/');
251         if (progname == NULL)
252                 progname = argv[0];
253         else
254                 ++progname;
255
256         dir = "";
257
258         while (-1 != (ch = getopt(argc, argv, "d:")))
259                 switch (ch) {
260                 case ('d'):
261                         dir = optarg;
262                         break;
263                 default:
264                         usage();
265                         return((int)MANDOCLEVEL_BADARG);
266                 }
267
268         argc -= optind;
269         argv += optind;
270
271         /*
272          * Set up temporary file-names into which we're going to write
273          * all of our data (both for the index and database).  These
274          * will be securely renamed to the real file-names after we've
275          * written all of our data.
276          */
277
278         ibuf[0] = ibuf[MAXPATHLEN - 2] =
279                 ibbuf[0] = ibbuf[MAXPATHLEN - 2] = 
280                 fbuf[0] = fbuf[MAXPATHLEN - 2] = 
281                 fbbuf[0] = fbbuf[MAXPATHLEN - 2] = '\0';
282
283         strlcat(fbuf, dir, MAXPATHLEN);
284         strlcat(fbuf, MANDOC_DB, MAXPATHLEN);
285
286         strlcat(fbbuf, fbuf, MAXPATHLEN);
287         strlcat(fbbuf, "~", MAXPATHLEN);
288
289         strlcat(ibuf, dir, MAXPATHLEN);
290         strlcat(ibuf, MANDOC_IDX, MAXPATHLEN);
291
292         strlcat(ibbuf, ibuf, MAXPATHLEN);
293         strlcat(ibbuf, "~", MAXPATHLEN);
294
295         if ('\0' != fbuf[MAXPATHLEN - 2] ||
296                         '\0' != fbbuf[MAXPATHLEN - 2] ||
297                         '\0' != ibuf[MAXPATHLEN - 2] ||
298                         '\0' != ibbuf[MAXPATHLEN - 2]) {
299                 fprintf(stderr, "%s: Path too long\n", progname);
300                 exit((int)MANDOCLEVEL_SYSERR);
301         }
302
303         /*
304          * For the keyword database, open a BTREE database that allows
305          * duplicates.  For the index database, use a standard RECNO
306          * database type.
307          */
308
309         memset(&info, 0, sizeof(BTREEINFO));
310         info.flags = R_DUP;
311         db = dbopen(fbbuf, MANDOC_FLAGS, 0644, DB_BTREE, &info);
312
313         if (NULL == db) {
314                 perror(fbbuf);
315                 exit((int)MANDOCLEVEL_SYSERR);
316         }
317
318         idx = dbopen(ibbuf, MANDOC_FLAGS, 0644, DB_RECNO, NULL);
319
320         if (NULL == db) {
321                 perror(ibbuf);
322                 (*db->close)(db);
323                 exit((int)MANDOCLEVEL_SYSERR);
324         }
325
326         /*
327          * Try parsing the manuals given on the command line.  If we
328          * totally fail, then just keep on going.  Take resulting trees
329          * and push them down into the database code.
330          * Use the auto-parser and don't report any errors.
331          */
332
333         mp = mparse_alloc(MPARSE_AUTO, MANDOCLEVEL_FATAL, NULL, NULL);
334
335         memset(&key, 0, sizeof(DBT));
336         memset(&val, 0, sizeof(DBT));
337         memset(&rkey, 0, sizeof(DBT));
338         memset(&rval, 0, sizeof(DBT));
339
340         val.size = sizeof(vbuf);
341         val.data = vbuf;
342         rkey.size = sizeof(recno_t);
343
344         rec = 1;
345         ksz = rsz = 0;
346
347         while (NULL != (fn = *argv++)) {
348                 mparse_reset(mp);
349
350                 /* Parse and get (non-empty) AST. */
351
352                 if (mparse_readfd(mp, -1, fn) >= MANDOCLEVEL_FATAL) {
353                         fprintf(stderr, "%s: Parse failure\n", fn);
354                         continue;
355                 }
356                 mparse_result(mp, &mdoc, &man);
357                 if (NULL == mdoc && NULL == man)
358                         continue;
359
360                 /* Manual section: can be empty string. */
361
362                 msec = NULL != mdoc ? 
363                         mdoc_meta(mdoc)->msec :
364                         man_meta(man)->msec;
365                 mtitle = NULL != mdoc ? 
366                         mdoc_meta(mdoc)->title :
367                         man_meta(man)->title;
368                 arch = NULL != mdoc ? mdoc_meta(mdoc)->arch : NULL;
369
370                 assert(msec);
371                 assert(mtitle);
372
373                 /* 
374                  * The index record value consists of a nil-terminated
375                  * filename, a nil-terminated manual section, and a
376                  * nil-terminated description.  Since the description
377                  * may not be set, we set a sentinel to see if we're
378                  * going to write a nil byte in its place.
379                  */
380
381                 dbt_init(&rval, &rsz);
382                 dbt_appendb(&rval, &rsz, fn, strlen(fn) + 1);
383                 dbt_appendb(&rval, &rsz, msec, strlen(msec) + 1);
384                 dbt_appendb(&rval, &rsz, mtitle, strlen(mtitle) + 1);
385                 dbt_appendb(&rval, &rsz, arch ? arch : "", 
386                                 arch ? strlen(arch) + 1 : 1);
387
388                 sv = rval.size;
389
390                 /* Fix the record number in the btree value. */
391
392                 memset(val.data, 0, sizeof(uint32_t));
393                 memcpy(val.data + 4, &rec, sizeof(uint32_t));
394
395                 if (mdoc)
396                         pmdoc(db, fbbuf, &key, &ksz, 
397                                 &val, &rval, &rsz, mdoc);
398                 else 
399                         pman(db, fbbuf, &key, &ksz, 
400                                 &val, &rval, &rsz, man);
401                 
402                 /*
403                  * Apply this to the index.  If we haven't had a
404                  * description set, put an empty one in now.
405                  */
406
407                 if (rval.size == sv)
408                         dbt_appendb(&rval, &rsz, "", 1);
409
410                 rkey.data = &rec;
411                 dbt_put(idx, ibbuf, &rkey, &rval);
412
413                 printf("Indexed: %s\n", fn);
414                 rec++;
415         }
416
417         (*db->close)(db);
418         (*idx->close)(idx);
419
420         mparse_free(mp);
421
422         free(key.data);
423         free(rval.data);
424
425         /* Atomically replace the file with our temporary one. */
426
427         if (-1 == rename(fbbuf, fbuf))
428                 perror(fbuf);
429         if (-1 == rename(ibbuf, ibuf))
430                 perror(fbuf);
431
432         return((int)MANDOCLEVEL_OK);
433 }
434
435 /*
436  * Initialise the stored database key whose data buffer is shared
437  * between uses (as the key must sometimes be constructed from an array
438  * of 
439  */
440 static void
441 dbt_init(DBT *key, size_t *ksz)
442 {
443
444         if (0 == *ksz) {
445                 assert(0 == key->size);
446                 assert(NULL == key->data);
447                 key->data = mandoc_malloc(MANDOC_BUFSZ);
448                 *ksz = MANDOC_BUFSZ;
449         }
450
451         key->size = 0;
452 }
453
454 /*
455  * Append a binary value to a database entry.  This can be invoked
456  * multiple times; the buffer is automatically resized.
457  */
458 static void
459 dbt_appendb(DBT *key, size_t *ksz, const void *cp, size_t sz)
460 {
461
462         assert(key->data);
463
464         /* Overshoot by MANDOC_BUFSZ. */
465
466         while (key->size + sz >= *ksz) {
467                 *ksz = key->size + sz + MANDOC_BUFSZ;
468                 key->data = mandoc_realloc(key->data, *ksz);
469         }
470
471         memcpy(key->data + (int)key->size, cp, sz);
472         key->size += sz;
473 }
474
475 /*
476  * Append a nil-terminated string to the database entry.  This can be
477  * invoked multiple times.  The database entry will be nil-terminated as
478  * well; if invoked multiple times, a space is put between strings.
479  */
480 static void
481 dbt_append(DBT *key, size_t *ksz, const char *cp)
482 {
483         size_t           sz;
484
485         if (0 == (sz = strlen(cp)))
486                 return;
487
488         assert(key->data);
489
490         if (key->size)
491                 ((char *)key->data)[(int)key->size - 1] = ' ';
492
493         dbt_appendb(key, ksz, cp, sz + 1);
494 }
495
496 /* ARGSUSED */
497 static void
498 pmdoc_An(MDOC_ARGS)
499 {
500         uint32_t         fl;
501         
502         if (SEC_AUTHORS != n->sec)
503                 return;
504
505         for (n = n->child; n; n = n->next)
506                 if (MDOC_TEXT == n->type)
507                         dbt_append(key, ksz, n->string);
508
509         fl = (uint32_t)MANDOC_AUTHOR;
510         memcpy(val->data, &fl, 4);
511 }
512
513 /* ARGSUSED */
514 static void
515 pmdoc_Fd(MDOC_ARGS)
516 {
517         uint32_t         fl;
518         const char      *start, *end;
519         size_t           sz;
520         
521         if (SEC_SYNOPSIS != n->sec)
522                 return;
523         if (NULL == (n = n->child) || MDOC_TEXT != n->type)
524                 return;
525
526         /*
527          * Only consider those `Fd' macro fields that begin with an
528          * "inclusion" token (versus, e.g., #define).
529          */
530         if (strcmp("#include", n->string))
531                 return;
532
533         if (NULL == (n = n->next) || MDOC_TEXT != n->type)
534                 return;
535
536         /*
537          * Strip away the enclosing angle brackets and make sure we're
538          * not zero-length.
539          */
540
541         start = n->string;
542         if ('<' == *start || '"' == *start)
543                 start++;
544
545         if (0 == (sz = strlen(start)))
546                 return;
547
548         end = &start[(int)sz - 1];
549         if ('>' == *end || '"' == *end)
550                 end--;
551
552         assert(end >= start);
553         dbt_appendb(key, ksz, start, (size_t)(end - start + 1));
554         dbt_appendb(key, ksz, "", 1);
555
556         fl = (uint32_t)MANDOC_INCLUDES;
557         memcpy(val->data, &fl, 4);
558 }
559
560 /* ARGSUSED */
561 static void
562 pmdoc_Cd(MDOC_ARGS)
563 {
564         uint32_t         fl;
565         
566         if (SEC_SYNOPSIS != n->sec)
567                 return;
568
569         for (n = n->child; n; n = n->next)
570                 if (MDOC_TEXT == n->type)
571                         dbt_append(key, ksz, n->string);
572
573         fl = (uint32_t)MANDOC_CONFIG;
574         memcpy(val->data, &fl, 4);
575 }
576
577 /* ARGSUSED */
578 static void
579 pmdoc_In(MDOC_ARGS)
580 {
581         uint32_t         fl;
582         
583         if (SEC_SYNOPSIS != n->sec)
584                 return;
585         if (NULL == n->child || MDOC_TEXT != n->child->type)
586                 return;
587
588         dbt_append(key, ksz, n->child->string);
589         fl = (uint32_t)MANDOC_INCLUDES;
590         memcpy(val->data, &fl, 4);
591 }
592
593 /* ARGSUSED */
594 static void
595 pmdoc_Fn(MDOC_ARGS)
596 {
597         uint32_t         fl;
598         const char      *cp;
599         
600         if (SEC_SYNOPSIS != n->sec)
601                 return;
602         if (NULL == n->child || MDOC_TEXT != n->child->type)
603                 return;
604
605         /* .Fn "struct type *arg" "foo" */
606
607         cp = strrchr(n->child->string, ' ');
608         if (NULL == cp)
609                 cp = n->child->string;
610
611         /* Strip away pointer symbol. */
612
613         while ('*' == *cp)
614                 cp++;
615
616         dbt_append(key, ksz, cp);
617         fl = (uint32_t)MANDOC_FUNCTION;
618         memcpy(val->data, &fl, 4);
619 }
620
621 /* ARGSUSED */
622 static void
623 pmdoc_St(MDOC_ARGS)
624 {
625         uint32_t         fl;
626         
627         if (SEC_STANDARDS != n->sec)
628                 return;
629         if (NULL == n->child || MDOC_TEXT != n->child->type)
630                 return;
631
632         dbt_append(key, ksz, n->child->string);
633         fl = (uint32_t)MANDOC_STANDARD;
634         memcpy(val->data, &fl, 4);
635 }
636
637 /* ARGSUSED */
638 static void
639 pmdoc_Vt(MDOC_ARGS)
640 {
641         uint32_t         fl;
642         const char      *start;
643         size_t           sz;
644         
645         if (SEC_SYNOPSIS != n->sec)
646                 return;
647         if (MDOC_Vt == n->tok && MDOC_BODY != n->type)
648                 return;
649         if (NULL == n->last || MDOC_TEXT != n->last->type)
650                 return;
651
652         /*
653          * Strip away leading pointer symbol '*' and trailing ';'.
654          */
655
656         start = n->last->string;
657
658         while ('*' == *start)
659                 start++;
660
661         if (0 == (sz = strlen(start)))
662                 return;
663
664         if (';' == start[(int)sz - 1])
665                 sz--;
666
667         if (0 == sz)
668                 return;
669
670         dbt_appendb(key, ksz, start, sz);
671         dbt_appendb(key, ksz, "", 1);
672
673         fl = (uint32_t)MANDOC_VARIABLE;
674         memcpy(val->data, &fl, 4);
675 }
676
677 /* ARGSUSED */
678 static void
679 pmdoc_Fo(MDOC_ARGS)
680 {
681         uint32_t         fl;
682         
683         if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type)
684                 return;
685         if (NULL == n->child || MDOC_TEXT != n->child->type)
686                 return;
687
688         dbt_append(key, ksz, n->child->string);
689         fl = (uint32_t)MANDOC_FUNCTION;
690         memcpy(val->data, &fl, 4);
691 }
692
693
694 /* ARGSUSED */
695 static void
696 pmdoc_Nd(MDOC_ARGS)
697 {
698         int              first;
699         
700         for (first = 1, n = n->child; n; n = n->next) {
701                 if (MDOC_TEXT != n->type)
702                         continue;
703                 if (first) 
704                         dbt_appendb(rval, rsz, n->string, strlen(n->string) + 1);
705                 else
706                         dbt_append(rval, rsz, n->string);
707                 first = 0;
708         }
709 }
710
711 /* ARGSUSED */
712 static void
713 pmdoc_Nm(MDOC_ARGS)
714 {
715         uint32_t         fl;
716         
717         if (SEC_NAME == n->sec) {
718                 for (n = n->child; n; n = n->next) {
719                         if (MDOC_TEXT != n->type)
720                                 continue;
721                         dbt_append(key, ksz, n->string);
722                 }
723                 fl = (uint32_t)MANDOC_NAME;
724                 memcpy(val->data, &fl, 4);
725                 return;
726         } else if (SEC_SYNOPSIS != n->sec || MDOC_HEAD != n->type)
727                 return;
728
729         for (n = n->child; n; n = n->next) {
730                 if (MDOC_TEXT != n->type)
731                         continue;
732                 dbt_append(key, ksz, n->string);
733         }
734
735         fl = (uint32_t)MANDOC_UTILITY;
736         memcpy(val->data, &fl, 4);
737 }
738
739 static void
740 dbt_put(DB *db, const char *dbn, DBT *key, DBT *val)
741 {
742
743         if (0 == key->size)
744                 return;
745
746         assert(key->data);
747         assert(val->size);
748         assert(val->data);
749
750         if (0 == (*db->put)(db, key, val, 0))
751                 return;
752         
753         perror(dbn);
754         exit((int)MANDOCLEVEL_SYSERR);
755         /* NOTREACHED */
756 }
757
758 /*
759  * Call out to per-macro handlers after clearing the persistent database
760  * key.  If the macro sets the database key, flush it to the database.
761  */
762 static void
763 pmdoc_node(MDOC_ARGS)
764 {
765
766         if (NULL == n)
767                 return;
768
769         switch (n->type) {
770         case (MDOC_HEAD):
771                 /* FALLTHROUGH */
772         case (MDOC_BODY):
773                 /* FALLTHROUGH */
774         case (MDOC_TAIL):
775                 /* FALLTHROUGH */
776         case (MDOC_BLOCK):
777                 /* FALLTHROUGH */
778         case (MDOC_ELEM):
779                 if (NULL == mdocs[n->tok])
780                         break;
781
782                 dbt_init(key, ksz);
783
784                 (*mdocs[n->tok])(db, dbn, key, ksz, val, rval, rsz, n);
785                 dbt_put(db, dbn, key, val);
786                 break;
787         default:
788                 break;
789         }
790
791         pmdoc_node(db, dbn, key, ksz, val, rval, rsz, n->child);
792         pmdoc_node(db, dbn, key, ksz, val, rval, rsz, n->next);
793 }
794
795 static int
796 pman_node(MAN_ARGS)
797 {
798         const struct man_node *head, *body;
799         const char      *start, *sv;
800         size_t           sz;
801         uint32_t         fl;
802
803         if (NULL == n)
804                 return(0);
805
806         /*
807          * We're only searching for one thing: the first text child in
808          * the BODY of a NAME section.  Since we don't keep track of
809          * sections in -man, run some hoops to find out whether we're in
810          * the correct section or not.
811          */
812
813         if (MAN_BODY == n->type && MAN_SH == n->tok) {
814                 body = n;
815                 assert(body->parent);
816                 if (NULL != (head = body->parent->head) &&
817                                 1 == head->nchild &&
818                                 NULL != (head = (head->child)) &&
819                                 MAN_TEXT == head->type &&
820                                 0 == strcmp(head->string, "NAME") &&
821                                 NULL != (body = body->child) &&
822                                 MAN_TEXT == body->type) {
823
824                         fl = (uint32_t)MANDOC_NAME;
825                         memcpy(val->data, &fl, 4);
826
827                         assert(body->string);
828                         start = sv = body->string;
829
830                         /* 
831                          * Go through a special heuristic dance here.
832                          * This is why -man manuals are great!
833                          * (I'm being sarcastic: my eyes are bleeding.)
834                          * Conventionally, one or more manual names are
835                          * comma-specified prior to a whitespace, then a
836                          * dash, then a description.  Try to puzzle out
837                          * the name parts here.
838                          */
839
840                         for ( ;; ) {
841                                 sz = strcspn(start, " ,");
842                                 if ('\0' == start[(int)sz])
843                                         break;
844
845                                 dbt_init(key, ksz);
846                                 dbt_appendb(key, ksz, start, sz);
847                                 dbt_appendb(key, ksz, "", 1);
848
849                                 dbt_put(db, dbn, key, val);
850
851                                 if (' ' == start[(int)sz]) {
852                                         start += (int)sz + 1;
853                                         break;
854                                 }
855
856                                 assert(',' == start[(int)sz]);
857                                 start += (int)sz + 1;
858                                 while (' ' == *start)
859                                         start++;
860                         }
861
862                         if (sv == start) {
863                                 dbt_init(key, ksz);
864                                 dbt_append(key, ksz, start);
865                                 return(1);
866                         }
867
868                         while (' ' == *start)
869                                 start++;
870
871                         if (0 == strncmp(start, "-", 1))
872                                 start += 1;
873                         else if (0 == strncmp(start, "\\-", 2))
874                                 start += 2;
875                         else if (0 == strncmp(start, "\\(en", 4))
876                                 start += 4;
877                         else if (0 == strncmp(start, "\\(em", 4))
878                                 start += 4;
879
880                         while (' ' == *start)
881                                 start++;
882
883                         dbt_appendb(rval, rsz, start, strlen(start) + 1);
884                 }
885         }
886
887         if (pman_node(db, dbn, key, ksz, val, rval, rsz, n->child))
888                 return(1);
889         if (pman_node(db, dbn, key, ksz, val, rval, rsz, n->next))
890                 return(1);
891
892         return(0);
893 }
894
895 static void
896 pman(DB *db, const char *dbn, DBT *key, size_t *ksz, 
897                 DBT *val, DBT *rval, size_t *rsz, struct man *m)
898 {
899
900         pman_node(db, dbn, key, ksz, val, rval, rsz, man_node(m));
901 }
902
903
904 static void
905 pmdoc(DB *db, const char *dbn, DBT *key, size_t *ksz, 
906                 DBT *val, DBT *rval, size_t *rsz, struct mdoc *m)
907 {
908
909         pmdoc_node(db, dbn, key, ksz, val, rval, rsz, mdoc_node(m));
910 }
911
912 static void
913 usage(void)
914 {
915
916         fprintf(stderr, "usage: %s "
917                         "[-d path] "
918                         "[file...]\n", 
919                         progname);
920 }