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