1/*	$NetBSD$	*/
2/*-
3 * Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay@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$");
35
36#include <err.h>
37#include <search.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42#include <util.h>
43
44#include "apropos-utils.h"
45#include "sqlite3.h"
46
47typedef struct apropos_flags {
48	int sec_nums[SECMAX];
49	int nresults;
50	int pager;
51	int no_context;
52	const char *machine;
53} apropos_flags;
54
55typedef struct callback_data {
56	int count;
57	FILE *out;
58	apropos_flags *aflags;
59} callback_data;
60
61static char *remove_stopwords(const char *);
62static int query_callback(void *, const char * , const char *, const char *,
63	const char *, size_t);
64__dead static void usage(void);
65
66#define _PATH_PAGER	"/usr/bin/more -s"
67
68int
69main(int argc, char *argv[])
70{
71#ifdef NOTYET
72	static const char *snippet_args[] = {"\033[1m", "\033[0m", "..."};
73#endif
74	query_args args;
75	char *query = NULL;	// the user query
76	char *errmsg = NULL;
77	char *str;
78	int ch, rc = 0;
79	int s;
80	callback_data cbdata;
81	cbdata.out = stdout;		// the default output stream
82	cbdata.count = 0;
83	apropos_flags aflags;
84	cbdata.aflags = &aflags;
85	sqlite3 *db;
86	setprogname(argv[0]);
87	if (argc < 2)
88		usage();
89
90	memset(&aflags, 0, sizeof(aflags));
91
92	/*If the user specifies a section number as an option, the corresponding
93	 * index element in sec_nums is set to the string representing that
94	 * section number.
95	 */
96	while ((ch = getopt(argc, argv, "123456789Ccn:pS:s:")) != -1) {
97		switch (ch) {
98		case '1':
99		case '2':
100		case '3':
101		case '4':
102		case '5':
103		case '6':
104		case '7':
105		case '8':
106		case '9':
107			aflags.sec_nums[ch - '1'] = 1;
108			break;
109		case 'C':
110			aflags.no_context = 1;
111			break;
112		case 'c':
113			aflags.no_context = 0;
114			break;
115		case 'n':
116			aflags.nresults = atoi(optarg);
117			break;
118		case 'p':	//user wants to view more than 10 results and page them
119			aflags.pager = 1;
120			aflags.nresults = -1;	// Fetch all records
121			break;
122		case 'S':
123			aflags.machine = optarg;
124			break;
125		case 's':
126			s = atoi(optarg);
127			if (s < 1 || s > 9)
128				errx(EXIT_FAILURE, "Invalid section");
129			aflags.sec_nums[s - 1] = 1;
130			break;
131		case '?':
132		default:
133			usage();
134		}
135	}
136
137	argc -= optind;
138	argv += optind;
139
140	if (!argc)
141		usage();
142
143	str = NULL;
144	while (argc--)
145		concat(&str, *argv++);
146	/* Eliminate any stopwords from the query */
147	query = remove_stopwords(lower(str));
148	free(str);
149
150	/* if any error occured in remove_stopwords, exit */
151	if (query == NULL)
152		errx(EXIT_FAILURE, "Try using more relevant keywords");
153
154	if ((db = init_db(MANDB_READONLY)) == NULL)
155		exit(EXIT_FAILURE);
156
157	/* If user wants to page the output, then set some settings */
158	if (aflags.pager) {
159		const char *pager = getenv("PAGER");
160		if (pager == NULL)
161			pager = _PATH_PAGER;
162		/* Open a pipe to the pager */
163		if ((cbdata.out = popen(pager, "w")) == NULL) {
164			close_db(db);
165			err(EXIT_FAILURE, "pipe failed");
166		}
167	}
168
169	args.search_str = query;
170	args.sec_nums = aflags.sec_nums;
171	args.nrec = aflags.nresults ? aflags.nresults : 10;
172	args.offset = 0;
173	args.machine = aflags.machine;
174	args.callback = &query_callback;
175	args.callback_data = &cbdata;
176	args.errmsg = &errmsg;
177
178#ifdef NOTYET
179	rc = run_query(db, snippet_args, &args);
180#else
181	rc = run_query_pager(db, &args);
182#endif
183
184	free(query);
185	close_db(db);
186	if (errmsg) {
187		warnx("%s", errmsg);
188		free(errmsg);
189		exit(EXIT_FAILURE);
190	}
191
192	if (rc < 0) {
193		/* Something wrong with the database. Exit */
194		exit(EXIT_FAILURE);
195	}
196
197	if (cbdata.count == 0) {
198		warnx("No relevant results obtained.\n"
199			  "Please make sure that you spelled all the terms correctly "
200			  "or try using better keywords.");
201	}
202	return 0;
203}
204
205/*
206 * query_callback --
207 *  Callback function for run_query.
208 *  It simply outputs the results from do_query. If the user specified the -p
209 *  option, then the output is sent to a pager, otherwise stdout is the default
210 *  output stream.
211 */
212static int
213query_callback(void *data, const char *section, const char *name,
214	const char *name_desc, const char *snippet, size_t snippet_length)
215{
216	callback_data *cbdata = (callback_data *) data;
217	FILE *out = cbdata->out;
218	cbdata->count++;
219	fprintf(out, "%s (%s)\t%s\n", name, section, name_desc);
220
221	if (cbdata->aflags->no_context == 0)
222		fprintf(out, "%s\n\n", snippet);
223
224	return 0;
225}
226
227#include "stopwords.c"
228
229/*
230 * remove_stopwords--
231 *  Scans the query and removes any stop words from it.
232 *  Returns the modified query or NULL, if it contained only stop words.
233 */
234
235static char *
236remove_stopwords(const char *query)
237{
238	size_t len, idx;
239	char *output, *buf;
240	const char *sep, *next;
241
242	output = buf = emalloc(strlen(query) + 1);
243
244	for (; query[0] != '\0'; query = next) {
245		sep = strchr(query, ' ');
246		if (sep == NULL) {
247			len = strlen(query);
248			next = query + len;
249		} else {
250			len = sep - query;
251			next = sep + 1;
252		}
253		if (len == 0)
254			continue;
255		idx = stopwords_hash(query, len);
256		if (memcmp(stopwords[idx], query, len) == 0 &&
257		    stopwords[idx][len] == '\0')
258			continue;
259		memcpy(buf, query, len);
260		buf += len;
261		*buf++ = ' ';
262	}
263
264	if (output == buf) {
265		free(output);
266		return NULL;
267	}
268	buf[-1] = '\0';
269	return output;
270}
271
272/*
273 * usage --
274 *	print usage message and die
275 */
276static void
277usage(void)
278{
279	fprintf(stderr,
280		"Usage: %s [-n Number of records] [-123456789Ccp] [-S machine] query\n",
281		getprogname());
282	exit(1);
283}
284