378 lines
8.9 KiB
C
378 lines
8.9 KiB
C
/* $NetBSD: apropos.c,v 1.27 2023/08/03 07:49:23 rin Exp $ */
|
|
/*-
|
|
* Copyright (c) 2011 Abhinav Upadhyay <er.abhinav.upadhyay@gmail.com>
|
|
* All rights reserved.
|
|
*
|
|
* This code was developed as part of Google's Summer of Code 2011 program.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__RCSID("$NetBSD: apropos.c,v 1.27 2023/08/03 07:49:23 rin Exp $");
|
|
|
|
#include <err.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <util.h>
|
|
|
|
#include "apropos-utils.h"
|
|
|
|
typedef struct apropos_flags {
|
|
char **sections;
|
|
int nresults;
|
|
int pager;
|
|
int no_context;
|
|
query_format format;
|
|
int legacy;
|
|
const char *machine;
|
|
const char *manconf;
|
|
} apropos_flags;
|
|
|
|
typedef struct callback_data {
|
|
int count;
|
|
FILE *out;
|
|
apropos_flags *aflags;
|
|
} callback_data;
|
|
|
|
static char *remove_stopwords(const char *);
|
|
static int query_callback(query_callback_args *);
|
|
__dead static void usage(void);
|
|
|
|
#define _PATH_PAGER "/usr/bin/more -s"
|
|
#define SECTIONS_ARGS_LENGTH 4;
|
|
|
|
static void
|
|
parseargs(int argc, char **argv, struct apropos_flags *aflags)
|
|
{
|
|
int ch;
|
|
size_t sections_offset = 0;
|
|
size_t sections_size = 0;
|
|
char **sections = NULL;
|
|
char *section;
|
|
aflags->manconf = MANCONF;
|
|
|
|
#define RESIZE_SECTIONS(newsize) \
|
|
if (sections == NULL || sections_offset > sections_size - 1) { \
|
|
sections_size += newsize; \
|
|
sections = erealloc(sections, sections_size * sizeof(*sections)); \
|
|
}
|
|
|
|
while ((ch = getopt(argc, argv, "123456789C:hilMmn:PprS:s:")) != -1) {
|
|
switch (ch) {
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
section = emalloc(2);
|
|
section[0] = ch;
|
|
section[1] = 0;
|
|
RESIZE_SECTIONS(SECTIONS_ARGS_LENGTH)
|
|
sections[sections_offset++] = section;
|
|
break;
|
|
case 'C':
|
|
aflags->manconf = optarg;
|
|
break;
|
|
case 'h':
|
|
aflags->format = APROPOS_HTML;
|
|
break;
|
|
case 'i':
|
|
aflags->format = APROPOS_TERM;
|
|
break;
|
|
case 'l':
|
|
aflags->legacy = 1;
|
|
aflags->no_context = 1;
|
|
aflags->format = APROPOS_NONE;
|
|
break;
|
|
case 'M':
|
|
aflags->no_context = 1;
|
|
break;
|
|
case 'm':
|
|
aflags->no_context = 0;
|
|
break;
|
|
case 'n':
|
|
aflags->nresults = atoi(optarg);
|
|
break;
|
|
case 'p': // user wants a pager
|
|
aflags->pager = 1;
|
|
/*FALLTHROUGH*/
|
|
case 'P':
|
|
aflags->format = APROPOS_PAGER;
|
|
break;
|
|
case 'r':
|
|
aflags->format = APROPOS_NONE;
|
|
break;
|
|
case 'S':
|
|
aflags->machine = optarg;
|
|
break;
|
|
case 's':
|
|
RESIZE_SECTIONS(SECTIONS_ARGS_LENGTH)
|
|
sections[sections_offset++] = estrdup(optarg);
|
|
break;
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
if (sections) {
|
|
RESIZE_SECTIONS(1)
|
|
sections[sections_offset] = NULL;
|
|
}
|
|
aflags->sections = sections;
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
query_args args;
|
|
char *query = NULL; // the user query
|
|
char *errmsg = NULL;
|
|
char *str;
|
|
int pc = 0;
|
|
int rc = 0;
|
|
size_t i;
|
|
int s;
|
|
callback_data cbdata;
|
|
cbdata.out = stdout; // the default output stream
|
|
cbdata.count = 0;
|
|
apropos_flags aflags;
|
|
aflags.sections = NULL;
|
|
cbdata.aflags = &aflags;
|
|
sqlite3 *db;
|
|
setprogname(argv[0]);
|
|
if (argc < 2)
|
|
usage();
|
|
|
|
memset(&aflags, 0, sizeof(aflags));
|
|
|
|
if (!isatty(STDOUT_FILENO))
|
|
aflags.format = APROPOS_NONE;
|
|
else
|
|
aflags.format = APROPOS_TERM;
|
|
|
|
if ((str = getenv("APROPOS")) != NULL) {
|
|
char **ptr = emalloc((strlen(str) + 2) * sizeof(*ptr));
|
|
#define WS "\t\n\r "
|
|
ptr[0] = __UNCONST(getprogname());
|
|
for (s = 1, str = strtok(str, WS); str;
|
|
str = strtok(NULL, WS), s++)
|
|
ptr[s] = str;
|
|
ptr[s] = NULL;
|
|
parseargs(s, ptr, &aflags);
|
|
free(ptr);
|
|
optreset = 1;
|
|
optind = 1;
|
|
}
|
|
|
|
parseargs(argc, argv, &aflags);
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (!argc)
|
|
usage();
|
|
|
|
str = NULL;
|
|
while (argc--)
|
|
concat(&str, *argv++);
|
|
query = remove_stopwords(lower(str));
|
|
|
|
/*
|
|
* If the query consisted only of stopwords and we removed all of
|
|
* them, use the original query.
|
|
*/
|
|
if (query == NULL)
|
|
query = str;
|
|
else
|
|
free(str);
|
|
|
|
if ((db = init_db(MANDB_READONLY, aflags.manconf)) == NULL)
|
|
exit(EXIT_FAILURE);
|
|
|
|
/* If user wants to page the output, then set some settings */
|
|
if (aflags.pager) {
|
|
const char *pager = getenv("PAGER");
|
|
if (pager == NULL)
|
|
pager = _PATH_PAGER;
|
|
|
|
/* Don't get killed by a broken pipe */
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
/* Open a pipe to the pager */
|
|
if ((cbdata.out = popen(pager, "w")) == NULL) {
|
|
close_db(db);
|
|
err(EXIT_FAILURE, "pipe failed");
|
|
}
|
|
}
|
|
|
|
args.search_str = query;
|
|
args.sections = aflags.sections;
|
|
args.legacy = aflags.legacy;
|
|
args.nrec = aflags.nresults ? aflags.nresults : -1;
|
|
args.offset = 0;
|
|
args.machine = aflags.machine;
|
|
args.callback = &query_callback;
|
|
args.callback_data = &cbdata;
|
|
args.errmsg = &errmsg;
|
|
|
|
if (aflags.format == APROPOS_HTML) {
|
|
fprintf(cbdata.out, "<html>\n<header>\n<title>apropos results "
|
|
"for %s</title></header>\n<body>\n<table cellpadding=\"4\""
|
|
"style=\"border: 1px solid #000000; border-collapse:"
|
|
"collapse;\" border=\"1\">\n", query);
|
|
}
|
|
rc = run_query(db, aflags.format, &args);
|
|
if (aflags.format == APROPOS_HTML)
|
|
fprintf(cbdata.out, "</table>\n</body>\n</html>\n");
|
|
|
|
if (aflags.pager)
|
|
pc = pclose(cbdata.out);
|
|
free(query);
|
|
|
|
if (aflags.sections) {
|
|
for(i = 0; aflags.sections[i]; i++)
|
|
free(aflags.sections[i]);
|
|
free(aflags.sections);
|
|
}
|
|
|
|
close_db(db);
|
|
if (errmsg) {
|
|
warnx("%s", errmsg);
|
|
free(errmsg);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (pc == -1)
|
|
err(EXIT_FAILURE, "pclose error");
|
|
|
|
/*
|
|
* Something wrong with the database, writing output, or a non-existent
|
|
* pager.
|
|
*/
|
|
if (rc < 0)
|
|
exit(EXIT_FAILURE);
|
|
|
|
if (cbdata.count == 0) {
|
|
warnx("No relevant results obtained.\n"
|
|
"Please make sure that you spelled all the terms correctly "
|
|
"or try using different keywords.");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* query_callback --
|
|
* Callback function for run_query.
|
|
* It simply outputs the results from run_query. If the user specified the -p
|
|
* option, then the output is sent to a pager, otherwise stdout is the default
|
|
* output stream.
|
|
*/
|
|
static int
|
|
query_callback(query_callback_args *qargs)
|
|
{
|
|
callback_data *cbdata = (callback_data *) qargs->other_data;
|
|
FILE *out = cbdata->out;
|
|
cbdata->count++;
|
|
if (cbdata->aflags->format != APROPOS_HTML) {
|
|
fprintf(out, cbdata->aflags->legacy ? "%s(%s) - %s\n" :
|
|
"%s (%s)\t%s\n", qargs->name, qargs->section, qargs->name_desc);
|
|
if (cbdata->aflags->no_context == 0)
|
|
fprintf(out, "%s\n\n", qargs->snippet);
|
|
} else {
|
|
fprintf(out, "<tr><td>%s(%s)</td><td>%s</td></tr>\n", qargs->name,
|
|
qargs->section, qargs->name_desc);
|
|
if (cbdata->aflags->no_context == 0)
|
|
fprintf(out, "<tr><td colspan=2>%s</td></tr>\n", qargs->snippet);
|
|
}
|
|
|
|
return fflush(out);
|
|
}
|
|
|
|
#include "stopwords.c"
|
|
|
|
/*
|
|
* remove_stopwords--
|
|
* Scans the query and removes any stop words from it.
|
|
* Returns the modified query or NULL, if it contained only stop words.
|
|
*/
|
|
|
|
static char *
|
|
remove_stopwords(const char *query)
|
|
{
|
|
size_t len, idx;
|
|
char *output, *buf;
|
|
const char *sep, *next;
|
|
|
|
output = buf = emalloc(strlen(query) + 1);
|
|
|
|
for (; query[0] != '\0'; query = next) {
|
|
sep = strchr(query, ' ');
|
|
if (sep == NULL) {
|
|
len = strlen(query);
|
|
next = query + len;
|
|
} else {
|
|
len = sep - query;
|
|
next = sep + 1;
|
|
}
|
|
if (len == 0)
|
|
continue;
|
|
idx = stopwords_hash(query, len);
|
|
if (memcmp(stopwords[idx], query, len) == 0 &&
|
|
stopwords[idx][len] == '\0')
|
|
continue;
|
|
memcpy(buf, query, len);
|
|
buf += len;
|
|
*buf++ = ' ';
|
|
}
|
|
|
|
if (output == buf) {
|
|
free(output);
|
|
return NULL;
|
|
}
|
|
buf[-1] = '\0';
|
|
return output;
|
|
}
|
|
|
|
/*
|
|
* usage --
|
|
* print usage message and die
|
|
*/
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s [-123456789ilMmpr] [-C path] [-n results] "
|
|
"[-S machine] [-s section] query\n",
|
|
getprogname());
|
|
exit(1);
|
|
}
|