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