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