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