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;
368 command = **cmd; (*cmd)++;
372 if (!get_alignment_args(cmd, &align, &align_flags,
373 &align_pad) || **cmd == '\0') {
383 nest_free = FALSE; nest_value = NULL;
386 int toplevel = nested_orig_cmd == NULL;
388 if (toplevel) nested_orig_cmd = cmd;
395 nest_value = parse_special(cmd, server, item, arglist,
396 &nest_free, arg_used,
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) */
418 value = get_special_value(cmd, server, item, arglist,
419 free_ret, arg_used, flags);
421 g_error("parse_special() : buffer overflow!");
423 if (value != NULL && *value != '\0' && (flags & PARSE_FLAG_ISSET_ANY))
427 while (**cmd != '}' && (*cmd)[1] != '\0')
431 if (nest_free) g_free(nest_value);
433 if (command == '[' && (flags & PARSE_FLAG_GETNAME) == 0) {
437 if (value == NULL) return "";
439 p = get_alignment(value, align, align_flags, align_pad);
440 if (*free_ret) g_free(value);
449 static void gstring_append_escaped(GString *str, const char *text, int flags)
451 char esc[4], *escpos;
454 if (flags & PARSE_FLAG_ESCAPE_VARS)
456 if (flags & PARSE_FLAG_ESCAPE_THEME) {
462 g_string_append(str, text);
467 while (*text != '\0') {
468 for (escpos = esc; *escpos != '\0'; escpos++) {
469 if (*text == *escpos) {
470 g_string_append_c(str, '%');
474 g_string_append_c(str, *text);
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)
483 char code, **arglist, *ret;
487 g_return_val_if_fail(cmd != NULL, NULL);
488 g_return_val_if_fail(data != NULL, NULL);
490 /* create the argument list */
491 arglist = g_strsplit(data, " ", -1);
493 if (arg_used != NULL) *arg_used = FALSE;
495 str = g_string_new(NULL);
496 while (*cmd != '\0') {
499 g_string_append_c(str, ';');
501 chr = expand_escape(&cmd);
502 g_string_append_c(str, chr != -1 ? chr : *cmd);
505 } else if (code == '$') {
508 ret = parse_special((char **) &cmd, server, item,
509 arglist, &need_free, arg_used,
512 gstring_append_escaped(str, ret, flags);
513 if (need_free) g_free(ret);
517 if (*cmd == '\\' || *cmd == '$')
520 g_string_append_c(str, *cmd);
528 g_string_free(str, FALSE);
532 #define is_split_char(str, start) \
533 ((str)[0] == ';' && ((start) == (str) || \
534 ((str)[-1] != '\\' && (str)[-1] != '$')))
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)
540 const char *cmdchars;
541 char *orig, *str, *start, *ret;
542 int arg_used, arg_used_ever;
546 arg_used_ever = FALSE;
547 cmdchars = settings_get_str("cmdchars");
549 /* get a list of all the commands to run */
550 orig = start = str = g_strdup(cmd);
552 if (is_split_char(str, start)) {
554 while (*str == ' ') str++;
555 } else if (*str != '\0') {
560 ret = parse_special_string(start, server, item,
563 if (arg_used) arg_used_ever = TRUE;
565 if (strchr(cmdchars, *ret) == NULL) {
566 /* no command char - let's put it there.. */
569 ret = g_strdup_printf("%c%s", *cmdchars, old);
572 commands = g_slist_append(commands, ret);
575 } while (*start != '\0');
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;
582 if (!arg_used_ever && *data != '\0') {
585 ret = g_strconcat(old, " ", data, NULL);
591 signal_emit("send command", 3, ret, server, item);
593 if (server != NULL && !server_unref(server)) {
594 /* the server was destroyed */
599 /* FIXME: window item would need reference counting as well,
600 eg. "/EVAL win close;say hello" wouldn't work now.. */
603 commands = g_slist_remove(commands, commands->data);
608 void special_history_func_set(SPECIAL_HISTORY_FUNC func)
613 static void update_signals_hash(GHashTable **hash, int *signals)
619 *hash = g_hash_table_new((GHashFunc) g_direct_hash,
620 (GCompareFunc) g_direct_equal);
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
631 arg_type = EXPANDO_ARG_NONE;
634 if (arg_type == 0) arg_type = signals[1];
635 g_hash_table_insert(*hash, signal_id,
636 GINT_TO_POINTER(arg_type));
641 static void get_signal_hash(void *signal_id, void *arg_type, int **pos)
643 (*pos)[0] = GPOINTER_TO_INT(signal_id);
644 (*pos)[1] = GPOINTER_TO_INT(arg_type);
648 static int *get_signals_list(GHashTable *hash)
653 /* no expandos in text - never needs updating */
657 pos = signals = g_new(int, g_hash_table_size(hash)*2 + 1);
658 g_hash_table_foreach(hash, (GHFunc) get_signal_hash, &pos);
661 g_hash_table_destroy(hash);
667 #define TASK_UNBIND 2
668 #define TASK_GET_SIGNALS 3
670 static int *special_vars_signals_task(const char *text, int funccount,
671 SIGNAL_FUNC *funcs, int task)
675 int need_free, *expando_signals;
678 while (*text != '\0') {
679 if (*text == '\\' && text[1] != '\0') {
682 } else if (*text == '$' && text[1] != '\0') {
685 expando = parse_special((char **) &text, NULL, NULL,
686 NULL, &need_free, NULL,
693 expando_bind(expando, funccount, funcs);
696 expando_unbind(expando, funccount, funcs);
698 case TASK_GET_SIGNALS:
699 expando_signals = expando_get_signals(expando);
700 if (expando_signals != NULL) {
701 update_signals_hash(&signals,
703 g_free(expando_signals);
707 if (need_free) g_free(expando);
714 if (task == TASK_GET_SIGNALS)
715 return get_signals_list(signals);
720 void special_vars_add_signals(const char *text,
721 int funccount, SIGNAL_FUNC *funcs)
723 special_vars_signals_task(text, funccount, funcs, TASK_BIND);
726 void special_vars_remove_signals(const char *text,
727 int funccount, SIGNAL_FUNC *funcs)
729 special_vars_signals_task(text, funccount, funcs, TASK_UNBIND);
732 int *special_vars_get_signals(const char *text)
734 return special_vars_signals_task(text, 0, NULL, TASK_GET_SIGNALS);