comparison mcabber/mcabber/help.c @ 1678:e489ead6574a

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