Mercurial > ~mikael > mcabber > hg
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... */ |