1 /*
2 * help.c -- Help command
3 *
4 * Copyright (C) 2006-2010 Mikael Berthe <mikael@lilotux.net>
5 * Copyright (C) 2009 Myhailo Danylenko <isbear@ukrpost.net>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or (at
10 * your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22 * How it works
23 *
24 * Main calls help_init, that installs option guards. These guards do
25 * nothing, but set help_dirs_stalled flag. When user issues help command,
26 * it checks, if help_dirs_stalled flag is set, and if it is, it calls
27 * init_help_dirs before performing help search.
28 *
29 * Options:
30 * lang List of semicolon-separated language codes. If unset, will
31 * be detected from locale, with fallback to english.
32 * help_dirs List of semicolon-seaparated directories, where search for
33 * help (in language subdirectories) will be performed.
34 * Defaults to DATA_DIR/mcabber/help.
35 * help_to_current Print help to current buddy's buffer.
36 *
37 * XXX:
38 * Remove command list from hlp.txt and print detected list of all help
39 * topics?
40 */
41
42 #include <glib.h>
43 #include <string.h>
44 #include <locale.h>
45 #include <sys/types.h>
46 #include <dirent.h>
47
48 #include "logprint.h"
49 #include "screen.h"
50 #include "hbuf.h"
51 #include "settings.h"
52 #include "utils.h"
53
54 static GSList *help_dirs = NULL;
55 static gboolean help_dirs_stalled = TRUE;
56
free_help_dirs(void)57 void free_help_dirs(void)
58 {
59 GSList *hel;
60
61 for (hel = help_dirs; hel; hel = hel->next)
62 g_free(hel->data);
63
64 g_slist_free(help_dirs);
65
66 help_dirs = NULL;
67 }
68
dir_push_languages(const char * langs,const char * dir)69 void dir_push_languages(const char *langs, const char *dir)
70 {
71 const char *lstart = langs;
72 const char *lend;
73 char *path = expand_filename(dir);
74
75 for (lend = strchr(lstart, ';'); lend; lend = strchr(lstart, ';')) {
76 char *lang = g_strndup(lstart, lend - lstart);
77 char *dir = g_strdup_printf("%s/%s", path, lang);
78
79 help_dirs = g_slist_append(help_dirs, dir);
80
81 g_free(lang);
82 lstart = lend + 1;
83 }
84
85 { // finishing element
86 char *dir = g_strdup_printf("%s/%s", path, lstart);
87
88 help_dirs = g_slist_append(help_dirs, dir);
89 }
90
91 g_free(path);
92 }
93
init_help_dirs(void)94 void init_help_dirs(void)
95 {
96 const char *paths;
97 const char *langs;
98 char lang[6];
99
100 if (help_dirs)
101 free_help_dirs();
102
103 // initialize variables
104 paths = settings_opt_get("help_dirs");
105 if (!paths || !*paths)
106 #ifdef DATA_DIR
107 paths = DATA_DIR "/mcabber/help";
108 #else
109 paths = "/usr/local/share/mcabber/help;/usr/share/mcabber/help";
110 #endif
111
112 langs = settings_opt_get("lang");
113
114 if (!langs || !*langs) {
115 char *locale = setlocale(LC_MESSAGES, NULL);
116
117 // XXX crude method to distinguish between xx_XX xx xx@xxx
118 // and C POSIX NULL etc.
119 if (locale && isalpha(locale[0]) && isalpha(locale[1])
120 && !isalpha(locale[2])) {
121 lang[0] = locale[0];
122 lang[1] = locale[1];
123
124 if (lang[0] == 'e' && lang[1] == 'n')
125 lang[2] = '\0';
126 else {
127 lang[2] = ';';
128 lang[3] = 'e';
129 lang[4] = 'n';
130 lang[5] = '\0';
131 }
132
133 langs = lang;
134 } else
135 langs = "en";
136 }
137
138 { // parse
139 const char *pstart = paths;
140 const char *pend;
141
142 for (pend = strchr(pstart, ';'); pend; pend = strchr(pstart, ';')) {
143 char *path = g_strndup(pstart, pend - pstart);
144
145 dir_push_languages(langs, path);
146
147 g_free(path);
148 pstart = pend + 1;
149 }
150
151 // last element
152 dir_push_languages(langs, pstart);
153 }
154
155 help_dirs_stalled = FALSE;
156 }
157
do_help_in_dir(const char * arg,const char * path,const char * jid)158 static gboolean do_help_in_dir(const char *arg, const char *path, const char *jid)
159 {
160 char *fname;
161 GIOChannel *channel;
162 GString *line;
163 int lines = 0;
164
165 if (arg && *arg)
166 fname = g_strdup_printf("%s/hlp_%s.txt", path, arg);
167 else
168 fname = g_strdup_printf("%s/hlp.txt", path);
169
170 channel = g_io_channel_new_file(fname, "r", NULL);
171
172 if (!channel)
173 return FALSE;
174
175 line = g_string_new(NULL);
176
177 while (TRUE) {
178 gsize endpos;
179 GIOStatus ret;
180
181 ret = g_io_channel_read_line_string(channel, line, &endpos, NULL);
182 if (ret != G_IO_STATUS_NORMAL) // XXX G_IO_STATUS_AGAIN?
183 break;
184
185 line->str[endpos] = '\0';
186
187 if (jid)
188 scr_WriteIncomingMessage(jid, line->str, 0,
189 HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
190 else
191 scr_LogPrint(LPRINT_NORMAL, "%s", line->str);
192
193 ++lines;
194 }
195
196 g_io_channel_unref(channel);
197
198 g_string_free(line, TRUE);
199
200 if (!lines)
201 return FALSE;
202
203 if (!jid) {
204 scr_setmsgflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE);
205 scr_setattentionflag_if_needed(SPECIAL_BUFFER_STATUS_ID, TRUE,
206 ROSTER_UI_PRIO_STATUS_WIN_MESSAGE, prio_max);
207 }
208
209 return TRUE;
210 }
211
help_process(char * arg)212 void help_process(char *arg)
213 {
214 gchar *string;
215 const char *jid = NULL;
216 gboolean done = FALSE;
217
218 if (help_dirs_stalled)
219 init_help_dirs();
220
221 { // check input
222 char *c;
223
224 for (c = arg; *c; ++c)
225 if (!isalnum(*c) && *c != '-' && *c != '_') {
226 scr_LogPrint(LPRINT_NORMAL, "Wrong help expression, "
227 "it can contain only alphbetic, numeric"
228 " characters and symbols '-' and '_'.");
229 return;
230 }
231
232 string = g_strdup(arg);
233 mc_strtolower(string);
234 }
235
236 if (settings_opt_get_int("help_to_current") && CURRENT_JID)
237 jid = CURRENT_JID;
238
239 { // search
240 GSList *hel;
241
242 for (hel = help_dirs; hel && !done; hel = hel->next) {
243 char *dir = (char *)hel->data;
244 done = do_help_in_dir(string, dir, jid);
245 }
246 }
247
248 if (!done && string && *string) { // match and print any similar topics
249 GSList *hel;
250 GSList *matches = NULL;
251
252 for (hel = help_dirs; hel; hel = hel->next) {
253 const char *path = (const char *)hel->data;
254 DIR *dd = opendir(path);
255
256 if (dd) {
257 struct dirent *file;
258
259 for (file = readdir(dd); file; file = readdir(dd)) {
260 const char *name = file->d_name;
261
262 if (name && name[0] == 'h' && name[1] == 'l' &&
263 name[2] == 'p' && name[3] == '_') {
264 const char *nstart = name + 4;
265 const char *nend = strrchr(nstart, '.');
266
267 if (nend) {
268 gsize len = nend - nstart;
269
270 if (g_strstr_len(nstart, len, string)) {
271 gchar *match = g_strndup(nstart, len);
272
273 if (!g_slist_find_custom(matches, match,
274 (GCompareFunc)strcmp))
275 matches = g_slist_append(matches, match);
276 else
277 g_free(match);
278
279 done = TRUE;
280 }
281 }
282 }
283 }
284
285 closedir(dd);
286 }
287 }
288
289 if (done) {
290 GString *message = g_string_new("No exact match found. "
291 "Keywords, that contain this word:");
292 GSList *wel;
293
294 for (wel = matches; wel; wel = wel->next) {
295 gchar *word = (gchar *)wel->data;
296
297 g_string_append_printf(message, " %s,", word);
298
299 g_free(wel->data);
300 }
301
302 message->str[message->len - 1] = '.';
303
304 g_slist_free(matches);
305
306 {
307 char *msg = g_string_free(message, FALSE);
308
309 if (jid)
310 scr_WriteIncomingMessage(jid, msg, 0,
311 HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
312 else
313 scr_LogPrint(LPRINT_NORMAL, "%s", msg);
314
315 g_free(msg);
316 }
317 }
318 }
319
320 if (!done) {
321 if (jid) // XXX
322 scr_WriteIncomingMessage(jid, "No help found.", 0,
323 HBB_PREFIX_INFO|HBB_PREFIX_NOFLAG, 0);
324 else
325 scr_LogPrint(LPRINT_NORMAL, "No help found.");
326 }
327
328 g_free(string);
329 }
330
help_guard(const gchar * key,const gchar * new_value)331 static gchar *help_guard(const gchar *key, const gchar *new_value)
332 {
333 help_dirs_stalled = TRUE;
334 return g_strdup(new_value);
335 }
336
help_init(void)337 void help_init(void)
338 {
339 settings_set_guard("lang", help_guard);
340 settings_set_guard("help_dirs", help_guard);
341 }
342
343 /* vim: set expandtab cindent cinoptions=>2\:2(0 sw=2 ts=2: For Vim users... */
344