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