apropos.c revision 1.16 1 /* $NetBSD: apropos.c,v 1.16 2013/04/02 17:16:50 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.16 2013/04/02 17:16:50 christos Exp $");
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
47 typedef struct apropos_flags {
48 int sec_nums[SECMAX];
49 int nresults;
50 int pager;
51 int no_context;
52 query_format format;
53 int legacy;
54 const char *machine;
55 } apropos_flags;
56
57 typedef struct callback_data {
58 int count;
59 FILE *out;
60 apropos_flags *aflags;
61 } callback_data;
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 while ((ch = getopt(argc, argv, "123456789Cchiln:PprS:s:")) != -1) {
75 switch (ch) {
76 case '1':
77 case '2':
78 case '3':
79 case '4':
80 case '5':
81 case '6':
82 case '7':
83 case '8':
84 case '9':
85 aflags->sec_nums[ch - '1'] = 1;
86 break;
87 case 'C':
88 aflags->no_context = 1;
89 break;
90 case 'c':
91 aflags->no_context = 0;
92 break;
93 case 'h':
94 aflags->format = APROPOS_HTML;
95 break;
96 case 'i':
97 aflags->format = APROPOS_TERM;
98 break;
99 case 'l':
100 aflags->legacy = 1;
101 aflags->no_context = 1;
102 aflags->format = APROPOS_NONE;
103 break;
104 case 'n':
105 aflags->nresults = atoi(optarg);
106 break;
107 case 'p': // user wants a pager
108 aflags->pager = 1;
109 /*FALLTHROUGH*/
110 case 'P':
111 aflags->format = APROPOS_PAGER;
112 break;
113 case 'r':
114 aflags->format = APROPOS_NONE;
115 break;
116 case 'S':
117 aflags->machine = optarg;
118 break;
119 case 's':
120 ch = atoi(optarg);
121 if (ch < 1 || ch > 9)
122 errx(EXIT_FAILURE, "Invalid section");
123 aflags->sec_nums[ch - 1] = 1;
124 break;
125 case '?':
126 default:
127 usage();
128 }
129 }
130 }
131
132 int
133 main(int argc, char *argv[])
134 {
135 query_args args;
136 char *query = NULL; // the user query
137 char *errmsg = NULL;
138 char *str;
139 int rc = 0;
140 int s;
141 callback_data cbdata;
142 cbdata.out = stdout; // the default output stream
143 cbdata.count = 0;
144 apropos_flags aflags;
145 cbdata.aflags = &aflags;
146 sqlite3 *db;
147 setprogname(argv[0]);
148 if (argc < 2)
149 usage();
150
151 memset(&aflags, 0, sizeof(aflags));
152
153 if (!isatty(STDOUT_FILENO))
154 aflags.format = APROPOS_NONE;
155 else
156 aflags.format = APROPOS_TERM;
157
158 if ((str = getenv("APROPOS")) != NULL) {
159 char **ptr = emalloc((strlen(str) + 2) * sizeof(*ptr));
160 #define WS "\t\n\r "
161 ptr[0] = __UNCONST(getprogname());
162 for (s = 1, str = strtok(str, WS); str;
163 str = strtok(NULL, WS), s++)
164 ptr[s] = str;
165 ptr[s] = NULL;
166 parseargs(s, ptr, &aflags);
167 free(ptr);
168 optreset = 1;
169 optind = 1;
170 }
171
172 parseargs(argc, argv, &aflags);
173
174 /*
175 * If the user specifies a section number as an option, the
176 * corresponding index element in sec_nums is set to the string
177 * representing that section number.
178 */
179
180 argc -= optind;
181 argv += optind;
182
183 if (!argc)
184 usage();
185
186 str = NULL;
187 while (argc--)
188 concat(&str, *argv++);
189 /* Eliminate any stopwords from the query */
190 query = remove_stopwords(lower(str));
191 free(str);
192
193 /* if any error occured in remove_stopwords, exit */
194 if (query == NULL)
195 errx(EXIT_FAILURE, "Try using more relevant keywords");
196
197 if ((db = init_db(MANDB_READONLY, MANCONF)) == NULL)
198 exit(EXIT_FAILURE);
199
200 /* If user wants to page the output, then set some settings */
201 if (aflags.pager) {
202 const char *pager = getenv("PAGER");
203 if (pager == NULL)
204 pager = _PATH_PAGER;
205 /* Open a pipe to the pager */
206 if ((cbdata.out = popen(pager, "w")) == NULL) {
207 close_db(db);
208 err(EXIT_FAILURE, "pipe failed");
209 }
210 }
211
212 args.search_str = query;
213 args.sec_nums = aflags.sec_nums;
214 args.legacy = aflags.legacy;
215 args.nrec = aflags.nresults ? aflags.nresults : -1;
216 args.offset = 0;
217 args.machine = aflags.machine;
218 args.callback = &query_callback;
219 args.callback_data = &cbdata;
220 args.errmsg = &errmsg;
221
222 if (aflags.format == APROPOS_HTML) {
223 fprintf(cbdata.out, "<html>\n<header>\n<title>apropos results "
224 "for %s</title></header>\n<body>\n<table cellpadding=\"4\""
225 "style=\"border: 1px solid #000000; border-collapse:"
226 "collapse;\" border=\"1\">\n", query);
227 }
228 rc = run_query(db, aflags.format, &args);
229 if (aflags.format == APROPOS_HTML)
230 fprintf(cbdata.out, "</table>\n</body>\n</html>\n");
231
232 free(query);
233 close_db(db);
234 if (errmsg) {
235 warnx("%s", errmsg);
236 free(errmsg);
237 exit(EXIT_FAILURE);
238 }
239
240 if (rc < 0) {
241 /* Something wrong with the database. Exit */
242 exit(EXIT_FAILURE);
243 }
244
245 if (cbdata.count == 0) {
246 warnx("No relevant results obtained.\n"
247 "Please make sure that you spelled all the terms correctly "
248 "or try using better keywords.");
249 }
250 return 0;
251 }
252
253 /*
254 * query_callback --
255 * Callback function for run_query.
256 * It simply outputs the results from do_query. If the user specified the -p
257 * option, then the output is sent to a pager, otherwise stdout is the default
258 * output stream.
259 */
260 static int
261 query_callback(void *data, const char *section, const char *name,
262 const char *name_desc, const char *snippet, size_t snippet_length)
263 {
264 callback_data *cbdata = (callback_data *) data;
265 FILE *out = cbdata->out;
266 cbdata->count++;
267 if (cbdata->aflags->format != APROPOS_HTML) {
268 fprintf(out, cbdata->aflags->legacy ? "%s(%s) - %s\n" :
269 "%s (%s)\t%s\n", name, section, name_desc);
270 if (cbdata->aflags->no_context == 0)
271 fprintf(out, "%s\n\n", snippet);
272 } else {
273 fprintf(out, "<tr><td>%s(%s)</td><td>%s</td></tr>\n", name,
274 section, name_desc);
275 if (cbdata->aflags->no_context == 0)
276 fprintf(out, "<tr><td colspan=2>%s</td></tr>\n", snippet);
277 }
278
279 return 0;
280 }
281
282 #include "stopwords.c"
283
284 /*
285 * remove_stopwords--
286 * Scans the query and removes any stop words from it.
287 * Returns the modified query or NULL, if it contained only stop words.
288 */
289
290 static char *
291 remove_stopwords(const char *query)
292 {
293 size_t len, idx;
294 char *output, *buf;
295 const char *sep, *next;
296
297 output = buf = emalloc(strlen(query) + 1);
298
299 for (; query[0] != '\0'; query = next) {
300 sep = strchr(query, ' ');
301 if (sep == NULL) {
302 len = strlen(query);
303 next = query + len;
304 } else {
305 len = sep - query;
306 next = sep + 1;
307 }
308 if (len == 0)
309 continue;
310 idx = stopwords_hash(query, len);
311 if (memcmp(stopwords[idx], query, len) == 0 &&
312 stopwords[idx][len] == '\0')
313 continue;
314 memcpy(buf, query, len);
315 buf += len;
316 *buf++ = ' ';
317 }
318
319 if (output == buf) {
320 free(output);
321 return NULL;
322 }
323 buf[-1] = '\0';
324 return output;
325 }
326
327 /*
328 * usage --
329 * print usage message and die
330 */
331 static void
332 usage(void)
333 {
334 fprintf(stderr, "Usage: %s [-123456789Ccilpr] [-n results] "
335 "[-S machine] [-s section] query\n",
336 getprogname());
337 exit(1);
338 }
339