Home | History | Annotate | Line # | Download | only in makemandb
apropos.c revision 1.20
      1 /*	$NetBSD: apropos.c,v 1.20 2016/04/23 14:15:36 christos Exp $	*/
      2 /*-
      3  * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay (at) gmail.com>
      4  * All rights reserved.
      5  *
      6  * This code was developed as part of Google's Summer of Code 2011 program.
      7  *
      8  * Redistribution and use in source and binary forms, with or without
      9  * modification, are permitted provided that the following conditions
     10  * are met:
     11  *
     12  * 1. Redistributions of source code must retain the above copyright
     13  *    notice, this list of conditions and the following disclaimer.
     14  * 2. Redistributions in binary form must reproduce the above copyright
     15  *    notice, this list of conditions and the following disclaimer in
     16  *    the documentation and/or other materials provided with the
     17  *    distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
     22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
     23  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
     24  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
     25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
     27  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
     28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
     29  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     30  * SUCH DAMAGE.
     31  */
     32 
     33 #include <sys/cdefs.h>
     34 __RCSID("$NetBSD: apropos.c,v 1.20 2016/04/23 14:15:36 christos Exp $");
     35 
     36 #include <err.h>
     37 #include <stdio.h>
     38 #include <stdlib.h>
     39 #include <string.h>
     40 #include <unistd.h>
     41 #include <util.h>
     42 
     43 #include "apropos-utils.h"
     44 
     45 typedef struct apropos_flags {
     46 	char *sec_nums;
     47 	int nresults;
     48 	int pager;
     49 	int no_context;
     50 	query_format format;
     51 	int legacy;
     52 	const char *machine;
     53 } apropos_flags;
     54 
     55 typedef struct callback_data {
     56 	int count;
     57 	FILE *out;
     58 	apropos_flags *aflags;
     59 } callback_data;
     60 
     61 static const unsigned int sections_args_length = 16;
     62 
     63 static char *remove_stopwords(const char *);
     64 static int query_callback(void *, const char * , const char *, const char *,
     65 	const char *, size_t);
     66 __dead static void usage(void);
     67 
     68 #define _PATH_PAGER	"/usr/bin/more -s"
     69 
     70 static void
     71 parseargs(int argc, char **argv, struct apropos_flags *aflags)
     72 {
     73 	int ch;
     74 	char sec[2] = {0, 0};
     75 
     76 	while ((ch = getopt(argc, argv, "123456789Cchiln:PprS:s:")) != -1) {
     77 		switch (ch) {
     78 		case '1':
     79 		case '2':
     80 		case '3':
     81 		case '4':
     82 		case '5':
     83 		case '6':
     84 		case '7':
     85 		case '8':
     86 		case '9':
     87 			/*
     88 			 *Generate a space separated list of all the
     89 			 * requested sections
     90 			 */
     91 			sec[0] = (char) ch ;
     92 			if (aflags->sec_nums == NULL) {
     93 				aflags->sec_nums =
     94 				    emalloc(sections_args_length);
     95 				memcpy(aflags->sec_nums, sec, 2);
     96 			} else
     97 				concat2(&aflags->sec_nums, sec, 1);
     98 			break;
     99 		case 'C':
    100 			aflags->no_context = 1;
    101 			break;
    102 		case 'c':
    103 			aflags->no_context = 0;
    104 			break;
    105 		case 'h':
    106 			aflags->format = APROPOS_HTML;
    107 			break;
    108 		case 'i':
    109 			aflags->format = APROPOS_TERM;
    110 			break;
    111 		case 'l':
    112 			aflags->legacy = 1;
    113 			aflags->no_context = 1;
    114 			aflags->format = APROPOS_NONE;
    115 			break;
    116 		case 'n':
    117 			aflags->nresults = atoi(optarg);
    118 			break;
    119 		case 'p':	// user wants a pager
    120 			aflags->pager = 1;
    121 			/*FALLTHROUGH*/
    122 		case 'P':
    123 			aflags->format = APROPOS_PAGER;
    124 			break;
    125 		case 'r':
    126 			aflags->format = APROPOS_NONE;
    127 			break;
    128 		case 'S':
    129 			aflags->machine = optarg;
    130 			break;
    131 		case 's':
    132 			if (aflags->sec_nums == NULL) {
    133 				size_t arglen = strlen(optarg);
    134 				aflags->sec_nums =
    135 				    arglen > sections_args_length
    136 					? emalloc(arglen + 1)
    137 					: emalloc(sections_args_length);
    138 				memcpy(aflags->sec_nums, optarg, arglen + 1);
    139 			} else
    140 				concat(&aflags->sec_nums, optarg);
    141 			break;
    142 		case '?':
    143 		default:
    144 			usage();
    145 		}
    146 	}
    147 }
    148 
    149 int
    150 main(int argc, char *argv[])
    151 {
    152 	query_args args;
    153 	char *query = NULL;	// the user query
    154 	char *errmsg = NULL;
    155 	char *str;
    156 	int rc = 0;
    157 	int s;
    158 	callback_data cbdata;
    159 	cbdata.out = stdout;		// the default output stream
    160 	cbdata.count = 0;
    161 	apropos_flags aflags;
    162 	aflags.sec_nums = NULL;
    163 	cbdata.aflags = &aflags;
    164 	sqlite3 *db;
    165 	setprogname(argv[0]);
    166 	if (argc < 2)
    167 		usage();
    168 
    169 	memset(&aflags, 0, sizeof(aflags));
    170 
    171 	if (!isatty(STDOUT_FILENO))
    172 		aflags.format = APROPOS_NONE;
    173 	else
    174 		aflags.format = APROPOS_TERM;
    175 
    176 	if ((str = getenv("APROPOS")) != NULL) {
    177 		char **ptr = emalloc((strlen(str) + 2) * sizeof(*ptr));
    178 #define WS "\t\n\r "
    179 		ptr[0] = __UNCONST(getprogname());
    180 		for (s = 1, str = strtok(str, WS); str;
    181 		    str = strtok(NULL, WS), s++)
    182 			ptr[s] = str;
    183 		ptr[s] = NULL;
    184 		parseargs(s, ptr, &aflags);
    185 		free(ptr);
    186 		optreset = 1;
    187 		optind = 1;
    188 	}
    189 
    190 	parseargs(argc, argv, &aflags);
    191 
    192 	argc -= optind;
    193 	argv += optind;
    194 
    195 	if (!argc)
    196 		usage();
    197 
    198 	str = NULL;
    199 	while (argc--)
    200 		concat(&str, *argv++);
    201 	/* Eliminate any stopwords from the query */
    202 	query = remove_stopwords(lower(str));
    203 
    204 	/*
    205 	 * If the query consisted only of stopwords and we removed all of
    206 	 * them, use the original query.
    207 	 */
    208 	if (query == NULL)
    209 		query = str;
    210 	else
    211 		free(str);
    212 
    213 	if ((db = init_db(MANDB_READONLY, MANCONF)) == NULL)
    214 		exit(EXIT_FAILURE);
    215 
    216 	/* If user wants to page the output, then set some settings */
    217 	if (aflags.pager) {
    218 		const char *pager = getenv("PAGER");
    219 		if (pager == NULL)
    220 			pager = _PATH_PAGER;
    221 		/* Open a pipe to the pager */
    222 		if ((cbdata.out = popen(pager, "w")) == NULL) {
    223 			close_db(db);
    224 			err(EXIT_FAILURE, "pipe failed");
    225 		}
    226 	}
    227 
    228 	args.search_str = query;
    229 	args.sec_nums = aflags.sec_nums;
    230 	args.legacy = aflags.legacy;
    231 	args.nrec = aflags.nresults ? aflags.nresults : -1;
    232 	args.offset = 0;
    233 	args.machine = aflags.machine;
    234 	args.callback = &query_callback;
    235 	args.callback_data = &cbdata;
    236 	args.errmsg = &errmsg;
    237 
    238 	if (aflags.format == APROPOS_HTML) {
    239 		fprintf(cbdata.out, "<html>\n<header>\n<title>apropos results "
    240 		    "for %s</title></header>\n<body>\n<table cellpadding=\"4\""
    241 		    "style=\"border: 1px solid #000000; border-collapse:"
    242 		    "collapse;\" border=\"1\">\n", query);
    243 	}
    244 	rc = run_query(db, aflags.format, &args);
    245 	if (aflags.format == APROPOS_HTML)
    246 		fprintf(cbdata.out, "</table>\n</body>\n</html>\n");
    247 
    248 	free(query);
    249 	free(aflags.sec_nums);
    250 	close_db(db);
    251 	if (errmsg) {
    252 		warnx("%s", errmsg);
    253 		free(errmsg);
    254 		exit(EXIT_FAILURE);
    255 	}
    256 
    257 	if (rc < 0) {
    258 		/* Something wrong with the database. Exit */
    259 		exit(EXIT_FAILURE);
    260 	}
    261 
    262 	if (cbdata.count == 0) {
    263 		warnx("No relevant results obtained.\n"
    264 		    "Please make sure that you spelled all the terms correctly "
    265 		    "or try using better keywords.");
    266 	}
    267 	return 0;
    268 }
    269 
    270 /*
    271  * query_callback --
    272  *  Callback function for run_query.
    273  *  It simply outputs the results from do_query. If the user specified the -p
    274  *  option, then the output is sent to a pager, otherwise stdout is the default
    275  *  output stream.
    276  */
    277 static int
    278 query_callback(void *data, const char *section, const char *name,
    279 	const char *name_desc, const char *snippet, size_t snippet_length)
    280 {
    281 	callback_data *cbdata = (callback_data *) data;
    282 	FILE *out = cbdata->out;
    283 	cbdata->count++;
    284 	if (cbdata->aflags->format != APROPOS_HTML) {
    285 	    fprintf(out, cbdata->aflags->legacy ? "%s(%s) - %s\n" :
    286 		"%s (%s)\t%s\n", name, section, name_desc);
    287 	    if (cbdata->aflags->no_context == 0)
    288 		    fprintf(out, "%s\n\n", snippet);
    289 	} else {
    290 	    fprintf(out, "<tr><td>%s(%s)</td><td>%s</td></tr>\n", name,
    291 		section, name_desc);
    292 	    if (cbdata->aflags->no_context == 0)
    293 		    fprintf(out, "<tr><td colspan=2>%s</td></tr>\n", snippet);
    294 	}
    295 
    296 	return 0;
    297 }
    298 
    299 #include "stopwords.c"
    300 
    301 /*
    302  * remove_stopwords--
    303  *  Scans the query and removes any stop words from it.
    304  *  Returns the modified query or NULL, if it contained only stop words.
    305  */
    306 
    307 static char *
    308 remove_stopwords(const char *query)
    309 {
    310 	size_t len, idx;
    311 	char *output, *buf;
    312 	const char *sep, *next;
    313 
    314 	output = buf = emalloc(strlen(query) + 1);
    315 
    316 	for (; query[0] != '\0'; query = next) {
    317 		sep = strchr(query, ' ');
    318 		if (sep == NULL) {
    319 			len = strlen(query);
    320 			next = query + len;
    321 		} else {
    322 			len = sep - query;
    323 			next = sep + 1;
    324 		}
    325 		if (len == 0)
    326 			continue;
    327 		idx = stopwords_hash(query, len);
    328 		if (memcmp(stopwords[idx], query, len) == 0 &&
    329 		    stopwords[idx][len] == '\0')
    330 			continue;
    331 		memcpy(buf, query, len);
    332 		buf += len;
    333 		*buf++ = ' ';
    334 	}
    335 
    336 	if (output == buf) {
    337 		free(output);
    338 		return NULL;
    339 	}
    340 	buf[-1] = '\0';
    341 	return output;
    342 }
    343 
    344 /*
    345  * usage --
    346  *	print usage message and die
    347  */
    348 static void
    349 usage(void)
    350 {
    351 	fprintf(stderr, "Usage: %s [-123456789Ccilpr] [-n results] "
    352 	    "[-S machine] [-s section] query\n",
    353 	    getprogname());
    354 	exit(1);
    355 }
    356