Annotation of mandoc/apropos.c, Revision 1.2
1.2 ! kristaps 1: /* $Id: apropos.c,v 1.1 2011/10/06 23:00:54 kristaps Exp $ */
1.1 kristaps 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/types.h>
22:
23: #include <assert.h>
24: #include <errno.h>
25: #include <fcntl.h>
26: #include <getopt.h>
27: #include <limits.h>
28: #include <regex.h>
29: #include <stdarg.h>
30: #include <stdint.h>
31: #include <stdio.h>
32: #include <stdlib.h>
33: #include <string.h>
34: #include <unistd.h>
35:
36: #ifdef __linux__
37: # include <db_185.h>
38: #else
39: # include <db.h>
40: #endif
41:
42: #include "mandoc.h"
43:
1.2 ! kristaps 44: #define MAXRESULTS 256
1.1 kristaps 45:
1.2 ! kristaps 46: /* Bit-fields. See mandocdb.8. */
! 47:
! 48: #define TYPE_NAME 0x01
! 49: #define TYPE_FUNCTION 0x02
! 50: #define TYPE_UTILITY 0x04
! 51: #define TYPE_INCLUDES 0x08
! 52: #define TYPE_VARIABLE 0x10
! 53: #define TYPE_STANDARD 0x20
! 54: #define TYPE_AUTHOR 0x40
! 55: #define TYPE_CONFIG 0x80
! 56: #define TYPE_DESC 0x100
! 57: #define TYPE_XREF 0x200
! 58: #define TYPE_PATH 0x400
! 59: #define TYPE_ENV 0x800
! 60: #define TYPE_ERR 0x1000
1.1 kristaps 61:
62: enum match {
63: MATCH_SUBSTR = 0,
64: MATCH_REGEX,
65: MATCH_EXACT
66: };
67:
68: enum sort {
69: SORT_TITLE = 0,
70: SORT_CAT,
71: SORT__MAX
72: };
73:
74: struct opts {
75: enum sort sort; /* output sorting */
76: const char *arch; /* restrict to architecture */
77: const char *cat; /* restrict to category */
78: int types; /* only types in bitmask */
79: int insens; /* case-insensitive match */
80: enum match match; /* match type */
81: };
82:
83: struct type {
84: int mask;
1.2 ! kristaps 85: const char *name; /* command-line type name */
1.1 kristaps 86: };
87:
88: struct rec {
1.2 ! kristaps 89: char *file; /* file in file-system */
! 90: char *cat; /* category (3p, 3, etc.) */
! 91: char *title; /* title (FOO, etc.) */
! 92: char *arch; /* arch (or empty string) */
! 93: char *desc; /* description (from Nd) */
! 94: recno_t rec; /* record in index */
1.1 kristaps 95: };
96:
97: struct res {
98: char *arch; /* architecture */
99: char *desc; /* free-form description */
100: char *keyword; /* matched keyword */
101: int types; /* bitmask of field selectors */
102: char *cat; /* manual section */
103: char *title; /* manual section */
104: char *uri; /* formatted uri of file */
105: recno_t rec; /* unique id of underlying manual */
106: };
107:
108: struct state {
109: DB *db; /* database */
110: DB *idx; /* index */
111: const char *dbf; /* database name */
112: const char *idxf; /* index name */
113: };
114:
115: static const char * const sorts[SORT__MAX] = {
116: "cat", /* SORT_CAT */
117: "title", /* SORT_TITLE */
118: };
119:
120: static const struct type types[] = {
121: { TYPE_NAME, "name" },
122: { TYPE_FUNCTION, "func" },
123: { TYPE_UTILITY, "utility" },
124: { TYPE_INCLUDES, "incl" },
125: { TYPE_VARIABLE, "var" },
126: { TYPE_STANDARD, "stand" },
127: { TYPE_AUTHOR, "auth" },
128: { TYPE_CONFIG, "conf" },
129: { TYPE_DESC, "desc" },
130: { TYPE_XREF, "xref" },
131: { TYPE_PATH, "path" },
132: { TYPE_ENV, "env" },
133: { TYPE_ERR, "err" },
134: { INT_MAX, "all" },
135: { 0, NULL }
136: };
137:
138: static void buf_alloc(char **, size_t *, size_t);
139: static void buf_dup(struct mchars *, char **, const char *);
140: static void buf_redup(struct mchars *, char **,
141: size_t *, const char *);
142: static int sort_cat(const void *, const void *);
143: static int sort_title(const void *, const void *);
1.2 ! kristaps 144: static int state_getrecord(struct state *,
! 145: recno_t, struct rec *);
1.1 kristaps 146: static void state_output(const struct res *, int);
147: static void state_search(struct state *,
148: const struct opts *, char *);
149: static void usage(void);
150:
1.2 ! kristaps 151: static char *progname;
1.1 kristaps 152:
153: int
154: main(int argc, char *argv[])
155: {
1.2 ! kristaps 156: BTREEINFO info;
! 157: int ch, i, rc;
1.1 kristaps 158: const char *dbf, *idxf;
159: struct state state;
160: char *q, *v;
161: struct opts opts;
162: extern int optind;
163: extern char *optarg;
164:
165: memset(&opts, 0, sizeof(struct opts));
1.2 ! kristaps 166: memset(&state, 0, sizeof(struct state));
1.1 kristaps 167:
168: dbf = "mandoc.db";
169: idxf = "mandoc.index";
170: q = NULL;
1.2 ! kristaps 171: rc = EXIT_FAILURE;
1.1 kristaps 172:
173: progname = strrchr(argv[0], '/');
174: if (progname == NULL)
175: progname = argv[0];
176: else
177: ++progname;
178:
179: opts.match = MATCH_SUBSTR;
180:
181: while (-1 != (ch = getopt(argc, argv, "a:c:eIrs:t:")))
182: switch (ch) {
183: case ('a'):
184: opts.arch = optarg;
185: break;
186: case ('c'):
187: opts.cat = optarg;
188: break;
189: case ('e'):
190: opts.match = MATCH_EXACT;
191: break;
192: case ('I'):
193: opts.insens = 1;
194: break;
195: case ('r'):
196: opts.match = MATCH_REGEX;
197: break;
198: case ('s'):
199: for (i = 0; i < SORT__MAX; i++) {
200: if (strcmp(optarg, sorts[i]))
201: continue;
202: opts.sort = (enum sort)i;
203: break;
204: }
205:
206: if (i < SORT__MAX)
207: break;
208:
1.2 ! kristaps 209: fprintf(stderr, "%s: Bad sort\n", optarg);
1.1 kristaps 210: return(EXIT_FAILURE);
211: case ('t'):
212: while (NULL != (v = strsep(&optarg, ","))) {
213: if ('\0' == *v)
214: continue;
215: for (i = 0; types[i].mask; i++) {
216: if (strcmp(types[i].name, v))
217: continue;
218: break;
219: }
220: if (0 == types[i].mask)
221: break;
222: opts.types |= types[i].mask;
223: }
224: if (NULL == v)
225: break;
226:
1.2 ! kristaps 227: fprintf(stderr, "%s: Bad type\n", v);
1.1 kristaps 228: return(EXIT_FAILURE);
229: default:
230: usage();
231: return(EXIT_FAILURE);
232: }
233:
234: argc -= optind;
235: argv += optind;
236:
237: if (0 == argc || '\0' == **argv) {
238: usage();
1.2 ! kristaps 239: goto out;
1.1 kristaps 240: } else
241: q = *argv;
242:
243: if (0 == opts.types)
244: opts.types = TYPE_NAME | TYPE_DESC;
245:
1.2 ! kristaps 246: /*
! 247: * Configure databases.
! 248: * The keyword database is a btree that allows for duplicate
! 249: * entries.
! 250: * The index database is a recno.
! 251: */
! 252:
! 253: memset(&info, 0, sizeof(BTREEINFO));
! 254: info.flags = R_DUP;
! 255:
! 256: state.db = dbopen(dbf, O_RDONLY, 0, DB_BTREE, &info);
! 257: if (NULL == state.db) {
! 258: perror(dbf);
! 259: goto out;
! 260: }
! 261:
! 262: state.idx = dbopen(idxf, O_RDONLY, 0, DB_RECNO, NULL);
! 263: if (NULL == state.idx) {
! 264: perror(idxf);
! 265: goto out;
1.1 kristaps 266: }
267:
1.2 ! kristaps 268: /* Main search function. */
! 269:
1.1 kristaps 270: state_search(&state, &opts, q);
271:
1.2 ! kristaps 272: rc = EXIT_SUCCESS;
! 273: out:
! 274: if (state.db)
! 275: (*state.db->close)(state.db);
! 276: if (state.idx)
! 277: (*state.idx->close)(state.idx);
! 278:
! 279: return(rc);
1.1 kristaps 280: }
281:
282: static void
283: state_search(struct state *p, const struct opts *opts, char *q)
284: {
285: int i, len, ch, rflags, dflag;
286: struct mchars *mc;
287: char *buf;
288: size_t bufsz;
289: recno_t rec;
290: uint32_t fl;
291: DBT key, val;
292: struct res res[MAXRESULTS];
293: regex_t reg;
294: regex_t *regp;
295: char filebuf[10];
296: struct rec record;
297:
298: len = 0;
299: buf = NULL;
300: bufsz = 0;
301: ch = 0;
302: regp = NULL;
303:
1.2 ! kristaps 304: /*
! 305: * Configure how we scan through results to see if we match:
! 306: * whether by regexp or exact matches.
! 307: */
! 308:
1.1 kristaps 309: switch (opts->match) {
310: case (MATCH_REGEX):
311: rflags = REG_EXTENDED | REG_NOSUB |
312: (opts->insens ? REG_ICASE : 0);
313:
314: if (0 != regcomp(®, q, rflags)) {
1.2 ! kristaps 315: fprintf(stderr, "%s: Bad pattern\n", q);
1.1 kristaps 316: return;
317: }
318:
319: regp = ®
320: dflag = R_FIRST;
321: break;
322: case (MATCH_EXACT):
323: key.data = q;
324: key.size = strlen(q) + 1;
325: dflag = R_CURSOR;
326: break;
327: default:
328: dflag = R_FIRST;
329: break;
330: }
331:
332: if (NULL == (mc = mchars_alloc())) {
333: perror(NULL);
334: exit(EXIT_FAILURE);
335: }
336:
337: /*
338: * Iterate over the entire keyword database.
339: * For each record, we must first translate the key into UTF-8.
340: * Following that, make sure it's acceptable.
341: * Lastly, add it to the available records.
342: */
343:
344: while (len < MAXRESULTS) {
345: if ((ch = (*p->db->seq)(p->db, &key, &val, dflag)))
346: break;
347:
348: dflag = R_NEXT;
349:
350: /*
351: * Keys must be sized as such: the keyword must be
352: * non-empty (nil terminator plus one character) and the
353: * value must be 8 (recno_t---uint32_t---index reference
354: * and a uint32_t flag field).
355: */
356:
357: if (key.size < 2 || 8 != val.size) {
1.2 ! kristaps 358: fprintf(stderr, "%s: Corrupt database\n", p->dbf);
1.1 kristaps 359: exit(EXIT_FAILURE);
360: }
361:
362: buf_redup(mc, &buf, &bufsz, (char *)key.data);
363:
364: fl = *(uint32_t *)val.data;
365:
366: if ( ! (fl & opts->types))
367: continue;
368:
369: switch (opts->match) {
370: case (MATCH_REGEX):
371: if (regexec(regp, buf, 0, NULL, 0))
372: continue;
373: break;
374: case (MATCH_EXACT):
375: if (opts->insens && strcasecmp(buf, q))
376: goto send;
377: if ( ! opts->insens && strcmp(buf, q))
378: goto send;
379: break;
380: default:
381: if (opts->insens && NULL == strcasestr(buf, q))
382: continue;
383: if ( ! opts->insens && NULL == strstr(buf, q))
384: continue;
385: break;
386: }
387:
388: /*
389: * Now look up the file itself in our index. The file's
390: * indexed by its recno for fast lookups.
391: */
392:
393: memcpy(&rec, val.data + 4, sizeof(recno_t));
394:
395: if ( ! state_getrecord(p, rec, &record))
396: exit(EXIT_FAILURE);
397:
398: /* If we're in a different section, skip... */
399:
400: if (opts->cat && strcasecmp(opts->cat, record.cat))
401: continue;
402: if (opts->arch && strcasecmp(opts->arch, record.arch))
403: continue;
404:
405: /* FIXME: this needs to be changed. Ugh. Linear. */
406:
407: for (i = 0; i < len; i++)
408: if (res[i].rec == record.rec)
409: break;
410:
411: if (i < len)
412: continue;
413:
414: /*
415: * Now we have our filename, keywords, types, and all
416: * other necessary information.
417: * Process it and add it to our list of results.
418: */
419:
420: filebuf[9] = '\0';
421: snprintf(filebuf, 10, "%u", record.rec);
422: assert('\0' == filebuf[9]);
423:
424: res[len].rec = record.rec;
425: res[len].types = fl;
426:
427: buf_dup(mc, &res[len].keyword, buf);
428: buf_dup(mc, &res[len].uri, filebuf);
429: buf_dup(mc, &res[len].cat, record.cat);
430: buf_dup(mc, &res[len].arch, record.arch);
431: buf_dup(mc, &res[len].title, record.title);
432: buf_dup(mc, &res[len].desc, record.desc);
433: len++;
434: }
435:
436: send:
437: if (ch < 0) {
438: perror(p->dbf);
439: exit(EXIT_FAILURE);
440: }
441:
1.2 ! kristaps 442: /*
! 443: * Sort our results.
! 444: * We do this post-scan (instead of an in-line sort) because
! 445: * it's more or less the same in terms of run-time. Assuming we
! 446: * sort in-line with a tree versus post:
! 447: *
! 448: * In-place: n * O(lg n)
! 449: * After: n + O(n lg n)
! 450: *
! 451: * Whatever. This also buys us simplicity.
! 452: */
! 453:
1.1 kristaps 454: switch (opts->sort) {
455: case (SORT_CAT):
456: qsort(res, len, sizeof(struct res), sort_cat);
457: break;
458: default:
459: qsort(res, len, sizeof(struct res), sort_title);
460: break;
461: }
462:
463: state_output(res, len);
464:
465: for (len-- ; len >= 0; len--) {
466: free(res[len].keyword);
467: free(res[len].title);
468: free(res[len].cat);
469: free(res[len].arch);
470: free(res[len].desc);
471: free(res[len].uri);
472: }
473:
474: free(buf);
475: mchars_free(mc);
476:
477: if (regp)
478: regfree(regp);
479: }
480:
481: /*
482: * Track allocated buffer size for buf_redup().
483: */
484: static inline void
485: buf_alloc(char **buf, size_t *bufsz, size_t sz)
486: {
487:
488: if (sz < *bufsz)
489: return;
490:
491: *bufsz = sz + 1024;
492: if (NULL == (*buf = realloc(*buf, *bufsz))) {
493: perror(NULL);
494: exit(EXIT_FAILURE);
495: }
496: }
497:
498: /*
499: * Like buf_redup() but throwing away the buffer size.
500: */
501: static void
502: buf_dup(struct mchars *mc, char **buf, const char *val)
503: {
504: size_t bufsz;
505:
506: bufsz = 0;
507: *buf = NULL;
508: buf_redup(mc, buf, &bufsz, val);
509: }
510:
511: /*
512: * Normalise strings from the index and database.
513: * These strings are escaped as defined by mandoc_char(7) along with
514: * other goop in mandoc.h (e.g., soft hyphens).
515: */
516: static void
517: buf_redup(struct mchars *mc, char **buf,
518: size_t *bufsz, const char *val)
519: {
520: size_t sz;
521: const char *seq, *cpp;
522: int len, pos;
523: enum mandoc_esc esc;
524: const char rsv[] = { '\\', ASCII_NBRSP, ASCII_HYPH, '\0' };
525:
526: /* Pre-allocate by the length of the input */
527:
528: buf_alloc(buf, bufsz, strlen(val) + 1);
529:
530: pos = 0;
531:
532: while ('\0' != *val) {
533: /*
534: * Halt on the first escape sequence.
535: * This also halts on the end of string, in which case
536: * we just copy, fallthrough, and exit the loop.
537: */
538: if ((sz = strcspn(val, rsv)) > 0) {
539: memcpy(&(*buf)[pos], val, sz);
540: pos += (int)sz;
541: val += (int)sz;
542: }
543:
544: if (ASCII_HYPH == *val) {
545: (*buf)[pos++] = '-';
546: val++;
547: continue;
548: } else if (ASCII_NBRSP == *val) {
549: (*buf)[pos++] = ' ';
550: val++;
551: continue;
552: } else if ('\\' != *val)
553: break;
554:
555: /* Read past the slash. */
556:
557: val++;
558:
559: /*
560: * Parse the escape sequence and see if it's a
561: * predefined character or special character.
562: */
563:
564: esc = mandoc_escape(&val, &seq, &len);
565: if (ESCAPE_ERROR == esc)
566: break;
567:
568: cpp = ESCAPE_SPECIAL == esc ?
569: mchars_spec2str(mc, seq, len, &sz) : NULL;
570:
571: if (NULL == cpp)
572: continue;
573:
574: /* Copy the rendered glyph into the stream. */
575:
576: buf_alloc(buf, bufsz, sz);
577:
578: memcpy(&(*buf)[pos], cpp, sz);
579: pos += (int)sz;
580: }
581:
582: (*buf)[pos] = '\0';
583: }
584:
585: static void
586: state_output(const struct res *res, int sz)
587: {
588: int i;
589:
590: for (i = 0; i < sz; i++)
591: printf("%s(%s%s%s) - %s\n", res[i].title,
592: res[i].cat,
593: *res[i].arch ? "/" : "",
594: *res[i].arch ? res[i].arch : "",
595: res[i].desc);
596: }
597:
598: static void
599: usage(void)
600: {
601:
602: fprintf(stderr, "usage: %s "
603: "[-eIr] "
604: "[-a arch] "
605: "[-c cat] "
606: "[-s sort] "
607: "[-t type[,...]] "
608: "key\n", progname);
609: }
610:
611: static int
612: state_getrecord(struct state *p, recno_t rec, struct rec *rp)
613: {
614: DBT key, val;
615: size_t sz;
616: int rc;
617:
618: key.data = &rec;
619: key.size = sizeof(recno_t);
620:
621: rc = (*p->idx->get)(p->idx, &key, &val, 0);
622: if (rc < 0) {
1.2 ! kristaps 623: perror(p->idxf);
1.1 kristaps 624: return(0);
1.2 ! kristaps 625: } else if (rc > 0)
! 626: goto err;
1.1 kristaps 627:
628: rp->file = (char *)val.data;
1.2 ! kristaps 629: if ((sz = strlen(rp->file) + 1) >= val.size)
! 630: goto err;
1.1 kristaps 631:
632: rp->cat = (char *)val.data + (int)sz;
1.2 ! kristaps 633: if ((sz += strlen(rp->cat) + 1) >= val.size)
! 634: goto err;
1.1 kristaps 635:
636: rp->title = (char *)val.data + (int)sz;
1.2 ! kristaps 637: if ((sz += strlen(rp->title) + 1) >= val.size)
! 638: goto err;
1.1 kristaps 639:
640: rp->arch = (char *)val.data + (int)sz;
1.2 ! kristaps 641: if ((sz += strlen(rp->arch) + 1) >= val.size)
! 642: goto err;
1.1 kristaps 643:
644: rp->desc = (char *)val.data + (int)sz;
645: rp->rec = rec;
646: return(1);
1.2 ! kristaps 647: err:
! 648: fprintf(stderr, "%s: Corrupt index\n", p->idxf);
! 649: return(0);
1.1 kristaps 650: }
651:
652: static int
653: sort_title(const void *p1, const void *p2)
654: {
655:
656: return(strcmp(((const struct res *)p1)->title,
657: ((const struct res *)p2)->title));
658: }
659:
660: static int
661: sort_cat(const void *p1, const void *p2)
662: {
663: int rc;
664:
665: rc = strcmp(((const struct res *)p1)->cat,
666: ((const struct res *)p2)->cat);
667:
668: return(0 == rc ? sort_title(p1, p2) : rc);
669: }
CVSweb