mcabber/help.c

344 lines
8.3 KiB
C

/*
* help.c -- Help command
*
* Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
* Copyright (C) 2009 Myhailo Danylenko <isbear@ukrpost.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/*
* How it works
*
* Main calls help_init, that installs option guards. These guards do
* nothing, but set help_dirs_stalled flag. When user issues help command,
* it checks, if help_dirs_stalled flag is set, and if it is, it calls
* init_help_dirs before performing help search.
*
* Options:
* lang List of semicolon-separated language codes. If unset, will
* be detected from locale, with fallback to english.
* help_dirs List of semicolon-seaparated directories, where search for
* help (in language subdirectories) will be performed.
* Defaults to DATA_DIR/mcabber/help.
* help_to_current Print help to current buddy's buffer.
*
* XXX:
* Remove command list from hlp.txt and print detected list of all help
* topics?
*/
#include <glib.h>
#include <string.h>
#include <locale.h>
#include <sys/types.h>
#include <dirent.h>
#include "logprint.h"
#include "screen.h"
#include "hbuf.h"
#include "settings.h"
#include "utils.h"
static GSList *help_dirs = NULL;
static gboolean help_dirs_stalled = TRUE;
void free_help_dirs(void)
{
GSList *hel;
for (hel = help_dirs; hel; hel = hel->next)
g_free(hel->data);
g_slist_free(help_dirs);
help_dirs = NULL;
}
void dir_push_languages(const char *langs, const char *dir)
{
const char *lstart = langs;
const char *lend;
char *path = expand_filename(dir);
for (lend = strchr(lstart, ';'); lend; lend = strchr(lstart, ';')) {
char *lang = g_strndup(lstart, lend - lstart);
char *dir = g_strdup_printf("%s/%s", path, lang);
help_dirs = g_slist_append(help_dirs, dir);
g_free(lang);
lstart = lend + 1;
}
{ // finishing element
char *dir = g_strdup_printf("%s/%s", path, lstart);
help_dirs = g_slist_append(help_dirs, dir);
}
g_free(path);
}
void init_help_dirs(void)
{
const char *paths;
const char *langs;
char lang[6];
if (help_dirs)
free_help_dirs();
// initialize variables
paths = settings_opt_get("help_dirs");
if (!paths || !*paths)
#ifdef DATA_DIR
paths = DATA_DIR "/mcabber/help";
#else
paths = "/usr/local/share/mcabber/help;/usr/share/mcabber/help";
#endif
langs = settings_opt_get("lang");
if (!langs || !*langs) {
char *locale = setlocale(LC_MESSAGES, NULL);
// XXX crude method to distinguish between xx_XX xx xx@xxx
// and C POSIX NULL etc.
if (locale && isalpha(locale[0]) && isalpha(locale[1])
&& !isalpha(locale[2])) {
lang[0] = locale[0];
lang[1] = locale[1];
if (lang[0] == 'e' && lang[1] == 'n')
lang[2] = '\0';
else {
lang[2] = ';';
lang[3] = 'e';
lang[4] = 'n';
lang[5] = '\0';
}
langs = lang;
} else
langs = "en";
}
{ // parse
const char *pstart = paths;
const char *pend;
for (pend = strchr(pstart, ';'); pend; pend = strchr(pstart, ';')) {
char *path = g_strndup(pstart, pend - pstart);
dir_push_languages(langs, path);
g_free(path);
pstart = pend + 1;
}
// last element
dir_push_languages(langs, pstart);
}
help_dirs_stalled = FALSE;
}
static gboolean do_help_in_dir(const char *arg, const char *path, const char *jid)
{
char *fname;
GIOChannel *channel;
GString *line;
int lines = 0;
if (arg && *arg)
fname = g_strdup_printf("%s/hlp_%s.txt", path, arg);
else
fname = g_strdup_printf("%s/hlp.txt", path);
channel = g_io_channel_new_file(fname, "r", NULL);
if (!channel)
return FALSE;
line = g_string_new(NULL);
while (TRUE) {
gsize endpos;
GIOStatus ret;
ret = g_io_channel_read_line_string(channel, line, &endpos, NULL);
if (ret != G_IO_STATUS_NORMAL) // XXX G_IO_STATUS_AGAIN?
break;
line->str[endpos] = '\0';
if (jid)
scr_WriteIncomingMessage(jid, line->str, 0,
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
else
scr_LogPrint(LPRINT_NORMAL, "%s", line->str);
++lines;
}
g_io_channel_unref(channel);
g_string_free(line, TRUE);
if (!lines)
return FALSE;
if (!jid) {
scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
}
return TRUE;
}
void help_process(char *arg)
{
gchar *string;
const char *jid = NULL;
gboolean done = FALSE;
if (help_dirs_stalled)
init_help_dirs();
{ // check input
char *c;
for (c = arg; *c; ++c)
if (!isalnum(*c) && *c != '-' && *c != '_') {
scr_LogPrint(LPRINT_NORMAL, "Wrong help expression, "
"it can contain only alphbetic, numeric"
" characters and symbols '-' and '_'.");
return;
}
string = g_strdup(arg);
mc_strtolower(string);
}
if (settings_opt_get_int("help_to_current") && CURRENT_JID)
jid = CURRENT_JID;
{ // search
GSList *hel;
for (hel = help_dirs; hel && !done; hel = hel->next) {
char *dir = (char *)hel->data;
done = do_help_in_dir(string, dir, jid);
}
}
if (!done && string && *string) { // match and print any similar topics
GSList *hel;
GSList *matches = NULL;
for (hel = help_dirs; hel; hel = hel->next) {
const char *path = (const char *)hel->data;
DIR *dd = opendir(path);
if (dd) {
struct dirent *file;
for (file = readdir(dd); file; file = readdir(dd)) {
const char *name = file->d_name;
if (name && name[0] == 'h' && name[1] == 'l' &&
name[2] == 'p' && name[3] == '_') {
const char *nstart = name + 4;
const char *nend = strrchr(nstart, '.');
if (nend) {
gsize len = nend - nstart;
if (g_strstr_len(nstart, len, string)) {
gchar *match = g_strndup(nstart, len);
if (!g_slist_find_custom(matches, match,
(GCompareFunc)strcmp))
matches = g_slist_append(matches, match);
else
g_free(match);
done = TRUE;
}
}
}
}
closedir(dd);
}
}
if (done) {
GString *message = g_string_new("No exact match found. "
"Keywords, that contain this word:");
GSList *wel;
for (wel = matches; wel; wel = wel->next) {
gchar *word = (gchar *)wel->data;
g_string_append_printf(message, " %s,", word);
g_free(wel->data);
}
message->str[message->len - 1] = '.';
g_slist_free(matches);
{
char *msg = g_string_free(message, FALSE);
if (jid)
scr_WriteIncomingMessage(jid, msg, 0,
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
else
scr_LogPrint(LPRINT_NORMAL, "%s", msg);
g_free(msg);
}
}
}
if (!done) {
if (jid) // XXX
scr_WriteIncomingMessage(jid, "No help found.", 0,
HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
else
scr_LogPrint(LPRINT_NORMAL, "No help found.");
}
g_free(string);
}
static gchar *help_guard(const gchar *key, const gchar *new_value)
{
help_dirs_stalled = TRUE;
return g_strdup(new_value);
}
void help_init(void)
{
settings_set_guard("lang", help_guard);
settings_set_guard("help_dirs", help_guard);
}
/* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */