[BACK]Return to apropos.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / usr.sbin / makemandb

File: [cvs.NetBSD.org] / src / usr.sbin / makemandb / apropos.c (download)

Revision 1.16, Tue Apr 2 17:16:50 2013 UTC (19 months, 3 weeks ago) by christos
Branch: MAIN
CVS Tags: yamt-pagecache-base9, tls-maxphys-base, tls-earlyentropy-base, tls-earlyentropy, riastradh-xf86-video-intel-2-7-1-pre-2-21-15, riastradh-drm2-base3, riastradh-drm2-base2, riastradh-drm2-base1, riastradh-drm2-base, riastradh-drm2, netbsd-7-base, netbsd-7, HEAD
Changes since 1.15: +36 -18 lines

instead of having a format and no format flag, and exposing various formatters,
provide a format enum and expose html formatting too.

/*	$NetBSD: apropos.c,v 1.16 2013/04/02 17:16:50 christos Exp $	*/
/*-
 * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay@gmail.com>
 * All rights reserved.
 *
 * This code was developed as part of Google's Summer of Code 2011 program.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: apropos.c,v 1.16 2013/04/02 17:16:50 christos Exp $");

#include <err.h>
#include <search.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

#include "apropos-utils.h"
#include "sqlite3.h"

typedef struct apropos_flags {
	int sec_nums[SECMAX];
	int nresults;
	int pager;
	int no_context;
	query_format format;
	int legacy;
	const char *machine;
} apropos_flags;

typedef struct callback_data {
	int count;
	FILE *out;
	apropos_flags *aflags;
} callback_data;

static char *remove_stopwords(const char *);
static int query_callback(void *, const char * , const char *, const char *,
	const char *, size_t);
__dead static void usage(void);

#define _PATH_PAGER	"/usr/bin/more -s"

static void
parseargs(int argc, char **argv, struct apropos_flags *aflags)
{
	int ch;
	while ((ch = getopt(argc, argv, "123456789Cchiln:PprS:s:")) != -1) {
		switch (ch) {
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			aflags->sec_nums[ch - '1'] = 1;
			break;
		case 'C':
			aflags->no_context = 1;
			break;
		case 'c':
			aflags->no_context = 0;
			break;
		case 'h':
			aflags->format = APROPOS_HTML;
			break;
		case 'i':
			aflags->format = APROPOS_TERM;
			break;
		case 'l':
			aflags->legacy = 1;
			aflags->no_context = 1;
			aflags->format = APROPOS_NONE;
			break;
		case 'n':
			aflags->nresults = atoi(optarg);
			break;
		case 'p':	// user wants a pager
			aflags->pager = 1;
			/*FALLTHROUGH*/
		case 'P':
			aflags->format = APROPOS_PAGER;
			break;
		case 'r':
			aflags->format = APROPOS_NONE;
			break;
		case 'S':
			aflags->machine = optarg;
			break;
		case 's':
			ch = atoi(optarg);
			if (ch < 1 || ch > 9)
				errx(EXIT_FAILURE, "Invalid section");
			aflags->sec_nums[ch - 1] = 1;
			break;
		case '?':
		default:
			usage();
		}
	}
}

