]> git.cameronkatri.com Git - mandoc.git/blobdiff - mansearch.c
missing features: line length and italic correction
[mandoc.git] / mansearch.c
index ad18f94285da9281984e890560f4d8300e612e2c..e054e4dd43e05600326a5e1eca8182693785cd1f 100644 (file)
@@ -1,6 +1,7 @@
-/*     $Id: mansearch.c,v 1.2 2012/06/08 14:14:30 kristaps Exp $ */
+/*     $Id: mansearch.c,v 1.8 2013/10/20 00:03:05 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
+ * Copyright (c) 2013 Ingo Schwarze <schwarze@openbsd.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
 #include "config.h"
 #endif
 
-#include <sys/param.h>
-
 #include <assert.h>
 #include <fcntl.h>
 #include <getopt.h>
+#include <limits.h>
+#include <regex.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <stddef.h>
 #include <string.h>
 #include <unistd.h>
 
+#ifdef HAVE_OHASH
 #include <ohash.h>
+#else
+#include "compat_ohash.h"
+#endif
 #include <sqlite3.h>
 
 #include "mandoc.h"
 #include "manpath.h"
-#include "mandocdb.h"
 #include "mansearch.h"
 
-#define        BIND_TEXT(_db, _s, _i, _v) \
-       if (SQLITE_OK != sqlite3_bind_text \
+#define        SQL_BIND_TEXT(_db, _s, _i, _v) \
+       do { if (SQLITE_OK != sqlite3_bind_text \
                ((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-               fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
-#define        BIND_INT64(_db, _s, _i, _v) \
-       if (SQLITE_OK != sqlite3_bind_int64 \
+               fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+       } while (0)
+#define        SQL_BIND_INT64(_db, _s, _i, _v) \
+       do { if (SQLITE_OK != sqlite3_bind_int64 \
                ((_s), (_i)++, (_v))) \
-               fprintf(stderr, "%s\n", sqlite3_errmsg((_db)))
+               fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+       } while (0)
+#define        SQL_BIND_BLOB(_db, _s, _i, _v) \
+       do { if (SQLITE_OK != sqlite3_bind_blob \
+               ((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
+               fprintf(stderr, "%s\n", sqlite3_errmsg((_db))); \
+       } while (0)
 
 struct expr {
-       int              glob; /* is glob? */
-       uint64_t         bits; /* type-mask */
-       const char      *v; /* search value */
-       struct expr     *next; /* next in sequence */
+       uint64_t         bits;    /* type-mask */
+       const char      *substr;  /* to search for, if applicable */
+       regex_t          regexp;  /* compiled regexp, if applicable */
+       struct expr     *next;    /* next in sequence */
 };
 
 struct match {
@@ -113,21 +124,26 @@ static    const struct type types[] = {
 static void            *hash_alloc(size_t, void *);
 static void             hash_free(void *, size_t, void *);
 static void            *hash_halloc(size_t, void *);
-static struct expr     *exprcomp(int, char *[]);
+static struct expr     *exprcomp(const struct mansearch *, 
+                               int, char *[]);
 static void             exprfree(struct expr *);
-static struct expr     *exprterm(char *);
+static struct expr     *exprterm(const struct mansearch *, char *, int);
+static void             sql_match(sqlite3_context *context,
+                               int argc, sqlite3_value **argv);
+static void             sql_regexp(sqlite3_context *context,
+                               int argc, sqlite3_value **argv);
 static char            *sql_statement(const struct expr *,
                                const char *, const char *);
 
 int
-mansearch(const struct manpaths *paths, 
-               const char *arch, const char *sec,
+mansearch(const struct mansearch *search,
+               const struct manpaths *paths, 
                int argc, char *argv[], 
                struct manpage **res, size_t *sz)
 {
        int              fd, rc, c;
        int64_t          id;
-       char             buf[MAXPATHLEN];
+       char             buf[PATH_MAX];
        char            *sql;
        struct expr     *e, *ep;
        sqlite3         *db;
@@ -154,7 +170,7 @@ mansearch(const struct manpaths *paths,
 
        if (0 == argc)
                goto out;
-       if (NULL == (e = exprcomp(argc, argv)))
+       if (NULL == (e = exprcomp(search, argc, argv)))
                goto out;
 
        /*
@@ -164,7 +180,7 @@ mansearch(const struct manpaths *paths,
         * on our current directory from which to start the chdir().
         */
 
-       if (NULL == getcwd(buf, MAXPATHLEN)) {
+       if (NULL == getcwd(buf, PATH_MAX)) {
                perror(NULL);
                goto out;
        } else if (-1 == (fd = open(buf, O_RDONLY, 0))) {
@@ -172,7 +188,7 @@ mansearch(const struct manpaths *paths,
                goto out;
        }
 
-       sql = sql_statement(e, arch, sec);
+       sql = sql_statement(e, search->arch, search->sec);
 
        /*
         * Loop over the directories (containing databases) for us to
@@ -202,19 +218,34 @@ mansearch(const struct manpaths *paths,
                        continue;
                }
 
+               /*
+                * Define the SQL functions for substring
+                * and regular expression matching.
+                */
+
+               c = sqlite3_create_function(db, "match", 2,
+                   SQLITE_ANY, NULL, sql_match, NULL, NULL);
+               assert(SQLITE_OK == c);
+               c = sqlite3_create_function(db, "regexp", 2,
+                   SQLITE_ANY, NULL, sql_regexp, NULL, NULL);
+               assert(SQLITE_OK == c);
+
                j = 1;
                c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
                if (SQLITE_OK != c)
                        fprintf(stderr, "%s\n", sqlite3_errmsg(db));
 
-               if (NULL != arch)
-                       BIND_TEXT(db, s, j, arch);
-               if (NULL != sec)
-                       BIND_TEXT(db, s, j, arch);
+               if (NULL != search->arch)
+                       SQL_BIND_TEXT(db, s, j, search->arch);
+               if (NULL != search->sec)
+                       SQL_BIND_TEXT(db, s, j, search->sec);
 
                for (ep = e; NULL != ep; ep = ep->next) {
-                       BIND_TEXT(db, s, j, ep->v);
-                       BIND_INT64(db, s, j, ep->bits);
+                       if (NULL == ep->substr) {
+                               SQL_BIND_BLOB(db, s, j, ep->regexp);
+                       } else
+                               SQL_BIND_TEXT(db, s, j, ep->substr);
+                       SQL_BIND_INT64(db, s, j, ep->bits);
                }
 
                memset(&htab, 0, sizeof(struct ohash));
@@ -262,9 +293,9 @@ mansearch(const struct manpaths *paths,
                                        (*res, maxres * sizeof(struct manpage));
                        }
                        strlcpy((*res)[cur].file, 
-                               paths->paths[i], MAXPATHLEN);
-                       strlcat((*res)[cur].file, "/", MAXPATHLEN);
-                       strlcat((*res)[cur].file, mp->file, MAXPATHLEN);
+                               paths->paths[i], PATH_MAX);
+                       strlcat((*res)[cur].file, "/", PATH_MAX);
+                       strlcat((*res)[cur].file, mp->file, PATH_MAX);
                        (*res)[cur].desc = mp->desc;
                        (*res)[cur].form = mp->form;
                        free(mp->file);
@@ -283,6 +314,37 @@ out:
        return(rc);
 }
 
+/*
+ * Implement substring match as an application-defined SQL function.
+ * Using the SQL LIKE or GLOB operators instead would be a bad idea
+ * because that would require escaping metacharacters in the string
+ * being searched for.
+ */
+static void
+sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+       assert(2 == argc);
+       sqlite3_result_int(context, NULL != strcasestr(
+           (const char *)sqlite3_value_text(argv[1]),
+           (const char *)sqlite3_value_text(argv[0])));
+}
+
+/*
+ * Implement regular expression match
+ * as an application-defined SQL function.
+ */
+static void
+sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
+{
+
+       assert(2 == argc);
+       sqlite3_result_int(context, !regexec(
+           (regex_t *)sqlite3_value_blob(argv[0]),
+           (const char *)sqlite3_value_text(argv[1]),
+           0, NULL, 0));
+}
+
 /*
  * Prepare the search SQL statement.
  * We search for any of the words specified in our match expression.
@@ -292,12 +354,12 @@ static char *
 sql_statement(const struct expr *e, const char *arch, const char *sec)
 {
        char            *sql;
-       const char      *glob = "(key GLOB ? AND bits & ?)";
-       const char      *eq = "(key = ? AND bits & ?)";
+       const char      *substr = "(key MATCH ? AND bits & ?)";
+       const char      *regexp = "(key REGEXP ? AND bits & ?)";
        const char      *andarch = "arch = ? AND ";
        const char      *andsec = "sec = ? AND ";
-       size_t           globsz;
-       size_t           eqsz;
+       size_t           substrsz;
+       size_t           regexpsz;
        size_t           sz;
 
        sql = mandoc_strdup
@@ -306,8 +368,8 @@ sql_statement(const struct expr *e, const char *arch, const char *sec)
                 "INNER JOIN docs ON docs.id=keys.docid "
                 "WHERE ");
        sz = strlen(sql);
-       globsz = strlen(glob);
-       eqsz = strlen(eq);
+       substrsz = strlen(substr);
+       regexpsz = strlen(regexp);
 
        if (NULL != arch) {
                sz += strlen(andarch) + 1;
@@ -326,10 +388,10 @@ sql_statement(const struct expr *e, const char *arch, const char *sec)
        strlcat(sql, "(", sz);
 
        for ( ; NULL != e; e = e->next) {
-               sz += (e->glob ? globsz : eqsz) + 
+               sz += (NULL == e->substr ? regexpsz : substrsz) + 
                        (NULL == e->next ? 3 : 5);
                sql = mandoc_realloc(sql, sz);
-               strlcat(sql, e->glob ? glob : eq, sz);
+               strlcat(sql, NULL == e->substr ? regexp : substr, sz);
                strlcat(sql, NULL == e->next ? ");" : " OR ", sz);
        }
 
@@ -342,15 +404,21 @@ sql_statement(const struct expr *e, const char *arch, const char *sec)
  * "(", "foo=bar", etc.).
  */
 static struct expr *
-exprcomp(int argc, char *argv[])
+exprcomp(const struct mansearch *search, int argc, char *argv[])
 {
-       int              i;
+       int              i, cs;
        struct expr     *first, *next, *cur;
 
        first = cur = NULL;
 
        for (i = 0; i < argc; i++) {
-               next = exprterm(argv[i]);
+               if (0 == strcmp("-i", argv[i])) {
+                       if (++i >= argc)
+                               return(NULL);
+                       cs = 0;
+               } else
+                       cs = 1;
+               next = exprterm(search, argv[i], cs);
                if (NULL == next) {
                        exprfree(first);
                        return(NULL);
@@ -366,7 +434,7 @@ exprcomp(int argc, char *argv[])
 }
 
 static struct expr *
-exprterm(char *buf)
+exprterm(const struct mansearch *search, char *buf, int cs)
 {
        struct expr     *e;
        char            *key, *v;
@@ -377,6 +445,14 @@ exprterm(char *buf)
 
        e = mandoc_calloc(1, sizeof(struct expr));
 
+       /*"whatis" mode uses an opaque string and default fields. */
+
+       if (MANSEARCH_WHATIS & search->flags) {
+               e->substr = buf;
+               e->bits = search->deftype;
+               return(e);
+       }
+
        /*
         * If no =~ is specified, search with equality over names and
         * descriptions.
@@ -384,15 +460,21 @@ exprterm(char *buf)
         */
 
        if (NULL == (v = strpbrk(buf, "=~"))) {
-               e->v = buf;
-               e->bits = TYPE_Nm | TYPE_Nd;
+               e->substr = buf;
+               e->bits = search->deftype;
                return(e);
        } else if (v == buf)
-               e->bits = TYPE_Nm | TYPE_Nd;
+               e->bits = search->deftype;
 
-       e->glob = '~' == *v;
-       *v++ = '\0';
-       e->v = v;
+       if ('~' == *v++) {
+               if (regcomp(&e->regexp, v,
+                   REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE))) {
+                       free(e);
+                       return(NULL);
+               }
+       } else
+               e->substr = v;
+       v[-1] = '\0';
 
        /*
         * Parse out all possible fields.