Added SILC Thread Queue API
[crypto.git] / apps / irssi / src / core / special-vars.c
1 /*
2  special-vars.c : irssi
3
4     Copyright (C) 2000 Timo Sirainen
5
6     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     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
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.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22 #include "signals.h"
23 #include "special-vars.h"
24 #include "expandos.h"
25 #include "settings.h"
26 #include "servers.h"
27 #include "misc.h"
28
29 #define ALIGN_RIGHT 0x01
30 #define ALIGN_CUT   0x02
31 #define ALIGN_PAD   0x04
32
33 #define isvarchar(c) \
34         (i_isalnum(c) || (c) == '_')
35
36 #define isarg(c) \
37         (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-')
38
39 static SPECIAL_HISTORY_FUNC history_func = NULL;
40
41 static char *get_argument(char **cmd, char **arglist)
42 {
43         GString *str;
44         char *ret;
45         int max, arg, argcount;
46
47         arg = 0;
48         max = -1;
49
50         argcount = arglist == NULL ? 0 : strarray_length(arglist);
51
52         if (**cmd == '*') {
53                 /* get all arguments */
54         } else if (**cmd == '~') {
55                 /* get last argument */
56                 arg = max = argcount-1;
57         } else {
58                 if (i_isdigit(**cmd)) {
59                         /* first argument */
60                         arg = max = (**cmd)-'0';
61                         (*cmd)++;
62                 }
63
64                 if (**cmd == '-') {
65                         /* get more than one argument */
66                         (*cmd)++;
67                         if (!i_isdigit(**cmd))
68                                 max = -1; /* get all the rest */
69                         else {
70                                 max = (**cmd)-'0';
71                                 (*cmd)++;
72                         }
73                 }
74                 (*cmd)--;
75         }
76
77         str = g_string_new(NULL);
78         while (arg >= 0 && arg < argcount && (arg <= max || max == -1)) {
79                 g_string_append(str, arglist[arg]);
80                 g_string_append_c(str, ' ');
81                 arg++;
82         }
83         if (str->len > 0) g_string_truncate(str, str->len-1);
84
85         ret = str->str;
86         g_string_free(str, FALSE);
87         return ret;
88 }
89
90 static char *get_internal_setting(const char *key, int type, int *free_ret)
91 {
92         switch (type) {
93         case SETTING_TYPE_BOOLEAN:
94                 return settings_get_bool(key) ? "yes" : "no";
95         case SETTING_TYPE_INT:
96                 *free_ret = TRUE;
97                 return g_strdup_printf("%d", settings_get_int(key));
98         case SETTING_TYPE_STRING:
99                 return (char *) settings_get_str(key);
100         }
101
102         return NULL;
103 }
104
105 static char *get_long_variable_value(const char *key, SERVER_REC *server,
106                                      void *item, int *free_ret)
107 {
108         EXPANDO_FUNC func;
109         const char *ret;
110         int type;
111
112         *free_ret = FALSE;
113
114         /* expando? */
115         func = expando_find_long(key);
116         if (func != NULL) {
117                 current_expando = key;
118                 return func(server, item, free_ret);
119         }
120
121         /* internal setting? */
122         type = settings_get_type(key);
123         if (type != -1)
124                 return get_internal_setting(key, type, free_ret);
125
126         /* environment variable? */
127         ret = g_getenv(key);
128         if (ret != NULL)
129                 return (char *) ret;
130
131         return NULL;
132 }
133
134 static char *get_long_variable(char **cmd, SERVER_REC *server,
135                                void *item, int *free_ret, int getname)
136 {
137         char *start, *var, *ret;
138
139         /* get variable name */
140         start = *cmd;
141         while (isvarchar((*cmd)[1])) (*cmd)++;
142
143         var = g_strndup(start, (int) (*cmd-start)+1);
144         if (getname) {
145                 *free_ret = TRUE;
146                 return var;
147         }
148         ret = get_long_variable_value(var, server, item, free_ret);
149         g_free(var);
150         return ret;
151 }
152
153 /* return the value of the variable found from `cmd'.
154    if 'getname' is TRUE, return the name of the variable instead it's value */
155 static char *get_variable(char **cmd, SERVER_REC *server, void *item,
156                           char **arglist, int *free_ret, int *arg_used,
157                           int getname)
158 {
159         EXPANDO_FUNC func;
160
161         if (isarg(**cmd)) {
162                 /* argument */
163                 *free_ret = TRUE;
164                 if (arg_used != NULL) *arg_used = TRUE;
165                 return getname ? g_strdup_printf("%c", **cmd) :
166                         get_argument(cmd, arglist);
167         }
168
169         if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
170                 /* long variable name.. */
171                 return get_long_variable(cmd, server, item, free_ret, getname);
172         }
173
174         /* single character variable. */
175         if (getname) {
176                 *free_ret = TRUE;
177                 return g_strdup_printf("%c", **cmd);
178         }
179         *free_ret = FALSE;
180         func = expando_find_char(**cmd);
181         if (func == NULL)
182                 return NULL;
183         else {
184                 char str[2];
185
186                 str[0] = **cmd; str[1] = '\0';
187                 current_expando = str;
188                 return func(server, item, free_ret);
189         }
190 }
191
192 static char *get_history(char **cmd, void *item, int *free_ret)
193 {
194         char *start, *text, *ret;
195
196         /* get variable name */
197         start = ++(*cmd);
198         while (**cmd != '\0' && **cmd != '!') (*cmd)++;
199
200         if (history_func == NULL)
201                 ret = NULL;
202         else {
203                 text = g_strndup(start, (int) (*cmd-start));
204                 ret = history_func(text, item, free_ret);
205                 g_free(text);
206         }
207
208         if (**cmd == '\0') (*cmd)--;
209         return ret;
210 }
211
212 static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
213                                char **arglist, int *free_ret, int *arg_used,
214                                int flags)
215 {
216         char command, *value, *p;
217         int len;
218
219         if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
220                 *free_ret = TRUE;
221                 return g_strdup_printf("$%c", **cmd);
222         }
223
224         if (**cmd == '!') {
225                 /* find text from command history */
226                 if (flags & PARSE_FLAG_GETNAME)
227                         return "!";
228
229                 return get_history(cmd, item, free_ret);
230         }
231
232         command = 0;
233         if (**cmd == '#' || **cmd == '@') {
234                 command = **cmd;
235                 if ((*cmd)[1] != '\0')
236                         (*cmd)++;
237                 else {
238                         /* default to $* */
239                         char *temp_cmd = "*";
240
241                         if (flags & PARSE_FLAG_GETNAME)
242                                 return "*";
243
244                         *free_ret = TRUE;
245                         return get_argument(&temp_cmd, arglist);
246                 }
247         }
248
249         value = get_variable(cmd, server, item, arglist, free_ret,
250                              arg_used, flags & PARSE_FLAG_GETNAME);
251
252         if (flags & PARSE_FLAG_GETNAME)
253                 return value;
254
255         if (command == '#') {
256                 /* number of words */
257                 if (value == NULL || *value == '\0') {
258                         if (value != NULL && *free_ret) {
259                                 g_free(value);
260                                 *free_ret = FALSE;
261                         }
262                         return "0";
263                 }
264
265                 len = 1;
266                 for (p = value; *p != '\0'; p++) {
267                         if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
268                                 len++;
269                 }
270                 if (*free_ret) g_free(value);
271
272                 *free_ret = TRUE;
273                 return g_strdup_printf("%d", len);
274         }
275
276         if (command == '@') {
277                 /* number of characters */
278                 if (value == NULL) return "0";
279
280                 len = strlen(value);
281                 if (*free_ret) g_free(value);
282
283                 *free_ret = TRUE;
284                 return g_strdup_printf("%d", len);
285         }
286
287         return value;
288 }
289
290 /* get alignment arguments (inside the []) */
291 static int get_alignment_args(char **data, int *align, int *flags, char *pad)
292 {
293         char *str;
294
295         *align = 0;
296         *flags = ALIGN_CUT|ALIGN_PAD;
297         *pad = ' ';
298
299         /* '!' = don't cut, '-' = right padding */
300         str = *data;
301         while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
302                 if (*str == '!')
303                         *flags &= ~ALIGN_CUT;
304                 else if (*str == '-')
305                         *flags |= ALIGN_RIGHT;
306                 else if (*str == '.')
307                          *flags &= ~ALIGN_PAD;
308                 str++;
309         }
310         if (!i_isdigit(*str))
311                 return FALSE; /* expecting number */
312
313         /* get the alignment size */
314         while (i_isdigit(*str)) {
315                 *align = (*align) * 10 + (*str-'0');
316                 str++;
317         }
318
319         /* get the pad character */
320         while (*str != '\0' && *str != ']') {
321                 *pad = *str;
322                 str++;
323         }
324
325         if (*str++ != ']') return FALSE;
326
327         *data = str;
328         return TRUE;
329 }
330
331 /* return the aligned text */
332 static char *get_alignment(const char *text, int align, int flags, char pad)
333 {
334         GString *str;
335         char *ret;
336
337         g_return_val_if_fail(text != NULL, NULL);
338
339         str = g_string_new(text);
340
341         /* cut */
342         if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
343                 g_string_truncate(str, align);
344
345         /* add pad characters */
346         if (flags & ALIGN_PAD) {
347                 while (str->len < align) {
348                         if (flags & ALIGN_RIGHT)
349                                 g_string_prepend_c(str, pad);
350                         else
351                                 g_string_append_c(str, pad);
352                 }
353         }
354
355         ret = str->str;
356         g_string_free(str, FALSE);
357         return ret;
358 }
359
360 /* Parse and expand text after '$' character. return value has to be
361    g_free()'d if `free_ret' is TRUE. */
362 char *parse_special(char **cmd, SERVER_REC *server, void *item,
363                     char **arglist, int *free_ret, int *arg_used, int flags)
364 {
365         static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
366         char command, *value;
367
368         char align_pad;
369         int align, align_flags;
370
371         char *nest_value;
372         int brackets, nest_free;
373
374         *free_ret = FALSE;
375         if (**cmd == '\0')
376                 return NULL;
377
378         command = **cmd; (*cmd)++;
379         switch (command) {
380         case '[':
381                 /* alignment */
382                 if (!get_alignment_args(cmd, &align, &align_flags,
383                                         &align_pad) || **cmd == '\0') {
384                         (*cmd)--;
385                         return NULL;
386                 }
387                 break;
388         default:
389                 command = 0;
390                 (*cmd)--;
391         }
392
393         nest_free = FALSE; nest_value = NULL;
394         if (**cmd == '(' && (*cmd)[1] != '\0') {
395                 /* subvariable */
396                 int toplevel = nested_orig_cmd == NULL;
397
398                 if (toplevel) nested_orig_cmd = cmd;
399                 (*cmd)++;
400                 if (**cmd != '$') {
401                         /* ... */
402                         nest_value = *cmd;
403                 } else {
404                         (*cmd)++;
405                         nest_value = parse_special(cmd, server, item, arglist,
406                                                    &nest_free, arg_used,
407                                                    flags);
408                 }
409
410                 if (nest_value == NULL || *nest_value == '\0')
411                         return NULL;
412
413                 while ((*nested_orig_cmd)[1] != '\0') {
414                         (*nested_orig_cmd)++;
415                         if (**nested_orig_cmd == ')')
416                                 break;
417                 }
418                 cmd = &nest_value;
419
420                 if (toplevel) nested_orig_cmd = NULL;
421         }
422
423         if (**cmd != '{')
424                 brackets = FALSE;
425         else {
426                 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
427                 if ((*cmd)[1] == '\0')
428                         return NULL;
429                 (*cmd)++;
430                 brackets = TRUE;
431         }
432
433         value = get_special_value(cmd, server, item, arglist,
434                                   free_ret, arg_used, flags);
435         if (**cmd == '\0')
436                 g_error("parse_special() : buffer overflow!");
437
438         if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
439                 *arg_used = TRUE;
440
441         if (brackets) {
442                 while (**cmd != '}' && (*cmd)[1] != '\0')
443                         (*cmd)++;
444         }
445
446         if (nest_free) g_free(nest_value);
447
448         if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
449                 /* alignment */
450                 char *p;
451
452                 if (value == NULL) return "";
453
454                 p = get_alignment(value, align, align_flags, align_pad);
455                 if (*free_ret) g_free(value);
456
457                 *free_ret = TRUE;
458                 return p;
459         }
460
461         return value;
462 }
463
464 static void gstring_append_escaped(GString *str, const char *text, int flags)
465 {
466         char esc[4], *escpos;
467         
468         escpos = esc;
469         if (flags & PARSE_FLAG_ESCAPE_VARS)
470                 *escpos++ = '%';
471         if (flags & PARSE_FLAG_ESCAPE_THEME) {
472                 *escpos++ = '{';
473                 *escpos++ = '}';
474         }
475
476         if (escpos == esc) {
477                 g_string_append(str, text);
478                 return;
479         }
480
481         *escpos = '\0'; 
482         while (*text != '\0') {
483                 for (escpos = esc; *escpos != '\0'; escpos++) {
484                         if (*text == *escpos) {
485                                 g_string_append_c(str, '%');
486                                 break;
487                         }
488                 }
489                 g_string_append_c(str, *text);
490                 text++;
491         }
492 }
493
494 /* parse the whole string. $ and \ chars are replaced */
495 char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
496                            const char *data, int *arg_used, int flags)
497 {
498         char code, **arglist, *ret;
499         GString *str;
500         int need_free, chr;
501
502         g_return_val_if_fail(cmd != NULL, NULL);
503         g_return_val_if_fail(data != NULL, NULL);
504
505         /* create the argument list */
506         arglist = g_strsplit(data, " ", -1);
507
508         if (arg_used != NULL) *arg_used = FALSE;
509         code = 0;
510         str = g_string_new(NULL);
511         while (*cmd != '\0') {
512                 if (code == '\\') {
513                         if (*cmd == ';')
514                                 g_string_append_c(str, ';');
515                         else {
516                                 chr = expand_escape(&cmd);
517                                 g_string_append_c(str, chr != -1 ? chr : *cmd);
518                         }
519                         code = 0;
520                 } else if (code == '$') {
521                         char *ret;
522
523                         ret = parse_special((char **) &cmd, server, item,
524                                             arglist, &need_free, arg_used,
525                                             flags);
526                         if (ret != NULL) {
527                                 gstring_append_escaped(str, ret, flags);
528                                 if (need_free) g_free(ret);
529                         }
530                         code = 0;
531                 } else {
532                         if (*cmd == '\\' || *cmd == '$')
533                                 code = *cmd;
534                         else
535                                 g_string_append_c(str, *cmd);
536                 }
537
538                 cmd++;
539         }
540         g_strfreev(arglist);
541
542         ret = str->str;
543         g_string_free(str, FALSE);
544         return ret;
545 }
546
547 #define is_split_char(str, start) \
548         ((str)[0] == ';' && ((start) == (str) || \
549                 ((str)[-1] != '\\' && (str)[-1] != '$')))
550
551 /* execute the commands in string - commands can be split with ';' */
552 void eval_special_string(const char *cmd, const char *data,
553                          SERVER_REC *server, void *item)
554 {
555         const char *cmdchars;
556         char *orig, *str, *start, *ret;
557         int arg_used, arg_used_ever;
558         GSList *commands;
559
560         commands = NULL;
561         arg_used_ever = FALSE;
562         cmdchars = settings_get_str("cmdchars");
563
564         /* get a list of all the commands to run */
565         orig = start = str = g_strdup(cmd);
566         do {
567                 if (is_split_char(str, start)) {
568                         *str++ = '\0';
569                         while (*str == ' ') str++;
570                 } else if (*str != '\0') {
571                         str++;
572                         continue;
573                 }
574
575                 ret = parse_special_string(start, server, item,
576                                            data, &arg_used, 0);
577                 if (*ret != '\0') {
578                         if (arg_used) arg_used_ever = TRUE;
579
580                         if (strchr(cmdchars, *ret) == NULL) {
581                                 /* no command char - let's put it there.. */
582                                 char *old = ret;
583
584                                 ret = g_strdup_printf("%c%s", *cmdchars, old);
585                                 g_free(old);
586                         }
587                         commands = g_slist_append(commands, ret);
588                 }
589                 start = str;
590         } while (*start != '\0');
591
592         /* run the command, if no arguments were ever used, append all of them
593            after each command */
594         while (commands != NULL) {
595                 ret = commands->data;
596
597                 if (!arg_used_ever && *data != '\0') {
598                         char *old = ret;
599
600                         ret = g_strconcat(old, " ", data, NULL);
601                         g_free(old);
602                 }
603
604                 if (server != NULL)
605                         server_ref(server);
606                 signal_emit("send command", 3, ret, server, item);
607
608                 if (server != NULL && !server_unref(server)) {
609                         /* the server was destroyed */
610                         server = NULL;
611                         item = NULL;
612                 }
613
614                 /* FIXME: window item would need reference counting as well,
615                    eg. "/EVAL win close;say hello" wouldn't work now.. */
616
617                 g_free(ret);
618                 commands = g_slist_remove(commands, commands->data);
619         }
620         g_free(orig);
621 }
622
623 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
624 {
625         history_func = func;
626 }
627
628 static void update_signals_hash(GHashTable **hash, int *signals)
629 {
630         void *signal_id;
631         int arg_type;
632
633         if (*hash == NULL) {
634                 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
635                                          (GCompareFunc) g_direct_equal);
636         }
637
638         while (*signals != -1) {
639                 signal_id = GINT_TO_POINTER(*signals);
640                 arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id));
641                 if (arg_type != 0 && arg_type != signals[1]) {
642                         /* same signal is used for different purposes ..
643                            not sure if this should ever happen, but change
644                            the argument type to none so it will at least
645                            work. */
646                         arg_type = EXPANDO_ARG_NONE;
647                 }
648
649                 if (arg_type == 0) arg_type = signals[1];
650                 g_hash_table_insert(*hash, signal_id,
651                                     GINT_TO_POINTER(arg_type));
652                 signals += 2;
653         }
654 }
655
656 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
657 {
658         (*pos)[0] = GPOINTER_TO_INT(signal_id);
659         (*pos)[1] = GPOINTER_TO_INT(arg_type);
660         (*pos) += 2;
661 }
662
663 static int *get_signals_list(GHashTable *hash)
664 {
665         int *signals, *pos;
666
667         if (hash == NULL) {
668                 /* no expandos in text - never needs updating */
669                 return NULL;
670         }
671
672         pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
673         g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
674         *pos = -1;
675
676         g_hash_table_destroy(hash);
677         return signals;
678
679 }
680
681 #define TASK_BIND               1
682 #define TASK_UNBIND             2
683 #define TASK_GET_SIGNALS        3
684
685 static int *special_vars_signals_task(const char *text, int funccount,
686                                       SIGNAL_FUNC *funcs, int task)
687 {
688         GHashTable *signals;
689         char *expando;
690         int need_free, *expando_signals;
691
692         signals = NULL;
693         while (*text != '\0') {
694                 if (*text == '\\' && text[1] != '\0') {
695                         /* escape */
696                         text += 2;
697                 } else if (*text == '$' && text[1] != '\0') {
698                         /* expando */
699                         text++;
700                         expando = parse_special((char **) &text, NULL, NULL,
701                                                 NULL, &need_free, NULL,
702                                                 PARSE_FLAG_GETNAME);
703                         if (expando == NULL)
704                                 continue;
705
706                         switch (task) {
707                         case TASK_BIND:
708                                 expando_bind(expando, funccount, funcs);
709                                 break;
710                         case TASK_UNBIND:
711                                 expando_unbind(expando, funccount, funcs);
712                                 break;
713                         case TASK_GET_SIGNALS:
714                                 expando_signals = expando_get_signals(expando);
715                                 if (expando_signals != NULL) {
716                                         update_signals_hash(&signals,
717                                                             expando_signals);
718                                         g_free(expando_signals);
719                                 }
720                                 break;
721                         }
722                         if (need_free) g_free(expando);
723                 } else {
724                         /* just a char */
725                         text++;
726                 }
727         }
728
729         if (task == TASK_GET_SIGNALS)
730                 return get_signals_list(signals);
731
732         return NULL;
733 }
734
735 void special_vars_add_signals(const char *text,
736                               int funccount, SIGNAL_FUNC *funcs)
737 {
738         special_vars_signals_task(text, funccount, funcs, TASK_BIND);
739 }
740
741 void special_vars_remove_signals(const char *text,
742                                  int funccount, SIGNAL_FUNC *funcs)
743 {
744         special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
745 }
746
747 int *special_vars_get_signals(const char *text)
748 {
749         return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);
750 }