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
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
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_internal_setting(const char *key, int type, int *free_ret)
93 case SETTING_TYPE_BOOLEAN:
94 return settings_get_bool(key) ? "yes" : "no";
95 case SETTING_TYPE_INT:
97 return g_strdup_printf("%d", settings_get_int(key));
98 case SETTING_TYPE_STRING:
99 return (char *) settings_get_str(key);
105 static char *get_long_variable_value(const char *key, SERVER_REC *server,
106 void *item, int *free_ret)
115 func = expando_find_long(key);
117 return func(server, item, free_ret);
119 /* internal setting? */
120 type = settings_get_type(key);
122 return get_internal_setting(key, type, free_ret);
124 /* environment variable? */
132 static char *get_long_variable(char **cmd, SERVER_REC *server,
133 void *item, int *free_ret, int getname)
135 char *start, *var, *ret;
137 /* get variable name */
139 while (isvarchar((*cmd)[1])) (*cmd)++;
141 var = g_strndup(start, (int) (*cmd-start)+1);
146 ret = get_long_variable_value(var, server, item, free_ret);
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,
162 if (arg_used != NULL) *arg_used = TRUE;
163 return getname ? g_strdup_printf("%c", **cmd) :
164 get_argument(cmd, arglist);
167 if (i_isalpha(**cmd) && isvarchar((*cmd)[1])) {
168 /* long variable name.. */
169 return get_long_variable(cmd, server, item, free_ret, getname);
172 /* single character variable. */
175 return g_strdup_printf("%c", **cmd);
178 func = expando_find_char(**cmd);
179 return func == NULL ? NULL : func(server, item, free_ret);
182 static char *get_history(char **cmd, void *item, int *free_ret)
184 char *start, *text, *ret;
186 /* get variable name */
188 while (**cmd != '\0' && **cmd != '!') (*cmd)++;
190 if (history_func == NULL)
193 text = g_strndup(start, (int) (*cmd-start));
194 ret = history_func(text, item, free_ret);
198 if (**cmd == '\0') (*cmd)--;
202 static char *get_special_value(char **cmd, SERVER_REC *server, void *item,
203 char **arglist, int *free_ret, int *arg_used,
206 char command, *value, *p;
209 if ((flags & PARSE_FLAG_ONLY_ARGS) && !isarg(**cmd)) {
211 return g_strdup_printf("$%c", **cmd);
215 /* find text from command history */
216 if (flags & PARSE_FLAG_GETNAME)
219 return get_history(cmd, item, free_ret);
223 if (**cmd == '#' || **cmd == '@') {
225 if ((*cmd)[1] != '\0')
229 char *temp_cmd = "*";
231 if (flags & PARSE_FLAG_GETNAME)
235 return get_argument(&temp_cmd, arglist);
239 value = get_variable(cmd, server, item, arglist, free_ret,
240 arg_used, flags & PARSE_FLAG_GETNAME);
242 if (flags & PARSE_FLAG_GETNAME)
245 if (command == '#') {
246 /* number of words */
247 if (value == NULL || *value == '\0') {
248 if (value != NULL && *free_ret) {
256 for (p = value; *p != '\0'; p++) {
257 if (*p == ' ' && (p[1] != ' ' && p[1] != '\0'))
260 if (*free_ret) g_free(value);
263 return g_strdup_printf("%d", len);
266 if (command == '@') {
267 /* number of characters */
268 if (value == NULL) return "0";
271 if (*free_ret) g_free(value);
274 return g_strdup_printf("%d", len);
280 /* get alignment arguments (inside the []) */
281 static int get_alignment_args(char **data, int *align, int *flags, char *pad)
286 *flags = ALIGN_CUT|ALIGN_PAD;
289 /* '!' = don't cut, '-' = right padding */
291 while (*str != '\0' && *str != ']' && !i_isdigit(*str)) {
293 *flags &= ~ALIGN_CUT;
294 else if (*str == '-')
295 *flags |= ALIGN_RIGHT;
296 else if (*str == '.')
297 *flags &= ~ALIGN_PAD;
300 if (!i_isdigit(*str))
301 return FALSE; /* expecting number */
303 /* get the alignment size */
304 while (i_isdigit(*str)) {
305 *align = (*align) * 10 + (*str-'0');
309 /* get the pad character */
310 while (*str != '\0' && *str != ']') {
315 if (*str++ != ']') return FALSE;
321 /* return the aligned text */
322 static char *get_alignment(const char *text, int align, int flags, char pad)
327 g_return_val_if_fail(text != NULL, NULL);
329 str = g_string_new(text);
332 if ((flags & ALIGN_CUT) && align > 0 && str->len > align)
333 g_string_truncate(str, align);
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);
341 g_string_append_c(str, pad);
346 g_string_free(str, FALSE);
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)
355 static char **nested_orig_cmd = NULL; /* FIXME: KLUDGE! */
356 char command, *value;
359 int align, align_flags;
362 int brackets, nest_free;
366 command = **cmd; (*cmd)++;
370 if (!get_alignment_args(cmd, &align, &align_flags,
371 &align_pad) || **cmd == '\0') {
381 nest_free = FALSE; nest_value = NULL;
384 int toplevel = nested_orig_cmd == NULL;
386 if (toplevel) nested_orig_cmd = cmd;
393 nest_value = parse_special(cmd, server, item, arglist,
394 &nest_free, arg_used,
398 while ((*nested_orig_cmd)[1] != '\0') {
399 (*nested_orig_cmd)++;
400 if (**nested_orig_cmd == ')')
405 if (toplevel) nested_orig_cmd = NULL;
411 /* special value is inside {...} (foo${test}bar -> fooXXXbar) */
416 value = get_special_value(cmd, server, item, arglist,
417 free_ret, arg_used, flags);
419 g_error("parse_special() : buffer overflow!");
421 if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
425 while (**cmd != '}' && (*cmd)[1] != '\0')
429 if (nest_free) g_free(nest_value);
431 if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
435 if (value == NULL) return "";
437 p = get_alignment(value, align, align_flags, align_pad);
438 if (*free_ret) g_free(value);
447 static void gstring_append_escaped(GString *str, const char *text, int flags)
449 char esc[4], *escpos;
452 if (flags & PARSE_FLAG_ESCAPE_VARS)
454 if (flags & PARSE_FLAG_ESCAPE_THEME) {
460 g_string_append(str, text);
465 while (*text != '\0') {
466 for (escpos = esc; *escpos != '\0'; escpos++) {
467 if (*text == *escpos) {
468 g_string_append_c(str, '%');
472 g_string_append_c(str, *text);
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)
481 char code, **arglist, *ret;
485 g_return_val_if_fail(cmd != NULL, NULL);
486 g_return_val_if_fail(data != NULL, NULL);
488 /* create the argument list */
489 arglist = g_strsplit(data, " ", -1);
491 if (arg_used != NULL) *arg_used = FALSE;
493 str = g_string_new(NULL);
494 while (*cmd != '\0') {
497 g_string_append_c(str, ';');
499 chr = expand_escape(&cmd);
500 g_string_append_c(str, chr != -1 ? chr : *cmd);
503 } else if (code == '$') {
506 ret = parse_special((char **) &cmd, server, item,
507 arglist, &need_free, arg_used,
510 gstring_append_escaped(str, ret, flags);
511 if (need_free) g_free(ret);
515 if (*cmd == '\\' || *cmd == '$')
518 g_string_append_c(str, *cmd);
526 g_string_free(str, FALSE);
530 #define is_split_char(str, start) \
531 ((str)[0] == ';' && ((start) == (str) || \
532 ((str)[-1] != '\\' && (str)[-1] != '$')))
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)
538 const char *cmdchars;
539 char *orig, *str, *start, *ret;
540 int arg_used, arg_used_ever;
544 arg_used_ever = FALSE;
545 cmdchars = settings_get_str("cmdchars");
547 /* get a list of all the commands to run */
548 orig = start = str = g_strdup(cmd);
550 if (is_split_char(str, start)) {
552 while (*str == ' ') str++;
553 } else if (*str != '\0') {
558 ret = parse_special_string(start, server, item,
561 if (arg_used) arg_used_ever = TRUE;
563 if (strchr(cmdchars, *ret) == NULL) {
564 /* no command char - let's put it there.. */
567 ret = g_strdup_printf("%c%s", *cmdchars, old);
570 commands = g_slist_append(commands, ret);
573 } while (*start != '\0');
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;
580 if (!arg_used_ever && *data != '\0') {
583 ret = g_strconcat(old, " ", data, NULL);
589 signal_emit("send command", 3, ret, server, item);
591 if (server != NULL && !server_unref(server)) {
592 /* the server was destroyed */
597 /* FIXME: window item would need reference counting as well,
598 eg. "/EVAL win close;say hello" wouldn't work now.. */
601 commands = g_slist_remove(commands, commands->data);
606 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
611 static void update_signals_hash(GHashTable **hash, int *signals)
617 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
618 (GCompareFunc) g_direct_equal);
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
629 arg_type = EXPANDO_ARG_NONE;
632 if (arg_type == 0) arg_type = signals[1];
633 g_hash_table_insert(*hash, signal_id,
634 GINT_TO_POINTER(arg_type));
639 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
641 (*pos)[0] = GPOINTER_TO_INT(signal_id);
642 (*pos)[1] = GPOINTER_TO_INT(arg_type);
646 static int *get_signals_list(GHashTable *hash)
651 /* no expandos in text - never needs updating */
655 pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
656 g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
659 g_hash_table_destroy(hash);
665 #define TASK_UNBIND 2
666 #define TASK_GET_SIGNALS 3
668 static int *special_vars_signals_task(const char *text, int funccount,
669 SIGNAL_FUNC *funcs, int task)
673 int need_free, *expando_signals;
676 while (*text != '\0') {
677 if (*text == '\\' && text[1] != '\0') {
680 } else if (*text == '$' && text[1] != '\0') {
683 expando = parse_special((char **) &text, NULL, NULL,
684 NULL, &need_free, NULL,
691 expando_bind(expando, funccount, funcs);
694 expando_unbind(expando, funccount, funcs);
696 case TASK_GET_SIGNALS:
697 expando_signals = expando_get_signals(expando);
698 if (expando_signals != NULL) {
699 update_signals_hash(&signals,
701 g_free(expando_signals);
705 if (need_free) g_free(expando);
712 if (task == TASK_GET_SIGNALS)
713 return get_signals_list(signals);
718 void special_vars_add_signals(const char *text,
719 int funccount, SIGNAL_FUNC *funcs)
721 special_vars_signals_task(text, funccount, funcs, TASK_BIND);
724 void special_vars_remove_signals(const char *text,
725 int funccount, SIGNAL_FUNC *funcs)
727 special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
730 int *special_vars_get_signals(const char *text)
732 return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);