updates
[silc.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 == '(') {
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                 while ((*nested_orig_cmd)[1] != '\0') {
411                         (*nested_orig_cmd)++;
412                         if (**nested_orig_cmd == ')')
413                                 break;
414                 }
415                 cmd = &nest_value;
416
417                 if (toplevel) nested_orig_cmd = NULL;
418         }
419
420         if (**cmd != '{')
421                 brackets = FALSE;
422         else {
423                 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
424                 (*cmd)++;
425                 brackets = TRUE;
426         }
427
428         value = get_special_value(cmd, server, item, arglist,
429                                   free_ret, arg_used, flags);
430         if (**cmd == '\0')
431                 g_error("parse_special() : buffer overflow!");
432
433         if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
434                 *arg_used = TRUE;
435
436         if (brackets) {
437                 while (**cmd != '}' && (*cmd)[1] != '\0')
438                         (*cmd)++;
439         }
440
441         if (nest_free) g_free(nest_value);
442
443         if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
444                 /* alignment */
445                 char *p;
446
447                 if (value == NULL) return "";
448
449                 p = get_alignment(value, align, align_flags, align_pad);
450                 if (*free_ret) g_free(value);
451
452                 *free_ret = TRUE;
453                 return p;
454         }
455
456         return value;
457 }
458
459 static void gstring_append_escaped(GString *str, const char *text, int flags)
460 {
461         char esc[4], *escpos;
462         
463         escpos = esc;
464         if (flags & PARSE_FLAG_ESCAPE_VARS)
465                 *escpos++ = '%';
466         if (flags & PARSE_FLAG_ESCAPE_THEME) {
467                 *escpos++ = '{';
468                 *escpos++ = '}';
469         }
470
471         if (escpos == esc) {
472                 g_string_append(str, text);
473                 return;
474         }
475
476         *escpos = '\0'; 
477         while (*text != '\0') {
478                 for (escpos = esc; *escpos != '\0'; escpos++) {
479                         if (*text == *escpos) {
480                                 g_string_append_c(str, '%');
481                                 break;
482                         }
483                 }
484                 g_string_append_c(str, *text);
485                 text++;
486         }
487 }
488
489 /* parse the whole string. $ and \ chars are replaced */
490 char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
491                            const char *data, int *arg_used, int flags)
492 {
493         char code, **arglist, *ret;
494         GString *str;
495         int need_free, chr;
496
497         g_return_val_if_fail(cmd != NULL, NULL);
498         g_return_val_if_fail(data != NULL, NULL);
499
500         /* create the argument list */
501         arglist = g_strsplit(data, " ", -1);
502
503         if (arg_used != NULL) *arg_used = FALSE;
504         code = 0;
505         str = g_string_new(NULL);
506         while (*cmd != '\0') {
507                 if (code == '\\') {
508                         if (*cmd == ';')
509                                 g_string_append_c(str, ';');
510                         else {
511                                 chr = expand_escape(&cmd);
512                                 g_string_append_c(str, chr != -1 ? chr : *cmd);
513                         }
514                         code = 0;
515                 } else if (code == '$') {
516                         char *ret;
517
518                         ret = parse_special((char **) &cmd, server, item,
519                                             arglist, &need_free, arg_used,
520                                             flags);
521                         if (ret != NULL) {
522                                 gstring_append_escaped(str, ret, flags);
523                                 if (need_free) g_free(ret);
524                         }
525                         code = 0;
526                 } else {
527                         if (*cmd == '\\' || *cmd == '$')
528                                 code = *cmd;
529                         else
530                                 g_string_append_c(str, *cmd);
531                 }
532
533                 cmd++;
534         }
535         g_strfreev(arglist);
536
537         ret = str->str;
538         g_string_free(str, FALSE);
539         return ret;
540 }
541
542 #define is_split_char(str, start) \
543         ((str)[0] == ';' && ((start) == (str) || \
544                 ((str)[-1] != '\\' && (str)[-1] != '$')))
545
546 /* execute the commands in string - commands can be split with ';' */
547 void eval_special_string(const char *cmd, const char *data,
548                          SERVER_REC *server, void *item)
549 {
550         const char *cmdchars;
551         char *orig, *str, *start, *ret;
552         int arg_used, arg_used_ever;
553         GSList *commands;
554
555         commands = NULL;
556         arg_used_ever = FALSE;
557         cmdchars = settings_get_str("cmdchars");
558
559         /* get a list of all the commands to run */
560         orig = start = str = g_strdup(cmd);
561         do {
562                 if (is_split_char(str, start)) {
563                         *str++ = '\0';
564                         while (*str == ' ') str++;
565                 } else if (*str != '\0') {
566                         str++;
567                         continue;
568                 }
569
570                 ret = parse_special_string(start, server, item,
571                                            data, &arg_used, 0);
572                 if (*ret != '\0') {
573                         if (arg_used) arg_used_ever = TRUE;
574
575                         if (strchr(cmdchars, *ret) == NULL) {
576                                 /* no command char - let's put it there.. */
577                                 char *old = ret;
578
579                                 ret = g_strdup_printf("%c%s", *cmdchars, old);
580                                 g_free(old);
581                         }
582                         commands = g_slist_append(commands, ret);
583                 }
584                 start = str;
585         } while (*start != '\0');
586
587         /* run the command, if no arguments were ever used, append all of them
588            after each command */
589         while (commands != NULL) {
590                 ret = commands->data;
591
592                 if (!arg_used_ever && *data != '\0') {
593                         char *old = ret;
594
595                         ret = g_strconcat(old, " ", data, NULL);
596                         g_free(old);
597                 }
598
599                 if (server != NULL)
600                         server_ref(server);
601                 signal_emit("send command", 3, ret, server, item);
602
603                 if (server != NULL && !server_unref(server)) {
604                         /* the server was destroyed */
605                         server = NULL;
606                         item = NULL;
607                 }
608
609                 /* FIXME: window item would need reference counting as well,
610                    eg. "/EVAL win close;say hello" wouldn't work now.. */
611
612                 g_free(ret);
613                 commands = g_slist_remove(commands, commands->data);
614         }
615         g_free(orig);
616 }
617
618 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
619 {
620         history_func = func;
621 }
622
623 static void update_signals_hash(GHashTable **hash, int *signals)
624 {
625         void *signal_id;
626         int arg_type;
627
628         if (*hash == NULL) {
629                 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
630                                          (GCompareFunc) g_direct_equal);
631         }
632
633         while (*signals != -1) {
634                 signal_id = GINT_TO_POINTER(*signals);
635                 arg_type = GPOINTER_TO_INT(g_hash_table_lookup(*hash, signal_id));
636                 if (arg_type != 0 && arg_type != signals[1]) {
637                         /* same signal is used for different purposes ..
638                            not sure if this should ever happen, but change
639                            the argument type to none so it will at least
640                            work. */
641                         arg_type = EXPANDO_ARG_NONE;
642                 }
643
644                 if (arg_type == 0) arg_type = signals[1];
645                 g_hash_table_insert(*hash, signal_id,
646                                     GINT_TO_POINTER(arg_type));
647                 signals += 2;
648         }
649 }
650
651 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
652 {
653         (*pos)[0] = GPOINTER_TO_INT(signal_id);
654         (*pos)[1] = GPOINTER_TO_INT(arg_type);
655         (*pos) += 2;
656 }
657
658 static int *get_signals_list(GHashTable *hash)
659 {
660         int *signals, *pos;
661
662         if (hash == NULL) {
663                 /* no expandos in text - never needs updating */
664                 return NULL;
665         }
666
667         pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
668         g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
669         *pos = -1;
670
671         g_hash_table_destroy(hash);
672         return signals;
673
674 }
675
676 #define TASK_BIND               1
677 #define TASK_UNBIND             2
678 #define TASK_GET_SIGNALS        3
679
680 static int *special_vars_signals_task(const char *text, int funccount,
681                                       SIGNAL_FUNC *funcs, int task)
682 {
683         GHashTable *signals;
684         char *expando;
685         int need_free, *expando_signals;
686
687         signals = NULL;
688         while (*text != '\0') {
689                 if (*text == '\\' && text[1] != '\0') {
690                         /* escape */
691                         text += 2;
692                 } else if (*text == '$' && text[1] != '\0') {
693                         /* expando */
694                         text++;
695                         expando = parse_special((char **) &text, NULL, NULL,
696                                                 NULL, &need_free, NULL,
697                                                 PARSE_FLAG_GETNAME);
698                         if (expando == NULL)
699                                 continue;
700
701                         switch (task) {
702                         case TASK_BIND:
703                                 expando_bind(expando, funccount, funcs);
704                                 break;
705                         case TASK_UNBIND:
706                                 expando_unbind(expando, funccount, funcs);
707                                 break;
708                         case TASK_GET_SIGNALS:
709                                 expando_signals = expando_get_signals(expando);
710                                 if (expando_signals != NULL) {
711                                         update_signals_hash(&signals,
712                                                             expando_signals);
713                                         g_free(expando_signals);
714                                 }
715                                 break;
716                         }
717                         if (need_free) g_free(expando);
718                 } else {
719                         /* just a char */
720                         text++;
721                 }
722         }
723
724         if (task == TASK_GET_SIGNALS)
725                 return get_signals_list(signals);
726
727         return NULL;
728 }
729
730 void special_vars_add_signals(const char *text,
731                               int funccount, SIGNAL_FUNC *funcs)
732 {
733         special_vars_signals_task(text, funccount, funcs, TASK_BIND);
734 }
735
736 void special_vars_remove_signals(const char *text,
737                                  int funccount, SIGNAL_FUNC *funcs)
738 {
739         special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
740 }
741
742 int *special_vars_get_signals(const char *text)
743 {
744         return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);
745 }