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