imported.
[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 "misc.h"
27
28 #define ALIGN_RIGHT 0x01
29 #define ALIGN_CUT   0x02
30 #define ALIGN_PAD   0x04
31
32 #define isvarchar(c) \
33         (isalnum(c) || (c) == '_')
34
35 static SPECIAL_HISTORY_FUNC history_func = NULL;
36
37 static char *get_argument(char **cmd, char **arglist)
38 {
39         GString *str;
40         char *ret;
41         int max, arg, argcount;
42
43         arg = 0;
44         max = -1;
45
46         argcount = strarray_length(arglist);
47
48         if (**cmd == '*') {
49                 /* get all arguments */
50         } else if (**cmd == '~') {
51                 /* get last argument */
52                 arg = max = argcount-1;
53         } else {
54                 if (isdigit(**cmd)) {
55                         /* first argument */
56                         arg = max = (**cmd)-'0';
57                         (*cmd)++;
58                 }
59
60                 if (**cmd == '-') {
61                         /* get more than one argument */
62                         (*cmd)++;
63                         if (!isdigit(**cmd))
64                                 max = -1; /* get all the rest */
65                         else {
66                                 max = (**cmd)-'0';
67                                 (*cmd)++;
68                         }
69                 }
70                 (*cmd)--;
71         }
72
73         str = g_string_new(NULL);
74         while (arg < argcount && (arg <= max || max == -1)) {
75                 g_string_append(str, arglist[arg]);
76                 g_string_append_c(str, ' ');
77                 arg++;
78         }
79         if (str->len > 0) g_string_truncate(str, str->len-1);
80
81         ret = str->str;
82         g_string_free(str, FALSE);
83         return ret;
84 }
85
86 static char *get_internal_setting(const char *key, int type, int *free_ret)
87 {
88         switch (type) {
89         case SETTING_TYPE_BOOLEAN:
90                 return settings_get_bool(key) ? "yes" : "no";
91         case SETTING_TYPE_INT:
92                 *free_ret = TRUE;
93                 return g_strdup_printf("%d", settings_get_int(key));
94         case SETTING_TYPE_STRING:
95                 return (char *) settings_get_str(key);
96         }
97
98         return NULL;
99 }
100
101 static char *get_long_variable_value(const char *key, SERVER_REC *server,
102                                      void *item, int *free_ret)
103 {
104         EXPANDO_FUNC func;
105         char *ret;
106         int type;
107
108         *free_ret = FALSE;
109
110         /* expando? */
111         func = expando_find_long(key);
112         if (func != NULL)
113                 return func(server, item, free_ret);
114
115         /* internal setting? */
116         type = settings_get_type(key);
117         if (type != -1)
118                 return get_internal_setting(key, type, free_ret);
119
120         /* environment variable? */
121         ret = g_getenv(key);
122         if (ret != NULL)
123                 return ret;
124
125         return NULL;
126 }
127
128 static char *get_long_variable(char **cmd, SERVER_REC *server,
129                                void *item, int *free_ret, int getname)
130 {
131         char *start, *var, *ret;
132
133         /* get variable name */
134         start = *cmd;
135         while (isvarchar((*cmd)[1])) (*cmd)++;
136
137         var = g_strndup(start, (int) (*cmd-start)+1);
138         if (getname) {
139                 *free_ret = TRUE;
140                 return var;
141         }
142         ret = get_long_variable_value(var, server, item, free_ret);
143         g_free(var);
144         return ret;
145 }
146
147 /* return the value of the variable found from `cmd'.
148    if 'getname' is TRUE, return the name of the variable instead it's value */
149 static char *get_variable(char **cmd, SERVER_REC *server, void *item,
150                           char **arglist, int *free_ret, int *arg_used,
151                           int getname)
152 {
153         EXPANDO_FUNC func;
154
155         if (isdigit(**cmd) || **cmd == '*' || **cmd == '-' || **cmd == '~') {
156                 /* argument */
157                 *free_ret = TRUE;
158                 if (arg_used != NULL) *arg_used = TRUE;
159                 return getname ? g_strdup_printf("%c", **cmd) :
160                         get_argument(cmd, arglist);
161         }
162
163         if (isalpha(**cmd) && isvarchar((*cmd)[1])) {
164                 /* long variable name.. */
165                 return get_long_variable(cmd, server, item, free_ret, getname);
166         }
167
168         /* single character variable. */
169         if (getname) {
170                 *free_ret = TRUE;
171                 return g_strdup_printf("%c", **cmd);
172         }
173         *free_ret = FALSE;
174         func = expando_find_char(**cmd);
175         return func == NULL ? NULL : func(server, item, free_ret);
176 }
177
178 static char *get_history(char **cmd, void *item, int *free_ret)
179 {
180         char *start, *text, *ret;
181
182         /* get variable name */
183         start = ++(*cmd);
184         while (**cmd != '\0' && **cmd != '!') (*cmd)++;
185
186         if (history_func == NULL)
187                 ret = NULL;
188         else {
189                 text = g_strndup(start, (int) (*cmd-start)+1);
190                 ret = history_func(text, item, free_ret);
191                 g_free(text);
192         }
193
194         if (**cmd == '\0') (*cmd)--;
195         return ret;
196 }
197
198 static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
199                                char **arglist, int *free_ret, int *arg_used,
200                                int flags)
201 {
202         char command, *value, *p;
203         int len;
204
205         if (**cmd == '!') {
206                 /* find text from command history */
207                 if (flags & PARSE_FLAG_GETNAME)
208                         return "!";
209
210                 return get_history(cmd, item, free_ret);
211         }
212
213         command = 0;
214         if (**cmd == '#' || **cmd == '@') {
215                 command = **cmd;
216                 if ((*cmd)[1] != '\0')
217                         (*cmd)++;
218                 else {
219                         /* default to $* */
220                         char *temp_cmd = "*";
221
222                         if (flags & PARSE_FLAG_GETNAME)
223                                 return "*";
224
225                         *free_ret = TRUE;
226                         return get_argument(&temp_cmd, arglist);
227                 }
228         }
229
230         value = get_variable(cmd, server, item, arglist, free_ret,
231                              arg_used, flags & PARSE_FLAG_GETNAME);
232
233         if (flags & PARSE_FLAG_GETNAME)
234                 return value;
235
236         if (command == '#') {
237                 /* number of words */
238                 if (value == NULL || *value == '\0') {
239                         if (value != NULL && *free_ret) {
240                                 g_free(value);
241                                 *free_ret = FALSE;
242                         }
243                         return "0";
244                 }
245
246                 len = 1;
247                 for (p = value; *p != '\0'; p++) {
248                         if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
249                                 len++;
250                 }
251                 if (*free_ret) g_free(value);
252
253                 *free_ret = TRUE;
254                 return g_strdup_printf("%d", len);
255         }
256
257         if (command == '@') {
258                 /* number of characters */
259                 if (value == NULL) return "0";
260
261                 len = strlen(value);
262                 if (*free_ret) g_free(value);
263
264                 *free_ret = TRUE;
265                 return g_strdup_printf("%d", len);
266         }
267
268         return value;
269 }
270
271 /* get alignment arguments (inside the []) */
272 static int get_alignment_args(char **data, int *align, int *flags, char *pad)
273 {
274         char *str;
275
276         *align = 0;
277         *flags = ALIGN_CUT|ALIGN_PAD;
278         *pad = ' ';
279
280         /* '!' = don't cut, '-' = right padding */
281         str = *data;
282         while (*str != '\0' && *str != ']' && !isdigit(*str)) {
283                 if (*str == '!')
284                         *flags &= ~ALIGN_CUT;
285                 else if (*str == '-')
286                         *flags |= ALIGN_RIGHT;
287                 else if (*str == '.')
288                          *flags &= ~ALIGN_PAD;
289                 str++;
290         }
291         if (!isdigit(*str))
292                 return FALSE; /* expecting number */
293
294         /* get the alignment size */
295         while (isdigit(*str)) {
296                 *align = (*align) * 10 + (*str-'0');
297                 str++;
298         }
299
300         /* get the pad character */
301         while (*str != '\0' && *str != ']') {
302                 *pad = *str;
303                 str++;
304         }
305
306         if (*str++ != ']') return FALSE;
307
308         *data = str;
309         return TRUE;
310 }
311
312 /* return the aligned text */
313 static char *get_alignment(const char *text, int align, int flags, char pad)
314 {
315         GString *str;
316         char *ret;
317
318         g_return_val_if_fail(text != NULL, NULL);
319
320         str = g_string_new(text);
321
322         /* cut */
323         if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
324                 g_string_truncate(str, align);
325
326         /* add pad characters */
327         if (flags & ALIGN_PAD) {
328                 while (str->len < align) {
329                         if (flags & ALIGN_RIGHT)
330                                 g_string_prepend_c(str, pad);
331                         else
332                                 g_string_append_c(str, pad);
333                 }
334         }
335
336         ret = str->str;
337         g_string_free(str, FALSE);
338         return ret;
339 }
340
341 /* Parse and expand text after '$' character. return value has to be
342    g_free()'d if `free_ret' is TRUE. */
343 char *parse_special(char **cmd, SERVER_REC *server, void *item,
344                     char **arglist, int *free_ret, int *arg_used, int flags)
345 {
346         static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
347         char command, *value;
348
349         char align_pad;
350         int align, align_flags;
351
352         char *nest_value;
353         int brackets, nest_free;
354
355         *free_ret = FALSE;
356
357         command = **cmd; (*cmd)++;
358         switch (command) {
359         case '[':
360                 /* alignment */
361                 if (!get_alignment_args(cmd, &align, &align_flags,
362                                         &align_pad) || **cmd == '\0') {
363                         (*cmd)--;
364                         return NULL;
365                 }
366                 break;
367         default:
368                 command = 0;
369                 (*cmd)--;
370         }
371
372         nest_free = FALSE; nest_value = NULL;
373         if (**cmd == '(') {
374                 /* subvariable */
375                 int toplevel = nested_orig_cmd == NULL;
376
377                 if (toplevel) nested_orig_cmd = cmd;
378                 (*cmd)++;
379                 if (**cmd != '$') {
380                         /* ... */
381                         nest_value = *cmd;
382                 } else {
383                         (*cmd)++;
384                         nest_value = parse_special(cmd, server, item, arglist,
385                                                    &nest_free, arg_used,
386                                                    flags);
387                 }
388
389                 while ((*nested_orig_cmd)[1] != '\0') {
390                         (*nested_orig_cmd)++;
391                         if (**nested_orig_cmd == ')')
392                                 break;
393                 }
394                 cmd = &nest_value;
395
396                 if (toplevel) nested_orig_cmd = NULL;
397         }
398
399         if (**cmd != '{')
400                 brackets = FALSE;
401         else {
402                 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
403                 (*cmd)++;
404                 brackets = TRUE;
405         }
406
407         value = get_special_value(cmd, server, item, arglist,
408                                   free_ret, arg_used, flags);
409         if (**cmd == '\0')
410                 g_error("parse_special() : buffer overflow!");
411
412         if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
413                 *arg_used = TRUE;
414
415         if (brackets) {
416                 while (**cmd != '}' && (*cmd)[1] != '\0')
417                         (*cmd)++;
418         }
419
420         if (nest_free) g_free(nest_value);
421
422         if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
423                 /* alignment */
424                 char *p;
425
426                 if (value == NULL) return "";
427
428                 p = get_alignment(value, align, align_flags, align_pad);
429                 if (*free_ret) g_free(value);
430
431                 *free_ret = TRUE;
432                 return p;
433         }
434
435         return value;
436 }
437
438 static void gstring_append_escaped(GString *str, const char *text)
439 {
440         while (*text != '\0') {
441                 if (*text == '%')
442                         g_string_append_c(str, '%');
443                 g_string_append_c(str, *text);
444                 text++;
445         }
446 }
447
448 /* parse the whole string. $ and \ chars are replaced */
449 char *parse_special_string(const char *cmd, SERVER_REC *server, void *item,
450                            const char *data, int *arg_used, int flags)
451 {
452         char code, **arglist, *ret;
453         GString *str;
454         int need_free;
455
456         g_return_val_if_fail(cmd != NULL, NULL);
457         g_return_val_if_fail(data != NULL, NULL);
458
459         /* create the argument list */
460         arglist = g_strsplit(data, " ", -1);
461
462         if (arg_used != NULL) *arg_used = FALSE;
463         code = 0;
464         str = g_string_new(NULL);
465         while (*cmd != '\0') {
466                 if (code == '\\'){
467                         switch (*cmd) {
468                         case 't':
469                                 g_string_append_c(str, '\t');
470                                 break;
471                         case 'n':
472                                 g_string_append_c(str, '\n');
473                                 break;
474                         default:
475                                 g_string_append_c(str, *cmd);
476                         }
477                         code = 0;
478                 } else if (code == '$') {
479                         char *ret;
480
481                         ret = parse_special((char **) &cmd, server, item,
482                                             arglist, &need_free, arg_used,
483                                             flags);
484                         if (ret != NULL) {
485                                 if ((flags & PARSE_FLAG_ESCAPE_VARS) == 0)
486                                         g_string_append(str, ret);
487                                 else
488                                         gstring_append_escaped(str, ret);
489                                 if (need_free) g_free(ret);
490                         }
491                         code = 0;
492                 } else {
493                         if (*cmd == '\\' || *cmd == '$')
494                                 code = *cmd;
495                         else
496                                 g_string_append_c(str, *cmd);
497                 }
498
499                 cmd++;
500         }
501         g_strfreev(arglist);
502
503         ret = str->str;
504         g_string_free(str, FALSE);
505         return ret;
506 }
507
508 #define is_split_char(str, start) \
509         ((str)[0] == ';' && ((start) == (str) || \
510                 ((str)[-1] != '\\' && (str)[-1] != '$')))
511
512 /* execute the commands in string - commands can be split with ';' */
513 void eval_special_string(const char *cmd, const char *data,
514                          SERVER_REC *server, void *item)
515 {
516         const char *cmdchars;
517         char *orig, *str, *start, *ret;
518         int arg_used, arg_used_ever;
519         GSList *commands;
520
521         commands = NULL;
522         arg_used_ever = FALSE;
523         cmdchars = settings_get_str("cmdchars");
524
525         /* get a list of all the commands to run */
526         orig = start = str = g_strdup(cmd);
527         do {
528                 if (is_split_char(str, start))
529                         *str++ = '\0';
530                 else if (*str != '\0') {
531                         str++;
532                         continue;
533                 }
534
535                 ret = parse_special_string(start, server, item,
536                                            data, &arg_used, 0);
537                 if (arg_used) arg_used_ever = TRUE;
538
539                 if (strchr(cmdchars, *ret) == NULL) {
540                         /* no command char - let's put it there.. */
541                         char *old = ret;
542
543                         ret = g_strdup_printf("%c%s", *cmdchars, old);
544                         g_free(old);
545                 }
546                 commands = g_slist_append(commands, ret);
547                 start = str;
548         } while (*start != '\0');
549
550         /* run the command, if no arguments were ever used, append all of them
551            after each command */
552         while (commands != NULL) {
553                 ret = commands->data;
554
555                 if (!arg_used_ever && *data != '\0') {
556                         char *old = ret;
557
558                         ret = g_strconcat(old, " ", data, NULL);
559                         g_free(old);
560                 }
561                 signal_emit("send command", 3, ret, server, item);
562
563                 g_free(ret);
564                 commands = g_slist_remove(commands, commands->data);
565         }
566         g_free(orig);
567 }
568
569 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
570 {
571         history_func = func;
572 }
573
574 static void special_vars_signals_do(const char *text, int funccount,
575                                     SIGNAL_FUNC *funcs, int bind)
576 {
577         char *ret;
578         int need_free;
579
580         while (*text != '\0') {
581                 if (*text == '\\' && text[1] != '\0') {
582                         text += 2;
583                 } else if (*text == '$' && text[1] != '\0') {
584                         text++;
585                         ret = parse_special((char **) &text, NULL, NULL,
586                                             NULL, &need_free, NULL,
587                                             PARSE_FLAG_GETNAME);
588                         if (ret != NULL) {
589                                 if (bind)
590                                         expando_bind(ret, funccount, funcs);
591                                 else
592                                         expando_unbind(ret, funccount, funcs);
593                                 if (need_free) g_free(ret);
594                         }
595
596                 }
597                 else text++;
598         }
599 }
600
601 void special_vars_add_signals(const char *text,
602                               int funccount, SIGNAL_FUNC *funcs)
603 {
604         special_vars_signals_do(text, funccount, funcs, TRUE);
605 }
606
607 void special_vars_remove_signals(const char *text,
608                                  int funccount, SIGNAL_FUNC *funcs)
609 {
610         special_vars_signals_do(text, funccount, funcs, FALSE);
611 }