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@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