]>
jfr.im git - irc/evilnet/x3.git/blob - src/helpfile.c
1 /* helpfile.c - Help file loading and display
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of x3.
6 * x3 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 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
28 #if defined(HAVE_DIRENT_H)
32 #if defined(HAVE_SYS_STAT_H)
36 static const struct message_entry msgtab
[] = {
37 { "HFMSG_MISSING_HELPFILE", "The help file could not be found. Sorry!" },
38 { "HFMSG_HELP_NOT_STRING", "Help file error (help data was not a string)." },
42 #define DEFAULT_LINE_SIZE MAX_LINE_SIZE
43 #define DEFAULT_TABLE_SIZE 80
45 extern struct userNode
*global
, *chanserv
, *opserv
, *nickserv
, *spamserv
;
46 struct userNode
*message_dest
;
47 struct userNode
*message_source
;
48 struct language
*lang_C
;
49 struct dict
*languages
;
51 static void language_cleanup(void)
53 dict_delete(languages
);
56 static void language_free_helpfile(void *data
)
58 struct helpfile
*hf
= data
;
62 static void language_free(void *data
)
64 struct language
*lang
= data
;
65 dict_delete(lang
->messages
);
66 dict_delete(lang
->helpfiles
);
71 static struct language
*language_alloc(const char *name
)
73 struct language
*lang
= calloc(1, sizeof(*lang
));
74 lang
->name
= strdup(name
);
75 lang
->parent
= lang_C
;
77 languages
= dict_new();
78 dict_set_free_data(languages
, language_free
);
80 dict_insert(languages
, lang
->name
, lang
);
84 /* Language names should use a lang or lang_COUNTRY type system, where
85 * lang is a two-letter code according to ISO-639-1 (or three-letter
86 * code according to ISO-639-2 for languages not in ISO-639-1), and
87 * COUNTRY is the ISO 3166 country code in all upper case.
90 * http://www.loc.gov/standards/iso639-2/
91 * http://www.loc.gov/standards/iso639-2/langhome.html
92 * http://www.iso.ch/iso/en/prods-services/iso3166ma/index.html
94 struct language
*language_find(const char *name
)
96 struct language
*lang
;
97 char alt_name
[MAXLEN
];
100 if ((lang
= dict_find(languages
, name
, NULL
)))
102 if ((uscore
= strchr(name
, '_'))) {
103 strncpy(alt_name
, name
, uscore
-name
);
104 alt_name
[uscore
-name
] = 0;
105 if ((lang
= dict_find(languages
, alt_name
, NULL
)))
109 lang_C
= language_alloc("C");
110 lang_C
->messages
= dict_new();
111 lang_C
->helpfiles
= dict_new();
116 static void language_set_messages(struct language
*lang
, dict_t dict
)
118 dict_iterator_t it
, it2
;
119 struct record_data
*rd
;
124 for (it
= dict_first(dict
), it2
= dict_first(lang_C
->messages
); it
; ) {
125 const char *msgid
= iter_key(it
);
126 int diff
= it2
? irccasecmp(msgid
, iter_key(it2
)) : -1;
131 } else if (diff
> 0) {
133 it2
= iter_next(it2
);
139 msg
= strdup(rd
->d
.qstring
);
141 case RECDB_STRING_LIST
:
142 /* XXX: maybe do an unlistify_help() type thing */
144 log_module(MAIN_LOG
, LOG_WARNING
, "Unsupported record type for message %s in language %s", msgid
, lang
->name
);
147 dict_insert(lang
->messages
, strdup(msgid
), msg
);
149 it2
= iter_next(it2
);
153 it2
= iter_next(it2
);
155 if (extra
|| missing
)
156 log_module(MAIN_LOG
, LOG_WARNING
, "In language %s, %d extra and %d missing messages.", lang
->name
, extra
, missing
);
159 static struct language
*language_read(const char *name
)
162 struct dirent
*dirent
;
163 struct language
*lang
;
165 char filename
[MAXLEN
], *uscore
;
169 /* Never try to read the C language from disk. */
170 if (!irccasecmp(name
, "C"))
173 /* Open the directory stream; if we can't, fail. */
174 snprintf(filename
, sizeof(filename
), "languages/%s", name
);
175 if (!(dir
= opendir(filename
))) {
176 log_module(MAIN_LOG
, LOG_ERROR
, "Unable to open language directory languages/%s: %s", name
, strerror(errno
));
179 if (!(lang
= dict_find(languages
, name
, NULL
)))
180 lang
= language_alloc(name
);
182 /* Find the parent language. */
183 snprintf(filename
, sizeof(filename
), "languages/%s/parent", name
);
184 if (!(file
= fopen(filename
, "r"))
185 || !fgets(filename
, sizeof(filename
), file
)) {
186 strcpy(filename
, "C");
188 if (!(lang
->parent
= language_find(filename
))) {
189 uscore
= strchr(filename
, '_');
192 lang
->parent
= language_find(filename
);
195 lang
->parent
= lang_C
;
198 /* (Re-)initialize the language's dicts. */
199 dict_delete(lang
->messages
);
200 lang
->messages
= dict_new();
201 dict_set_free_keys(lang
->messages
, free
);
202 dict_set_free_data(lang
->messages
, free
);
203 lang
->helpfiles
= dict_new();
204 dict_set_free_data(lang
->helpfiles
, language_free_helpfile
);
206 /* Read all the translations from the directory. */
207 while ((dirent
= readdir(dir
))) {
208 snprintf(filename
, sizeof(filename
), "languages/%s/%s", name
, dirent
->d_name
);
209 if (!strcmp(dirent
->d_name
,"parent")) {
211 } else if (!strcmp(dirent
->d_name
, "strings.db")) {
212 dict
= parse_database(filename
);
213 language_set_messages(lang
, dict
);
215 } else if ((hf
= dict_find(lang_C
->helpfiles
, dirent
->d_name
, NULL
))) {
216 hf
= open_helpfile(filename
, hf
->expand
);
217 dict_insert(lang
->helpfiles
, hf
->name
, hf
);
226 static void language_read_list(void)
229 struct dirent
*dirent
;
231 char namebuf
[MAXLEN
];
233 if (!(dir
= opendir("languages")))
235 while ((dirent
= readdir(dir
))) {
236 if (dirent
->d_name
[0] == '.')
238 snprintf(namebuf
, sizeof(namebuf
), "languages/%s", dirent
->d_name
);
239 if (stat(namebuf
, &sbuf
) < 0) {
240 log_module(MAIN_LOG
, LOG_INFO
, "Skipping language entry '%s' (unable to stat).", dirent
->d_name
);
243 if (!S_ISDIR(sbuf
.st_mode
)) {
244 log_module(MAIN_LOG
, LOG_INFO
, "Skipping language entry '%s' (not directory).", dirent
->d_name
);
247 language_alloc(dirent
->d_name
);
252 const char *language_find_message(struct language
*lang
, const char *msgid
) {
253 struct language
*curr
;
257 for (curr
= lang
; curr
; curr
= curr
->parent
)
258 if ((msg
= dict_find(curr
->messages
, msgid
, NULL
)))
260 log_module(MAIN_LOG
, LOG_ERROR
, "Tried to find unregistered message \"%s\" (original language %s)", msgid
, lang
->name
);
264 int strlen_vis(char *str
)
267 for(count
=0;*str
;str
++)
274 table_send(struct userNode
*from
, const char *to
, unsigned int size
, irc_send_func irc_send
, struct helpfile_table table
) {
275 unsigned int ii
, jj
, len
, nreps
, reps
, tot_width
, pos
, spaces
, *max_width
;
276 char line
[MAX_LINE_SIZE
+1];
277 struct handle_info
*hi
;
279 unsigned int sepsize
= 0;
281 if (IsChannelName(to
) || *to
== '$') {
285 message_dest
= GetUserH(to
);
287 log_module(MAIN_LOG
, LOG_ERROR
, "Unable to find user with nickname %s (in table_send from %s).", to
, from
->nick
);
290 hi
= message_dest
->handle_info
;
291 #ifdef WITH_PROTOCOL_P10
292 to
= message_dest
->numeric
;
295 message_source
= from
;
297 /* If size or irc_send are 0, we should try to use a default. */
301 size
= DEFAULT_TABLE_SIZE
;
302 else if (hi
->table_width
)
303 size
= hi
->table_width
;
304 else if (hi
->screen_width
)
305 size
= hi
->screen_width
;
307 size
= DEFAULT_TABLE_SIZE
;
310 {} /* use that function */
311 else if(IsChannelName(to
)) {
312 irc_send
= irc_privmsg
;
314 else if (message_dest
->no_notice
) {
315 irc_send
= irc_privmsg
;
318 irc_send
= HANDLE_FLAGGED(hi
, USE_PRIVMSG
) ? irc_privmsg
: irc_notice
;
321 irc_send
= irc_notice
;
324 /* Limit size to how much we can show at once */
325 if (size
> sizeof(line
))
328 /* Figure out how wide columns should be */
329 max_width
= alloca(table
.width
* sizeof(int));
330 for (jj
=tot_width
=0; jj
<table
.width
; jj
++) {
331 /* Find the widest width for this column */
333 for (ii
=0; ii
<table
.length
; ii
++) {
334 len
= strlen(table
.contents
[ii
][jj
]);
335 if (len
> max_width
[jj
])
338 /* Separate columns with spaces */
339 tot_width
+= max_width
[jj
] + 1;
341 /* How many rows to put in a line? */
342 if ((table
.flags
& TABLE_REPEAT_ROWS
) && (size
> tot_width
))
343 nreps
= size
/ tot_width
;
346 /* Send headers line.. */
347 if (table
.flags
& TABLE_NO_HEADERS
) {
350 /* Sending headers needs special treatment: either show them
351 * once, or repeat them as many times as we repeat the columns
353 for (pos
=ii
=0; ii
<((table
.flags
& TABLE_REPEAT_HEADERS
)?nreps
:1); ii
++) {
355 len
= strlen(table
.contents
[0][jj
]);
356 //line[pos++] = '\02'; /* bold header */
357 spaces
= max_width
[jj
] - len
;
358 if (table
.flags
& TABLE_PAD_LEFT
)
361 memcpy(line
+pos
, table
.contents
[0][jj
], len
);
363 //line[pos++] = '\02'; /* end bold header */
364 if (++jj
== table
.width
)
366 if (!(table
.flags
& TABLE_PAD_LEFT
))
375 /* Dont show ---- bars in 'clean' style. */
376 if(!(hi
&& hi
->userlist_style
== HI_STYLE_CLEAN
)) {
377 //sepsize = strlen_vis(line);
378 sepsize
= tot_width
* nreps
;
379 sepstr
= malloc(sepsize
+ 1);
380 memset(sepstr
, '-', sepsize
);
382 irc_send(from
, to
, sepstr
); /* ----------------- */
384 irc_send(from
, to
, line
); /* alpha beta roe */
385 if(!(hi
&& hi
->userlist_style
== HI_STYLE_CLEAN
))
386 irc_send(from
, to
, sepstr
); /* ----------------- */
389 /* Send the table. */
390 for (jj
=0, pos
=0, reps
=0; ii
<table
.length
; ) {
392 int PAD_COL_LEFT
= false;
393 if(table
.flags
& TABLE_PAD_LEFT
|| str_is_number(table
.contents
[ii
][jj
]))
395 len
= strlen(table
.contents
[ii
][jj
]);
396 spaces
= max_width
[jj
] - len
;
397 //if (table.flags & TABLE_PAD_LEFT)
399 while (spaces
--) line
[pos
++] = ' ';
400 memcpy(line
+pos
, table
.contents
[ii
][jj
], len
);
402 if (++jj
== table
.width
) {
403 jj
= 0, ++ii
, ++reps
;
404 if ((reps
== nreps
) || (ii
== table
.length
)) {
406 irc_send(from
, to
, line
);
411 //if (!(table.flags & TABLE_PAD_LEFT))
419 if (!(table
.flags
& TABLE_NO_HEADERS
) &&
420 !(table
.flags
& TABLE_NO_FOOTER
) &&
421 !(hi
&& hi
->userlist_style
== HI_STYLE_CLEAN
)) {
422 char endstr
[MAX_LINE_SIZE
+1];
423 sprintf(endstr
, "End (%d rows)", table
.length
-1);
425 /* Copy above into the sepstr, centered */
426 if(sepsize
> strlen(endstr
)+2)
429 char* sptr
= sepstr
+(sepsize
/2) - (strlen(endstr
)+2)/2;
433 irc_send(from
, to
, sepstr
);
436 if(!(hi
&& hi
->userlist_style
== HI_STYLE_CLEAN
))
439 if (!(table
.flags
& TABLE_NO_FREE
)) {
440 /* Deallocate table memory (but not the string memory). */
441 for (ii
=0; ii
<table
.length
; ii
++)
442 free(table
.contents
[ii
]);
443 free(table
.contents
);
448 vsend_message(const char *dest
, struct userNode
*src
, struct handle_info
*handle
, int msg_type
, expand_func_t expand_f
, const char *format
, va_list al
)
450 void (*irc_send
)(struct userNode
*from
, const char *to
, const char *msg
);
451 static struct string_buffer input
;
452 unsigned int size
, ipos
, pos
, length
, chars_sent
, use_color
;
453 unsigned int expand_pos
, expand_ipos
, newline_ipos
;
454 char line
[MAX_LINE_SIZE
];
455 struct service
*service
;
456 static char* trigger
= NULL
;
458 if (IsChannelName(dest
) || *dest
== '$') {
460 } else if (!(message_dest
= GetUserH(dest
))) {
461 log_module(MAIN_LOG
, LOG_ERROR
, "Unable to find user with nickname %s (in vsend_message from %s).", dest
, src
->nick
);
463 } else if (message_dest
->dead
) {
464 /* No point in sending to a user who is leaving. */
467 #ifdef WITH_PROTOCOL_P10
468 dest
= message_dest
->numeric
;
471 message_source
= src
;
472 if (!(msg_type
& MSG_TYPE_NOXLATE
)
473 && !(format
= handle_find_message(handle
, format
)))
475 /* fill in a buffer with the string */
477 string_buffer_append_vprintf(&input
, format
, al
);
479 /* figure out how to send the messages */
481 msg_type
|= (HANDLE_FLAGGED(handle
, USE_PRIVMSG
) ? 1 : 0);
482 use_color
= HANDLE_FLAGGED(handle
, MIRC_COLOR
);
483 size
= handle
->screen_width
;
484 if (size
> sizeof(line
))
490 if (!size
|| !(msg_type
& MSG_TYPE_MULTILINE
))
491 size
= DEFAULT_LINE_SIZE
;
492 switch (msg_type
& 3) {
494 if(message_dest
&& message_dest
->no_notice
)
495 irc_send
= irc_privmsg
;
497 irc_send
= irc_notice
;
500 irc_send
= irc_wallchops
;
504 irc_send
= irc_privmsg
;
507 /* This used to be two passes, but if you do that and allow
508 * arbitrary sizes for ${}-expansions (as with help indexes),
509 * that requires a very big intermediate buffer.
511 expand_ipos
= newline_ipos
= ipos
= 0;
512 expand_pos
= pos
= 0;
514 while (input
.list
[ipos
]) {
515 char ch
, *value
, *free_value
;
517 while ((ch
= input
.list
[ipos
]) && (ch
!= '$') && (ch
!= '\n') && (pos
< size
)) {
522 if (!input
.list
[ipos
])
524 if (input
.list
[ipos
] == '\n') {
529 unsigned int new_ipos
;
530 /* Scan backwards for a space in the input, until we hit
531 * either the last newline or the last variable expansion.
532 * Print the line up to that point, and start from there.
534 for (new_ipos
= ipos
;
535 (new_ipos
> expand_ipos
) && (new_ipos
> newline_ipos
);
537 if (input
.list
[new_ipos
] == ' ')
539 pos
-= ipos
- new_ipos
;
540 if (new_ipos
== newline_ipos
) {
541 /* Single word was too big to fit on one line; skip
542 * forward to its end and print it as a whole.
544 while (input
.list
[new_ipos
]
545 && (input
.list
[new_ipos
] != ' ')
546 && (input
.list
[new_ipos
] != '\n')
547 && (input
.list
[new_ipos
] != '$'))
548 line
[pos
++] = input
.list
[new_ipos
++];
551 while (input
.list
[ipos
] == ' ')
557 switch (input
.list
[++ipos
]) {
558 /* Literal '$' or end of string. */
564 /* The following two expand to mIRC color codes if enabled
567 value
= use_color
? "\002" : "";
570 value
= use_color
? "\017" : "";
573 value
= use_color
? "\026" : "";
576 value
= use_color
? "\037" : "";
583 value
= global
? global
->nick
: "Global";
586 value
= chanserv
? chanserv
->nick
: "ChanServ";
589 value
= opserv
? opserv
->nick
: "OpServ";
592 value
= nickserv
? nickserv
->nick
: "NickServ";
595 value
= spamserv
? spamserv
->nick
: "SpamServ";
601 value
= handle
? handle
->handle
: "Account";
603 case '!': /* Command Char for chanserv */
605 trigger
= calloc(sizeof(*trigger
), 2);
606 if(chanserv
&& (service
= service_find(chanserv
->nick
)))
607 trigger
[0] = service
->trigger
;
612 #define SEND_LINE(TRUNCED) do { \
615 if (!(msg_type & MSG_TYPE_MULTILINE) && (pos > 1) && TRUNCED) \
616 line[pos-2] = line[pos-1] = '.'; \
617 irc_send(src, dest, line); \
621 newline_ipos = ipos; \
622 if (!(msg_type & MSG_TYPE_MULTILINE)) return chars_sent; \
624 /* Custom expansion handled by helpfile-specific function. */
627 struct helpfile_expansion exp
;
628 char *name_end
= input
.list
+ ipos
+ 1, *colon
= NULL
;
630 while (*name_end
!= '}' && *name_end
!= ')' && *name_end
) {
631 if (*name_end
== ':') {
641 struct module *module = module_find(input
.list
+ ipos
+ 1);
642 if (module && module->expand_help
)
643 exp
= module->expand_help(colon
+ 1);
649 exp
= expand_f(input
.list
+ ipos
+ 1);
654 free_value
= value
= exp
.value
.str
;
659 /* Must send current line, then emit table. */
661 table_send(src
, (message_dest
? message_dest
->nick
: dest
), 0, irc_send
, exp
.value
.table
);
666 log_module(MAIN_LOG
, LOG_ERROR
, "Invalid exp.type %d from expansion function %p.", exp
.type
, expand_f
);
669 ipos
= name_end
- input
.list
;
676 value
[1] = input
.list
[ipos
];
680 while ((pos
+ strlen(value
) > size
) || strchr(value
, '\n')) {
682 avail
= size
- pos
- 1;
683 length
= strcspn(value
, "\n ");
684 if (length
<= avail
) {
685 strncpy(line
+pos
, value
, length
);
688 /* copy over spaces, until (possible) end of line */
689 while (*value
== ' ') {
691 line
[pos
++] = *value
;
695 /* word to send is too big to send now.. what to do? */
697 /* try to put it on a separate line */
700 /* already at start of line; only send part of it */
701 strncpy(line
, value
, avail
);
704 /* skip any trailing spaces */
705 while (*value
== ' ')
709 /* if we're looking at a newline, send the accumulated text */
710 if (*value
== '\n') {
715 length
= strlen(value
);
716 memcpy(line
+ pos
, value
, length
);
720 if ((pos
< size
-1) && input
.list
[ipos
]) {
735 send_message(struct userNode
*dest
, struct userNode
*src
, const char *format
, ...)
740 if (IsLocal(dest
)) return 0;
741 va_start(ap
, format
);
742 res
= vsend_message(dest
->nick
, src
, dest
->handle_info
, 0, NULL
, format
, ap
);
748 send_message_type(int msg_type
, struct userNode
*dest
, struct userNode
*src
, const char *format
, ...) {
752 if (IsLocal(dest
)) return 0;
753 va_start(ap
, format
);
754 res
= vsend_message(dest
->nick
, src
, dest
->handle_info
, msg_type
, NULL
, format
, ap
);
760 send_target_message(int msg_type
, const char *dest
, struct userNode
*src
, const char *format
, ...)
765 va_start(ap
, format
);
766 res
= vsend_message(dest
, src
, NULL
, msg_type
, NULL
, format
, ap
);
772 _send_help(struct userNode
*dest
, struct userNode
*src
, expand_func_t expand
, const char *format
, ...)
777 va_start(ap
, format
);
778 res
= vsend_message(dest
->nick
, src
, dest
->handle_info
, 12, expand
, format
, ap
);
784 send_help(struct userNode
*dest
, struct userNode
*src
, struct helpfile
*hf
, const char *topic
)
786 struct helpfile
*lang_hf
;
787 struct record_data
*rec
;
788 struct language
*curr
;
793 _send_help(dest
, src
, NULL
, "HFMSG_MISSING_HELPFILE");
796 for (curr
= (dest
->handle_info
? dest
->handle_info
->language
: lang_C
);
798 curr
= curr
->parent
) {
799 lang_hf
= dict_find(curr
->helpfiles
, hf
->name
, NULL
);
802 rec
= dict_find(lang_hf
->db
, topic
, NULL
);
803 if (rec
&& rec
->type
== RECDB_QSTRING
) {
804 _send_help(dest
, src
, hf
->expand
, rec
->d
.qstring
);
808 rec
= dict_find(hf
->db
, "<missing>", NULL
);
811 if (rec
->type
!= RECDB_QSTRING
) {
812 send_message(dest
, src
, "HFMSG_HELP_NOT_STRING");
815 _send_help(dest
, src
, hf
->expand
, rec
->d
.qstring
);
820 send_help_brief(struct userNode
*dest
, struct userNode
*src
, struct helpfile
*hf
, const char *topic
)
822 struct helpfile
*lang_hf
;
823 struct record_data
*rec
;
824 struct language
*curr
;
829 _send_help(dest
, src
, NULL
, "HFMSG_MISSING_HELPFILE");
832 for (curr
= (dest
->handle_info
? dest
->handle_info
->language
: lang_C
);
834 curr
= curr
->parent
) {
835 lang_hf
= dict_find(curr
->helpfiles
, hf
->name
, NULL
);
838 rec
= dict_find(lang_hf
->db
, topic
, NULL
);
839 if (rec
&& rec
->type
== RECDB_QSTRING
)
844 buf
= malloc(strlen(rec
->d
.qstring
) + 1);
845 strcpy(buf
, rec
->d
.qstring
);
846 *strchr(buf
, '\n') = 0;
848 res
= _send_help(dest
, src
, hf
->expand
, buf
);
853 rec
= dict_find(hf
->db
, "<missing>", NULL
);
855 return 0; /* send_message(dest, src, "MSG_TOPIC_UNKNOWN"); */
856 if (rec
->type
!= RECDB_QSTRING
)
857 return 0; /* send_message(dest, src, "HFMSG_HELP_NOT_STRING"); */
858 return _send_help(dest
, src
, hf
->expand
, rec
->d
.qstring
);
861 /* Grammar supported by this parser:
862 * condition = expr | prefix expr
863 * expr = atomicexpr | atomicexpr op atomicexpr
864 * op = '&&' | '||' | 'and' | 'or'
865 * atomicexpr = '(' expr ')' | identifier
866 * identifier = ( '0'-'9' 'A'-'Z' 'a'-'z' '-' '_' '/' )+ | ! identifier
868 * Whitespace is ignored. The parser is implemented as a recursive
869 * descent parser by functions like:
870 * static int helpfile_eval_<element>(const char *start, const char **end);
879 static const struct {
882 } helpfile_operators
[] = {
883 { "&&", OP_BOOL_AND
},
884 { "and", OP_BOOL_AND
},
885 { "||", OP_BOOL_OR
},
886 { "or", OP_BOOL_OR
},
890 static const char *identifier_chars
= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_/";
892 static int helpfile_eval_expr(const char *start
, const char **end
);
893 static int helpfile_eval_atomicexpr(const char *start
, const char **end
);
894 static int helpfile_eval_identifier(const char *start
, const char **end
);
897 helpfile_eval_identifier(const char *start
, const char **end
)
899 /* Skip leading whitespace. */
900 while (isspace(*start
) && (start
< *end
))
903 log_module(MAIN_LOG
, LOG_FATAL
, "Expected identifier in helpfile condition.");
907 if (start
[0] == '!') {
908 int res
= helpfile_eval_identifier(start
+1, end
);
912 } else if (start
[0] == '/') {
914 char *id_str
, *value
;
917 strchr(identifier_chars
, sep
[0]) && (sep
< *end
);
919 memcpy(id_str
= alloca(sep
+1-start
), start
, sep
-start
);
920 id_str
[sep
-start
] = '\0';
921 value
= conf_get_data(id_str
+1, RECDB_QSTRING
);
925 return enabled_string(value
) || true_string(value
);
926 } else if ((*end
- start
>= 4) && !ircncasecmp(start
, "true", 4)) {
929 } else if ((*end
- start
>= 5) && !ircncasecmp(start
, "false", 5)) {
933 log_module(MAIN_LOG
, LOG_FATAL
, "Unexpected helpfile identifier '%.*s'.", (int)(*end
-start
), start
);
939 helpfile_eval_atomicexpr(const char *start
, const char **end
)
944 /* Skip leading whitespace. */
945 while (isspace(*start
) && (start
< *end
))
948 log_module(MAIN_LOG
, LOG_FATAL
, "Expected atomic expression in helpfile condition.");
952 /* If it's not parenthesized, it better be a valid identifier. */
954 return helpfile_eval_identifier(start
, end
);
956 /* Parse the internal expression. */
959 res
= helpfile_eval_expr(start
, &sep
);
961 /* Check for the closing parenthesis. */
962 while (isspace(*sep
) && (sep
< *end
))
964 if ((sep
== *end
) || (sep
[0] != ')')) {
965 log_module(MAIN_LOG
, LOG_FATAL
, "Expected close parenthesis at '%.*s'.", (int)(*end
-sep
), sep
);
969 /* Report the end location and result. */
975 helpfile_eval_expr(const char *start
, const char **end
)
977 const char *sep
, *sep2
;
978 unsigned int ii
, len
;
982 /* Parse the first atomicexpr. */
984 res_a
= helpfile_eval_atomicexpr(start
, &sep
);
988 /* Figure out what follows that. */
989 while (isspace(*sep
) && (sep
< *end
))
994 for (ii
= 0; helpfile_operators
[ii
].str
; ++ii
) {
995 len
= strlen(helpfile_operators
[ii
].str
);
996 if (ircncasecmp(sep
, helpfile_operators
[ii
].str
, len
))
998 op
= helpfile_operators
[ii
].op
;
1001 if (op
== OP_INVALID
) {
1002 log_module(MAIN_LOG
, LOG_FATAL
, "Unrecognized helpfile operator at '%.*s'.", (int)(*end
-sep
), sep
);
1006 /* Parse the next atomicexpr. */
1008 res_b
= helpfile_eval_atomicexpr(sep
, &sep2
);
1012 /* Make sure there's no trailing garbage */
1013 while (isspace(*sep2
) && (sep2
< *end
))
1016 log_module(MAIN_LOG
, LOG_FATAL
, "Trailing garbage in helpfile expression: '%.*s'.", (int)(*end
-sep2
), sep2
);
1020 /* Record where we stopped parsing. */
1023 /* Do the logic on the subexpressions. */
1026 return res_a
&& res_b
;
1028 return res_a
|| res_b
;
1035 helpfile_eval_condition(const char *start
, const char **end
)
1039 /* Skip the prefix if there is one. */
1040 for (term
= start
; isalnum(*term
) && (term
< *end
); ++term
) ;
1041 if (term
!= start
) {
1042 if ((term
+ 2 >= *end
) || (term
[0] != ':') || (term
[1] != ' ')) {
1043 log_module(MAIN_LOG
, LOG_FATAL
, "In helpfile condition '%.*s' expected prefix to end with ': '.",(int)(*end
-start
), start
);
1049 /* Evaluate the remaining string as an expression. */
1050 return helpfile_eval_expr(start
, end
);
1054 unlistify_help(const char *key
, void *data
, void *extra
)
1056 struct record_data
*rd
= data
;
1057 dict_t newdb
= extra
;
1061 dict_insert(newdb
, strdup(key
), alloc_record_data_qstring(GET_RECORD_QSTRING(rd
)));
1063 case RECDB_STRING_LIST
: {
1064 struct string_list
*slist
= GET_RECORD_STRING_LIST(rd
);
1066 unsigned int totlen
, len
, i
;
1068 for (i
=totlen
=0; i
<slist
->used
; i
++)
1069 totlen
= totlen
+ strlen(slist
->list
[i
]) + 1;
1070 dest
= alloca(totlen
+1);
1071 for (i
=totlen
=0; i
<slist
->used
; i
++) {
1072 len
= strlen(slist
->list
[i
]);
1073 memcpy(dest
+totlen
, slist
->list
[i
], len
);
1074 dest
[totlen
+len
] = '\n';
1075 totlen
= totlen
+ len
+ 1;
1078 dict_insert(newdb
, strdup(key
), alloc_record_data_qstring(dest
));
1081 case RECDB_OBJECT
: {
1084 for (it
= dict_first(GET_RECORD_OBJECT(rd
)); it
; it
= iter_next(it
)) {
1085 const char *k2
, *end
;
1088 /* Evaluate the expression for this subentry. */
1090 end
= k2
+ strlen(k2
);
1091 res
= helpfile_eval_condition(k2
, &end
);
1092 /* If the evaluation failed, bail. */
1094 log_module(MAIN_LOG
, LOG_FATAL
, " .. while processing entry '%s' condition '%s'.", key
, k2
);
1097 /* If the condition was false, try another. */
1100 /* If we cannot unlistify the contents, bail. */
1101 if (unlistify_help(key
, iter_data(it
), extra
))
1105 /* If none of the conditions apply, just omit the entry. */
1114 open_helpfile(const char *fname
, expand_func_t expand
)
1116 struct helpfile
*hf
;
1118 dict_t db
= parse_database(fname
);
1119 hf
= calloc(1, sizeof(*hf
));
1120 hf
->expand
= expand
;
1121 hf
->db
= alloc_database();
1122 dict_set_free_keys(hf
->db
, free
);
1123 if ((slash
= strrchr(fname
, '/'))) {
1124 hf
->name
= strdup(slash
+ 1);
1126 hf
->name
= strdup(fname
);
1127 dict_insert(language_find("C")->helpfiles
, hf
->name
, hf
);
1130 dict_foreach(db
, unlistify_help
, hf
->db
);
1136 void close_helpfile(struct helpfile
*hf
)
1140 free((char*)hf
->name
);
1141 free_database(hf
->db
);
1145 void message_register_table(const struct message_entry
*table
)
1149 while (table
->msgid
) {
1150 dict_insert(lang_C
->messages
, table
->msgid
, (char*)table
->format
);
1155 void helpfile_init(void)
1157 message_register_table(msgtab
);
1158 language_read_list();
1161 void helpfile_finalize(void)
1164 for (it
= dict_first(languages
); it
; it
= iter_next(it
))
1165 language_read(iter_key(it
));
1166 reg_exit_func(language_cleanup
);