int
main(int argc, char *argv[])
{
	query_args args;
	char *query = NULL;	// the user query
	char *errmsg = NULL;
	char *str;
	int rc = 0;
	int s;
	callback_data cbdata;
	cbdata.out = stdout;		// the default output stream
	cbdata.count = 0;
	apropos_flags aflags;
	cbdata.aflags = &aflags;
	sqlite3 *db;
	setprogname(argv[0]);
	if (argc < 2)
		usage();

	memset(&aflags, 0, sizeof(aflags));

	if (!isatty(STDOUT_FILENO))
		aflags.format = APROPOS_NONE;
	else
		aflags.format = APROPOS_TERM;

	if ((str = getenv("APROPOS")) != NULL) {
		char **ptr = emalloc((strlen(str) + 2) * sizeof(*ptr));
#define WS "\t\n\r "
		ptr[0] = __UNCONST(getprogname());
		for (s = 1, str = strtok(str, WS); str;
		    str = strtok(NULL, WS), s++)
			ptr[s] = str;
		ptr[s] = NULL;
		parseargs(s, ptr, &aflags);
		free(ptr);
		optreset = 1;
		optind = 1;
	}

	parseargs(argc, argv, &aflags);

	/*
	 * If the user specifies a section number as an option, the
	 * corresponding index element in sec_nums is set to the string
	 * representing that section number.
	 */

	argc -= optind;
	argv += optind;

	if (!argc)
		usage();

	str = NULL;
	while (argc--)
		concat(&str, *argv++);
	/* Eliminate any stopwords from the query */
	query = remove_stopwords(lower(str));
	free(str);

	/* if any error occured in remove_stopwords, exit */
	if (query == NULL)
		errx(EXIT_FAILURE, "Try using more relevant keywords");

	if ((db = init_db(MANDB_READONLY, MANCONF)) == NULL)
		exit(EXIT_FAILURE);

	/* If user wants to page the output, then set some settings */
	if (aflags.pager) {
		const char *pager = getenv("PAGER");
		if (pager == NULL)
			pager = _PATH_PAGER;
		/* Open a pipe to the pager */
		if ((cbdata.out = popen(pager, "w")) == NULL) {
			close_db(db);
			err(EXIT_FAILURE, "pipe failed");
		}
	}

	args.search_str = query;
	args.sec_nums = aflags.sec_nums;
	args.legacy = aflags.legacy;
	args.nrec = aflags.nresults ? aflags.nresults : -1;
	args.offset = 0;
	args.machine = aflags.machine;
	args.callback = &query_callback;
	args.callback_data = &cbdata;
	args.errmsg = &errmsg;

	if (aflags.format == APROPOS_HTML) {
		fprintf(cbdata.out, "<html>\n<header>\n<title>apropos results "
		    "for %s</title></header>\n<body>\n<table cellpadding=\"4\""
		    "style=\"border: 1px solid #000000; border-collapse:"
		    "collapse;\" border=\"1\">\n", query);
	}
	rc = run_query(db, aflags.format, &args);
	if (aflags.format == APROPOS_HTML)
		fprintf(cbdata.out, "</table>\n</body>\n</html>\n");

	free(query);
	close_db(db);
	if (errmsg) {
		warnx("%s", errmsg);
		free(errmsg);
		exit(EXIT_FAILURE);
	}

	if (rc < 0) {
		/* Something wrong with the database. Exit */
		exit(EXIT_FAILURE);
	}

	if (cbdata.count == 0) {
		warnx("No relevant results obtained.\n"
		    "Please make sure that you spelled all the terms correctly "
		    "or try using better keywords.");
	}
	return 0;
}

/*
 * query_callback --
 *  Callback function for run_query.
 *  It simply outputs the results from do_query. If the user specified the -p
 *  option, then the output is sent to a pager, otherwise stdout is the default
 *  output stream.
 */
static int
query_callback(void *data, const char *section, const char *name,
	const char *name_desc, const char *snippet, size_t snippet_length)
{
	callback_data *cbdata = (callback_data *) data;
	FILE *out = cbdata->out;
	cbdata->count++;
	if (cbdata->aflags->format != APROPOS_HTML) {
	    fprintf(out, cbdata->aflags->legacy ? "%s(%s) - %s\n" :
		"%s (%s)\t%s\n", name, section, name_desc);
	    if (cbdata->aflags->no_context == 0)
		    fprintf(out, "%s\n\n", snippet);
	} else {
	    fprintf(out, "<tr><td>%s(%s)</td><td>%s</td></tr>\n", name,
		section, name_desc);
	    if (cbdata->aflags->no_context == 0)
		    fprintf(out, "<tr><td colspan=2>%s</td></tr>\n", snippet);
	}

	return 0;
}

#include "stopwords.c"

/*
 * remove_stopwords--
 *  Scans the query and removes any stop words from it.
 *  Returns the modified query or NULL, if it contained only stop words.
 */

static char *
remove_stopwords(const char *query)
{
	size_t len, idx;
	char *output, *buf;
	const char *sep, *next;

	output = buf = emalloc(strlen(query) + 1);

	for (; query[0] != '\0'; query = next) {
		sep = strchr(query, ' ');
		if (sep == NULL) {
			len = strlen(query);
			next = query + len;
		} else {
			len = sep - query;
			next = sep + 1;
		}
		if (len == 0)
			continue;
		idx = stopwords_hash(query, len);
		if (memcmp(stopwords[idx], query, len) == 0 &&
		    stopwords[idx][len] == '\0')
			continue;
		memcpy(buf, query, len);
		buf += len;
		*buf++ = ' ';
	}

	if (output == buf) {
		free(output);
		return NULL;
	}
	buf[-1] = '\0';
	return output;
}

/*
 * usage --
 *	print usage message and die
 */
static void
usage(void)
{
	fprintf(stderr, "Usage: %s [-123456789Ccilpr] [-n results] "
	    "[-S machine] [-s section] query\n",
	    getprogname());
	exit(1);
}