4 Copyright (C) 2000 Timo Sirainen
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.
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.
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.
23 #include "special-vars.h"
29 #define ALIGN_RIGHT 0x01
30 #define ALIGN_CUT 0x02
31 #define ALIGN_PAD 0x04
33 #define isvarchar(c) \
34 (i_isalnum(c) || (c) == '_')
37 (i_isdigit(c) || (c) == '*' || (c) == '~' || (c) == '-')
39 static SPECIAL_HISTORY_FUNC history_func = NULL;
41 static char *get_argument(char **cmd, char **arglist)
45 int max, arg, argcount;
50 argcount = arglist == NULL ? 0 : strarray_length(arglist);
53 /* get all arguments */
54 } else if (**cmd == '~') {
55 /* get last argument */
56 arg = max = argcount-1;
58 if (i_isdigit(**cmd)) {
60 arg = max = (**cmd)-'0';
65 /* get more than one argument */
67 if (!i_isdigit(**cmd))
68 max = -1; /* get all the rest */
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, ' ');
83 if (str->len > 0) g_string_truncate(str, str->len-1);
86 g_string_free(str, FALSE);
90 static char *get_long_variable_value(const char *key, SERVER_REC *server,
91 void *item, int *free_ret)
100 func = expando_find_long(key);
102 current_expando = key;
103 return func(server, item, free_ret);
106 /* internal setting? */
107 rec = settings_get_record(key);
110 return settings_get_print(rec);
113 /* environment variable? */
121 static char *get_long_variable(char **cmd, SERVER_REC *server,
122 void *item, int *free_ret, int getname)
124 char *start, *var, *ret;
126 /* get variable name */
128 while (isvarchar((*cmd)[1])) (*cmd)++;
130 var = g_strndup(start, (int) (*cmd-start)+1);
135 ret = get_long_variable_value(var, server, item, free_ret);
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,
151 if (arg_used != NULL) *arg_used = TRUE;
152 return getname ? g_strdup_printf("%c", **cmd) :
153 get_argument(cmd, arglist);
156 if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
157 /* long variable name.. */
158 return get_long_variable(cmd, server, item, free_ret, getname);
161 /* single character variable. */
164 return g_strdup_printf("%c", **cmd);
167 func = expando_find_char(**cmd);
173 str[0] = **cmd; str[1] = '\0';
174 current_expando = str;
175 return func(server, item, free_ret);
179 static char *get_history(char **cmd, void *item, int *free_ret)
181 char *start, *text, *ret;
183 /* get variable name */
185 while (**cmd != '\0' && **cmd != '!') (*cmd)++;
187 if (history_func == NULL)
190 text = g_strndup(start, (int) (*cmd-start));
191 ret = history_func(text, item, free_ret);
195 if (**cmd == '\0') (*cmd)--;
199 static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
200 char **arglist, int *free_ret, int *arg_used,
203 char command, *value, *p;
206 if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
208 return g_strdup_printf("$%c", **cmd);
212 /* find text from command history */
213 if (flags & PARSE_FLAG_GETNAME)
216 return get_history(cmd, item, free_ret);
220 if (**cmd == '#' || **cmd == '@') {
222 if ((*cmd)[1] != '\0')
226 char *temp_cmd = "*";
228 if (flags & PARSE_FLAG_GETNAME)
232 return get_argument(&temp_cmd, arglist);
236 value = get_variable(cmd, server, item, arglist, free_ret,
237 arg_used, flags & PARSE_FLAG_GETNAME);
239 if (flags & PARSE_FLAG_GETNAME)
242 if (command == '#') {
243 /* number of words */
244 if (value == NULL || *value == '\0') {
245 if (value != NULL && *free_ret) {
253 for (p = value; *p != '\0'; p++) {
254 if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
257 if (*free_ret) g_free(value);
260 return g_strdup_printf("%d", len);
263 if (command == '@') {
264 /* number of characters */
265 if (value == NULL) return "0";
268 if (*free_ret) g_free(value);
271 return g_strdup_printf("%d", len);
277 /* get alignment arguments (inside the []) */
278 static int get_alignment_args(char **data, int *align, int *flags, char *pad)
283 *flags = ALIGN_CUT|ALIGN_PAD;
286 /* '!' = don't cut, '-' = right padding */
288 while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
290 *flags &= ~ALIGN_CUT;
291 else if (*str == '-')
292 *flags |= ALIGN_RIGHT;
293 else if (*str == '.')
294 *flags &= ~ALIGN_PAD;
297 if (!i_isdigit(*str))
298 return FALSE; /* expecting number */
300 /* get the alignment size */
301 while (i_isdigit(*str)) {
302 *align = (*align) * 10 + (*str-'0');
306 /* get the pad character */
307 while (*str != '\0' && *str != ']') {
312 if (*str++ != ']') return FALSE;
318 /* return the aligned text */
319 static char *get_alignment(const char *text, int align, int flags, char pad)
324 g_return_val_if_fail(text != NULL, NULL);
326 str = g_string_new(text);
329 if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
330 g_string_truncate(str, align);
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);
338 g_string_append_c(str, pad);
343 g_string_free(str, FALSE);
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)
352 static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
353 char command, *value;
355 char align_pad = '\0';
356 int align = 0, align_flags = 0;
359 int brackets, nest_free;
365 command = **cmd; (*cmd)++;
369 if (!get_alignment_args(cmd, &align, &align_flags,
370 &align_pad) || **cmd == '\0') {
380 nest_free = FALSE; nest_value = NULL;
381 if (**cmd == '(' && (*cmd)[1] != '\0') {
383 int toplevel = nested_orig_cmd == NULL;
385 if (toplevel) nested_orig_cmd = cmd;
392 nest_value = parse_special(cmd, server, item, arglist,
393 &nest_free, arg_used,
397 if (nest_value == NULL || *nest_value == '\0')
400 while ((*nested_orig_cmd)[1] != '\0') {
401 (*nested_orig_cmd)++;
402 if (**nested_orig_cmd == ')')
407 if (toplevel) nested_orig_cmd = NULL;
413 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
414 if ((*cmd)[1] == '\0')
420 value = get_special_value(cmd, server, item, arglist,
421 free_ret, arg_used, flags);
423 g_error("parse_special() : buffer overflow!");
425 if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
429 while (**cmd != '}' && (*cmd)[1] != '\0')
433 if (nest_free) g_free(nest_value);
435 if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
439 if (value == NULL) return "";
441 p = get_alignment(value, align, align_flags, align_pad);
442 if (*free_ret) g_free(value);
451 static void gstring_append_escaped(GString *str, const char *text, int flags)
453 char esc[4], *escpos;
456 if (flags & PARSE_FLAG_ESCAPE_VARS)
458 if (flags & PARSE_FLAG_ESCAPE_THEME) {
464 g_string_append(str, text);
469 while (*text != '\0') {
470 for (escpos = esc; *escpos != '\0'; escpos++) {
471 if (*text == *escpos) {
472 g_string_append_c(str, '%');
476 g_string_append_c(str, *text);
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)
485 char code, **arglist, *ret;
489 g_return_val_if_fail(cmd != NULL, NULL);
490 g_return_val_if_fail(data != NULL, NULL);
492 /* create the argument list */
493 arglist = g_strsplit(data, " ", -1);
495 if (arg_used != NULL) *arg_used = FALSE;
497 str = g_string_new(NULL);
498 while (*cmd != '\0') {
501 g_string_append_c(str, ';');
503 chr = expand_escape(&cmd);
504 g_string_append_c(str, chr != -1 ? chr : *cmd);
507 } else if (code == '$') {
510 ret = parse_special((char **) &cmd, server, item,
511 arglist, &need_free, arg_used,
514 gstring_append_escaped(str, ret, flags);
515 if (need_free) g_free(ret);
519 if (*cmd == '\\' || *cmd == '$')
522 g_string_append_c(str, *cmd);
530 g_string_free(str, FALSE);
534 #define is_split_char(str, start) \
535 ((str)[0] == ';' && ((start) == (str) || \
536 ((str)[-1] != '\\' && (str)[-1] != '$')))
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)
542 const char *cmdchars;
543 char *orig, *str, *start, *ret;
544 int arg_used, arg_used_ever;
548 arg_used_ever = FALSE;
549 cmdchars = settings_get_str("cmdchars");
551 /* get a list of all the commands to run */
552 orig = start = str = g_strdup(cmd);
554 if (is_split_char(str, start)) {
556 while (*str == ' ') str++;
557 } else if (*str != '\0') {
562 ret = parse_special_string(start, server, item,
565 if (arg_used) arg_used_ever = TRUE;
567 if (strchr(cmdchars, *ret) == NULL) {
568 /* no command char - let's put it there.. */
571 ret = g_strdup_printf("%c%s", *cmdchars, old);
574 commands = g_slist_append(commands, ret);
577 } while (*start != '\0');
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;
584 if (!arg_used_ever && *data != '\0') {
587 ret = g_strconcat(old, " ", data, NULL);
593 signal_emit("send command", 3, ret, server, item);
595 if (server != NULL && !server_unref(server)) {
596 /* the server was destroyed */
601 /* FIXME: window item would need reference counting as well,
602 eg. "/EVAL win close;say hello" wouldn't work now.. */
605 commands = g_slist_remove(commands, commands->data);
610 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
615 static void update_signals_hash(GHashTable **hash, int *signals)
621 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
622 (GCompareFunc) g_direct_equal);
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
633 arg_type = EXPANDO_ARG_NONE;
636 if (arg_type == 0) arg_type = signals[1];
637 g_hash_table_insert(*hash, signal_id,
638 GINT_TO_POINTER(arg_type));
643 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
645 (*pos)[0] = GPOINTER_TO_INT(signal_id);
646 (*pos)[1] = GPOINTER_TO_INT(arg_type);
650 static int *get_signals_list(GHashTable *hash)
655 /* no expandos in text - never needs updating */
659 pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
660 g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
663 g_hash_table_destroy(hash);
669 #define TASK_UNBIND 2
670 #define TASK_GET_SIGNALS 3
672 static int *special_vars_signals_task(const char *text, int funccount,
673 SIGNAL_FUNC *funcs, int task)
677 int need_free, *expando_signals;
680 while (*text != '\0') {
681 if (*text == '\\' && text[1] != '\0') {
684 } else if (*text == '$' && text[1] != '\0') {
687 expando = parse_special((char **) &text, NULL, NULL,
688 NULL, &need_free, NULL,
695 expando_bind(expando, funccount, funcs);
698 expando_unbind(expando, funccount, funcs);
700 case TASK_GET_SIGNALS:
701 expando_signals = expando_get_signals(expando);
702 if (expando_signals != NULL) {
703 update_signals_hash(&signals,
705 g_free(expando_signals);
709 if (need_free) g_free(expando);
716 if (task == TASK_GET_SIGNALS)
717 return get_signals_list(signals);
722 void special_vars_add_signals(const char *text,
723 int funccount, SIGNAL_FUNC *funcs)
725 special_vars_signals_task(text, funccount, funcs, TASK_BIND);
728 void special_vars_remove_signals(const char *text,
729 int funccount, SIGNAL_FUNC *funcs)
731 special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
734 int *special_vars_get_signals(const char *text)
736 return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);